From 59b48421179589c2d64f8f52b1fc468e108fdbfa Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Tue, 15 Apr 2025 17:18:50 +0200 Subject: [PATCH] Cycles: Adaptive subdivision triangular patches There is a corner case where one side of a quad needs splitting and the other side has only one segment. Previously this would produce either gaps or after recent changes to stitch together geometry, uninitialized memory. Now solve this by splitting into triangular patches, as suggested in the DiagSplit paper. These triangular patches can be further subdivided themselves. Dicing has special cases for 1 or 2 segments on edges. For more segments it works the same as: quad dicing: A regular inner triangle grid stitched to the outer edges. Fix #136973: Inconsistent results with adaptive subdivision Pull Request: https://projects.blender.org/blender/blender/pulls/139062 --- intern/cycles/subd/dice.cpp | 286 +++++++++++++++++- intern/cycles/subd/dice.h | 6 +- intern/cycles/subd/split.cpp | 184 ++++++++++- intern/cycles/subd/split.h | 2 + intern/cycles/subd/subpatch.h | 88 +++++- intern/cycles/util/algorithm.h | 2 +- .../cycles_renders/both_displacement.png | 4 +- .../cycles_renders/compare_bump.png | 4 +- .../cycles_renders/dicing_camera.png | 4 +- .../cycles_renders/offscreen_dicing.png | 4 +- 10 files changed, 552 insertions(+), 32 deletions(-) diff --git a/intern/cycles/subd/dice.cpp b/intern/cycles/subd/dice.cpp index 370cbc867eb..b89ca0071ef 100644 --- a/intern/cycles/subd/dice.cpp +++ b/intern/cycles/subd/dice.cpp @@ -333,7 +333,7 @@ void EdgeDice::add_triangle_strip(const SubPatch &sub, const int left_edge, cons } } -void EdgeDice::set_sides(const SubPatch &sub) +void EdgeDice::quad_set_sides(const SubPatch &sub) { for (int edge = 0; edge < 4; edge++) { const int t = sub.edges[edge].edge->T; @@ -367,7 +367,7 @@ void EdgeDice::set_sides(const SubPatch &sub) } } -void EdgeDice::dice(const SubPatch &sub) +void EdgeDice::quad_dice(const SubPatch &sub) { /* Compute inner grid size with scale factor. */ const int Mu = max(sub.edges[0].edge->T, sub.edges[2].edge->T); @@ -394,6 +394,272 @@ void EdgeDice::dice(const SubPatch &sub) } } +void EdgeDice::tri_set_sides(const SubPatch &sub) +{ + for (int edge = 0; edge < 3; edge++) { + const int t = sub.edges[edge].edge->T; + const int i_start = (sub.edges[edge].own_vertex) ? 0 : 1; + const int i_end = (sub.edges[edge].own_edge) ? t : 1; + + /* set verts on the edge of the patch */ + for (int i = i_start; i < i_end; i++) { + const float f = i / (float)t; + + float2 uv; + switch (edge) { + case 0: + uv = make_float2(f, 0.0f); + break; + case 1: + uv = make_float2(1.0f - f, f); + break; + case 2: + default: + uv = make_float2(0.0f, 1.0f - f); + break; + } + + const int vert_index = sub.get_vert_along_edge(edge, i); + set_vertex(sub, vert_index, sub.map_uv(uv)); + } + } +} + +void EdgeDice::tri_dice(const SubPatch &sub) +{ + const int M = max(max(sub.edges[0].edge->T, sub.edges[1].edge->T), sub.edges[2].edge->T); + const float d = 1.0f / (float)(M + 1); + + int triangle_index = sub.triangles_offset; + + if (M == 1) { + /* Single triangle. */ + set_triangle(sub, + triangle_index++, + sub.edges[0].start_vert_index(), + sub.edges[1].start_vert_index(), + sub.edges[2].start_vert_index(), + sub.map_uv(make_float2(0.0f, 0.0f)), + sub.map_uv(make_float2(1.0f, 0.0f)), + sub.map_uv(make_float2(0.0f, 1.0f))); + assert(triangle_index == sub.triangles_offset + sub.calc_num_triangles()); + return; + } + if (M == 2) { + /* Edges have 2 segments or less. */ + int num_split = 0; + int split_0 = -1; + for (int i = 0; i < 3; i++) { + if (sub.edges[i].edge->T == 2) { + num_split++; + if (split_0 == -1) { + split_0 = i; + } + } + } + /* When two edges have 2 segments, we assume split_0 is the first of two consecutive edges. */ + if (split_0 == 0 && sub.edges[2].edge->T == 2) { + split_0 = 2; + } + + const int split_1 = (split_0 + 1) % 3; + const int split_2 = (split_0 + 2) % 3; + + const int v[3] = {sub.edges[0].start_vert_index(), + sub.edges[1].start_vert_index(), + sub.edges[2].start_vert_index()}; + const int mid_v[3] = {sub.get_vert_along_edge(0, 1), + sub.get_vert_along_edge(1, 1), + sub.get_vert_along_edge(2, 1)}; + const float2 uv[3] = {sub.map_uv(make_float2(0.0f, 0.0f)), + sub.map_uv(make_float2(1.0f, 0.0f)), + sub.map_uv(make_float2(0.0f, 1.0f))}; + const float2 mid_uv[3] = {sub.map_uv(make_float2(0.5f, 0.0f)), + sub.map_uv(make_float2(0.5f, 0.5f)), + sub.map_uv(make_float2(0.0f, 0.5f))}; + + if (num_split == 3) { + /* All edges have two segments + * /\ + * /--\ + * / \/ \ + * ------- */ + set_triangle(sub, triangle_index++, v[0], mid_v[0], mid_v[2], uv[0], mid_uv[0], mid_uv[2]); + set_triangle(sub, triangle_index++, v[1], mid_v[1], mid_v[0], uv[1], mid_uv[1], mid_uv[0]); + set_triangle(sub, triangle_index++, v[2], mid_v[2], mid_v[1], uv[2], mid_uv[2], mid_uv[1]); + set_triangle( + sub, triangle_index++, mid_v[0], mid_v[1], mid_v[2], mid_uv[0], mid_uv[1], mid_uv[2]); + } + else { + /* One edge has two segments. + * / \ + * / | \ + * / | \ + * ------- */ + set_triangle(sub, + triangle_index++, + v[split_0], + mid_v[split_0], + v[split_2], + uv[split_0], + mid_uv[split_0], + uv[split_2]); + if (num_split == 1) { + set_triangle(sub, + triangle_index++, + mid_v[split_0], + v[split_1], + v[split_2], + mid_uv[split_0], + uv[split_1], + uv[split_2]); + } + else { + /* Two edges have two segments. + * /|\ + * / | \ + * / |/ \ + * ------- */ + set_triangle(sub, + triangle_index++, + mid_v[split_0], + v[split_1], + mid_v[split_1], + mid_uv[split_0], + uv[split_1], + mid_uv[split_1]); + set_triangle(sub, + triangle_index++, + mid_v[split_0], + mid_v[split_1], + v[split_2], + mid_uv[split_0], + mid_uv[split_1], + uv[split_2]); + } + } + assert(triangle_index == sub.triangles_offset + sub.calc_num_triangles()); + return; + } + + const int inner_M = M - 2; + + for (int j = 0; j < inner_M; j++) { + for (int i = 0; i < j + 1; i++) { + const int i_next = i + 1; + const int j_next = j + 1; + + const float2 inner_uv = make_float2(d, d); + + const int v0 = sub.get_inner_grid_vert_triangle(i, j); + const int v1 = sub.get_inner_grid_vert_triangle(i_next, j_next); + const int v2 = sub.get_inner_grid_vert_triangle(i, j_next); + + const float2 uv0 = sub.map_uv(inner_uv + make_float2(i, j - i) * d); + const float2 uv1 = sub.map_uv(inner_uv + make_float2(i_next, j - i) * d); + const float2 uv2 = sub.map_uv(inner_uv + make_float2(i, j_next - i) * d); + + set_vertex(sub, v0, uv0); + if (j == inner_M - 1) { + set_vertex(sub, v1, uv1); + set_vertex(sub, v2, uv2); + } + + set_triangle(sub, triangle_index++, v0, v1, v2, uv0, uv1, uv2); + + if (i < j) { + const int v3 = sub.get_inner_grid_vert_triangle(i_next, j); + const float2 uv3 = sub.map_uv(inner_uv + make_float2(i_next, j - i_next) * d); + + set_vertex(sub, v3, uv3); + + set_triangle(sub, triangle_index++, v0, v3, v1, uv0, uv3, uv1); + } + } + } + + assert(triangle_index == sub.triangles_offset + inner_M * inner_M); + + /* Stitch inner grid to edges. */ + for (int edge = 0; edge < 3; edge++) { + const int outer_T = sub.edges[edge].edge->T; + const int inner_T = inner_M; + + float2 inner_uv, outer_uv, inner_uv_step, outer_uv_step; + switch (edge) { + case 0: + inner_uv = make_float2(d, d); + outer_uv = make_float2(0.0f, 0.0f); + inner_uv_step = make_float2(d, 0.0f); + outer_uv_step = make_float2(1.0f / (float)outer_T, 0.0f); + break; + case 1: + inner_uv = make_float2(1.0f - 2.0f * d, d); + outer_uv = make_float2(1.0f, 0.0f); + inner_uv_step = make_float2(-d, d); + outer_uv_step = make_float2(-1.0f / (float)outer_T, 1.0f / (float)outer_T); + break; + case 2: + default: + inner_uv = make_float2(d, 1.0f - 2.0f * d); + outer_uv = make_float2(0.0f, 1.0f); + inner_uv_step = make_float2(0.0f, -d); + outer_uv_step = make_float2(0.0f, -1.0f / (float)outer_T); + break; + } + + /* Stitch together two arrays of verts with triangles. At each step, we compare using + * the next verts on both sides, to find the split direction with the smallest + * diagonal, and use that in order to keep the triangle shape reasonable. */ + for (size_t i = 0, j = 0; i < inner_T || j < outer_T;) { + const int v0 = sub.get_vert_along_grid_edge(edge, i); + const int v1 = sub.get_vert_along_edge(edge, j); + int v2; + + const float2 uv0 = sub.map_uv(inner_uv); + const float2 uv1 = sub.map_uv(outer_uv); + float2 uv2; + + if (j == outer_T) { + v2 = sub.get_vert_along_grid_edge(edge, ++i); + inner_uv += inner_uv_step; + uv2 = sub.map_uv(inner_uv); + } + else if (i == inner_T) { + v2 = sub.get_vert_along_edge(edge, ++j); + outer_uv += outer_uv_step; + uv2 = sub.map_uv(outer_uv); + } + else { + /* Length of diagonals. */ + const int v2_a = sub.get_vert_along_edge(edge, j + 1); + const int v2_b = sub.get_vert_along_grid_edge(edge, i + 1); + + const float len_a = len_squared(mesh_P[v0] - mesh_P[v2_a]); + const float len_b = len_squared(mesh_P[v1] - mesh_P[v2_b]); + + /* Use smallest diagonal. */ + if (len_a < len_b) { + v2 = v2_a; + outer_uv += outer_uv_step; + uv2 = sub.map_uv(outer_uv); + j++; + } + else { + v2 = v2_b; + inner_uv += inner_uv_step; + uv2 = sub.map_uv(inner_uv); + i++; + } + } + + set_triangle(sub, triangle_index++, v0, v1, v2, uv0, uv1, uv2); + } + } + + assert(triangle_index == sub.triangles_offset + sub.calc_num_triangles()); +} + void EdgeDice::dice(const DiagSplit &split) { const size_t num_subpatches = split.get_num_subpatches(); @@ -402,14 +668,26 @@ void EdgeDice::dice(const DiagSplit &split) * on these coordinates and they are unique assigned to a subpatch for determinism. */ parallel_for(blocked_range(0, num_subpatches, 8), [&](const blocked_range &r) { for (size_t i = r.begin(); i != r.end(); i++) { - set_sides(split.get_subpatch(i)); + const SubPatch &subpatch = split.get_subpatch(i); + if (subpatch.shape == SubPatch::TRIANGLE) { + tri_set_sides(subpatch); + } + else { + quad_set_sides(subpatch); + } } }); /* Inner vertex coordinates and triangles. */ parallel_for(blocked_range(0, num_subpatches, 8), [&](const blocked_range &r) { for (size_t i = r.begin(); i != r.end(); i++) { - dice(split.get_subpatch(i)); + const SubPatch &subpatch = split.get_subpatch(i); + if (subpatch.shape == SubPatch::TRIANGLE) { + tri_dice(subpatch); + } + else { + quad_dice(subpatch); + } } }); } diff --git a/intern/cycles/subd/dice.h b/intern/cycles/subd/dice.h index 0ec855f34ad..8e9f181cd57 100644 --- a/intern/cycles/subd/dice.h +++ b/intern/cycles/subd/dice.h @@ -56,7 +56,8 @@ class EdgeDice { void dice(const DiagSplit &split); protected: - void dice(const SubPatch &sub); + void tri_dice(const SubPatch &sub); + void quad_dice(const SubPatch &sub); void set_vertex(const SubPatch &sub, const int index, const float2 uv); void set_triangle(const SubPatch &sub, @@ -73,7 +74,8 @@ class EdgeDice { float3 eval_projected(const SubPatch &sub, const float2 uv); - void set_sides(const SubPatch &sub); + void tri_set_sides(const SubPatch &sub); + void quad_set_sides(const SubPatch &sub); float quad_area(const float3 &a, const float3 &b, const float3 &c, const float3 &d); float scale_factor(const SubPatch &sub, const int Mu, const int Mv); diff --git a/intern/cycles/subd/split.cpp b/intern/cycles/subd/split.cpp index 9e913d7f669..6ea909a5103 100644 --- a/intern/cycles/subd/split.cpp +++ b/intern/cycles/subd/split.cpp @@ -70,7 +70,9 @@ void DiagSplit::alloc_subpatch(SubPatch &&sub) assert(sub.edges[0].edge->T >= 1); assert(sub.edges[1].edge->T >= 1); assert(sub.edges[2].edge->T >= 1); - assert(sub.edges[3].edge->T >= 1); + if (sub.shape == SubPatch::QUAD) { + assert(sub.edges[3].edge->T >= 1); + } sub.inner_grid_vert_offset = alloc_verts(sub.calc_num_inner_verts()); sub.triangles_offset = num_triangles; @@ -205,25 +207,32 @@ void DiagSplit::assign_edge_factor(SubEdge *edge, void DiagSplit::resolve_edge_factors(const SubPatch &sub) { - /* Compute edge factor if not already set. Or if DSPLIT_NON_UNIFORM and splitting is - * no longer possible because the opposite side can't be split. */ SubEdge *edge0 = sub.edges[0].edge; SubEdge *edge1 = sub.edges[1].edge; SubEdge *edge2 = sub.edges[2].edge; - SubEdge *edge3 = sub.edges[3].edge; /* Compute edge factor if not already set. */ - if (edge0->T == 0 || (edge0->must_split() && edge2->T == 1)) { + if (edge0->T == 0) { assign_edge_factor(edge0, sub.patch, sub.uvs[0], sub.uvs[1], true); } - if (edge1->T == 0 || (edge0->must_split() && edge3->T == 1)) { + if (edge1->T == 0) { assign_edge_factor(edge1, sub.patch, sub.uvs[1], sub.uvs[2], true); } - if (edge2->T == 0 || (edge0->must_split() && edge0->T == 1)) { - assign_edge_factor(edge2, sub.patch, sub.uvs[2], sub.uvs[3], true); + + if (sub.shape == SubPatch::TRIANGLE) { + if (edge2->T == 0) { + assign_edge_factor(edge2, sub.patch, sub.uvs[2], sub.uvs[0], true); + } } - if (edge3->T == 0 || (edge0->must_split() && edge1->T == 1)) { - assign_edge_factor(edge3, sub.patch, sub.uvs[3], sub.uvs[0], true); + else { + SubEdge *edge3 = sub.edges[3].edge; + + if (edge2->T == 0) { + assign_edge_factor(edge2, sub.patch, sub.uvs[2], sub.uvs[3], true); + } + if (edge3->T == 0) { + assign_edge_factor(edge3, sub.patch, sub.uvs[3], sub.uvs[0], true); + } } } @@ -369,6 +378,15 @@ void DiagSplit::split_quad(SubPatch &&sub) return; } + /* Split into triangles if one side must the split, and the opposite side has + * only a single segment. Then we can't do an even split across the quad. */ + if ((split_u && (sub.edges[0].edge->T == 1 || sub.edges[2].edge->T == 1)) || + (!split_u && (sub.edges[1].edge->T == 1 || sub.edges[3].edge->T == 1))) + { + split_quad_into_triangles(std::move(sub)); + return; + } + /* Copy into new subpatches. */ SubPatch sub_a(sub); SubPatch sub_b(sub); @@ -501,6 +519,152 @@ void DiagSplit::split_quad(SubPatch &&sub) split_quad(std::move(sub_b)); } +void DiagSplit::split_quad_into_triangles(SubPatch &&sub) +{ + assert(sub.shape == SubPatch::QUAD); + + /* Copy into new subpatches. */ + SubPatch sub_a(sub); + SubPatch sub_b(sub); + + sub_a.shape = SubPatch::TRIANGLE; + sub_b.shape = SubPatch::TRIANGLE; + + for (int i = 0; i < 4; i++) { + sub_a.edges[i].own_edge = false; + sub_a.edges[i].own_vertex = false; + sub_b.edges[i].own_edge = false; + sub_b.edges[i].own_vertex = false; + } + + const int split_edge_depth = std::max({sub.edges[0].edge->depth, + sub.edges[1].edge->depth, + sub.edges[2].edge->depth, + sub.edges[3].edge->depth}); + + sub_a.edges[0] = sub.edges[0]; + sub_a.edges[1] = sub.edges[1]; + sub_a.uvs[0] = sub.uvs[0]; + sub_a.uvs[1] = sub.uvs[1]; + sub_a.uvs[2] = sub.uvs[2]; + alloc_edge(&sub_a.edges[2], + sub.edges[2].start_vert_index(), + sub.edges[0].start_vert_index(), + split_edge_depth, + true, + false); + + sub_b.edges[1] = sub.edges[2]; + sub_b.edges[2] = sub.edges[3]; + sub_b.uvs[0] = sub.uvs[0]; + sub_b.uvs[1] = sub.uvs[2]; + sub_b.uvs[2] = sub.uvs[3]; + alloc_edge(&sub_b.edges[0], + sub.edges[0].start_vert_index(), + sub.edges[2].start_vert_index(), + split_edge_depth, + true, + false); + + /* Set T for new edge. */ + assign_edge_factor(sub_b.edges[0].edge, sub.patch, sub.uvs[0], sub.uvs[2]); + + /* Recurse */ + split_triangle(std::move(sub_a)); + split_triangle(std::move(sub_b)); +} + +void DiagSplit::split_triangle(SubPatch &&sub) +{ + assert(sub.shape == SubPatch::TRIANGLE); + + /* Set edge factors if we haven't already. */ + resolve_edge_factors(sub); + + const bool do_split = sub.edges[0].edge->must_split() || sub.edges[1].edge->must_split() || + sub.edges[2].edge->must_split(); + if (!do_split) { + /* Add the unsplit subpatch. */ + alloc_subpatch(std::move(sub)); + return; + } + + /* Slight bias so that for equal length edges, we get consistent results across + * platforms rather than choice being decided by precision. */ + const float bias = 1.00012345f; + + /* Pick longest edge that must be split. */ + float max_length = 0; + int split_index_0 = 0; + for (int i = 0; i < 3; i++) { + if (sub.edges[i].edge->must_split() && sub.edges[i].edge->length > max_length) { + split_index_0 = i; + max_length = sub.edges[i].edge->length * bias; + } + } + + /* Copy into new subpatches. */ + SubPatch sub_a(sub); + SubPatch sub_b(sub); + + for (int i = 0; i < 4; i++) { + sub_a.edges[i].own_edge = false; + sub_a.edges[i].own_vertex = false; + sub_b.edges[i].own_edge = false; + sub_b.edges[i].own_vertex = false; + } + + const int split_index_1 = (split_index_0 + 1) % 3; + const int split_index_2 = (split_index_0 + 2) % 3; + + sub_a.edges[2] = sub.edges[split_index_2]; + sub_b.edges[1] = sub.edges[split_index_1]; + + /* + * uv_opposite + * 2 2 + * / | | \ + * / | | \ + * / A | | B \ + * / | | \ + * 0 --- 1 0 --- 1 + * uv_split + */ + + /* Allocate new edges and vertices. */ + const float2 uv_split = split_edge(sub.patch, + &sub.edges[split_index_0], + &sub_a.edges[0], + &sub_b.edges[0], + sub.uvs[split_index_0], + sub.uvs[split_index_1]); + + /* Set UVs. */ + sub_a.uvs[0] = sub.uvs[split_index_0]; + sub_a.uvs[1] = uv_split; + sub_a.uvs[2] = sub.uvs[split_index_2]; + sub_b.uvs[0] = uv_split; + sub_b.uvs[1] = sub.uvs[split_index_1]; + sub_b.uvs[2] = sub.uvs[split_index_2]; + + /* Create new edge */ + const int vsplit = sub.edges[split_index_0].mid_vert_index(); + const int vopposite = sub.edges[split_index_2].start_vert_index(); + + const int split_edge_depth = sub.edges[split_index_0].edge->depth + 1; + + alloc_edge(&sub_a.edges[1], vsplit, vopposite, split_edge_depth, true, false); + alloc_edge(&sub_b.edges[2], vopposite, vsplit, split_edge_depth, true, false); + + /* Set T for split edge. */ + const float2 uv_opposite = sub.uvs[split_index_2]; + assign_edge_factor(sub_a.edges[1].edge, sub.patch, uv_split, uv_opposite); + + /* Recurse */ + split_triangle(std::move(sub_a)); + split_triangle(std::move(sub_b)); +} + void DiagSplit::split_quad(const Mesh::SubdFace &face, const int face_index, const Patch *patch) { const int *subd_face_corners = params.mesh->get_subd_face_corners().data(); diff --git a/intern/cycles/subd/split.h b/intern/cycles/subd/split.h index 858283e52a4..514d5c3249d 100644 --- a/intern/cycles/subd/split.h +++ b/intern/cycles/subd/split.h @@ -70,6 +70,8 @@ class DiagSplit { float2 uv_start, float2 uv_end); void split_quad(SubPatch &&sub); + void split_triangle(SubPatch &&sub); + void split_quad_into_triangles(SubPatch &&sub); void split_quad(const Mesh::SubdFace &face, const int face_index, const Patch *patch); void split_ngon(const Mesh::SubdFace &face, const int face_index, diff --git a/intern/cycles/subd/subpatch.h b/intern/cycles/subd/subpatch.h index f7df35ab348..6032d9ecca1 100644 --- a/intern/cycles/subd/subpatch.h +++ b/intern/cycles/subd/subpatch.h @@ -95,6 +95,8 @@ class SubPatch { /* Face and corner. */ int face_index = 0; int corner = 0; + /* Is a triangular patch instead of a quad patch? */ + enum { TRIANGLE, QUAD } shape = QUAD; /* Vertex indices for inner grid start at this index. */ int inner_grid_vert_offset = 0; /* Triangle indices. */ @@ -138,13 +140,22 @@ class SubPatch { }; /* - * edges[2] - * uv3 ←------------ uv2 - * | ↑ - * edges[3] | | edges[1] - * ↓ | - * uv0 ------------→ uv1 - * edges[0] + * edge2 + * uv3 ←------------ uv2 + * | ↑ + * edge3 | | edge1 + * ↓ | + * uv0 ------------→ uv1 + * edge0 + * + * uv2 + * | \ + * | \ + * edge2 | \ edge1 + * | \ + * ↓ \ + * uv0 --→ uv1 + * edge0 */ /* UV within patch, counter-clockwise starting from uv (0, 0) towards (1, 0) etc. */ @@ -160,6 +171,16 @@ class SubPatch { int calc_num_inner_verts() const { + if (shape == TRIANGLE) { + const int M = max(max(edges[0].edge->T, edges[1].edge->T), edges[2].edge->T); + if (M <= 2) { + /* No inner grid. */ + return 0; + } + /* 1 + 2 + .. + M-1 */ + return M * (M - 1) / 2; + } + const int Mu = max(edges[0].edge->T, edges[2].edge->T); const int Mv = max(edges[3].edge->T, edges[1].edge->T); return (Mu - 1) * (Mv - 1); @@ -167,6 +188,22 @@ class SubPatch { int calc_num_triangles() const { + if (shape == TRIANGLE) { + const int M = max(max(edges[0].edge->T, edges[1].edge->T), edges[2].edge->T); + if (M == 1) { + return 1; + } + if (M == 2) { + return edges[0].edge->T + edges[1].edge->T + edges[2].edge->T - 2; + } + + const int inner_M = M - 2; + const int inner_triangles = inner_M * inner_M; + const int edge_triangles = edges[0].edge->T + edges[1].edge->T + edges[2].edge->T + + inner_M * 3; + return inner_triangles + edge_triangles; + } + const int Mu = max(edges[0].edge->T, edges[2].edge->T); const int Mv = max(edges[3].edge->T, edges[1].edge->T); @@ -194,8 +231,39 @@ class SubPatch { return get_vert_along_edge(edge, edges[edge].edge->T - n); } + int get_inner_grid_vert_triangle(int i, int j) const + { + /* Rowows (1 + 2 + .. + j), and column i. */ + const int offset = j * (j + 1) / 2 + i; + assert(offset < calc_num_inner_verts()); + return inner_grid_vert_offset + offset; + } + int get_vert_along_grid_edge(const int edge, const int n) const { + if (shape == TRIANGLE) { + const int M = max(max(edges[0].edge->T, edges[1].edge->T), edges[2].edge->T); + const int inner_M = M - 2; + assert(M >= 2); + + switch (edge) { + case 0: { + return get_inner_grid_vert_triangle(n, n); + } + case 1: { + return get_inner_grid_vert_triangle(inner_M - n, inner_M); + } + case 2: { + return get_inner_grid_vert_triangle(0, inner_M - n); + } + default: + assert(0); + break; + } + + return -1; + } + const int Mu = max(edges[0].edge->T, edges[2].edge->T); const int Mv = max(edges[3].edge->T, edges[1].edge->T); @@ -227,6 +295,12 @@ class SubPatch { float2 map_uv(float2 uv) const { /* Map UV from subpatch to patch parametric coordinates. */ + if (shape == TRIANGLE) { + return clamp((1.0f - uv.x - uv.y) * uvs[0] + uv.x * uvs[1] + uv.y * uvs[2], + zero_float2(), + one_float2()); + } + const float2 d0 = interp(uvs[0], uvs[3], uv.y); const float2 d1 = interp(uvs[1], uvs[2], uv.y); return clamp(interp(d0, d1, uv.x), zero_float2(), one_float2()); diff --git a/intern/cycles/util/algorithm.h b/intern/cycles/util/algorithm.h index 6bda4aa1bdc..6d551fdddd6 100644 --- a/intern/cycles/util/algorithm.h +++ b/intern/cycles/util/algorithm.h @@ -4,7 +4,7 @@ #pragma once -#include +#include // IWYU pragma: export CCL_NAMESPACE_BEGIN diff --git a/tests/files/render/displacement/cycles_renders/both_displacement.png b/tests/files/render/displacement/cycles_renders/both_displacement.png index 6d9942b2e85..978343281ab 100644 --- a/tests/files/render/displacement/cycles_renders/both_displacement.png +++ b/tests/files/render/displacement/cycles_renders/both_displacement.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0715f26581e01a11c332cdae59f15616635339de74530e8a894dde1be2da60e0 -size 30580 +oid sha256:24824ddd837c2921921817dd30159217d31f905005e2a21b3d264a985787f0b5 +size 30511 diff --git a/tests/files/render/displacement/cycles_renders/compare_bump.png b/tests/files/render/displacement/cycles_renders/compare_bump.png index 1e9f4795b79..7b34cb77d72 100644 --- a/tests/files/render/displacement/cycles_renders/compare_bump.png +++ b/tests/files/render/displacement/cycles_renders/compare_bump.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd2d001fa967bac87f6e4aed835e83e6c3cc000a37a62d6e7661b9bb7fb5e658 -size 26121 +oid sha256:dafa90dd5c8474d7e505515aff6c2a6d6917abd40e216494d8d6e37e1c894000 +size 26053 diff --git a/tests/files/render/displacement/cycles_renders/dicing_camera.png b/tests/files/render/displacement/cycles_renders/dicing_camera.png index aa367dac4ae..3d13d84b533 100644 --- a/tests/files/render/displacement/cycles_renders/dicing_camera.png +++ b/tests/files/render/displacement/cycles_renders/dicing_camera.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28059841afd02a3030197e250b652c6a3c28fb0485cd87f27a28b746c2208c1d -size 29405 +oid sha256:63882d0296fe5ca23fd97af91bd936fd87c1640d91ed11984f2ab1282de56804 +size 30618 diff --git a/tests/files/render/displacement/cycles_renders/offscreen_dicing.png b/tests/files/render/displacement/cycles_renders/offscreen_dicing.png index d2532b3ef79..d551d419221 100644 --- a/tests/files/render/displacement/cycles_renders/offscreen_dicing.png +++ b/tests/files/render/displacement/cycles_renders/offscreen_dicing.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a8c87444559c09146aee894185b479a771444d36ddf898988146f5884b249320 -size 27961 +oid sha256:8eea87256e9949a318373ffa984c08d9afa84bac1d17d3ac14242b46aa1e151d +size 28092