/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation * * SPDX-License-Identifier: Apache-2.0 */ /* Primitive Utilities * * Generic functions to look up mesh, curve and volume primitive attributes for * shading and render passes. */ #pragma once #include "kernel/globals.h" #include "kernel/camera/projection.h" #include "kernel/geom/attribute.h" #include "kernel/geom/curve.h" #include "kernel/geom/object.h" #include "kernel/geom/point.h" #include "kernel/geom/triangle.h" #include "kernel/geom/volume.h" CCL_NAMESPACE_BEGIN /* Surface Attributes * * Read geometry attributes for surface shading. This is distinct from volume * attributes for performance, mainly for GPU performance to avoid bringing in * heavy volume interpolation code. */ template ccl_device_forceinline T primitive_surface_attribute(KernelGlobals kg, const ccl_private ShaderData *sd, const AttributeDescriptor desc) { using BaseT = dual_base_t; if (desc.element & (ATTR_ELEMENT_OBJECT | ATTR_ELEMENT_MESH)) { return T(attribute_data_fetch(kg, desc.element, desc.offset)); } if (sd->type & PRIMITIVE_TRIANGLE) { return triangle_attribute(kg, sd, desc); } #ifdef __HAIR__ if (sd->type & PRIMITIVE_CURVE) { return curve_attribute(kg, sd, desc); } #endif #ifdef __POINTCLOUD__ else if (sd->type & PRIMITIVE_POINT) { return point_attribute(kg, sd, desc); } #endif else { return make_zero(); } } /* Set sd->N to the undisplaced normal. For smooth shading, use the stored undisplaced * normal attribute. For flat shading, compute the geometric face normal from undisplaced * triangle positions. */ ccl_device void primitive_normal_set_undisplaced(KernelGlobals kg, ccl_private ShaderData *sd, const int position_undisplaced_offset) { float3 N; if (sd->shader & SHADER_SMOOTH_NORMAL) { const AttributeDescriptor ndesc = find_attribute(kg, sd, ATTR_STD_NORMAL_UNDISPLACED); if (!is_attribute_found(ndesc)) { return; } N = safe_normalize(primitive_surface_attribute(kg, sd, ndesc)); } else { N = triangle_face_normal_undisplaced(kg, sd, position_undisplaced_offset); } object_normal_transform(kg, sd, &N); sd->N = (sd->flag & SD_BACKFACING) ? -N : N; } #ifdef __VOLUME__ /* Volume Attributes * * Read geometry attributes for volume shading. This is distinct from surface * attributes for performance, mainly for GPU performance to avoid bringing in * heavy volume interpolation code. */ ccl_device_forceinline bool primitive_is_volume_attribute(const ccl_private ShaderData *sd) { return sd->type == PRIMITIVE_VOLUME; } template ccl_device_inline T primitive_volume_attribute(KernelGlobals kg, ccl_private ShaderData *sd, const AttributeDescriptor desc, const bool stochastic) { if (primitive_is_volume_attribute(sd)) { return volume_attribute_value(volume_attribute_float4(kg, sd, desc, stochastic)); } return make_zero(); } #endif /* Default UV coordinate */ ccl_device_forceinline float3 primitive_uv(KernelGlobals kg, const ccl_private ShaderData *sd) { const AttributeDescriptor desc = find_attribute(kg, sd, ATTR_STD_UV); if (!is_attribute_found(desc)) { return make_float3(0.0f, 0.0f, 0.0f); } const float2 uv = primitive_surface_attribute(kg, sd, desc); return make_float3(uv.x, uv.y, 1.0f); } /* PTEX coordinates. */ ccl_device bool primitive_ptex(KernelGlobals kg, ccl_private ShaderData *sd, ccl_private float2 *uv, ccl_private int *face_id) { /* storing ptex data as attributes is not memory efficient but simple for tests */ const AttributeDescriptor desc_face_id = find_attribute(kg, sd, ATTR_STD_PTEX_FACE_ID); const AttributeDescriptor desc_uv = find_attribute(kg, sd, ATTR_STD_PTEX_UV); if (!is_attribute_found(desc_face_id) || !is_attribute_found(desc_uv)) { return false; } const float3 uv3 = primitive_surface_attribute(kg, sd, desc_uv); const float face_id_f = primitive_surface_attribute(kg, sd, desc_face_id); *uv = make_float2(uv3.x, uv3.y); *face_id = (int)face_id_f; return true; } /* Surface tangent */ template ccl_device Float3Type primitive_tangent(KernelGlobals kg, ccl_private ShaderData *sd) { #if defined(__HAIR__) || defined(__POINTCLOUD__) if (sd->type & (PRIMITIVE_CURVE | PRIMITIVE_POINT)) { # ifdef __DPDU__ return Float3Type(normalize(sd->dPdu)); } # else return make_zero(); # endif #endif /* try to create spherical tangent from generated coordinates */ const AttributeDescriptor desc = find_attribute(kg, sd, ATTR_STD_GENERATED); if (is_attribute_found(desc)) { if constexpr (is_dual_v) { dual3 data = primitive_surface_attribute(kg, sd, desc); data = make_float3(-(data.y() - 0.5f), (data.x() - 0.5f), dual1()); object_normal_transform(kg, sd, &data); return cross(sd->N, normalize(cross(data, sd->N))); } else { float3 data = primitive_surface_attribute(kg, sd, desc); data = make_float3(-(data.y - 0.5f), (data.x - 0.5f), 0.0f); object_normal_transform(kg, sd, &data); return cross(sd->N, normalize(cross(data, sd->N))); } } /* otherwise use surface derivatives */ #ifdef __DPDU__ return Float3Type(normalize(sd->dPdu)); #else return make_zero(); #endif } /* Motion vector common */ ccl_device_inline float3 primitive_motion_position(KernelGlobals kg, const ccl_private ShaderData *sd, const int offset) { #if defined(__HAIR__) if (sd->type & PRIMITIVE_CURVE) { const KernelCurve curve = kernel_data_fetch(curves, sd->prim); const int k0 = curve.first_key + PRIMITIVE_UNPACK_SEGMENT(sd->type); const int k1 = k0 + 1; const float4 f0 = kernel_data_fetch(curve_keys, offset + k0); const float4 f1 = kernel_data_fetch(curve_keys, offset + k1); return make_float3(mix(f0, f1, sd->u)); } #endif #if defined(__POINTCLOUD__) if (sd->type & PRIMITIVE_POINT) { return make_float3(kernel_data_fetch(points, offset + sd->prim)); } #endif const uint3 tri_vindex = kernel_data_fetch(tri_vindex, sd->prim); const float3 v0 = kernel_data_fetch(tri_verts, offset + tri_vindex.x); const float3 v1 = kernel_data_fetch(tri_verts, offset + tri_vindex.y); const float3 v2 = kernel_data_fetch(tri_verts, offset + tri_vindex.z); return triangle_interpolate(sd->u, sd->v, v0, v1, v2); } ccl_device_forceinline void primitive_motion_data_without_camera(KernelGlobals kg, const ccl_private ShaderData *sd, ccl_private float3 *motion_center, ccl_private float3 *motion_pre, ccl_private float3 *motion_post) { #if defined(__HAIR__) || defined(__POINTCLOUD__) const bool is_curve_or_point = sd->type & (PRIMITIVE_CURVE | PRIMITIVE_POINT); if (is_curve_or_point) { *motion_center = make_float3(0.0f, 0.0f, 0.0f); if (sd->type & PRIMITIVE_CURVE) { # if defined(__HAIR__) *motion_center = curve_motion_center_location(kg, sd); # endif } else if (sd->type & PRIMITIVE_POINT) { # if defined(__POINTCLOUD__) *motion_center = point_motion_center_location(kg, sd); # endif } if (!(sd->object_flag & SD_OBJECT_TRANSFORM_APPLIED)) { object_position_transform(kg, sd, motion_center); } } else #endif { *motion_center = sd->P; } *motion_pre = *motion_center; *motion_post = *motion_center; /* deformation motion */ const ccl_global KernelObject &kobject = kernel_data_fetch(objects, sd->object); const int pos_offset = kobject.position_offset; const int numverts = kobject.numverts; const int num_motion_steps = kobject.num_geom_steps; if (sd->object_flag & SD_OBJECT_HAS_VERTEX_MOTION) { /* Motion steps are stored after the center position in the dedicated position arrays. */ int offset = pos_offset + numverts; *motion_pre = primitive_motion_position(kg, sd, offset); if (num_motion_steps > 2) { offset += numverts; *motion_post = primitive_motion_position(kg, sd, offset); } else { object_inverse_position_transform(kg, sd, motion_post); } } /* object motion. note that depending on the mesh having motion vectors, this * transformation was set match the world/object space of motion_pre/post */ Transform tfm; tfm = object_fetch_motion_pass_transform(kg, sd->object, OBJECT_PASS_MOTION_PRE); *motion_pre = transform_point(&tfm, *motion_pre); tfm = object_fetch_motion_pass_transform(kg, sd->object, OBJECT_PASS_MOTION_POST); *motion_post = transform_point(&tfm, *motion_post); } ccl_device_forceinline void primitive_motion_data_camera_step(KernelGlobals kg, ccl_private float3 *motion_center, ccl_private float3 *motion_pre, ccl_private float3 *motion_post) { Transform tfm; /* camera motion, for perspective/orthographic motion.pre/post will be a * world-to-raster matrix, for panorama it's world-to-camera, for custom * we fall back to the world position until we have inverse mapping for it */ if (kernel_data.cam.type == CAMERA_CUSTOM) { /* TODO: Custom cameras don't have inverse mappings yet, so we fall back to * camera-space vectors here for now. */ tfm = kernel_data.cam.worldtocamera; *motion_center = normalize(transform_point(&tfm, *motion_center)); tfm = kernel_data.cam.motion_pass_pre; *motion_pre = normalize(transform_point(&tfm, *motion_pre)); tfm = kernel_data.cam.motion_pass_post; *motion_post = normalize(transform_point(&tfm, *motion_post)); } else if (kernel_data.cam.type != CAMERA_PANORAMA) { /* Perspective and orthographics camera use the world-to-raster matrix. */ ProjectionTransform projection = kernel_data.cam.worldtoraster; *motion_center = transform_perspective(&projection, *motion_center); projection = kernel_data.cam.perspective_pre; *motion_pre = transform_perspective(&projection, *motion_pre); projection = kernel_data.cam.perspective_post; *motion_post = transform_perspective(&projection, *motion_post); } else { /* Panorama cameras have their own inverse mappings. */ tfm = kernel_data.cam.worldtocamera; *motion_center = normalize(transform_point(&tfm, *motion_center)); *motion_center = make_float3(direction_to_panorama(&kernel_data.cam, *motion_center)); motion_center->x *= kernel_data.cam.width; motion_center->y *= kernel_data.cam.height; tfm = kernel_data.cam.motion_pass_pre; *motion_pre = normalize(transform_point(&tfm, *motion_pre)); *motion_pre = make_float3(direction_to_panorama(&kernel_data.cam, *motion_pre)); motion_pre->x *= kernel_data.cam.width; motion_pre->y *= kernel_data.cam.height; tfm = kernel_data.cam.motion_pass_post; *motion_post = normalize(transform_point(&tfm, *motion_post)); *motion_post = make_float3(direction_to_panorama(&kernel_data.cam, *motion_post)); motion_post->x *= kernel_data.cam.width; motion_post->y *= kernel_data.cam.height; } } /* Motion vector for motion pass */ ccl_device_forceinline float4 primitive_motion_vector(KernelGlobals kg, const ccl_private ShaderData *sd) { float3 motion_center, motion_pre, motion_post; primitive_motion_data_without_camera(kg, sd, &motion_center, &motion_pre, &motion_post); primitive_motion_data_camera_step(kg, &motion_center, &motion_pre, &motion_post); motion_pre = motion_pre - motion_center; motion_post = motion_center - motion_post; return make_float4(motion_pre.x, motion_pre.y, motion_post.x, motion_post.y); } /* Motion vector for denoising backward motion pass */ ccl_device_forceinline float3 primitive_motion_vector_backward_depth_delta(KernelGlobals kg, const ccl_private ShaderData *sd) { Transform tfm; float3 motion_center, motion_pre, motion_post; primitive_motion_data_without_camera(kg, sd, &motion_center, &motion_pre, &motion_post); /* Get camera-space vectors for linear depth delta. */ tfm = kernel_data.cam.worldtocamera; float3 motion_center_cam = transform_point(&tfm, motion_center); tfm = kernel_data.cam.motion_pass_pre; float3 motion_pre_cam = transform_point(&tfm, motion_pre); primitive_motion_data_camera_step(kg, &motion_center, &motion_pre, &motion_post); motion_pre = motion_pre - motion_center; float linear_depth_delta_pre = motion_pre_cam.z - motion_center_cam.z; return make_float3(motion_pre.x, motion_pre.y, linear_depth_delta_pre); } CCL_NAMESPACE_END