Refactor: Move more subdivision code into subd/

No logic or functional changes.

Pull Request: https://projects.blender.org/blender/blender/pulls/135681
This commit is contained in:
Brecht Van Lommel
2025-03-09 02:15:10 +01:00
parent 6ec541ca4e
commit 189efd8eeb
6 changed files with 448 additions and 376 deletions

View File

@@ -1,393 +1,20 @@
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
/* SPDX-FileCopyrightText: 2011-2025 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "scene/attribute.h"
#include "scene/camera.h"
#include "scene/mesh.h"
#include "subd/osd.h"
#include "subd/patch.h"
#include "subd/patch_table.h"
#include "subd/split.h"
#include "util/algorithm.h"
#include "util/vector.h"
CCL_NAMESPACE_BEGIN
#ifdef WITH_OPENSUBDIV
CCL_NAMESPACE_END
# include <opensubdiv/far/patchMap.h>
# include <opensubdiv/far/patchTableFactory.h>
# include <opensubdiv/far/primvarRefiner.h>
# include <opensubdiv/far/topologyRefinerFactory.h>
/* specializations of TopologyRefinerFactory for ccl::Mesh */
namespace OpenSubdiv::OPENSUBDIV_VERSION::Far {
template<>
bool TopologyRefinerFactory<ccl::Mesh>::resizeComponentTopology(TopologyRefiner &refiner,
const ccl::Mesh &mesh)
{
setNumBaseVertices(refiner, mesh.get_verts().size());
setNumBaseFaces(refiner, mesh.get_num_subd_faces());
for (int i = 0; i < mesh.get_num_subd_faces(); i++) {
setNumBaseFaceVertices(refiner, i, mesh.get_subd_num_corners()[i]);
}
return true;
}
template<>
bool TopologyRefinerFactory<ccl::Mesh>::assignComponentTopology(TopologyRefiner &refiner,
const ccl::Mesh &mesh)
{
const ccl::array<int> &subd_face_corners = mesh.get_subd_face_corners();
const ccl::array<int> &subd_start_corner = mesh.get_subd_start_corner();
const ccl::array<int> &subd_num_corners = mesh.get_subd_num_corners();
for (int i = 0; i < mesh.get_num_subd_faces(); i++) {
IndexArray face_verts = getBaseFaceVertices(refiner, i);
const int start_corner = subd_start_corner[i];
int *corner = &subd_face_corners[start_corner];
for (int j = 0; j < subd_num_corners[i]; j++, corner++) {
face_verts[j] = *corner;
}
}
return true;
}
template<>
bool TopologyRefinerFactory<ccl::Mesh>::assignComponentTags(TopologyRefiner &refiner,
const ccl::Mesh &mesh)
{
/* Historical maximum crease weight used at Pixar, influencing the maximum in OpenSubDiv. */
static constexpr float CREASE_SCALE = 10.0f;
const size_t num_creases = mesh.get_subd_creases_weight().size();
const size_t num_vertex_creases = mesh.get_subd_vert_creases().size();
/* The last loop is over the vertices, so early exit to avoid iterating them needlessly. */
if (num_creases == 0 && num_vertex_creases == 0) {
return true;
}
for (int i = 0; i < num_creases; i++) {
const ccl::Mesh::SubdEdgeCrease crease = mesh.get_subd_crease(i);
const Index edge = findBaseEdge(refiner, crease.v[0], crease.v[1]);
if (edge != INDEX_INVALID) {
setBaseEdgeSharpness(refiner, edge, crease.crease * CREASE_SCALE);
}
}
std::map<int, float> vertex_creases;
for (size_t i = 0; i < num_vertex_creases; ++i) {
const int vertex_idx = mesh.get_subd_vert_creases()[i];
const float weight = mesh.get_subd_vert_creases_weight()[i];
vertex_creases[vertex_idx] = weight * CREASE_SCALE;
}
for (int i = 0; i < mesh.get_verts().size(); i++) {
float sharpness = 0.0f;
const std::map<int, float>::const_iterator iter = vertex_creases.find(i);
if (iter != vertex_creases.end()) {
sharpness = iter->second;
}
const ConstIndexArray vert_edges = getBaseVertexEdges(refiner, i);
if (vert_edges.size() == 2) {
const float sharpness0 = refiner.getLevel(0).getEdgeSharpness(vert_edges[0]);
const float sharpness1 = refiner.getLevel(0).getEdgeSharpness(vert_edges[1]);
sharpness += ccl::min(sharpness0, sharpness1);
sharpness = ccl::min(sharpness, CREASE_SCALE);
}
if (sharpness != 0.0f) {
setBaseVertexSharpness(refiner, i, sharpness);
}
}
return true;
}
template<>
bool TopologyRefinerFactory<ccl::Mesh>::assignFaceVaryingTopology(TopologyRefiner & /*refiner*/,
const ccl::Mesh & /*mesh*/)
{
return true;
}
template<>
void TopologyRefinerFactory<ccl::Mesh>::reportInvalidTopology(TopologyError /*err_code*/,
const char * /*msg*/,
const ccl::Mesh & /*mesh*/)
{
}
} // namespace OpenSubdiv::OPENSUBDIV_VERSION::Far
CCL_NAMESPACE_BEGIN
using namespace OpenSubdiv;
/* struct that implements OpenSubdiv's vertex interface */
template<typename T> struct OsdValue {
T value;
OsdValue() = default;
void Clear(void * /*unused*/ = nullptr)
{
memset(&value, 0, sizeof(T));
}
void AddWithWeight(const OsdValue<T> &src, const float weight)
{
value += src.value * weight;
}
};
template<> void OsdValue<uchar4>::AddWithWeight(const OsdValue<uchar4> &src, const float weight)
{
for (int i = 0; i < 4; i++) {
value[i] += (uchar)(src.value[i] * weight);
}
}
/* class for holding OpenSubdiv data used during tessellation */
class OsdData {
Mesh *mesh = nullptr;
vector<OsdValue<float3>> verts;
unique_ptr<Far::TopologyRefiner> refiner;
unique_ptr<Far::PatchTable> patch_table;
unique_ptr<Far::PatchMap> patch_map;
public:
OsdData() = default;
void build_from_mesh(Mesh *mesh_)
{
mesh = mesh_;
/* type and options */
const Sdc::SchemeType type = Sdc::SCHEME_CATMARK;
Sdc::Options options;
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
/* create refiner */
refiner.reset(Far::TopologyRefinerFactory<Mesh>::Create(
*mesh, Far::TopologyRefinerFactory<Mesh>::Options(type, options)));
/* adaptive refinement */
const int max_isolation = calculate_max_isolation();
refiner->RefineAdaptive(Far::TopologyRefiner::AdaptiveOptions(max_isolation));
/* create patch table */
Far::PatchTableFactory::Options patch_options;
patch_options.endCapType = Far::PatchTableFactory::Options::ENDCAP_GREGORY_BASIS;
patch_table.reset(Far::PatchTableFactory::Create(*refiner, patch_options));
/* interpolate verts */
const int num_refiner_verts = refiner->GetNumVerticesTotal();
const int num_local_points = patch_table->GetNumLocalPoints();
verts.resize(num_refiner_verts + num_local_points);
for (int i = 0; i < mesh->get_verts().size(); i++) {
verts[i].value = mesh->get_verts()[i];
}
OsdValue<float3> *src = verts.data();
for (int i = 0; i < refiner->GetMaxLevel(); i++) {
OsdValue<float3> *dest = src + refiner->GetLevel(i).GetNumVertices();
Far::PrimvarRefiner(*refiner).Interpolate(i + 1, src, dest);
src = dest;
}
if (num_local_points) {
patch_table->ComputeLocalPointValues(verts.data(), &verts[num_refiner_verts]);
}
/* create patch map */
patch_map = make_unique<Far::PatchMap>(*patch_table);
}
void subdivide_attribute(Attribute &attr)
{
const Far::PrimvarRefiner primvar_refiner(*refiner);
if (attr.element == ATTR_ELEMENT_VERTEX) {
const int num_refiner_verts = refiner->GetNumVerticesTotal();
const int num_local_points = patch_table->GetNumLocalPoints();
attr.resize(num_refiner_verts + num_local_points);
attr.flags |= ATTR_FINAL_SIZE;
char *src = attr.buffer.data();
for (int i = 0; i < refiner->GetMaxLevel(); i++) {
char *dest = src + refiner->GetLevel(i).GetNumVertices() * attr.data_sizeof();
if (ccl::Attribute::same_storage(attr.type, TypeFloat)) {
primvar_refiner.Interpolate(i + 1, (OsdValue<float> *)src, (OsdValue<float> *&)dest);
}
else if (ccl::Attribute::same_storage(attr.type, TypeFloat2)) {
primvar_refiner.Interpolate(i + 1, (OsdValue<float2> *)src, (OsdValue<float2> *&)dest);
// float3 is not interchangeable with float4 and so needs to be handled
// separately
}
else if (ccl::Attribute::same_storage(attr.type, TypeFloat4)) {
primvar_refiner.Interpolate(i + 1, (OsdValue<float4> *)src, (OsdValue<float4> *&)dest);
}
else {
primvar_refiner.Interpolate(i + 1, (OsdValue<float3> *)src, (OsdValue<float3> *&)dest);
}
src = dest;
}
if (num_local_points) {
if (ccl::Attribute::same_storage(attr.type, TypeFloat)) {
patch_table->ComputeLocalPointValues(
(OsdValue<float> *)attr.buffer.data(),
(OsdValue<float> *)&attr.buffer[num_refiner_verts * attr.data_sizeof()]);
}
else if (ccl::Attribute::same_storage(attr.type, TypeFloat2)) {
patch_table->ComputeLocalPointValues(
(OsdValue<float2> *)attr.buffer.data(),
(OsdValue<float2> *)&attr.buffer[num_refiner_verts * attr.data_sizeof()]);
}
else if (ccl::Attribute::same_storage(attr.type, TypeFloat4)) {
// float3 is not interchangeable with float4 and so needs to be handled
// separately
patch_table->ComputeLocalPointValues(
(OsdValue<float4> *)attr.buffer.data(),
(OsdValue<float4> *)&attr.buffer[num_refiner_verts * attr.data_sizeof()]);
}
else {
// float3 is not interchangeable with float4 and so needs to be handled
// separately
patch_table->ComputeLocalPointValues(
(OsdValue<float3> *)attr.buffer.data(),
(OsdValue<float3> *)&attr.buffer[num_refiner_verts * attr.data_sizeof()]);
}
}
}
else if (attr.element == ATTR_ELEMENT_CORNER || attr.element == ATTR_ELEMENT_CORNER_BYTE) {
// TODO(mai): fvar interpolation
}
}
int calculate_max_isolation()
{
/* loop over all edges to find longest in screen space */
const Far::TopologyLevel &level = refiner->GetLevel(0);
const SubdParams *subd_params = mesh->get_subd_params();
const Transform objecttoworld = subd_params->objecttoworld;
Camera *cam = subd_params->camera;
float longest_edge = 0.0f;
for (size_t i = 0; i < level.GetNumEdges(); i++) {
const Far::ConstIndexArray verts = level.GetEdgeVertices(i);
float3 a = mesh->get_verts()[verts[0]];
float3 b = mesh->get_verts()[verts[1]];
float edge_len;
if (cam) {
a = transform_point(&objecttoworld, a);
b = transform_point(&objecttoworld, b);
edge_len = len(a - b) / cam->world_to_raster_size((a + b) * 0.5f);
}
else {
edge_len = len(a - b);
}
longest_edge = max(longest_edge, edge_len);
}
/* calculate isolation level */
const int isolation = (int)(log2f(max(longest_edge / subd_params->dicing_rate, 1.0f)) + 1.0f);
return min(isolation, 10);
}
friend struct OsdPatch;
friend class Mesh;
};
/* ccl::Patch implementation that uses OpenSubdiv for eval */
struct OsdPatch : Patch {
OsdData *osd_data;
OsdPatch() = default;
OsdPatch(OsdData *data) : osd_data(data) {}
void eval(float3 *P, float3 *dPdu, float3 *dPdv, float3 *N, const float u, float v) override
{
const Far::PatchTable::PatchHandle *handle = osd_data->patch_map->FindPatch(
patch_index, (double)u, (double)v);
assert(handle);
float p_weights[20];
float du_weights[20];
float dv_weights[20];
osd_data->patch_table->EvaluateBasis(*handle, u, v, p_weights, du_weights, dv_weights);
const Far::ConstIndexArray cv = osd_data->patch_table->GetPatchVertices(*handle);
float3 du;
float3 dv;
if (P) {
*P = zero_float3();
}
du = zero_float3();
dv = zero_float3();
for (int i = 0; i < cv.size(); i++) {
const float3 p = osd_data->verts[cv[i]].value;
if (P) {
*P += p * p_weights[i];
}
du += p * du_weights[i];
dv += p * dv_weights[i];
}
if (dPdu) {
*dPdu = du;
}
if (dPdv) {
*dPdv = dv;
}
if (N) {
*N = cross(du, dv);
const float t = len(*N);
*N = (t != 0.0f) ? *N / t : make_float3(0.0f, 0.0f, 1.0f);
}
}
};
#endif
void Mesh::tessellate(DiagSplit *split)
{
/* reset the number of subdivision vertices, in case the Mesh was not cleared

View File

@@ -12,6 +12,8 @@ set(INC_SYS
set(SRC
dice.cpp
interpolation.cpp
osd.cpp
patch.cpp
split.cpp
patch_table.cpp
@@ -19,6 +21,8 @@ set(SRC
set(SRC_HEADERS
dice.h
interpolation.h
osd.h
patch.h
patch_table.h
split.h

View File

@@ -0,0 +1,83 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "subd/interpolation.h"
#include "subd/osd.h"
#include "scene/attribute.h"
#include "scene/mesh.h"
CCL_NAMESPACE_BEGIN
#ifdef WITH_OPENSUBDIV
void OsdData::subdivide_attribute(Attribute &attr)
{
const Far::PrimvarRefiner primvar_refiner(*refiner);
if (attr.element == ATTR_ELEMENT_VERTEX) {
const int num_refiner_verts = refiner->GetNumVerticesTotal();
const int num_local_points = patch_table->GetNumLocalPoints();
attr.resize(num_refiner_verts + num_local_points);
attr.flags |= ATTR_FINAL_SIZE;
char *src = attr.buffer.data();
for (int i = 0; i < refiner->GetMaxLevel(); i++) {
char *dest = src + refiner->GetLevel(i).GetNumVertices() * attr.data_sizeof();
if (ccl::Attribute::same_storage(attr.type, TypeFloat)) {
primvar_refiner.Interpolate(i + 1, (OsdValue<float> *)src, (OsdValue<float> *&)dest);
}
else if (ccl::Attribute::same_storage(attr.type, TypeFloat2)) {
primvar_refiner.Interpolate(i + 1, (OsdValue<float2> *)src, (OsdValue<float2> *&)dest);
// float3 is not interchangeable with float4 and so needs to be handled
// separately
}
else if (ccl::Attribute::same_storage(attr.type, TypeFloat4)) {
primvar_refiner.Interpolate(i + 1, (OsdValue<float4> *)src, (OsdValue<float4> *&)dest);
}
else {
primvar_refiner.Interpolate(i + 1, (OsdValue<float3> *)src, (OsdValue<float3> *&)dest);
}
src = dest;
}
if (num_local_points) {
if (ccl::Attribute::same_storage(attr.type, TypeFloat)) {
patch_table->ComputeLocalPointValues(
(OsdValue<float> *)attr.buffer.data(),
(OsdValue<float> *)&attr.buffer[num_refiner_verts * attr.data_sizeof()]);
}
else if (ccl::Attribute::same_storage(attr.type, TypeFloat2)) {
patch_table->ComputeLocalPointValues(
(OsdValue<float2> *)attr.buffer.data(),
(OsdValue<float2> *)&attr.buffer[num_refiner_verts * attr.data_sizeof()]);
}
else if (ccl::Attribute::same_storage(attr.type, TypeFloat4)) {
// float3 is not interchangeable with float4 and so needs to be handled
// separately
patch_table->ComputeLocalPointValues(
(OsdValue<float4> *)attr.buffer.data(),
(OsdValue<float4> *)&attr.buffer[num_refiner_verts * attr.data_sizeof()]);
}
else {
// float3 is not interchangeable with float4 and so needs to be handled
// separately
patch_table->ComputeLocalPointValues(
(OsdValue<float3> *)attr.buffer.data(),
(OsdValue<float3> *)&attr.buffer[num_refiner_verts * attr.data_sizeof()]);
}
}
}
else if (attr.element == ATTR_ELEMENT_CORNER || attr.element == ATTR_ELEMENT_CORNER_BYTE) {
// TODO(mai): fvar interpolation
}
}
#endif
CCL_NAMESPACE_END

View File

@@ -0,0 +1,9 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
CCL_NAMESPACE_BEGIN
CCL_NAMESPACE_END

264
intern/cycles/subd/osd.cpp Normal file
View File

@@ -0,0 +1,264 @@
/* SPDX-FileCopyrightText: 2011-2024 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#ifdef WITH_OPENSUBDIV
# include "subd/osd.h"
# include "scene/attribute.h"
# include "scene/camera.h"
# include "scene/mesh.h"
/* Specialization of TopologyRefinerFactory for OsdMesh */
namespace OpenSubdiv::OPENSUBDIV_VERSION::Far {
template<>
bool TopologyRefinerFactory<ccl::Mesh>::resizeComponentTopology(TopologyRefiner &refiner,
const ccl::Mesh &mesh)
{
setNumBaseVertices(refiner, mesh.get_verts().size());
setNumBaseFaces(refiner, mesh.get_num_subd_faces());
for (int i = 0; i < mesh.get_num_subd_faces(); i++) {
setNumBaseFaceVertices(refiner, i, mesh.get_subd_num_corners()[i]);
}
return true;
}
template<>
bool TopologyRefinerFactory<ccl::Mesh>::assignComponentTopology(TopologyRefiner &refiner,
const ccl::Mesh &mesh)
{
const ccl::array<int> &subd_face_corners = mesh.get_subd_face_corners();
const ccl::array<int> &subd_start_corner = mesh.get_subd_start_corner();
const ccl::array<int> &subd_num_corners = mesh.get_subd_num_corners();
for (int i = 0; i < mesh.get_num_subd_faces(); i++) {
IndexArray face_verts = getBaseFaceVertices(refiner, i);
const int start_corner = subd_start_corner[i];
int *corner = &subd_face_corners[start_corner];
for (int j = 0; j < subd_num_corners[i]; j++, corner++) {
face_verts[j] = *corner;
}
}
return true;
}
template<>
bool TopologyRefinerFactory<ccl::Mesh>::assignComponentTags(TopologyRefiner &refiner,
const ccl::Mesh &mesh)
{
/* Historical maximum crease weight used at Pixar, influencing the maximum in OpenSubDiv. */
static constexpr float CREASE_SCALE = 10.0f;
const size_t num_creases = mesh.get_subd_creases_weight().size();
const size_t num_vertex_creases = mesh.get_subd_vert_creases().size();
/* The last loop is over the vertices, so early exit to avoid iterating them needlessly. */
if (num_creases == 0 && num_vertex_creases == 0) {
return true;
}
for (int i = 0; i < num_creases; i++) {
const ccl::Mesh::SubdEdgeCrease crease = mesh.get_subd_crease(i);
const Index edge = findBaseEdge(refiner, crease.v[0], crease.v[1]);
if (edge != INDEX_INVALID) {
setBaseEdgeSharpness(refiner, edge, crease.crease * CREASE_SCALE);
}
}
std::map<int, float> vertex_creases;
for (size_t i = 0; i < num_vertex_creases; ++i) {
const int vertex_idx = mesh.get_subd_vert_creases()[i];
const float weight = mesh.get_subd_vert_creases_weight()[i];
vertex_creases[vertex_idx] = weight * CREASE_SCALE;
}
for (int i = 0; i < mesh.get_verts().size(); i++) {
float sharpness = 0.0f;
const std::map<int, float>::const_iterator iter = vertex_creases.find(i);
if (iter != vertex_creases.end()) {
sharpness = iter->second;
}
const ConstIndexArray vert_edges = getBaseVertexEdges(refiner, i);
if (vert_edges.size() == 2) {
const float sharpness0 = refiner.getLevel(0).getEdgeSharpness(vert_edges[0]);
const float sharpness1 = refiner.getLevel(0).getEdgeSharpness(vert_edges[1]);
sharpness += ccl::min(sharpness0, sharpness1);
sharpness = ccl::min(sharpness, CREASE_SCALE);
}
if (sharpness != 0.0f) {
setBaseVertexSharpness(refiner, i, sharpness);
}
}
return true;
}
template<>
bool TopologyRefinerFactory<ccl::Mesh>::assignFaceVaryingTopology(TopologyRefiner & /*refiner*/,
const ccl::Mesh & /*mesh*/)
{
return true;
}
template<>
void TopologyRefinerFactory<ccl::Mesh>::reportInvalidTopology(TopologyError /*err_code*/,
const char * /*msg*/,
const ccl::Mesh & /*mesh*/)
{
}
} // namespace OpenSubdiv::OPENSUBDIV_VERSION::Far
CCL_NAMESPACE_BEGIN
/* OsdData */
void OsdData::build_from_mesh(Mesh *mesh_)
{
mesh = mesh_;
/* type and options */
const Sdc::SchemeType type = Sdc::SCHEME_CATMARK;
Sdc::Options options;
options.SetVtxBoundaryInterpolation(Sdc::Options::VTX_BOUNDARY_EDGE_ONLY);
/* create refiner */
refiner.reset(Far::TopologyRefinerFactory<Mesh>::Create(
*mesh, Far::TopologyRefinerFactory<Mesh>::Options(type, options)));
/* adaptive refinement */
const int max_isolation = calculate_max_isolation();
refiner->RefineAdaptive(Far::TopologyRefiner::AdaptiveOptions(max_isolation));
/* create patch table */
Far::PatchTableFactory::Options patch_options;
patch_options.endCapType = Far::PatchTableFactory::Options::ENDCAP_GREGORY_BASIS;
patch_table.reset(Far::PatchTableFactory::Create(*refiner, patch_options));
/* interpolate verts */
const int num_refiner_verts = refiner->GetNumVerticesTotal();
const int num_local_points = patch_table->GetNumLocalPoints();
verts.resize(num_refiner_verts + num_local_points);
for (int i = 0; i < mesh->get_verts().size(); i++) {
verts[i].value = mesh->get_verts()[i];
}
OsdValue<float3> *src = verts.data();
for (int i = 0; i < refiner->GetMaxLevel(); i++) {
OsdValue<float3> *dest = src + refiner->GetLevel(i).GetNumVertices();
Far::PrimvarRefiner(*refiner).Interpolate(i + 1, src, dest);
src = dest;
}
if (num_local_points) {
patch_table->ComputeLocalPointValues(verts.data(), &verts[num_refiner_verts]);
}
/* create patch map */
patch_map = make_unique<Far::PatchMap>(*patch_table);
}
int OsdData::calculate_max_isolation()
{
/* loop over all edges to find longest in screen space */
const Far::TopologyLevel &level = refiner->GetLevel(0);
const SubdParams *subd_params = mesh->get_subd_params();
const Transform objecttoworld = subd_params->objecttoworld;
Camera *cam = subd_params->camera;
float longest_edge = 0.0f;
for (size_t i = 0; i < level.GetNumEdges(); i++) {
const Far::ConstIndexArray verts = level.GetEdgeVertices(i);
float3 a = mesh->get_verts()[verts[0]];
float3 b = mesh->get_verts()[verts[1]];
float edge_len;
if (cam) {
a = transform_point(&objecttoworld, a);
b = transform_point(&objecttoworld, b);
edge_len = len(a - b) / cam->world_to_raster_size((a + b) * 0.5f);
}
else {
edge_len = len(a - b);
}
longest_edge = max(longest_edge, edge_len);
}
/* calculate isolation level */
const int isolation = (int)(log2f(max(longest_edge / subd_params->dicing_rate, 1.0f)) + 1.0f);
return min(isolation, 10);
}
/* OsdPatch */
void OsdPatch::eval(float3 *P, float3 *dPdu, float3 *dPdv, float3 *N, const float u, float v)
{
const Far::PatchTable::PatchHandle *handle = osd_data->patch_map->FindPatch(
patch_index, (double)u, (double)v);
assert(handle);
float p_weights[20];
float du_weights[20];
float dv_weights[20];
osd_data->patch_table->EvaluateBasis(*handle, u, v, p_weights, du_weights, dv_weights);
const Far::ConstIndexArray cv = osd_data->patch_table->GetPatchVertices(*handle);
float3 du;
float3 dv;
if (P) {
*P = zero_float3();
}
du = zero_float3();
dv = zero_float3();
for (int i = 0; i < cv.size(); i++) {
const float3 p = osd_data->verts[cv[i]].value;
if (P) {
*P += p * p_weights[i];
}
du += p * du_weights[i];
dv += p * dv_weights[i];
}
if (dPdu) {
*dPdu = du;
}
if (dPdv) {
*dPdv = dv;
}
if (N) {
*N = cross(du, dv);
const float t = len(*N);
*N = (t != 0.0f) ? *N / t : make_float3(0.0f, 0.0f, 1.0f);
}
}
CCL_NAMESPACE_END
#endif

85
intern/cycles/subd/osd.h Normal file
View File

@@ -0,0 +1,85 @@
/* SPDX-FileCopyrightText: 2011-2025 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
#ifdef WITH_OPENSUBDIV
# include "scene/attribute.h"
# include "scene/mesh.h"
# include "subd/patch.h"
# include "subd/split.h"
# include <opensubdiv/far/patchMap.h>
# include <opensubdiv/far/patchTableFactory.h>
# include <opensubdiv/far/primvarRefiner.h>
# include <opensubdiv/far/topologyRefinerFactory.h>
/* specializations of TopologyRefinerFactory for ccl::Mesh */
CCL_NAMESPACE_BEGIN
using namespace OpenSubdiv;
/* struct that implements OpenSubdiv's vertex interface */
template<typename T> struct OsdValue {
T value;
OsdValue() = default;
void Clear(void * /*unused*/ = nullptr)
{
memset(&value, 0, sizeof(T));
}
void AddWithWeight(const OsdValue<T> &src, const float weight)
{
value += src.value * weight;
}
};
template<>
inline void OsdValue<uchar4>::AddWithWeight(const OsdValue<uchar4> &src, const float weight)
{
for (int i = 0; i < 4; i++) {
value[i] += (uchar)(src.value[i] * weight);
}
}
/* class for holding OpenSubdiv data used during tessellation */
class OsdData {
Mesh *mesh = nullptr;
vector<OsdValue<float3>> verts;
unique_ptr<Far::TopologyRefiner> refiner;
unique_ptr<Far::PatchTable> patch_table;
unique_ptr<Far::PatchMap> patch_map;
public:
OsdData() = default;
void build_from_mesh(Mesh *mesh_);
int calculate_max_isolation();
void subdivide_attribute(Attribute &attr);
friend struct OsdPatch;
friend class Mesh;
};
/* ccl::Patch implementation that uses OpenSubdiv for eval */
struct OsdPatch : Patch {
OsdData *osd_data;
OsdPatch() = default;
OsdPatch(OsdData *data) : osd_data(data) {}
void eval(float3 *P, float3 *dPdu, float3 *dPdv, float3 *N, const float u, float v) override;
};
CCL_NAMESPACE_END
#endif