Blender V4.3
hydra/mesh.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2022 NVIDIA Corporation
2 * SPDX-FileCopyrightText: 2022 Blender Foundation
3 *
4 * SPDX-License-Identifier: Apache-2.0 */
5
6#include "hydra/mesh.h"
7#include "hydra/geometry.inl"
8#include "scene/mesh.h"
9
10#include <pxr/base/gf/vec2f.h>
11#include <pxr/imaging/hd/extComputationUtils.h>
12
14
15namespace {
16
17template<typename T>
18VtValue ComputeTriangulatedUniformPrimvar(VtValue value, const VtIntArray &primitiveParams)
19{
20 T output;
21 output.reserve(primitiveParams.size());
22 const T &input = value.Get<T>();
23
24 for (size_t i = 0; i < primitiveParams.size(); ++i) {
25 const int faceIndex = HdMeshUtil::DecodeFaceIndexFromCoarseFaceParam(primitiveParams[i]);
26
27 output.push_back(input[faceIndex]);
28 }
29
30 return VtValue(output);
31}
32
34 const HdType valueType,
35 const VtIntArray &primitiveParams)
36{
37 switch (valueType) {
38 case HdTypeFloat:
39 return ComputeTriangulatedUniformPrimvar<VtFloatArray>(value, primitiveParams);
40 case HdTypeFloatVec2:
41 return ComputeTriangulatedUniformPrimvar<VtVec2fArray>(value, primitiveParams);
42 case HdTypeFloatVec3:
43 return ComputeTriangulatedUniformPrimvar<VtVec3fArray>(value, primitiveParams);
44 case HdTypeFloatVec4:
45 return ComputeTriangulatedUniformPrimvar<VtVec4fArray>(value, primitiveParams);
46 default:
47 TF_RUNTIME_ERROR("Unsupported attribute type %d", static_cast<int>(valueType));
48 return VtValue();
49 }
50}
51
53 const HdType valueType,
54 HdMeshUtil &meshUtil)
55{
56 if (meshUtil.ComputeTriangulatedFaceVaryingPrimvar(
57 HdGetValueData(value), value.GetArraySize(), valueType, &value))
58 {
59 return value;
60 }
61
62 return VtValue();
63}
64
65} // namespace
66
67Transform convert_transform(const GfMatrix4d &matrix)
68{
69 return make_transform(matrix[0][0],
70 matrix[1][0],
71 matrix[2][0],
72 matrix[3][0],
73 matrix[0][1],
74 matrix[1][1],
75 matrix[2][1],
76 matrix[3][1],
77 matrix[0][2],
78 matrix[1][2],
79 matrix[2][2],
80 matrix[3][2]);
81}
82
83HdCyclesMesh::HdCyclesMesh(const SdfPath &rprimId
84#if PXR_VERSION < 2102
85 ,
86 const SdfPath &instancerId
87#endif
88 )
89 : HdCyclesGeometry(rprimId
90#if PXR_VERSION < 2102
91 ,
92 instancerId
93#endif
94 ),
95 _util(&_topology, rprimId)
96{
97}
98
100
102{
104 bits |= HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyNormals |
105 HdChangeTracker::DirtyPrimvar | HdChangeTracker::DirtyTopology |
106 HdChangeTracker::DirtyDisplayStyle | HdChangeTracker::DirtySubdivTags;
107 return bits;
108}
109
110HdDirtyBits HdCyclesMesh::_PropagateDirtyBits(HdDirtyBits bits) const
111{
112 if (bits & (HdChangeTracker::DirtyMaterialId)) {
113 // Update used shaders from geometry subsets if any exist in the topology
114 bits |= HdChangeTracker::DirtyTopology;
115 }
116
117 if (bits & (HdChangeTracker::DirtyTopology | HdChangeTracker::DirtyDisplayStyle |
118 HdChangeTracker::DirtySubdivTags))
119 {
120 // Do full topology update when display style or subdivision changes
121 bits |= HdChangeTracker::DirtyTopology | HdChangeTracker::DirtyDisplayStyle |
122 HdChangeTracker::DirtySubdivTags;
123 }
124
125 if (bits & (HdChangeTracker::DirtyTopology)) {
126 // Changing topology clears the geometry, so need to populate everything again
127 bits |= HdChangeTracker::DirtyPoints | HdChangeTracker::DirtyNormals |
128 HdChangeTracker::DirtyPrimvar;
129 }
130
131 return bits;
132}
133
134void HdCyclesMesh::Populate(HdSceneDelegate *sceneDelegate, HdDirtyBits dirtyBits, bool &rebuild)
135{
136 if (HdChangeTracker::IsTopologyDirty(dirtyBits, GetId())) {
137 PopulateTopology(sceneDelegate);
138 }
139
140 if (dirtyBits & HdChangeTracker::DirtyPoints) {
141 PopulatePoints(sceneDelegate);
142 }
143
144 // Must happen after topology update, so that normals attribute size can be calculated
145 if (dirtyBits & HdChangeTracker::DirtyNormals) {
146 PopulateNormals(sceneDelegate);
147 }
148
149 // Must happen after topology update, so that appropriate attribute set can be selected
150 if (dirtyBits & HdChangeTracker::DirtyPrimvar) {
151 PopulatePrimvars(sceneDelegate);
152 }
153
154 rebuild = (_geom->triangles_is_modified()) || (_geom->subd_start_corner_is_modified()) ||
155 (_geom->subd_num_corners_is_modified()) || (_geom->subd_shader_is_modified()) ||
156 (_geom->subd_smooth_is_modified()) || (_geom->subd_ptex_offset_is_modified()) ||
157 (_geom->subd_face_corners_is_modified());
158}
159
160void HdCyclesMesh::PopulatePoints(HdSceneDelegate *sceneDelegate)
161{
162 VtValue value;
163
164 for (const HdExtComputationPrimvarDescriptor &desc :
165 sceneDelegate->GetExtComputationPrimvarDescriptors(GetId(), HdInterpolationVertex))
166 {
167 if (desc.name == HdTokens->points) {
168 auto valueStore = HdExtComputationUtils::GetComputedPrimvarValues({desc}, sceneDelegate);
169 const auto valueStoreIt = valueStore.find(desc.name);
170 if (valueStoreIt != valueStore.end()) {
171 value = std::move(valueStoreIt->second);
172 }
173 break;
174 }
175 }
176
177 if (value.IsEmpty()) {
178 value = GetPoints(sceneDelegate);
179 }
180
181 if (!value.IsHolding<VtVec3fArray>()) {
182 TF_WARN("Invalid points data for %s", GetId().GetText());
183 return;
184 }
185
186 const auto &points = value.UncheckedGet<VtVec3fArray>();
187
188 TF_VERIFY(points.size() >= static_cast<size_t>(_topology.GetNumPoints()));
189
190 array<float3> pointsDataCycles;
191 pointsDataCycles.reserve(points.size());
192 for (const GfVec3f &point : points) {
193 pointsDataCycles.push_back_reserved(make_float3(point[0], point[1], point[2]));
194 }
195
196 _geom->set_verts(pointsDataCycles);
197}
198
199void HdCyclesMesh::PopulateNormals(HdSceneDelegate *sceneDelegate)
200{
201 _geom->attributes.remove(ATTR_STD_FACE_NORMAL);
202 _geom->attributes.remove(ATTR_STD_VERTEX_NORMAL);
203
204 // Authored normals should only exist on triangle meshes
205 if (_geom->get_subdivision_type() != Mesh::SUBDIVISION_NONE) {
206 return;
207 }
208
209 VtValue value;
210 HdInterpolation interpolation = HdInterpolationCount;
211
212 for (int i = 0; i < HdInterpolationCount && interpolation == HdInterpolationCount; ++i) {
213 for (const HdExtComputationPrimvarDescriptor &desc :
214 sceneDelegate->GetExtComputationPrimvarDescriptors(GetId(),
215 static_cast<HdInterpolation>(i)))
216 {
217 if (desc.name == HdTokens->normals) {
218 auto valueStore = HdExtComputationUtils::GetComputedPrimvarValues({desc}, sceneDelegate);
219 const auto valueStoreIt = valueStore.find(desc.name);
220 if (valueStoreIt != valueStore.end()) {
221 value = std::move(valueStoreIt->second);
222 interpolation = static_cast<HdInterpolation>(i);
223 }
224 break;
225 }
226 }
227 }
228
229 if (value.IsEmpty()) {
230 interpolation = GetPrimvarInterpolation(sceneDelegate, HdTokens->normals);
231 if (interpolation == HdInterpolationCount) {
232 return; // Ignore missing normals
233 }
234
235 value = GetNormals(sceneDelegate);
236 }
237
238 if (!value.IsHolding<VtVec3fArray>()) {
239 TF_WARN("Invalid normals data for %s", GetId().GetText());
240 return;
241 }
242
243 const auto &normals = value.UncheckedGet<VtVec3fArray>();
244
245 if (interpolation == HdInterpolationConstant) {
246 TF_VERIFY(normals.size() == 1);
247
248 const GfVec3f constantNormal = normals[0];
249
250 float3 *const N = _geom->attributes.add(ATTR_STD_VERTEX_NORMAL)->data_float3();
251 for (size_t i = 0; i < _geom->get_verts().size(); ++i) {
252 N[i] = make_float3(constantNormal[0], constantNormal[1], constantNormal[2]);
253 }
254 }
255 else if (interpolation == HdInterpolationUniform) {
256 TF_VERIFY(normals.size() == static_cast<size_t>(_topology.GetNumFaces()));
257
258 float3 *const N = _geom->attributes.add(ATTR_STD_FACE_NORMAL)->data_float3();
259 for (size_t i = 0; i < _geom->num_triangles(); ++i) {
260 const int faceIndex = HdMeshUtil::DecodeFaceIndexFromCoarseFaceParam(_primitiveParams[i]);
261
262 N[i] = make_float3(normals[faceIndex][0], normals[faceIndex][1], normals[faceIndex][2]);
263 }
264 }
265 else if (interpolation == HdInterpolationVertex || interpolation == HdInterpolationVarying) {
266 TF_VERIFY(normals.size() == static_cast<size_t>(_topology.GetNumPoints()) &&
267 static_cast<size_t>(_topology.GetNumPoints()) == _geom->get_verts().size());
268
269 float3 *const N = _geom->attributes.add(ATTR_STD_VERTEX_NORMAL)->data_float3();
270 for (size_t i = 0; i < _geom->get_verts().size(); ++i) {
271 N[i] = make_float3(normals[i][0], normals[i][1], normals[i][2]);
272 }
273 }
274 else if (interpolation == HdInterpolationFaceVarying) {
275 TF_VERIFY(normals.size() == static_cast<size_t>(_topology.GetNumFaceVaryings()));
276
277 if (!_util.ComputeTriangulatedFaceVaryingPrimvar(
278 normals.data(), normals.size(), HdTypeFloatVec3, &value))
279 {
280 return;
281 }
282
283 const auto &normalsTriangulated = value.UncheckedGet<VtVec3fArray>();
284
285 // Cycles has no standard attribute for face-varying normals, so this is a lossy transformation
286 float3 *const N = _geom->attributes.add(ATTR_STD_FACE_NORMAL)->data_float3();
287 for (size_t i = 0; i < _geom->num_triangles(); ++i) {
288 GfVec3f averageNormal = normalsTriangulated[i * 3] + normalsTriangulated[i * 3 + 1] +
289 normalsTriangulated[i * 3 + 2];
290 GfNormalize(&averageNormal);
291
292 N[i] = make_float3(averageNormal[0], averageNormal[1], averageNormal[2]);
293 }
294 }
295}
296
297void HdCyclesMesh::PopulatePrimvars(HdSceneDelegate *sceneDelegate)
298{
299 Scene *const scene = (Scene *)_geom->get_owner();
300
301 const bool subdivision = _geom->get_subdivision_type() != Mesh::SUBDIVISION_NONE;
302 AttributeSet &attributes = subdivision ? _geom->subd_attributes : _geom->attributes;
303
304 const std::pair<HdInterpolation, AttributeElement> interpolations[] = {
305 std::make_pair(HdInterpolationFaceVarying, ATTR_ELEMENT_CORNER),
306 std::make_pair(HdInterpolationUniform, ATTR_ELEMENT_FACE),
307 std::make_pair(HdInterpolationVertex, ATTR_ELEMENT_VERTEX),
308 std::make_pair(HdInterpolationVarying, ATTR_ELEMENT_VERTEX),
309 std::make_pair(HdInterpolationConstant, ATTR_ELEMENT_OBJECT),
310 };
311
312 for (const auto &interpolation : interpolations) {
313 for (const HdPrimvarDescriptor &desc :
314 GetPrimvarDescriptors(sceneDelegate, interpolation.first))
315 {
316 // Skip special primvars that are handled separately
317 if (desc.name == HdTokens->points || desc.name == HdTokens->normals) {
318 continue;
319 }
320
321 VtValue value = GetPrimvar(sceneDelegate, desc.name);
322 if (value.IsEmpty()) {
323 continue;
324 }
325
326 const ustring name(desc.name.GetString());
327
329 if (desc.role == HdPrimvarRoleTokens->textureCoordinate) {
330 std = ATTR_STD_UV;
331 }
332 else if (interpolation.first == HdInterpolationVertex) {
333 if (desc.name == HdTokens->displayColor || desc.role == HdPrimvarRoleTokens->color) {
335 }
336 else if (desc.name == HdTokens->normals) {
338 }
339 }
340 else if (desc.name == HdTokens->displayColor &&
341 interpolation.first == HdInterpolationConstant)
342 {
343 if (value.IsHolding<VtVec3fArray>() && value.GetArraySize() == 1) {
344 const GfVec3f color = value.UncheckedGet<VtVec3fArray>()[0];
345 _instances[0]->set_color(make_float3(color[0], color[1], color[2]));
346 }
347 }
348
349 // Skip attributes that are not needed
350 if ((std != ATTR_STD_NONE && _geom->need_attribute(scene, std)) ||
351 _geom->need_attribute(scene, name))
352 {
353 const HdType valueType = HdGetValueTupleType(value).type;
354
355 if (!subdivision) {
356 // Adjust attributes for polygons that were triangulated
357 if (interpolation.first == HdInterpolationUniform) {
358 value = ComputeTriangulatedUniformPrimvar(value, valueType, _primitiveParams);
359 if (value.IsEmpty()) {
360 continue;
361 }
362 }
363 else if (interpolation.first == HdInterpolationFaceVarying) {
364 value = ComputeTriangulatedFaceVaryingPrimvar(value, valueType, _util);
365 if (value.IsEmpty()) {
366 continue;
367 }
368 }
369 }
370
371 ApplyPrimvars(attributes, name, value, interpolation.second, std);
372 }
373 }
374 }
375}
376
377void HdCyclesMesh::PopulateTopology(HdSceneDelegate *sceneDelegate)
378{
379 // Clear geometry before populating it again with updated topology
380 _geom->clear(true);
381
382 const HdDisplayStyle displayStyle = GetDisplayStyle(sceneDelegate);
383 _topology = HdMeshTopology(GetMeshTopology(sceneDelegate), displayStyle.refineLevel);
384
385 const TfToken subdivScheme = _topology.GetScheme();
386 if (subdivScheme == PxOsdOpenSubdivTokens->bilinear && _topology.GetRefineLevel() > 0) {
387 _geom->set_subdivision_type(Mesh::SUBDIVISION_LINEAR);
388 }
389 else if (subdivScheme == PxOsdOpenSubdivTokens->catmullClark && _topology.GetRefineLevel() > 0) {
390 _geom->set_subdivision_type(Mesh::SUBDIVISION_CATMULL_CLARK);
391 }
392 else {
393 _geom->set_subdivision_type(Mesh::SUBDIVISION_NONE);
394 }
395
396 const bool smooth = !displayStyle.flatShadingEnabled;
397 const bool subdivision = _geom->get_subdivision_type() != Mesh::SUBDIVISION_NONE;
398
399 // Initialize lookup table from polygon face to material shader index
400 VtIntArray faceShaders(_topology.GetNumFaces(), 0);
401
402 HdGeomSubsets const &geomSubsets = _topology.GetGeomSubsets();
403 if (!geomSubsets.empty()) {
404 array<Node *> usedShaders = std::move(_geom->get_used_shaders());
405 // Remove any previous materials except for the material assigned to the prim
406 usedShaders.resize(1);
407
408 std::unordered_map<SdfPath, int, SdfPath::Hash> materials;
409
410 for (const HdGeomSubset &geomSubset : geomSubsets) {
411 TF_VERIFY(geomSubset.type == HdGeomSubset::TypeFaceSet);
412
413 int shader = 0;
414 const auto it = materials.find(geomSubset.materialId);
415 if (it != materials.end()) {
416 shader = it->second;
417 }
418 else {
419 const auto material = static_cast<const HdCyclesMaterial *>(
420 sceneDelegate->GetRenderIndex().GetSprim(HdPrimTypeTokens->material,
421 geomSubset.materialId));
422
423 if (material && material->GetCyclesShader()) {
424 shader = static_cast<int>(usedShaders.size());
425 usedShaders.push_back_slow(material->GetCyclesShader());
426
427 materials.emplace(geomSubset.materialId, shader);
428 }
429 }
430
431 for (int face : geomSubset.indices) {
432 faceShaders[face] = shader;
433 }
434 }
435
436 _geom->set_used_shaders(usedShaders);
437 }
438
439 const VtIntArray vertIndx = _topology.GetFaceVertexIndices();
440 const VtIntArray vertCounts = _topology.GetFaceVertexCounts();
441
442 if (!subdivision) {
443 VtVec3iArray triangles;
444 _util.ComputeTriangleIndices(&triangles, &_primitiveParams);
445
446 _geom->reserve_mesh(_topology.GetNumPoints(), triangles.size());
447
448 for (size_t i = 0; i < _primitiveParams.size(); ++i) {
449 const int faceIndex = HdMeshUtil::DecodeFaceIndexFromCoarseFaceParam(_primitiveParams[i]);
450
451 const GfVec3i triangle = triangles[i];
452 _geom->add_triangle(triangle[0], triangle[1], triangle[2], faceShaders[faceIndex], smooth);
453 }
454 }
455 else {
456 PxOsdSubdivTags subdivTags = GetSubdivTags(sceneDelegate);
457 _topology.SetSubdivTags(subdivTags);
458
459 size_t numNgons = 0;
460 size_t numCorners = 0;
461 for (int vertCount : vertCounts) {
462 numNgons += (vertCount == 4) ? 0 : 1;
463 numCorners += vertCount;
464 }
465
466 _geom->reserve_subd_faces(_topology.GetNumFaces(), numNgons, numCorners);
467
468 // TODO: Handle hole indices
469 size_t faceIndex = 0;
470 size_t indexOffset = 0;
471 for (int vertCount : vertCounts) {
472 _geom->add_subd_face(&vertIndx[indexOffset], vertCount, faceShaders[faceIndex], smooth);
473
474 faceIndex++;
475 indexOffset += vertCount;
476 }
477
478 const VtIntArray creaseLengths = subdivTags.GetCreaseLengths();
479 if (!creaseLengths.empty()) {
480 size_t numCreases = 0;
481 for (int creaseLength : creaseLengths) {
482 numCreases += creaseLength - 1;
483 }
484
485 _geom->reserve_subd_creases(numCreases);
486
487 const VtIntArray creaseIndices = subdivTags.GetCreaseIndices();
488 const VtFloatArray creaseWeights = subdivTags.GetCreaseWeights();
489
490 indexOffset = 0;
491 size_t creaseLengthOffset = 0;
492 size_t createWeightOffset = 0;
493 for (int creaseLength : creaseLengths) {
494 for (int j = 0; j < creaseLength - 1; ++j, ++createWeightOffset) {
495 const int v0 = creaseIndices[indexOffset + j];
496 const int v1 = creaseIndices[indexOffset + j + 1];
497
498 float weight = creaseWeights.size() == creaseLengths.size() ?
499 creaseWeights[creaseLengthOffset] :
500 creaseWeights[createWeightOffset];
501
502 _geom->add_edge_crease(v0, v1, weight);
503 }
504
505 indexOffset += creaseLength;
506 creaseLengthOffset++;
507 }
508
509 const VtIntArray cornerIndices = subdivTags.GetCornerIndices();
510 const VtFloatArray cornerWeights = subdivTags.GetCornerWeights();
511
512 for (size_t i = 0; i < cornerIndices.size(); ++i) {
513 _geom->add_vertex_crease(cornerIndices[i], cornerWeights[i]);
514 }
515 }
516
517 _geom->set_subd_dicing_rate(1.0f);
518 _geom->set_subd_max_level(_topology.GetRefineLevel());
519 _geom->set_subd_objecttoworld(_instances[0]->get_tfm());
520 }
521}
522
523void HdCyclesMesh::Finalize(PXR_NS::HdRenderParam *renderParam)
524{
525 _topology = HdMeshTopology();
526 _primitiveParams.clear();
527
529}
530
struct Scene Scene
Group Output data from inside of a node group A color picker Mix two input colors RGB to Convert a color s luminance to a grayscale value Generate a normal vector and a dot product Brightness Control the brightness and contrast of the input color Vector Map input vector components with curves Camera Retrieve information about the camera and how it relates to the current shading point s position Clamp a value between a minimum and a maximum Vector Perform vector math operation Invert Invert a color
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a point
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
#define output
VecBase< float, 3 > float3
HdCyclesGeometry(const PXR_NS::SdfPath &rprimId, const PXR_NS::SdfPath &instancerId)
Definition geometry.inl:23
virtual void Finalize(PXR_NS::HdRenderParam *renderParam) override
Definition geometry.inl:189
PXR_NS::HdInterpolation GetPrimvarInterpolation(PXR_NS::HdSceneDelegate *sceneDelegate, const PXR_NS::TfToken &name) const
Definition geometry.inl:242
PXR_NS::HdDirtyBits GetInitialDirtyBitsMask() const override
Definition geometry.inl:49
std::vector< CCL_NS::Object * > _instances
PXR_NS::HdDirtyBits GetInitialDirtyBitsMask() const override
HdCyclesMesh(const PXR_NS::SdfPath &rprimId, const PXR_NS::SdfPath &instancerId={})
void Finalize(PXR_NS::HdRenderParam *renderParam) override
~HdCyclesMesh() override
T * resize(size_t newsize)
size_t size() const
void push_back_reserved(const T &t)
void reserve(size_t newcapacity)
void push_back_slow(const T &t)
ccl_device_forceinline float3 make_float3(const float x, const float y, const float z)
smooth(Type::VEC3, "P") .flat(Type out_color storage_buf(0, Qualifier::READ, "Surfel", "surfels_buf[]") .push_constant(Type smooth(Type::VEC4, "interp_color")
static float normals[][3]
HDCYCLES_NAMESPACE_OPEN_SCOPE void ApplyPrimvars(AttributeSet &attributes, const ustring &name, VtValue value, AttributeElement elem, AttributeStandard std)
#define HDCYCLES_NAMESPACE_CLOSE_SCOPE
Transform convert_transform(const GfMatrix4d &matrix)
AttributeStandard
@ ATTR_STD_UV
@ ATTR_STD_VERTEX_NORMAL
@ ATTR_STD_NONE
@ ATTR_STD_VERTEX_COLOR
@ ATTR_STD_FACE_NORMAL
@ ATTR_ELEMENT_CORNER
@ ATTR_ELEMENT_OBJECT
@ ATTR_ELEMENT_VERTEX
@ ATTR_ELEMENT_FACE
#define N
#define T
VtValue ComputeTriangulatedUniformPrimvar(VtValue value, const VtIntArray &primitiveParams)
VtValue ComputeTriangulatedFaceVaryingPrimvar(VtValue value, const HdType valueType, HdMeshUtil &meshUtil)
@ SUBDIVISION_NONE
Definition scene/mesh.h:121
@ SUBDIVISION_LINEAR
Definition scene/mesh.h:122
@ SUBDIVISION_CATMULL_CLARK
Definition scene/mesh.h:123
ccl_device_inline Transform make_transform(float a, float b, float c, float d, float e, float f, float g, float h, float i, float j, float k, float l)
Definition transform.h:133