Cycles: Use per-corner normals instead of splitting

This is more consistent with Blender and fixes various discrepancies
between Cycles and Blender with sharp edges and mixed flat and smooth
faces. This uses more memory, but is compensated by earlier octahedral
mapping and in the benchmark scenes memory is always the same or lower.

Fix #152812: Mix of flat and smooth faces has incorrect sharp edges
Fix #152496: Smooth faces that share one vertex have bad normals
Fix #100425: Issues with motion blur and autosmooth or edge splitting

Pull Request: https://projects.blender.org/blender/blender/pulls/153836
This commit is contained in:
Brecht Van Lommel 2026-02-02 00:06:36 +01:00
parent 9b8810a351
commit 6bbdad08fd
24 changed files with 307 additions and 165 deletions

View file

@ -34,45 +34,11 @@
#include "BKE_mesh.hh"
#include "GEO_mesh_split_edges.hh"
#include "util/types_normal.h"
using blender::Attribute;
CCL_NAMESPACE_BEGIN
void mesh_split_edges_for_corner_normals(blender::Mesh &mesh)
{
using namespace blender;
const OffsetIndices polys = mesh.faces();
const Span<int> corner_edges = mesh.corner_edges();
const bke::AttributeAccessor attributes = mesh.attributes();
const VArray<bool> mesh_sharp_edges = *attributes.lookup_or_default<bool>(
"sharp_edge", bke::AttrDomain::Edge, false);
const VArraySpan<bool> sharp_faces = *attributes.lookup<bool>("sharp_face",
bke::AttrDomain::Face);
Array<bool> sharp_edges(mesh.edges_num);
mesh_sharp_edges.materialize(sharp_edges);
threading::parallel_for(polys.index_range(), 1024, [&](const blender::IndexRange range) {
for (const int face_i : range) {
if (!sharp_faces.is_empty() && sharp_faces[face_i]) {
for (const int edge : corner_edges.slice(polys[face_i])) {
sharp_edges[edge] = true;
}
}
}
});
IndexMaskMemory memory;
const IndexMask split_mask = IndexMask::from_bools(sharp_edges, memory);
if (split_mask.is_empty()) {
return;
}
geometry::split_edges(mesh, split_mask, {});
}
static void attr_create_motion_from_velocity(Mesh *mesh,
const blender::Span<blender::float3> b_attr,
const float motion_scale)
@ -641,10 +607,6 @@ static void create_mesh(Scene *scene,
"material_index", blender::bke::AttrDomain::Face);
const blender::VArraySpan sharp_faces = *b_attributes.lookup<bool>(
"sharp_face", blender::bke::AttrDomain::Face);
blender::Span<blender::float3> corner_normals;
if (use_corner_normals) {
corner_normals = b_mesh.corner_normals();
}
int numtris = 0;
if (!subdivision) {
@ -662,13 +624,14 @@ static void create_mesh(Scene *scene,
}
AttributeSet &attributes = (subdivision) ? mesh->subd_attributes : mesh->attributes;
Attribute *attr_N = attributes.add(ATTR_STD_VERTEX_NORMAL);
packed_normal *N = attr_N->data_normal();
if (subdivision || !(use_corner_normals && !corner_normals.is_empty())) {
if (subdivision || !use_corner_normals) {
Attribute *attr_N = attributes.add(ATTR_STD_VERTEX_NORMAL);
packed_normal *N = attr_N->data_normal();
const blender::Span<blender::float3> vert_normals = b_mesh.vert_normals();
for (const int i : vert_normals.index_range()) {
N[i] = make_float3(vert_normals[i][0], vert_normals[i][1], vert_normals[i][2]);
N[i] = packed_normal(
make_float3(vert_normals[i][0], vert_normals[i][1], vert_normals[i][2]));
}
}
@ -717,9 +680,9 @@ static void create_mesh(Scene *scene,
bool *smooth = mesh->get_smooth().data();
int *shader = mesh->get_shader().data();
const blender::Span<blender::int3> corner_tris = b_mesh.corner_tris();
for (const int i : corner_tris.index_range()) {
const blender::int3 &tri = corner_tris[i];
const blender::Span<blender::int3> b_corner_tris = b_mesh.corner_tris();
for (const int i : b_corner_tris.index_range()) {
const blender::int3 &tri = b_corner_tris[i];
triangles[i * 3 + 0] = corner_verts[tri[0]];
triangles[i * 3 + 1] = corner_verts[tri[1]];
triangles[i * 3 + 2] = corner_verts[tri[2]];
@ -737,7 +700,7 @@ static void create_mesh(Scene *scene,
std::fill(shader, shader + numtris, 0);
}
if (!sharp_faces.is_empty() && !(use_corner_normals && !corner_normals.is_empty())) {
if (!sharp_faces.is_empty()) {
for (const int face : faces.index_range()) {
const bool face_smooth = !sharp_faces[face];
const blender::IndexRange face_tris = blender::bke::mesh::face_triangles_range(faces,
@ -750,14 +713,17 @@ static void create_mesh(Scene *scene,
std::fill(smooth, smooth + numtris, normals_domain != blender::bke::MeshNormalDomain::Face);
}
if (use_corner_normals && !corner_normals.is_empty()) {
for (const int i : corner_tris.index_range()) {
const blender::int3 &tri = corner_tris[i];
for (int i = 0; i < 3; i++) {
const int corner = tri[i];
const int vert = corner_verts[corner];
const float *normal = corner_normals[corner];
N[vert] = packed_normal(make_float3(normal[0], normal[1], normal[2]));
if (use_corner_normals) {
const blender::Span<blender::float3> b_corner_normals = b_mesh.corner_normals();
Attribute *attr_N = attributes.add(ATTR_STD_CORNER_NORMAL);
packed_normal *N = attr_N->data_normal();
for (const int i : b_corner_tris.index_range()) {
const blender::int3 &tri = b_corner_tris[i];
for (int j = 0; j < 3; j++) {
const int corner = tri[j];
const float *normal = b_corner_normals[corner];
N[i * 3 + j] = packed_normal(make_float3(normal[0], normal[1], normal[2]));
}
}
}
@ -999,6 +965,7 @@ void BlenderSync::sync_mesh_motion(BObjectInfo &b_ob_info, Mesh *mesh, const int
{
/* Skip if no vertices were exported. */
const size_t numverts = mesh->get_verts().size();
const size_t numtris = mesh->num_triangles();
if (numverts == 0) {
return;
}
@ -1030,7 +997,9 @@ void BlenderSync::sync_mesh_motion(BObjectInfo &b_ob_info, Mesh *mesh, const int
/* Find attributes. */
Attribute *attr_mP = attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
Attribute *attr_mN = attributes.find(ATTR_STD_MOTION_VERTEX_NORMAL);
Attribute *attr_mcN = mesh->attributes.find(ATTR_STD_MOTION_CORNER_NORMAL);
Attribute *attr_N = attributes.find(ATTR_STD_VERTEX_NORMAL);
Attribute *attr_cN = mesh->attributes.find(ATTR_STD_CORNER_NORMAL);
bool new_attribute = false;
/* Add new attributes if they don't exist already. */
if (!attr_mP) {
@ -1038,12 +1007,19 @@ void BlenderSync::sync_mesh_motion(BObjectInfo &b_ob_info, Mesh *mesh, const int
if (attr_N) {
attr_mN = attributes.add(ATTR_STD_MOTION_VERTEX_NORMAL);
}
if (attr_cN) {
attr_mcN = mesh->attributes.add(ATTR_STD_MOTION_CORNER_NORMAL);
}
new_attribute = true;
}
/* Load vertex data from mesh. */
float3 *mP = attr_mP->data_float3() + motion_step * numverts;
packed_normal *mN = (attr_mN) ? attr_mN->data_normal() + motion_step * numverts : nullptr;
packed_normal *mcN = (attr_mcN) ? attr_mcN->data_normal() + motion_step * numtris * 3 :
nullptr;
bool topology_changed = b_verts_num != numverts;
/* NOTE: We don't copy more that existing amount of vertices to prevent
* possible memory corruption.
@ -1058,13 +1034,28 @@ void BlenderSync::sync_mesh_motion(BObjectInfo &b_ob_info, Mesh *mesh, const int
make_float3(b_vert_normals[i][0], b_vert_normals[i][1], b_vert_normals[i][2]));
}
}
if (mcN) {
const blender::Span<blender::float3> b_corner_normals = b_mesh->corner_normals();
const blender::Span<blender::int3> b_corner_tris = b_mesh->corner_tris();
const int mincorners = std::min<int>(b_corner_tris.size(), numtris);
for (int i = 0; i < mincorners; i++) {
const blender::int3 &tri = b_corner_tris[i];
for (int j = 0; j < 3; j++) {
const int corner = tri[j];
const float *normal = b_corner_normals[corner];
mcN[i * 3 + j] = packed_normal(make_float3(normal[0], normal[1], normal[2]));
}
}
topology_changed |= b_corner_tris.size() != numtris;
}
if (new_attribute) {
/* In case of new attribute, we verify if there really was any motion. */
if (b_verts_num != numverts ||
memcmp(mP, mesh->get_verts().data(), sizeof(float3) * numverts) == 0)
if (topology_changed || memcmp(mP, mesh->get_verts().data(), sizeof(float3) * numverts) == 0)
{
/* no motion, remove attributes again */
if (b_verts_num != numverts) {
if (topology_changed) {
LOG_WARNING << "Topology differs, disabling motion blur for object " << ob_name;
}
else {
@ -1074,6 +1065,9 @@ void BlenderSync::sync_mesh_motion(BObjectInfo &b_ob_info, Mesh *mesh, const int
if (attr_mN) {
attributes.remove(ATTR_STD_MOTION_VERTEX_NORMAL);
}
if (attr_mcN) {
attributes.remove(ATTR_STD_MOTION_CORNER_NORMAL);
}
}
else if (motion_step > 0) {
LOG_TRACE << "Filling deformation motion for object " << ob_name;
@ -1081,24 +1075,32 @@ void BlenderSync::sync_mesh_motion(BObjectInfo &b_ob_info, Mesh *mesh, const int
* they had no motion, but we need them anyway now */
const float3 *P = mesh->get_verts().data();
const packed_normal *N = (attr_N) ? attr_N->data_normal() : nullptr;
const packed_normal *cN = (attr_cN) ? attr_cN->data_normal() : nullptr;
for (int step = 0; step < motion_step; step++) {
std::copy_n(P, numverts, attr_mP->data_float3() + step * numverts);
if (attr_mN) {
std::copy_n(N, numverts, attr_mN->data_normal() + step * numverts);
}
if (attr_mcN) {
std::copy_n(cN, numtris * 3, attr_mcN->data_normal() + step * (numtris * 3));
}
}
}
}
else {
if (b_verts_num != numverts) {
if (topology_changed) {
LOG_WARNING << "Topology differs, discarding motion blur for object " << ob_name
<< " at time " << motion_step;
const float3 *P = mesh->get_verts().data();
const packed_normal *N = (attr_N) ? attr_N->data_normal() : nullptr;
const packed_normal *cN = (attr_cN) ? attr_cN->data_normal() : nullptr;
std::copy_n(P, numverts, mP);
if (mN != nullptr) {
std::copy_n(N, numverts, mN);
}
if (mcN != nullptr) {
std::copy_n(cN, numtris * 3, mcN);
}
}
}

View file

@ -89,50 +89,28 @@ int blender_attribute_name_split_type(ustring name, string *r_real_name);
void python_thread_state_save(void **python_thread_state);
void python_thread_state_restore(void **python_thread_state);
static bool mesh_use_corner_normals(const BObjectInfo &b_ob_info, blender::Mesh *mesh)
{
return mesh && !b_ob_info.use_adaptive_subdivision &&
(mesh->normals_domain(true) == blender::bke::MeshNormalDomain::Corner);
}
void mesh_split_edges_for_corner_normals(blender::Mesh &mesh);
static inline blender::Mesh *object_to_mesh(BObjectInfo &b_ob_info)
{
blender::Mesh *mesh = (GS(b_ob_info.object_data->name) == blender::ID_ME) ?
blender::id_cast<blender::Mesh *>(b_ob_info.object_data) :
nullptr;
bool use_corner_normals = false;
if (b_ob_info.is_real_object_data()) {
if (mesh) {
if (mesh->runtime->edit_mesh) {
/* Flush edit-mesh to mesh, including all data layers. */
mesh = object_copy_mesh_data(b_ob_info);
use_corner_normals = mesh_use_corner_normals(b_ob_info, mesh);
}
else if (mesh_use_corner_normals(b_ob_info, mesh)) {
/* Make a copy to split faces. */
mesh = object_copy_mesh_data(b_ob_info);
use_corner_normals = true;
}
}
else {
mesh = object_copy_mesh_data(b_ob_info);
use_corner_normals = mesh_use_corner_normals(b_ob_info, mesh);
}
}
else {
/* TODO: what to do about non-mesh geometry instances? */
use_corner_normals = mesh_use_corner_normals(b_ob_info, mesh);
}
if (mesh) {
if (use_corner_normals) {
mesh_split_edges_for_corner_normals(*mesh);
}
if (b_ob_info.use_adaptive_subdivision) {
mesh->corner_tris();
}

View file

@ -44,12 +44,6 @@ ccl_device_inline T curve_attribute_dfdy(const ccl_private differential &du,
return du.dy * (f1 - f0);
}
# ifdef __KERNEL_METAL__
template<typename U, typename V> using curve_attribute_is_same = metal::is_same<U, V>;
# else
template<typename U, typename V> using curve_attribute_is_same = std::is_same<U, V>;
# endif
/* Read attributes on various curve elements, and compute the partial derivatives if requested. */
template<typename T>

View file

@ -54,9 +54,10 @@ ccl_device_inline void motion_triangle_verts_for_step(KernelGlobals kg,
ccl_device_inline void motion_triangle_normals_for_step(KernelGlobals kg,
const int object,
const int object_flag,
const int prim,
const uint3 tri_vindex,
int offset,
const int numverts,
const int numsteps,
int step,
float3 normals[3])
@ -70,12 +71,30 @@ ccl_device_inline void motion_triangle_normals_for_step(KernelGlobals kg,
if (step > numsteps) {
step--;
}
offset += step * numverts;
}
attribute_data_fetch_normals(
kg, offset, offset + tri_vindex.x, offset + tri_vindex.y, offset + tri_vindex.z, normals);
int i0, i1, i2;
if (object_flag & SD_OBJECT_HAS_CORNER_NORMALS) {
if (step != numsteps) {
offset += step * kernel_data_fetch(objects, object).numprims * 3;
}
i0 = prim * 3 + 0;
i1 = prim * 3 + 1;
i2 = prim * 3 + 2;
}
else {
if (step != numsteps) {
offset += step * kernel_data_fetch(objects, object).numverts;
}
i0 = tri_vindex.x;
i1 = tri_vindex.y;
i2 = tri_vindex.z;
}
attribute_data_fetch_normals(kg, offset, i0, i1, i2, normals);
}
ccl_device_inline void motion_triangle_compute_info(KernelGlobals kg,
@ -138,23 +157,28 @@ ccl_device_inline void motion_triangle_vertices(
ccl_device_inline void motion_triangle_normals(KernelGlobals kg,
const int object,
const int prim,
const uint3 tri_vindex,
const int numsteps,
const int numverts,
const int step,
const float t,
float3 normals[3])
{
/* Find attribute. */
const int offset = intersection_find_attribute(kg, object, ATTR_STD_MOTION_VERTEX_NORMAL);
const int object_flag = kernel_data_fetch(object_flag, object);
const int offset = intersection_find_attribute(kg,
object,
(object_flag & SD_OBJECT_HAS_CORNER_NORMALS) ?
ATTR_STD_MOTION_CORNER_NORMAL :
ATTR_STD_MOTION_VERTEX_NORMAL);
kernel_assert(offset != ATTR_STD_NOT_FOUND);
/* Fetch normals. */
float3 next_normals[3];
motion_triangle_normals_for_step(
kg, object, tri_vindex, offset, numverts, numsteps, step, normals);
kg, object, object_flag, prim, tri_vindex, offset, numsteps, step, normals);
motion_triangle_normals_for_step(
kg, object, tri_vindex, offset, numverts, numsteps, step + 1, next_normals);
kg, object, object_flag, prim, tri_vindex, offset, numsteps, step + 1, next_normals);
/* Interpolate between steps. */
normals[0] = normalize((1.0f - t) * normals[0] + t * next_normals[0]);
@ -163,26 +187,25 @@ ccl_device_inline void motion_triangle_normals(KernelGlobals kg,
}
ccl_device_inline void motion_triangle_vertices_and_normals(KernelGlobals kg,
const int object,
const int prim,
const float time,
const ccl_private ShaderData *sd,
float3 verts[3],
float3 normals[3])
{
int numsteps;
int step;
const int object = sd->object;
int numsteps, step;
float t;
uint3 tri_vindex;
motion_triangle_compute_info(kg, object, time, prim, &tri_vindex, &numsteps, &step, &t);
motion_triangle_compute_info(kg, object, sd->time, sd->prim, &tri_vindex, &numsteps, &step, &t);
const int numverts = kernel_data_fetch(objects, object).numverts;
motion_triangle_vertices(kg, object, tri_vindex, numsteps, numverts, step, t, verts);
motion_triangle_normals(kg, object, tri_vindex, numsteps, numverts, step, t, normals);
motion_triangle_normals(kg, object, sd->prim, tri_vindex, numsteps, step, t, normals);
}
ccl_device_inline float3 motion_triangle_smooth_normal(KernelGlobals kg,
const float3 Ng,
const int object,
const int prim,
const uint3 tri_vindex,
const int numsteps,
const int step,
@ -191,8 +214,7 @@ ccl_device_inline float3 motion_triangle_smooth_normal(KernelGlobals kg,
const float v)
{
float3 normals[3];
const int numverts = kernel_data_fetch(objects, object).numverts;
motion_triangle_normals(kg, object, tri_vindex, numsteps, numverts, step, t, normals);
motion_triangle_normals(kg, object, prim, tri_vindex, numsteps, step, t, normals);
/* Interpolate between normals. */
const float w = 1.0f - u - v;
@ -215,7 +237,7 @@ ccl_device_inline float3 motion_triangle_smooth_normal(KernelGlobals kg,
uint3 tri_vindex;
motion_triangle_compute_info(kg, object, time, prim, &tri_vindex, &numsteps, &step, &t);
return motion_triangle_smooth_normal(kg, Ng, object, tri_vindex, numsteps, step, t, u, v);
return motion_triangle_smooth_normal(kg, Ng, object, prim, tri_vindex, numsteps, step, t, u, v);
}
/* Compute motion triangle normals at the hit position, and offsetted positions in x and y
@ -238,8 +260,7 @@ ccl_device_inline float3 motion_triangle_smooth_normal(KernelGlobals kg,
motion_triangle_compute_info(kg, object, time, prim, &tri_vindex, &numsteps, &step, &t);
float3 n[3];
const int numverts = kernel_data_fetch(objects, object).numverts;
motion_triangle_normals(kg, object, tri_vindex, numsteps, numverts, step, t, n);
motion_triangle_normals(kg, object, prim, tri_vindex, numsteps, step, t, n);
const float3 N = safe_normalize(triangle_interpolate(u, v, n[0], n[1], n[2]));
N_x = safe_normalize(triangle_interpolate(u + du.dx, v + dv.dx, n[0], n[1], n[2]));

View file

@ -65,7 +65,7 @@ ccl_device_noinline void motion_triangle_shader_setup(KernelGlobals kg, ccl_priv
/* Compute smooth normal. */
if (sd->shader & SHADER_SMOOTH_NORMAL) {
sd->N = motion_triangle_smooth_normal(
kg, Ng, sd->object, tri_vindex, numsteps, step, t, sd->u, sd->v);
kg, Ng, sd->object, sd->prim, tri_vindex, numsteps, step, t, sd->u, sd->v);
}
}

View file

@ -187,7 +187,8 @@ ccl_device_inline void shader_setup_from_sample(KernelGlobals kg,
if (sd->type == PRIMITIVE_TRIANGLE) {
/* smooth normal */
if (sd->shader & SHADER_SMOOTH_NORMAL) {
sd->N = triangle_smooth_normal(kg, Ng, sd->object, sd->prim, sd->u, sd->v);
sd->N = triangle_smooth_normal(
kg, Ng, sd->object, sd->object_flag, sd->prim, sd->u, sd->v);
if (!(sd->object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {
object_normal_transform_auto(kg, sd, &sd->N);

View file

@ -105,18 +105,29 @@ ccl_device_inline void triangle_vertices(KernelGlobals kg, const int prim, float
/* Triangle vertex locations and vertex normals */
ccl_device_inline void triangle_vertices_and_normals(
KernelGlobals kg, const int object, const int prim, float3 P[3], float3 N[3])
ccl_device_inline void triangle_vertices_and_normals(KernelGlobals kg,
ccl_private const ShaderData *sd,
float3 P[3],
float3 N[3])
{
const uint3 tri_vindex = kernel_data_fetch(tri_vindex, prim);
const uint3 tri_vindex = kernel_data_fetch(tri_vindex, sd->prim);
P[0] = kernel_data_fetch(tri_verts, tri_vindex.x);
P[1] = kernel_data_fetch(tri_verts, tri_vindex.y);
P[2] = kernel_data_fetch(tri_verts, tri_vindex.z);
const int normal_offset = kernel_data_fetch(objects, object).normal_attr_offset;
const int i0 = tri_vindex.x;
const int i1 = tri_vindex.y;
const int i2 = tri_vindex.z;
const int normal_offset = kernel_data_fetch(objects, sd->object).normal_attr_offset;
int i0, i1, i2;
if (sd->object_flag & SD_OBJECT_HAS_CORNER_NORMALS) {
i0 = sd->prim * 3 + 0;
i1 = sd->prim * 3 + 1;
i2 = sd->prim * 3 + 2;
}
else {
i0 = tri_vindex.x;
i1 = tri_vindex.y;
i2 = tri_vindex.z;
}
attribute_data_fetch_normals(kg, normal_offset, i0, i1, i2, N);
}
@ -124,28 +135,39 @@ ccl_device_inline void triangle_vertices_and_normals(
/* Interpolate smooth vertex normal from vertices */
ccl_device_forceinline float3 triangle_smooth_normal_unnormalized(
KernelGlobals kg, float3 Ng, int object, int prim, float u, float v)
KernelGlobals kg, float3 Ng, int object, int object_flag, int prim, float u, float v)
{
const int normal_offset = kernel_data_fetch(objects, object).normal_attr_offset;
const uint3 tri_vindex = kernel_data_fetch(tri_vindex, prim);
const int i0 = tri_vindex.x;
const int i1 = tri_vindex.y;
const int i2 = tri_vindex.z;
int i0, i1, i2;
if (object_flag & SD_OBJECT_HAS_CORNER_NORMALS) {
i0 = prim * 3 + 0;
i1 = prim * 3 + 1;
i2 = prim * 3 + 2;
}
else {
const uint3 tri_vindex = kernel_data_fetch(tri_vindex, prim);
i0 = tri_vindex.x;
i1 = tri_vindex.y;
i2 = tri_vindex.z;
}
const float3 N = attribute_data_interpolate_normals(kg, normal_offset, i0, i1, i2, u, v);
return is_zero(N) ? Ng : N;
}
ccl_device_inline float3
triangle_smooth_normal(KernelGlobals kg, float3 Ng, int object, int prim, float u, float v)
ccl_device_inline float3 triangle_smooth_normal(
KernelGlobals kg, float3 Ng, int object, int object_flag, int prim, float u, float v)
{
return safe_normalize(triangle_smooth_normal_unnormalized(kg, Ng, object, prim, u, v));
return safe_normalize(
triangle_smooth_normal_unnormalized(kg, Ng, object, object_flag, prim, u, v));
}
ccl_device_inline float3 triangle_smooth_normal_unnormalized(KernelGlobals kg,
ccl_private const ShaderData *sd)
{
return triangle_smooth_normal_unnormalized(kg, sd->Ng, sd->object, sd->prim, sd->u, sd->v);
return triangle_smooth_normal_unnormalized(
kg, sd->Ng, sd->object, sd->object_flag, sd->prim, sd->u, sd->v);
}
/* Compute triangle normals at the hit position, and offsetted positions in x and y direction for
@ -153,6 +175,7 @@ ccl_device_inline float3 triangle_smooth_normal_unnormalized(KernelGlobals kg,
ccl_device_inline float3 triangle_smooth_normal(KernelGlobals kg,
const float3 Ng,
const int object,
const int object_flag,
const int prim,
const float u,
float v,
@ -162,11 +185,19 @@ ccl_device_inline float3 triangle_smooth_normal(KernelGlobals kg,
ccl_private float3 &N_y)
{
const int normal_offset = kernel_data_fetch(objects, object).normal_attr_offset;
int i0, i1, i2;
const uint3 tri_vindex = kernel_data_fetch(tri_vindex, prim);
const int i0 = tri_vindex.x;
const int i1 = tri_vindex.y;
const int i2 = tri_vindex.z;
if (object_flag & SD_OBJECT_HAS_CORNER_NORMALS) {
i0 = prim * 3 + 0;
i1 = prim * 3 + 1;
i2 = prim * 3 + 2;
}
else {
const uint3 tri_vindex = kernel_data_fetch(tri_vindex, prim);
i0 = tri_vindex.x;
i1 = tri_vindex.y;
i2 = tri_vindex.z;
}
float3 n[3];
attribute_data_fetch_normals(kg, normal_offset, i0, i1, i2, n);
@ -227,12 +258,6 @@ ccl_device_inline T triangle_attribute_dfdy(const ccl_private differential &du,
return du.dy * f1 + dv.dy * f2 - (du.dy + dv.dy) * f0;
}
#ifdef __KERNEL_METAL__
template<typename U, typename V> using triangle_attribute_is_same = metal::is_same<U, V>;
#else
template<typename U, typename V> using triangle_attribute_is_same = std::is_same<U, V>;
#endif
/* Read attributes on various triangle elements, and compute the partial derivatives if requested.
*/
template<typename T>

View file

@ -154,7 +154,7 @@ ccl_device_inline void triangle_shader_setup(KernelGlobals kg, ccl_private Shade
/* Smooth normal. */
if (sd->shader & SHADER_SMOOTH_NORMAL) {
sd->N = triangle_smooth_normal(kg, Ng, sd->object, sd->prim, sd->u, sd->v);
sd->N = triangle_smooth_normal(kg, Ng, sd->object, sd->object_flag, sd->prim, sd->u, sd->v);
}
#ifdef __DPDU__

View file

@ -216,7 +216,7 @@ ccl_device bool integrator_init_from_bake(KernelGlobals kg,
else {
/* Surface baking. */
float3 N = (shader & SHADER_SMOOTH_NORMAL) ?
triangle_smooth_normal(kg, Ng, object, prim, u, v) :
triangle_smooth_normal(kg, Ng, object, object_flag, prim, u, v) :
Ng;
if (!(object_flag & SD_OBJECT_TRANSFORM_APPLIED)) {

View file

@ -149,7 +149,7 @@ ccl_device_inline void mnee_setup_manifold_vertex(KernelGlobals kg,
float3 normals[3];
if (sd_vtx->type & PRIMITIVE_TRIANGLE) {
/* Load triangle vertices and normals. */
triangle_vertices_and_normals(kg, sd_vtx->object, sd_vtx->prim, verts, normals);
triangle_vertices_and_normals(kg, sd_vtx, verts, normals);
/* Compute refined position (same code as in triangle_point_from_uv). */
sd_vtx->P = (1.f - isect->u - isect->v) * verts[0] + isect->u * verts[1] + isect->v * verts[2];
@ -160,8 +160,7 @@ ccl_device_inline void mnee_setup_manifold_vertex(KernelGlobals kg,
}
else { /* if (sd_vtx->type & PRIMITIVE_MOTION_TRIANGLE) */
/* Load triangle vertices and normals. */
motion_triangle_vertices_and_normals(
kg, sd_vtx->object, sd_vtx->prim, sd_vtx->time, verts, normals);
motion_triangle_vertices_and_normals(kg, sd_vtx, verts, normals);
/* Compute refined position. */
sd_vtx->P = motion_triangle_point_from_uv(kg, sd_vtx, isect->u, isect->v, verts);

View file

@ -166,11 +166,11 @@ ccl_device_inline float3 shadow_ray_smooth_surface_offset(
float3 N[3];
if (sd->type == PRIMITIVE_MOTION_TRIANGLE) {
motion_triangle_vertices_and_normals(kg, sd->object, sd->prim, sd->time, V, N);
motion_triangle_vertices_and_normals(kg, sd, V, N);
}
else {
kernel_assert(sd->type == PRIMITIVE_TRIANGLE);
triangle_vertices_and_normals(kg, sd->object, sd->prim, V, N);
triangle_vertices_and_normals(kg, sd, V, N);
}
const float u = 1.0f - sd->u - sd->v;

View file

@ -68,7 +68,7 @@ ccl_device bool attribute_bump_map_normal(KernelGlobals kg,
if (sd->type == PRIMITIVE_TRIANGLE) {
f.val = triangle_smooth_normal(
kg, Ng, sd->object, sd->prim, sd->u, sd->v, sd->du, sd->dv, f.dx, f.dy);
kg, Ng, sd->object, sd->object_flag, sd->prim, sd->u, sd->v, sd->du, sd->dv, f.dx, f.dy);
}
else {
assert(sd->type & PRIMITIVE_MOTION_TRIANGLE);

View file

@ -241,11 +241,11 @@ ccl_device float3 svm_bevel(
const float v = isect.hits[hit].v;
if (sd->type == PRIMITIVE_TRIANGLE) {
N = triangle_smooth_normal(kg, N, object, prim, u, v);
N = triangle_smooth_normal(kg, N, object, object_flag, prim, u, v);
}
# ifdef __OBJECT_MOTION__
else if (sd->type == PRIMITIVE_MOTION_TRIANGLE) {
N = motion_triangle_smooth_normal(kg, N, sd->object, prim, u, v, sd->time);
N = motion_triangle_smooth_normal(kg, N, object, prim, u, v, sd->time);
}
# endif /* __OBJECT_MOTION__ */
}

View file

@ -100,7 +100,7 @@ ccl_device RaycastResult svm_raycast(KernelGlobals kg,
/* Compute smooth normal. */
if (shader & SHADER_SMOOTH_NORMAL) {
if (isect.type == PRIMITIVE_TRIANGLE) {
result.normal = triangle_smooth_normal(kg, Ng, object, prim, u, v);
result.normal = triangle_smooth_normal(kg, Ng, object, object_flag, prim, u, v);
}
# ifdef __OBJECT_MOTION__
else if (isect.type == PRIMITIVE_MOTION_TRIANGLE) {

View file

@ -128,7 +128,8 @@ ccl_device_inline float3 texco_normal_from_uv(KernelGlobals kg,
float3 N;
if ((sd->type & PRIMITIVE_TRIANGLE) && (sd->shader & SHADER_SMOOTH_NORMAL)) {
N = (sd->type == PRIMITIVE_TRIANGLE) ?
triangle_smooth_normal(kg, zero_float3(), sd->object, sd->prim, u, v) :
triangle_smooth_normal(
kg, zero_float3(), sd->object, sd->object_flag, sd->prim, u, v) :
motion_triangle_smooth_normal(kg, zero_float3(), sd->object, sd->prim, u, v, sd->time);
if (is_zero(N)) {
N = sd->Ng;

View file

@ -696,6 +696,9 @@ enum AttributeElement {
ATTR_ELEMENT_IS_MOTION,
ATTR_ELEMENT_CORNER_BYTE = ATTR_ELEMENT_CORNER | ATTR_ELEMENT_IS_BYTE,
ATTR_ELEMENT_CORNER_NORMAL = ATTR_ELEMENT_CORNER | ATTR_ELEMENT_IS_NORMAL,
ATTR_ELEMENT_CORNER_NORMAL_MOTION = ATTR_ELEMENT_CORNER | ATTR_ELEMENT_IS_NORMAL |
ATTR_ELEMENT_IS_MOTION,
ATTR_ELEMENT_CURVE_KEY_MOTION = ATTR_ELEMENT_CURVE_KEY | ATTR_ELEMENT_IS_MOTION,
ATTR_ELEMENT_CURVE_KEY_NORMAL = ATTR_ELEMENT_CURVE_KEY | ATTR_ELEMENT_IS_NORMAL,
@ -944,6 +947,8 @@ enum ShaderDataObjectFlag : uint {
SD_OBJECT_CAUSTICS_RECEIVER = (1u << 10),
/* object has attribute for volume motion */
SD_OBJECT_HAS_VOLUME_MOTION = (1u << 11),
/* Geometry has per-corner normals instead of per-vertex. */
SD_OBJECT_HAS_CORNER_NORMALS = (1u << 12),
/* object is using caustics */
SD_OBJECT_CAUSTICS = (SD_OBJECT_CAUSTICS_CASTER | SD_OBJECT_CAUSTICS_RECEIVER),
@ -952,7 +957,7 @@ enum ShaderDataObjectFlag : uint {
SD_OBJECT_NEGATIVE_SCALE | SD_OBJECT_HAS_VOLUME |
SD_OBJECT_INTERSECTS_VOLUME | SD_OBJECT_SHADOW_CATCHER |
SD_OBJECT_HAS_VOLUME_ATTRIBUTES | SD_OBJECT_CAUSTICS |
SD_OBJECT_HAS_VOLUME_MOTION)
SD_OBJECT_HAS_VOLUME_MOTION | SD_OBJECT_HAS_CORNER_NORMALS)
};
struct ccl_align(16) ShaderData {

View file

@ -223,7 +223,7 @@ size_t Attribute::element_size(Geometry *geom, AttributePrimitive prim) const
if (geom->is_mesh() || geom->is_volume()) {
Mesh *mesh = static_cast<Mesh *>(geom);
if (prim == ATTR_PRIM_SUBD) {
size = mesh->get_verts().size();
size = mesh->get_num_subd_base_verts();
}
else {
size = mesh->get_verts().size();
@ -264,6 +264,8 @@ size_t Attribute::element_size(Geometry *geom, AttributePrimitive prim) const
break;
case ATTR_ELEMENT_CORNER:
case ATTR_ELEMENT_CORNER_BYTE:
case ATTR_ELEMENT_CORNER_NORMAL:
case ATTR_ELEMENT_CORNER_NORMAL_MOTION:
if (geom->is_mesh()) {
Mesh *mesh = static_cast<Mesh *>(geom);
if (prim == ATTR_PRIM_SUBD) {
@ -577,6 +579,12 @@ Attribute *AttributeSet::add(AttributeStandard std, ustring name)
case ATTR_STD_MOTION_VERTEX_NORMAL:
attr = add(name, TypeNormal, ATTR_ELEMENT_VERTEX_NORMAL_MOTION);
break;
case ATTR_STD_CORNER_NORMAL:
attr = add(name, TypeNormal, ATTR_ELEMENT_CORNER_NORMAL);
break;
case ATTR_STD_MOTION_CORNER_NORMAL:
attr = add(name, TypeNormal, ATTR_ELEMENT_CORNER_NORMAL_MOTION);
break;
case ATTR_STD_PTEX_FACE_ID:
attr = add(name, TypeFloat, ATTR_ELEMENT_FACE);
break;
@ -624,6 +632,9 @@ Attribute *AttributeSet::add(AttributeStandard std, ustring name)
case ATTR_STD_VERTEX_NORMAL:
attr = add(name, TypeNormal, ATTR_ELEMENT_VERTEX_NORMAL);
break;
case ATTR_STD_CORNER_NORMAL:
attr = add(name, TypeNormal, ATTR_ELEMENT_CORNER_NORMAL);
break;
case ATTR_STD_VOLUME_DENSITY:
case ATTR_STD_VOLUME_FLAME:
case ATTR_STD_VOLUME_HEAT:

View file

@ -940,6 +940,7 @@ void GeometryManager::device_update(Device *device,
Mesh *mesh = static_cast<Mesh *>(geom);
if (displace(device, scene, mesh, progress)) {
displacement_done = true;
need_flags_update = true;
}
}
}

View file

@ -472,6 +472,8 @@ void GeometryManager::device_update_attributes(Device *device,
switch (attr.std) {
case ATTR_STD_VERTEX_NORMAL:
case ATTR_STD_MOTION_VERTEX_NORMAL:
case ATTR_STD_CORNER_NORMAL:
case ATTR_STD_MOTION_CORNER_NORMAL:
case ATTR_STD_SHADOW_TRANSPARENCY:
geom_attributes[i].add(attr.std);
break;

View file

@ -29,11 +29,13 @@ CCL_NAMESPACE_BEGIN
struct MikkMeshWrapper {
MikkMeshWrapper(const Mesh *mesh,
const packed_normal *vertex_normal,
const packed_normal *corner_normal,
const float2 *uv,
float3 *tangent,
float *tangent_sign)
: mesh(mesh),
vertex_normal(vertex_normal),
corner_normal(corner_normal),
uv(uv),
tangent(tangent),
tangent_sign(tangent_sign)
@ -86,7 +88,9 @@ struct MikkMeshWrapper {
{
float3 vN;
if (mesh->get_smooth()[face_num]) {
vN = vertex_normal[VertexIndex(face_num, vert_num)].decode();
vN = ((corner_normal) ? corner_normal[CornerIndex(face_num, vert_num)] :
vertex_normal[VertexIndex(face_num, vert_num)])
.decode();
}
else {
const Mesh::Triangle tri = mesh->get_triangle(face_num);
@ -112,6 +116,7 @@ struct MikkMeshWrapper {
const Mesh *mesh;
const packed_normal *vertex_normal;
const packed_normal *corner_normal;
const float2 *uv;
float3 *tangent;
@ -130,12 +135,14 @@ static void mikk_compute_tangents(Attribute *attr_uv,
AttributeSet &attributes = mesh->attributes;
Attribute *attr_vN = attributes.find(ATTR_STD_VERTEX_NORMAL);
if (attr_vN == nullptr) {
Attribute *attr_cN = attributes.find(ATTR_STD_CORNER_NORMAL);
if (attr_vN == nullptr && attr_cN == nullptr) {
/* no normals */
return;
}
const packed_normal *vertex_normal = attr_vN ? attr_vN->data_normal() : nullptr;
const packed_normal *corner_normal = attr_cN ? attr_cN->data_normal() : nullptr;
const float2 *uv = (attr_uv) ? attr_uv->data_float2() : nullptr;
const ustring name = ustring((attr_uv) ? attr_uv->name.string() + tangent_postfix :
@ -163,7 +170,7 @@ static void mikk_compute_tangents(Attribute *attr_uv,
tangent_sign = attr_sign->data_float();
}
MikkMeshWrapper userdata(mesh, vertex_normal, uv, tangent, tangent_sign);
MikkMeshWrapper userdata(mesh, vertex_normal, corner_normal, uv, tangent, tangent_sign);
/* Compute tangents. */
mikk::Mikktspace(userdata).genTangSpace();
}
@ -545,6 +552,14 @@ void Mesh::copy_center_to_motion_step(const int motion_step)
const packed_normal *N = attr_N->data_normal();
std::copy_n(N, numverts, attr_mN->data_normal() + motion_step * numverts);
}
Attribute *attr_mcN = attributes.find(ATTR_STD_MOTION_CORNER_NORMAL);
Attribute *attr_cN = attributes.find(ATTR_STD_CORNER_NORMAL);
if (attr_mcN && attr_cN) {
const size_t numcorners = triangles.size();
packed_normal *N = attr_cN->data_normal();
std::copy_n(N, numcorners, attr_mcN->data_normal() + motion_step * numcorners);
}
}
}
@ -640,6 +655,17 @@ void Mesh::apply_transform(const Transform &tfm, const bool apply_to_motion)
}
}
Attribute *attr_cN = attributes.find(ATTR_STD_CORNER_NORMAL);
if (attr_cN) {
const Transform ntfm = transform_normal;
const size_t num_corners = triangles.size();
packed_normal *cN = attr_cN->data_normal();
for (size_t i = 0; i < num_corners; i++) {
cN[i] = packed_normal(normalize(transform_direction(&ntfm, cN[i].decode())));
}
}
if (apply_to_motion) {
Attribute *attr = attributes.find(ATTR_STD_MOTION_VERTEX_POSITION);
@ -664,11 +690,36 @@ void Mesh::apply_transform(const Transform &tfm, const bool apply_to_motion)
normalize(transform_direction(&ntfm, normal_steps[i].decode())));
}
}
Attribute *attr_mcN = attributes.find(ATTR_STD_MOTION_CORNER_NORMAL);
if (attr_mcN) {
const Transform ntfm = transform_normal;
const size_t steps_size = triangles.size() * (motion_steps - 1);
packed_normal *normal_steps = attr_mcN->data_normal();
for (size_t i = 0; i < steps_size; i++) {
normal_steps[i] = packed_normal(
normalize(transform_direction(&ntfm, normal_steps[i].decode())));
}
}
}
}
void Mesh::add_vertex_normals()
{
if (attributes.find(ATTR_STD_CORNER_NORMAL)) {
/* Not needed if we already have corner normals overriding these.
* If there is motion blur without motion corner normals we can't
* render correctly, discard corner normals. */
if (has_motion_blur() && !attributes.find(ATTR_STD_MOTION_CORNER_NORMAL)) {
attributes.remove(ATTR_STD_CORNER_NORMAL);
}
else {
return;
}
}
const bool flip = transform_negative_scaled;
const size_t verts_size = verts.size();
const size_t triangles_size = num_triangles();
@ -822,7 +873,7 @@ void Mesh::update_tangents(Scene *scene, bool undisplaced)
return;
}
assert(attributes.find(ATTR_STD_VERTEX_NORMAL));
assert(attributes.find(ATTR_STD_VERTEX_NORMAL) || attributes.find(ATTR_STD_CORNER_NORMAL));
ccl::set<ustring> uv_maps;
Attribute *attr_std_uv = attributes.find(ATTR_STD_UV);

View file

@ -187,6 +187,20 @@ bool GeometryManager::displace(Device *device, Scene *scene, Mesh *mesh, Progres
bool need_recompute_vertex_normals = false;
bool need_recompute_all_vertex_normals = false;
/* Corner normals can't be preserved through displacement. */
if (mesh->attributes.find(ATTR_STD_CORNER_NORMAL)) {
mesh->attributes.remove(ATTR_STD_CORNER_NORMAL);
mesh->attributes.add(ATTR_STD_VERTEX_NORMAL);
need_recompute_vertex_normals = true;
need_recompute_all_vertex_normals = true;
}
if (mesh->attributes.find(ATTR_STD_MOTION_CORNER_NORMAL)) {
mesh->attributes.remove(ATTR_STD_MOTION_CORNER_NORMAL);
mesh->attributes.add(ATTR_STD_MOTION_VERTEX_NORMAL);
need_recompute_vertex_normals = true;
need_recompute_all_vertex_normals = true;
}
for (Node *node : mesh->get_used_shaders()) {
Shader *shader = static_cast<Shader *>(node);
if (shader->has_displacement && shader->get_displacement_method() == DISPLACE_TRUE) {
@ -207,7 +221,7 @@ bool GeometryManager::displace(Device *device, Scene *scene, Mesh *mesh, Progres
scene->default_surface;
tri_recompute[i] = shader->has_displacement &&
shader->get_displacement_method() == DISPLACE_TRUE;
shader->get_displacement_method() == DISPLACE_TRUE;
}
}

View file

@ -5,6 +5,7 @@
#include "scene/object.h"
#include "device/device.h"
#include "kernel/types.h"
#include "scene/camera.h"
#include "scene/curves.h"
#include "scene/hair.h"
@ -14,6 +15,7 @@
#include "scene/particles.h"
#include "scene/pointcloud.h"
#include "scene/scene.h"
#include "scene/shader.h"
#include "scene/stats.h"
#include "scene/volume.h"
@ -558,6 +560,9 @@ void ObjectManager::device_update_object_transform(UpdateObjectTransformState *s
{
flag |= SD_OBJECT_HAS_VERTEX_MOTION;
}
else if (mesh->attributes.find(ATTR_STD_CORNER_NORMAL)) {
flag |= SD_OBJECT_HAS_CORNER_NORMALS;
}
}
else if (geom->is_volume()) {
Volume *volume = static_cast<Volume *>(geom);
@ -949,6 +954,14 @@ void ObjectManager::device_update_flags(Device * /*unused*/,
object_flag[object->index] &= ~SD_OBJECT_SHADOW_CATCHER;
}
/* Corner normals might get removed by subdivision and displacement. */
if (object->geometry->attributes.find(ATTR_STD_CORNER_NORMAL)) {
object_flag[object->index] |= SD_OBJECT_HAS_CORNER_NORMALS;
}
else {
object_flag[object->index] &= ~SD_OBJECT_HAS_CORNER_NORMALS;
}
if (bounds_valid) {
object->intersects_volume = false;
for (Object *volume_object : volume_objects) {
@ -979,7 +992,7 @@ void ObjectManager::device_update_geom_offsets(Device * /*unused*/,
DeviceScene *dscene,
Scene *scene)
{
if (scene->objects.size() == 0) {
if (dscene->objects.size() == 0) {
return;
}
@ -997,6 +1010,7 @@ void ObjectManager::device_update_geom_offsets(Device * /*unused*/,
if (attr_map_offset == 0) {
attr_map_offset = geom->attr_map_offset;
}
if (kobject.attribute_map_offset != attr_map_offset) {
kobject.attribute_map_offset = attr_map_offset;
update = true;
@ -1008,8 +1022,15 @@ void ObjectManager::device_update_geom_offsets(Device * /*unused*/,
normal_attr_offset = find_attribute(dscene->attributes_map.data(),
attr_map_offset,
PRIMITIVE_TRIANGLE,
ATTR_STD_VERTEX_NORMAL)
ATTR_STD_CORNER_NORMAL)
.offset;
if (normal_attr_offset == ATTR_STD_NOT_FOUND) {
normal_attr_offset = find_attribute(dscene->attributes_map.data(),
attr_map_offset,
PRIMITIVE_TRIANGLE,
ATTR_STD_VERTEX_NORMAL)
.offset;
}
assert(normal_attr_offset != ATTR_STD_NOT_FOUND ||
static_cast<Mesh *>(geom)->num_triangles() == 0);
}

View file

@ -95,6 +95,8 @@ bool SubdAttributeInterpolation::support_interp_attribute(const Attribute &attr)
/* Smooth normals are computed from derivatives, for linear interpolate. */
case ATTR_STD_VERTEX_NORMAL:
case ATTR_STD_MOTION_VERTEX_NORMAL:
case ATTR_STD_CORNER_NORMAL:
case ATTR_STD_MOTION_CORNER_NORMAL:
if (mesh.get_subdivision_type() == Mesh::SUBDIVISION_CATMULL_CLARK) {
return false;
}
@ -110,11 +112,13 @@ bool SubdAttributeInterpolation::support_interp_attribute(const Attribute &attr)
/* Skip element types that should not exist for subd attributes anyway. */
switch (attr.element) {
case ATTR_ELEMENT_VERTEX:
case ATTR_ELEMENT_VERTEX_MOTION:
case ATTR_ELEMENT_VERTEX_NORMAL:
case ATTR_ELEMENT_VERTEX_NORMAL_MOTION:
case ATTR_ELEMENT_CORNER:
case ATTR_ELEMENT_CORNER_BYTE:
case ATTR_ELEMENT_VERTEX_MOTION:
case ATTR_ELEMENT_VERTEX_NORMAL_MOTION:
case ATTR_ELEMENT_CORNER_NORMAL:
case ATTR_ELEMENT_CORNER_NORMAL_MOTION:
case ATTR_ELEMENT_FACE:
break;
default:
@ -334,13 +338,17 @@ void SubdAttributeInterpolation::setup_attribute_vertex_smooth(const Attribute &
template<typename T>
void SubdAttributeInterpolation::setup_attribute_corner_linear(const Attribute &subd_attr,
Attribute &mesh_attr)
Attribute &mesh_attr,
const int motion_step)
{
SubdAttribute attr;
/* Interpolate values at corners. */
const typename T::Type *subd_data = reinterpret_cast<const typename T::Type *>(subd_attr.data());
typename T::Type *mesh_data = reinterpret_cast<typename T::Type *>(mesh_attr.data());
const typename T::Type *subd_data = reinterpret_cast<const typename T::Type *>(
subd_attr.data()) +
motion_step * mesh.get_subd_face_corners().size();
typename T::Type *mesh_data = reinterpret_cast<typename T::Type *>(mesh_attr.data()) +
motion_step * mesh.num_triangles() * 3;
assert(mesh_data != nullptr);
@ -533,8 +541,6 @@ void SubdAttributeInterpolation::setup_attribute_type(const Attribute &subd_attr
case ATTR_STD_GENERATED:
case ATTR_STD_POSITION_UNDEFORMED:
case ATTR_STD_POSITION_UNDISPLACED:
case ATTR_STD_VERTEX_NORMAL:
case ATTR_STD_NORMAL_UNDISPLACED:
setup_attribute_vertex_smooth<T>(subd_attr, mesh_attr);
break;
default:
@ -550,7 +556,8 @@ void SubdAttributeInterpolation::setup_attribute_type(const Attribute &subd_attr
break;
}
case ATTR_ELEMENT_CORNER:
case ATTR_ELEMENT_CORNER_BYTE: {
case ATTR_ELEMENT_CORNER_BYTE:
case ATTR_ELEMENT_CORNER_NORMAL: {
#ifdef WITH_OPENSUBDIV
if (osd_mesh.use_smooth_fvar(subd_attr)) {
for (const auto &merged_fvar : osd_mesh.merged_fvars) {
@ -582,6 +589,13 @@ void SubdAttributeInterpolation::setup_attribute_type(const Attribute &subd_attr
}
break;
}
case ATTR_ELEMENT_CORNER_NORMAL_MOTION: {
/* Interpolate each motion step individually. */
for (int step = 0; step < mesh.get_motion_steps() - 1; step++) {
setup_attribute_corner_linear<T>(subd_attr, mesh_attr, step);
}
break;
}
case ATTR_ELEMENT_FACE: {
setup_attribute_face<T>(subd_attr, mesh_attr);
break;

View file

@ -56,7 +56,9 @@ class SubdAttributeInterpolation {
const int motion_step = 0);
template<typename T>
void setup_attribute_corner_linear(const Attribute &subd_attr, Attribute &mesh_attr);
void setup_attribute_corner_linear(const Attribute &subd_attr,
Attribute &mesh_attr,
const int motion_step = 0);
#ifdef WITH_OPENSUBDIV
template<typename T>