Blender V4.3
scene/volume.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2020-2022 Blender Foundation
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#include "scene/volume.h"
6#include "scene/attribute.h"
7#include "scene/image_vdb.h"
8#include "scene/scene.h"
9
10#ifdef WITH_OPENVDB
11# include <openvdb/tools/Dense.h>
12# include <openvdb/tools/GridTransformer.h>
13# include <openvdb/tools/Morphology.h>
14# include <openvdb/tools/Statistics.h>
15#endif
16
17#include "util/hash.h"
18#include "util/log.h"
19#include "util/openvdb.h"
20#include "util/progress.h"
21#include "util/types.h"
22
24
26{
27 NodeType *type = NodeType::add("volume", create, NodeType::NONE, Mesh::get_node_type());
28
29 SOCKET_FLOAT(clipping, "Clipping", 0.001f);
30 SOCKET_FLOAT(step_size, "Step Size", 0.0f);
31 SOCKET_BOOLEAN(object_space, "Object Space", false);
32 SOCKET_FLOAT(velocity_scale, "Velocity Scale", 1.0f);
33
34 return type;
35}
36
37Volume::Volume() : Mesh(get_node_type(), Geometry::VOLUME)
38{
39 clipping = 0.001f;
40 step_size = 0.0f;
41 object_space = false;
42}
43
44void Volume::clear(bool preserve_shaders)
45{
46 Mesh::clear(preserve_shaders, true);
47}
48
49struct QuadData {
50 int v0, v1, v2, v3;
51
53};
54
55enum {
62};
63
64#ifdef WITH_OPENVDB
65const int quads_indices[6][4] = {
66 /* QUAD_X_MIN */
67 {4, 0, 3, 7},
68 /* QUAD_X_MAX */
69 {1, 5, 6, 2},
70 /* QUAD_Y_MIN */
71 {4, 5, 1, 0},
72 /* QUAD_Y_MAX */
73 {3, 2, 6, 7},
74 /* QUAD_Z_MIN */
75 {0, 1, 2, 3},
76 /* QUAD_Z_MAX */
77 {5, 4, 7, 6},
78};
79
80const float3 quads_normals[6] = {
81 /* QUAD_X_MIN */
82 make_float3(-1.0f, 0.0f, 0.0f),
83 /* QUAD_X_MAX */
84 make_float3(1.0f, 0.0f, 0.0f),
85 /* QUAD_Y_MIN */
86 make_float3(0.0f, -1.0f, 0.0f),
87 /* QUAD_Y_MAX */
88 make_float3(0.0f, 1.0f, 0.0f),
89 /* QUAD_Z_MIN */
90 make_float3(0.0f, 0.0f, -1.0f),
91 /* QUAD_Z_MAX */
92 make_float3(0.0f, 0.0f, 1.0f),
93};
94
95static int add_vertex(int3 v,
96 vector<int3> &vertices,
97 int3 res,
98 unordered_map<size_t, int> &used_verts)
99{
100 size_t vert_key = v.x + v.y * (res.x + 1) + v.z * (res.x + 1) * (res.y + 1);
101 unordered_map<size_t, int>::iterator it = used_verts.find(vert_key);
102
103 if (it != used_verts.end()) {
104 return it->second;
105 }
106
107 int vertex_offset = vertices.size();
108 used_verts[vert_key] = vertex_offset;
109 vertices.push_back(v);
110 return vertex_offset;
111}
112
113static void create_quad(int3 corners[8],
114 vector<int3> &vertices,
115 vector<QuadData> &quads,
116 int3 res,
117 unordered_map<size_t, int> &used_verts,
118 int face_index)
119{
121 quad.v0 = add_vertex(corners[quads_indices[face_index][0]], vertices, res, used_verts);
122 quad.v1 = add_vertex(corners[quads_indices[face_index][1]], vertices, res, used_verts);
123 quad.v2 = add_vertex(corners[quads_indices[face_index][2]], vertices, res, used_verts);
124 quad.v3 = add_vertex(corners[quads_indices[face_index][3]], vertices, res, used_verts);
125 quad.normal = quads_normals[face_index];
126
127 quads.push_back(quad);
128}
129#endif
130
131/* Create a mesh from a volume.
132 *
133 * The way the algorithm works is as follows:
134 *
135 * - The topologies of input OpenVDB grids are merged into a temporary grid.
136 * - Voxels of the temporary grid are dilated to account for the padding necessary for volume
137 * sampling.
138 * - Quads are created on the boundary between active and inactive leaf nodes of the temporary
139 * grid.
140 */
142 public:
143#ifdef WITH_OPENVDB
144 /* use a MaskGrid to store the topology to save memory */
145 openvdb::MaskGrid::Ptr topology_grid;
146 openvdb::CoordBBox bbox;
147#endif
149
151
152#ifdef WITH_OPENVDB
153 void add_grid(openvdb::GridBase::ConstPtr grid, bool do_clipping, float volume_clipping);
154#endif
155
156 void add_padding(int pad_size);
157
158 void create_mesh(vector<float3> &vertices,
159 vector<int> &indices,
160 vector<float3> &face_normals,
161 const float face_overlap_avoidance);
162
164
165 void convert_object_space(const vector<int3> &vertices,
166 vector<float3> &out_vertices,
167 const float face_overlap_avoidance);
168
169 void convert_quads_to_tris(const vector<QuadData> &quads,
170 vector<int> &tris,
171 vector<float3> &face_normals);
172
173 bool empty_grid() const;
174
175#ifdef WITH_OPENVDB
176 template<typename GridType>
177 void merge_grid(openvdb::GridBase::ConstPtr grid, bool do_clipping, float volume_clipping)
178 {
179 typename GridType::ConstPtr typed_grid = openvdb::gridConstPtrCast<GridType>(grid);
180
181 if (do_clipping) {
182 using ValueType = typename GridType::ValueType;
183 typename GridType::Ptr copy = typed_grid->deepCopy();
184 typename GridType::ValueOnIter iter = copy->beginValueOn();
185
186 for (; iter; ++iter) {
187 if (openvdb::math::Abs(iter.getValue()) < ValueType(volume_clipping)) {
188 iter.setValueOff();
189 }
190 }
191
192 typed_grid = copy;
193 }
194
195 topology_grid->topologyUnion(*typed_grid);
196 }
197#endif
198};
199
204
205#ifdef WITH_OPENVDB
206void VolumeMeshBuilder::add_grid(openvdb::GridBase::ConstPtr grid,
207 bool do_clipping,
208 float volume_clipping)
209{
210 /* set the transform of our grid from the first one */
211 if (first_grid) {
212 topology_grid = openvdb::MaskGrid::create();
213 topology_grid->setTransform(grid->transform().copy());
214 first_grid = false;
215 }
216 /* if the transforms do not match, we need to resample one of the grids so that
217 * its index space registers with that of the other, here we resample our mask
218 * grid so memory usage is kept low */
219 else if (topology_grid->transform() != grid->transform()) {
220 openvdb::MaskGrid::Ptr temp_grid = topology_grid->copyWithNewTree();
221 temp_grid->setTransform(grid->transform().copy());
222 openvdb::tools::resampleToMatch<openvdb::tools::BoxSampler>(*topology_grid, *temp_grid);
223 topology_grid = temp_grid;
224 topology_grid->setTransform(grid->transform().copy());
225 }
226
227 if (grid->isType<openvdb::FloatGrid>()) {
228 merge_grid<openvdb::FloatGrid>(grid, do_clipping, volume_clipping);
229 }
230 else if (grid->isType<openvdb::Vec3fGrid>()) {
231 merge_grid<openvdb::Vec3fGrid>(grid, do_clipping, volume_clipping);
232 }
233 else if (grid->isType<openvdb::Vec4fGrid>()) {
234 merge_grid<openvdb::Vec4fGrid>(grid, do_clipping, volume_clipping);
235 }
236 else if (grid->isType<openvdb::BoolGrid>()) {
237 merge_grid<openvdb::BoolGrid>(grid, do_clipping, volume_clipping);
238 }
239 else if (grid->isType<openvdb::DoubleGrid>()) {
240 merge_grid<openvdb::DoubleGrid>(grid, do_clipping, volume_clipping);
241 }
242 else if (grid->isType<openvdb::Int32Grid>()) {
243 merge_grid<openvdb::Int32Grid>(grid, do_clipping, volume_clipping);
244 }
245 else if (grid->isType<openvdb::Int64Grid>()) {
246 merge_grid<openvdb::Int64Grid>(grid, do_clipping, volume_clipping);
247 }
248 else if (grid->isType<openvdb::Vec3IGrid>()) {
249 merge_grid<openvdb::Vec3IGrid>(grid, do_clipping, volume_clipping);
250 }
251 else if (grid->isType<openvdb::Vec3dGrid>()) {
252 merge_grid<openvdb::Vec3dGrid>(grid, do_clipping, volume_clipping);
253 }
254 else if (grid->isType<openvdb::MaskGrid>()) {
255 topology_grid->topologyUnion(*openvdb::gridConstPtrCast<openvdb::MaskGrid>(grid));
256 }
257}
258#endif
259
261{
262#ifdef WITH_OPENVDB
263 openvdb::tools::dilateActiveValues(
264 topology_grid->tree(), pad_size, openvdb::tools::NN_FACE, openvdb::tools::IGNORE_TILES);
265#else
266 (void)pad_size;
267#endif
268}
269
271 vector<int> &indices,
272 vector<float3> &face_normals,
273 const float face_overlap_avoidance)
274{
275#ifdef WITH_OPENVDB
276 /* We create vertices in index space (is), and only convert them to object
277 * space when done. */
278 vector<int3> vertices_is;
279 vector<QuadData> quads;
280
281 /* make sure we only have leaf nodes in the tree, as tiles are not handled by
282 * this algorithm */
283 topology_grid->tree().voxelizeActiveTiles();
284
285 generate_vertices_and_quads(vertices_is, quads);
286
287 convert_object_space(vertices_is, vertices, face_overlap_avoidance);
288
289 convert_quads_to_tris(quads, indices, face_normals);
290#else
291 (void)vertices;
292 (void)indices;
293 (void)face_normals;
294 (void)face_overlap_avoidance;
295#endif
296}
297
298#ifdef WITH_OPENVDB
299static bool is_non_empty_leaf(const openvdb::MaskGrid::TreeType &tree, const openvdb::Coord coord)
300{
301 auto *leaf_node = tree.probeLeaf(coord);
302 return (leaf_node && !leaf_node->isEmpty());
303}
304#endif
305
307 vector<QuadData> &quads)
308{
309#ifdef WITH_OPENVDB
310 const openvdb::MaskGrid::TreeType &tree = topology_grid->tree();
311 tree.evalLeafBoundingBox(bbox);
312
313 const int3 resolution = make_int3(bbox.dim().x(), bbox.dim().y(), bbox.dim().z());
314
315 unordered_map<size_t, int> used_verts;
316
317 for (auto iter = tree.cbeginLeaf(); iter; ++iter) {
318 if (iter->isEmpty()) {
319 continue;
320 }
321
322 openvdb::CoordBBox leaf_bbox = iter->getNodeBoundingBox();
323 /* +1 to convert from exclusive to include bounds. */
324 leaf_bbox.max() = leaf_bbox.max().offsetBy(1);
325
326 int3 min = make_int3(leaf_bbox.min().x(), leaf_bbox.min().y(), leaf_bbox.min().z());
327 int3 max = make_int3(leaf_bbox.max().x(), leaf_bbox.max().y(), leaf_bbox.max().z());
328
329 int3 corners[8] = {
330 make_int3(min[0], min[1], min[2]),
331 make_int3(max[0], min[1], min[2]),
332 make_int3(max[0], max[1], min[2]),
333 make_int3(min[0], max[1], min[2]),
334 make_int3(min[0], min[1], max[2]),
335 make_int3(max[0], min[1], max[2]),
336 make_int3(max[0], max[1], max[2]),
337 make_int3(min[0], max[1], max[2]),
338 };
339
340 /* Only create a quad if on the border between an active and an inactive leaf.
341 *
342 * We verify that a leaf exists by probing a coordinate that is at its center,
343 * to do so we compute the center of the current leaf and offset this coordinate
344 * by the size of a leaf in each direction.
345 */
346 static const int LEAF_DIM = openvdb::MaskGrid::TreeType::LeafNodeType::DIM;
347 auto center = leaf_bbox.min() + openvdb::Coord(LEAF_DIM / 2);
348
349 if (!is_non_empty_leaf(tree, openvdb::Coord(center.x() - LEAF_DIM, center.y(), center.z()))) {
350 create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_X_MIN);
351 }
352
353 if (!is_non_empty_leaf(tree, openvdb::Coord(center.x() + LEAF_DIM, center.y(), center.z()))) {
354 create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_X_MAX);
355 }
356
357 if (!is_non_empty_leaf(tree, openvdb::Coord(center.x(), center.y() - LEAF_DIM, center.z()))) {
358 create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Y_MIN);
359 }
360
361 if (!is_non_empty_leaf(tree, openvdb::Coord(center.x(), center.y() + LEAF_DIM, center.z()))) {
362 create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Y_MAX);
363 }
364
365 if (!is_non_empty_leaf(tree, openvdb::Coord(center.x(), center.y(), center.z() - LEAF_DIM))) {
366 create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Z_MIN);
367 }
368
369 if (!is_non_empty_leaf(tree, openvdb::Coord(center.x(), center.y(), center.z() + LEAF_DIM))) {
370 create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Z_MAX);
371 }
372 }
373#else
374 (void)vertices_is;
375 (void)quads;
376#endif
377}
378
380 vector<float3> &out_vertices,
381 const float face_overlap_avoidance)
382{
383#ifdef WITH_OPENVDB
384 /* compute the offset for the face overlap avoidance */
385 bbox = topology_grid->evalActiveVoxelBoundingBox();
386 openvdb::Coord dim = bbox.dim();
387
388 float3 cell_size = make_float3(1.0f / dim.x(), 1.0f / dim.y(), 1.0f / dim.z());
389 float3 point_offset = cell_size * face_overlap_avoidance;
390
391 out_vertices.reserve(vertices.size());
392
393 for (size_t i = 0; i < vertices.size(); ++i) {
394 openvdb::math::Vec3d p = topology_grid->indexToWorld(
395 openvdb::math::Vec3d(vertices[i].x, vertices[i].y, vertices[i].z));
396 float3 vertex = make_float3((float)p.x(), (float)p.y(), (float)p.z());
397 out_vertices.push_back(vertex + point_offset);
398 }
399#else
400 (void)vertices;
401 (void)out_vertices;
402 (void)face_overlap_avoidance;
403#endif
404}
405
407 vector<int> &tris,
408 vector<float3> &face_normals)
409{
410 int index_offset = 0;
411 tris.resize(quads.size() * 6);
412 face_normals.reserve(quads.size() * 2);
413
414 for (size_t i = 0; i < quads.size(); ++i) {
415 tris[index_offset++] = quads[i].v0;
416 tris[index_offset++] = quads[i].v2;
417 tris[index_offset++] = quads[i].v1;
418
419 face_normals.push_back(quads[i].normal);
420
421 tris[index_offset++] = quads[i].v0;
422 tris[index_offset++] = quads[i].v3;
423 tris[index_offset++] = quads[i].v2;
424
425 face_normals.push_back(quads[i].normal);
426 }
427}
428
430{
431#ifdef WITH_OPENVDB
432 return !topology_grid ||
433 (!topology_grid->tree().hasActiveTiles() && topology_grid->tree().leafCount() == 0);
434#else
435 return true;
436#endif
437}
438
439#ifdef WITH_OPENVDB
440template<typename GridType>
441static openvdb::GridBase::ConstPtr openvdb_grid_from_device_texture(device_texture *image_memory,
442 float volume_clipping,
443 Transform transform_3d)
444{
445 using ValueType = typename GridType::ValueType;
446
447 openvdb::CoordBBox dense_bbox(0,
448 0,
449 0,
450 image_memory->data_width - 1,
451 image_memory->data_height - 1,
452 image_memory->data_depth - 1);
453
454 typename GridType::Ptr sparse = GridType::create(ValueType(0.0f));
455 if (dense_bbox.empty()) {
456 return sparse;
457 }
458
459 openvdb::tools::Dense<ValueType, openvdb::tools::MemoryLayout::LayoutXYZ> dense(
460 dense_bbox, static_cast<ValueType *>(image_memory->host_pointer));
461
462 openvdb::tools::copyFromDense(dense, *sparse, ValueType(volume_clipping));
463
464 /* #copyFromDense will remove any leaf node that contains constant data and replace it with a
465 * tile, however, we need to preserve the leaves in order to generate the mesh, so re-voxelize
466 * the leaves that were pruned. This should not affect areas that were skipped due to the
467 * volume_clipping parameter. */
468 sparse->tree().voxelizeActiveTiles();
469
470 /* Compute index to world matrix. */
471 float3 voxel_size = make_float3(1.0f / image_memory->data_width,
472 1.0f / image_memory->data_height,
473 1.0f / image_memory->data_depth);
474
475 transform_3d = transform_inverse(transform_3d);
476
477 openvdb::Mat4R index_to_world_mat((double)(voxel_size.x * transform_3d[0][0]),
478 0.0,
479 0.0,
480 0.0,
481 0.0,
482 (double)(voxel_size.y * transform_3d[1][1]),
483 0.0,
484 0.0,
485 0.0,
486 0.0,
487 (double)(voxel_size.z * transform_3d[2][2]),
488 0.0,
489 (double)transform_3d[0][3],
490 (double)transform_3d[1][3],
491 (double)transform_3d[2][3],
492 1.0);
493
494 openvdb::math::Transform::Ptr index_to_world_tfm =
495 openvdb::math::Transform::createLinearTransform(index_to_world_mat);
496
497 sparse->setTransform(index_to_world_tfm);
498
499 return sparse;
500}
501
502static int estimate_required_velocity_padding(openvdb::GridBase::ConstPtr grid,
503 float velocity_scale)
504{
505 /* TODO: we may need to also find outliers and clamp them to avoid adding too much padding. */
506 openvdb::math::Extrema extrema;
507 openvdb::Vec3d voxel_size;
508
509 /* External `.vdb` files have a vec3 type for velocity,
510 * but the Blender exporter creates a vec4. */
511 if (grid->isType<openvdb::Vec3fGrid>()) {
512 openvdb::Vec3fGrid::ConstPtr vel_grid = openvdb::gridConstPtrCast<openvdb::Vec3fGrid>(grid);
513 extrema = openvdb::tools::extrema(vel_grid->cbeginValueOn());
514 voxel_size = vel_grid->voxelSize();
515 }
516 else if (grid->isType<openvdb::Vec4fGrid>()) {
517 openvdb::Vec4fGrid::ConstPtr vel_grid = openvdb::gridConstPtrCast<openvdb::Vec4fGrid>(grid);
518 extrema = openvdb::tools::extrema(vel_grid->cbeginValueOn());
519 voxel_size = vel_grid->voxelSize();
520 }
521 else {
522 assert(0);
523 return 0;
524 }
525
526 /* We should only have uniform grids, so x = y = z, but we never know. */
527 const double max_voxel_size = openvdb::math::Max(voxel_size.x(), voxel_size.y(), voxel_size.z());
528 if (max_voxel_size == 0.0) {
529 return 0;
530 }
531
532 const double estimated_padding = extrema.max() * static_cast<double>(velocity_scale) /
533 max_voxel_size;
534
535 return static_cast<int>(std::ceil(estimated_padding));
536}
537
538static openvdb::FloatGrid::ConstPtr get_vdb_for_attribute(Volume *volume, AttributeStandard std)
539{
540 Attribute *attr = volume->attributes.find(std);
541 if (!attr) {
542 return nullptr;
543 }
544
545 ImageHandle &handle = attr->data_voxel();
546 VDBImageLoader *vdb_loader = handle.vdb_loader();
547 if (!vdb_loader) {
548 return nullptr;
549 }
550
551 openvdb::GridBase::ConstPtr grid = vdb_loader->get_grid();
552 if (!grid) {
553 return nullptr;
554 }
555
556 if (!grid->isType<openvdb::FloatGrid>()) {
557 return nullptr;
558 }
559
560 return openvdb::gridConstPtrCast<openvdb::FloatGrid>(grid);
561}
562
563class MergeScalarGrids {
564 typedef openvdb::FloatTree ScalarTree;
565
566 openvdb::tree::ValueAccessor<const ScalarTree> m_acc_x, m_acc_y, m_acc_z;
567
568 public:
569 MergeScalarGrids(const ScalarTree *x_tree, const ScalarTree *y_tree, const ScalarTree *z_tree)
570 : m_acc_x(*x_tree), m_acc_y(*y_tree), m_acc_z(*z_tree)
571 {
572 }
573
574 MergeScalarGrids(const MergeScalarGrids &other)
575 : m_acc_x(other.m_acc_x), m_acc_y(other.m_acc_y), m_acc_z(other.m_acc_z)
576 {
577 }
578
579 void operator()(const openvdb::Vec3STree::ValueOnIter &it) const
580 {
581 using namespace openvdb;
582
583 const math::Coord xyz = it.getCoord();
584 float x = m_acc_x.getValue(xyz);
585 float y = m_acc_y.getValue(xyz);
586 float z = m_acc_z.getValue(xyz);
587
588 it.setValue(math::Vec3s(x, y, z));
589 }
590};
591
592static void merge_scalar_grids_for_velocity(const Scene *scene, Volume *volume)
593{
594 if (volume->attributes.find(ATTR_STD_VOLUME_VELOCITY)) {
595 /* A vector grid for velocity is already available. */
596 return;
597 }
598
599 openvdb::FloatGrid::ConstPtr vel_x_grid = get_vdb_for_attribute(volume,
601 openvdb::FloatGrid::ConstPtr vel_y_grid = get_vdb_for_attribute(volume,
603 openvdb::FloatGrid::ConstPtr vel_z_grid = get_vdb_for_attribute(volume,
605
606 if (!(vel_x_grid && vel_y_grid && vel_z_grid)) {
607 return;
608 }
609
610 openvdb::Vec3fGrid::Ptr vecgrid = openvdb::Vec3SGrid::create(openvdb::Vec3s(0.0f));
611
612 /* Activate voxels in the vector grid based on the scalar grids to ensure thread safety during
613 * the merge. */
614 vecgrid->tree().topologyUnion(vel_x_grid->tree());
615 vecgrid->tree().topologyUnion(vel_y_grid->tree());
616 vecgrid->tree().topologyUnion(vel_z_grid->tree());
617
618 MergeScalarGrids op(&vel_x_grid->tree(), &vel_y_grid->tree(), &vel_z_grid->tree());
619 openvdb::tools::foreach (vecgrid->beginValueOn(), op, true, false);
620
621 /* Assume all grids have the same transformation. */
622 openvdb::math::Transform::Ptr transform = openvdb::ConstPtrCast<openvdb::math::Transform>(
623 vel_x_grid->transformPtr());
624 vecgrid->setTransform(transform);
625
626 /* Make an attribute for it. */
627 Attribute *attr = volume->attributes.add(ATTR_STD_VOLUME_VELOCITY);
628 ImageLoader *loader = new VDBImageLoader(vecgrid, "merged_velocity");
630 attr->data_voxel() = scene->image_manager->add_image(loader, params);
631}
632#endif
633
634/* ************************************************************************** */
635
636void GeometryManager::create_volume_mesh(const Scene *scene, Volume *volume, Progress &progress)
637{
638 string msg = string_printf("Computing Volume Mesh %s", volume->name.c_str());
639 progress.set_status("Updating Mesh", msg);
640
641 /* Find shader and compute padding based on volume shader interpolation settings. */
642 Shader *volume_shader = NULL;
643 int pad_size = 0;
644
645 for (Node *node : volume->get_used_shaders()) {
646 Shader *shader = static_cast<Shader *>(node);
647
648 if (!shader->has_volume) {
649 continue;
650 }
651
652 volume_shader = shader;
653
654 if (shader->get_volume_interpolation_method() == VOLUME_INTERPOLATION_LINEAR) {
655 pad_size = max(1, pad_size);
656 }
657 else if (shader->get_volume_interpolation_method() == VOLUME_INTERPOLATION_CUBIC) {
658 pad_size = max(2, pad_size);
659 }
660
661 break;
662 }
663
664 /* Clear existing volume mesh, done here in case we early out due to
665 * empty grid or missing volume shader.
666 * Also keep the shaders to avoid infinite loops when synchronizing, as this will tag the shaders
667 * as having changed. */
668 volume->clear(true);
669 volume->need_update_rebuild = true;
670
671 if (!volume_shader) {
672 return;
673 }
674
675 /* Create volume mesh builder. */
676 VolumeMeshBuilder builder;
677
678#ifdef WITH_OPENVDB
679 merge_scalar_grids_for_velocity(scene, volume);
680
681 for (Attribute &attr : volume->attributes.attributes) {
682 if (attr.element != ATTR_ELEMENT_VOXEL) {
683 continue;
684 }
685
686 bool do_clipping = false;
687
688 ImageHandle &handle = attr.data_voxel();
689
690 /* Try building from OpenVDB grid directly. */
691 VDBImageLoader *vdb_loader = handle.vdb_loader();
692 openvdb::GridBase::ConstPtr grid;
693 if (vdb_loader) {
694 grid = vdb_loader->get_grid();
695
696 /* If building from an OpenVDB grid, we need to manually clip the values. */
697 do_clipping = true;
698 }
699
700 /* Else fall back to creating an OpenVDB grid from the dense volume data. */
701 if (!grid) {
702 device_texture *image_memory = handle.image_memory();
703
704 if (image_memory->data_elements == 1) {
705 grid = openvdb_grid_from_device_texture<openvdb::FloatGrid>(
706 image_memory, volume->get_clipping(), handle.metadata().transform_3d);
707 }
708 else if (image_memory->data_elements == 3) {
709 grid = openvdb_grid_from_device_texture<openvdb::Vec3fGrid>(
710 image_memory, volume->get_clipping(), handle.metadata().transform_3d);
711 }
712 else if (image_memory->data_elements == 4) {
713 grid = openvdb_grid_from_device_texture<openvdb::Vec4fGrid>(
714 image_memory, volume->get_clipping(), handle.metadata().transform_3d);
715 }
716 }
717
718 if (grid) {
719 /* Add padding based on the maximum velocity vector. */
720 if (attr.std == ATTR_STD_VOLUME_VELOCITY && scene->need_motion() != Scene::MOTION_NONE) {
721 pad_size = max(pad_size,
722 estimate_required_velocity_padding(grid, volume->get_velocity_scale()));
723 }
724
725 builder.add_grid(grid, do_clipping, volume->get_clipping());
726 }
727 }
728#else
729 (void)scene;
730#endif
731
732 /* If nothing to build, early out. */
733 if (builder.empty_grid()) {
734 return;
735 }
736
737 builder.add_padding(pad_size);
738
739 /* Slightly offset vertex coordinates to avoid overlapping faces with other
740 * volumes or meshes. The proper solution would be to improve intersection in
741 * the kernel to support robust handling of multiple overlapping faces or use
742 * an all-hit intersection similar to shadows. */
743 const float face_overlap_avoidance = 0.1f *
744 hash_uint_to_float(hash_string(volume->name.c_str()));
745
746 /* Create mesh. */
747 vector<float3> vertices;
749 vector<float3> face_normals;
750 builder.create_mesh(vertices, indices, face_normals, face_overlap_avoidance);
751
752 volume->reserve_mesh(vertices.size(), indices.size() / 3);
753 volume->used_shaders.clear();
754 volume->used_shaders.push_back_slow(volume_shader);
755
756 for (size_t i = 0; i < vertices.size(); ++i) {
757 volume->add_vertex(vertices[i]);
758 }
759
760 for (size_t i = 0; i < indices.size(); i += 3) {
761 volume->add_triangle(indices[i], indices[i + 1], indices[i + 2], 0, false);
762 }
763
764 Attribute *attr_fN = volume->attributes.add(ATTR_STD_FACE_NORMAL);
765 float3 *fN = attr_fN->data_float3();
766
767 for (size_t i = 0; i < face_normals.size(); ++i) {
768 fN[i] = face_normals[i];
769 }
770
771 /* Print stats. */
772 VLOG_WORK << "Memory usage volume mesh: "
773 << ((vertices.size() + face_normals.size()) * sizeof(float3) +
774 indices.size() * sizeof(int)) /
775 (1024.0 * 1024.0)
776 << "Mb.";
777}
778
ATTR_WARN_UNUSED_RESULT const BMVert * v
SIMD_FORCE_INLINE const btScalar & z() const
Return the z value.
Definition btQuadWord.h:117
SIMD_FORCE_INLINE btVector3 operator()(const btVector3 &x) const
Return the transform of the vector.
Definition btTransform.h:90
AttributeElement element
AttributeStandard std
ImageHandle & data_voxel()
float3 * data_float3()
void add(const float &f)
void create_volume_mesh(const Scene *scene, Volume *volume, Progress &progress)
void set_status(const string &status_, const string &substatus_="")
Definition progress.h:263
void convert_quads_to_tris(const vector< QuadData > &quads, vector< int > &tris, vector< float3 > &face_normals)
void add_padding(int pad_size)
bool empty_grid() const
void convert_object_space(const vector< int3 > &vertices, vector< float3 > &out_vertices, const float face_overlap_avoidance)
void create_mesh(vector< float3 > &vertices, vector< int > &indices, vector< float3 > &face_normals, const float face_overlap_avoidance)
void generate_vertices_and_quads(vector< int3 > &vertices_is, vector< QuadData > &quads)
OperationNode * node
#define CCL_NAMESPACE_END
ccl_device_forceinline float3 make_float3(const float x, const float y, const float z)
#define NULL
ccl_device_forceinline int3 make_int3(const int x, const int y, const int z)
KDTree_3d * tree
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
static ushort indices[]
blender::gpu::Batch * quad
static uint hash_string(const char *str)
Definition hash.h:532
ccl_device_inline float hash_uint_to_float(uint kx)
Definition hash.h:136
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
AttributeStandard
@ ATTR_STD_VOLUME_VELOCITY_Y
@ ATTR_STD_VOLUME_VELOCITY_Z
@ ATTR_STD_VOLUME_VELOCITY
@ ATTR_STD_FACE_NORMAL
@ ATTR_STD_VOLUME_VELOCITY_X
@ ATTR_ELEMENT_VOXEL
#define VLOG_WORK
Definition log.h:75
static void copy(bNodeTree *dest_ntree, bNode *dest_node, const bNode *src_node)
#define SOCKET_FLOAT(name, ui_name, default_value,...)
Definition node_type.h:200
#define NODE_DEFINE(structname)
Definition node_type.h:148
#define SOCKET_BOOLEAN(name, ui_name, default_value,...)
Definition node_type.h:192
@ VOLUME_INTERPOLATION_LINEAR
@ VOLUME_INTERPOLATION_CUBIC
@ QUAD_X_MAX
@ QUAD_Y_MIN
@ QUAD_Z_MIN
@ QUAD_Y_MAX
@ QUAD_X_MIN
@ QUAD_Z_MAX
#define min(a, b)
Definition sort.c:32
CCL_NAMESPACE_BEGIN string string_printf(const char *format,...)
Definition string.cpp:23
void clear(bool preserve_shaders=false) override
static NodeType * add(const char *name, CreateFunc create, Type type=NONE, const NodeType *base=NULL)
float3 normal
@ MOTION_NONE
Definition scene.h:177
NODE_DECLARE Volume()
virtual void clear(bool preserve_shaders=false) override
float z
Definition sky_float3.h:27
float y
Definition sky_float3.h:27
float x
Definition sky_float3.h:27
ccl_device_inline Transform transform_inverse(const Transform tfm)
Definition transform.h:423
float max