Blender V4.3
volume_render.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include "MEM_guardedalloc.h"
10
11#include "BLI_array.hh"
12#include "BLI_math_matrix.h"
13#include "BLI_math_vector.h"
15#include "BLI_task.hh"
16#include "BLI_vector.hh"
17
18#include "DNA_volume_types.h"
19
20#include "BKE_volume_grid.hh"
21#include "BKE_volume_openvdb.hh"
22#include "BKE_volume_render.hh"
23
24#ifdef WITH_OPENVDB
25# include <openvdb/openvdb.h>
26# include <openvdb/tools/Dense.h>
27#endif
28
29/* Dense Voxels */
30
31#ifdef WITH_OPENVDB
32
33template<typename GridType, typename VoxelType>
34static void extract_dense_voxels(const openvdb::GridBase &grid,
35 const openvdb::CoordBBox bbox,
36 VoxelType *r_voxels)
37{
38 BLI_assert(grid.isType<GridType>());
39 blender::threading::memory_bandwidth_bound_task(bbox.volume() * sizeof(VoxelType), [&]() {
40 openvdb::tools::Dense<VoxelType, openvdb::tools::LayoutXYZ> dense(bbox, r_voxels);
41 openvdb::tools::copyToDense(static_cast<const GridType &>(grid), dense);
42 });
43}
44
45static void extract_dense_float_voxels(const VolumeGridType grid_type,
46 const openvdb::GridBase &grid,
47 const openvdb::CoordBBox &bbox,
48 float *r_voxels)
49{
50 switch (grid_type) {
52 return extract_dense_voxels<openvdb::BoolGrid, float>(grid, bbox, r_voxels);
54 return extract_dense_voxels<openvdb::FloatGrid, float>(grid, bbox, r_voxels);
56 return extract_dense_voxels<openvdb::DoubleGrid, float>(grid, bbox, r_voxels);
57 case VOLUME_GRID_INT:
58 return extract_dense_voxels<openvdb::Int32Grid, float>(grid, bbox, r_voxels);
60 return extract_dense_voxels<openvdb::Int64Grid, float>(grid, bbox, r_voxels);
62 return extract_dense_voxels<openvdb::MaskGrid, float>(grid, bbox, r_voxels);
64 return extract_dense_voxels<openvdb::Vec3fGrid, openvdb::Vec3f>(
65 grid, bbox, reinterpret_cast<openvdb::Vec3f *>(r_voxels));
67 return extract_dense_voxels<openvdb::Vec3dGrid, openvdb::Vec3f>(
68 grid, bbox, reinterpret_cast<openvdb::Vec3f *>(r_voxels));
70 return extract_dense_voxels<openvdb::Vec3IGrid, openvdb::Vec3f>(
71 grid, bbox, reinterpret_cast<openvdb::Vec3f *>(r_voxels));
74 /* Zero channels to copy. */
75 break;
76 }
77}
78
79static void create_texture_to_object_matrix(const openvdb::Mat4d &grid_transform,
80 const openvdb::CoordBBox &bbox,
81 float r_texture_to_object[4][4])
82{
83 float index_to_object[4][4];
84 memcpy(index_to_object, openvdb::Mat4s(grid_transform).asPointer(), sizeof(index_to_object));
85
86 float texture_to_index[4][4];
87 const openvdb::Vec3f loc = bbox.min().asVec3s();
88 const openvdb::Vec3f size = bbox.dim().asVec3s();
89 size_to_mat4(texture_to_index, size.asV());
90 copy_v3_v3(texture_to_index[3], loc.asV());
91
92 mul_m4_m4m4(r_texture_to_object, index_to_object, texture_to_index);
93}
94
95#endif
96
98 const blender::bke::VolumeGridData *volume_grid,
99 DenseFloatVolumeGrid *r_dense_grid)
100{
101#ifdef WITH_OPENVDB
102 const VolumeGridType grid_type = volume_grid->grid_type();
103 blender::bke::VolumeTreeAccessToken tree_token;
104 const openvdb::GridBase &grid = volume_grid->grid(tree_token);
105
106 const openvdb::CoordBBox bbox = grid.evalActiveVoxelBoundingBox();
107 if (bbox.empty()) {
108 return false;
109 }
110 const std::array<int64_t, 6> bbox_indices = {UNPACK3(openvdb::math::Abs(bbox.min())),
111 UNPACK3(openvdb::math::Abs(bbox.max()))};
112 const int64_t max_bbox_index = *std::max_element(bbox_indices.begin(), bbox_indices.end());
113 if (max_bbox_index > (1 << 30)) {
114 /* There is an integer overflow when trying to extract dense voxels when the indices are very
115 * large. */
116 return false;
117 }
118
119 const openvdb::Vec3i resolution = bbox.dim().asVec3i();
120 const int64_t num_voxels = int64_t(resolution[0]) * int64_t(resolution[1]) *
121 int64_t(resolution[2]);
122 const int channels = blender::bke::volume_grid::get_channels_num(grid_type);
123 const int elem_size = sizeof(float) * channels;
124 float *voxels = static_cast<float *>(MEM_malloc_arrayN(num_voxels, elem_size, __func__));
125 if (voxels == nullptr) {
126 return false;
127 }
128
129 extract_dense_float_voxels(grid_type, grid, bbox, voxels);
130 create_texture_to_object_matrix(grid.transform().baseMap()->getAffineMap()->getMat4(),
131 bbox,
132 r_dense_grid->texture_to_object);
133
134 r_dense_grid->voxels = voxels;
135 r_dense_grid->channels = channels;
136 copy_v3_v3_int(r_dense_grid->resolution, resolution.asV());
137 return true;
138#endif
139 UNUSED_VARS(volume, volume_grid, r_dense_grid);
140 return false;
141}
142
144{
145 if (dense_grid->voxels != nullptr) {
146 MEM_freeN(dense_grid->voxels);
147 }
148}
149
150/* Wireframe */
151
152#ifdef WITH_OPENVDB
153
155template<typename GridType>
156static blender::Vector<openvdb::CoordBBox> get_bounding_boxes(const GridType &grid,
157 const bool coarse)
158{
159 using TreeType = typename GridType::TreeType;
160 using Depth2Type = typename TreeType::RootNodeType::ChildNodeType::ChildNodeType;
161 using NodeCIter = typename TreeType::NodeCIter;
162
164 const int depth = coarse ? 2 : 3;
165
166 NodeCIter iter = grid.tree().cbeginNode();
167 iter.setMaxDepth(depth);
168
169 for (; iter; ++iter) {
170 if (iter.getDepth() != depth) {
171 continue;
172 }
173
174 openvdb::CoordBBox box;
175 if (depth == 2) {
176 /* Internal node at depth 2. */
177 const Depth2Type *node = nullptr;
178 iter.getNode(node);
179 if (node) {
180 node->evalActiveBoundingBox(box, false);
181 }
182 else {
183 continue;
184 }
185 }
186 else {
187 /* Leaf node. */
188 if (!iter.getBoundingBox(box)) {
189 continue;
190 }
191 }
192
193 /* +1 to convert from exclusive to inclusive bounds. */
194 box.max() = box.max().offsetBy(1);
195
196 boxes.append(box);
197 }
198
199 return boxes;
200}
201
202struct GetBoundingBoxesOp {
203 const openvdb::GridBase &grid;
204 const bool coarse;
205
206 template<typename GridType> blender::Vector<openvdb::CoordBBox> operator()()
207 {
208 return get_bounding_boxes(static_cast<const GridType &>(grid), coarse);
209 }
210};
211
212static blender::Vector<openvdb::CoordBBox> get_bounding_boxes(VolumeGridType grid_type,
213 const openvdb::GridBase &grid,
214 const bool coarse)
215{
216 GetBoundingBoxesOp op{grid, coarse};
217 return BKE_volume_grid_type_operation(grid_type, op);
218}
219
220static void boxes_to_center_points(blender::Span<openvdb::CoordBBox> boxes,
221 const openvdb::math::Transform &transform,
223{
224 BLI_assert(boxes.size() == r_verts.size());
225 for (const int i : boxes.index_range()) {
226 openvdb::Vec3d center = transform.indexToWorld(boxes[i].getCenter());
227 r_verts[i] = blender::float3(center[0], center[1], center[2]);
228 }
229}
230
231static void boxes_to_corner_points(blender::Span<openvdb::CoordBBox> boxes,
232 const openvdb::math::Transform &transform,
234{
235 BLI_assert(boxes.size() * 8 == r_verts.size());
236 for (const int i : boxes.index_range()) {
237 const openvdb::CoordBBox &box = boxes[i];
238
239 /* The ordering of the corner points is lexicographic. */
240 std::array<openvdb::Coord, 8> corners;
241 box.getCornerPoints(corners.data());
242
243 for (int j = 0; j < 8; j++) {
244 openvdb::Coord corner_i = corners[j];
245 openvdb::Vec3d corner_d = transform.indexToWorld(corner_i);
246 r_verts[8 * i + j] = blender::float3(corner_d[0], corner_d[1], corner_d[2]);
247 }
248 }
249}
250
251static void boxes_to_edge_mesh(blender::Span<openvdb::CoordBBox> boxes,
252 const openvdb::math::Transform &transform,
254 blender::Vector<std::array<int, 2>> &r_edges)
255{
256 /* TODO: Deduplicate edges, hide flat edges? */
257
258 const int box_edges[12][2] = {
259 {0, 1},
260 {0, 2},
261 {0, 4},
262 {1, 3},
263 {1, 5},
264 {2, 3},
265 {2, 6},
266 {3, 7},
267 {4, 5},
268 {4, 6},
269 {5, 7},
270 {6, 7},
271 };
272
273 int vert_offset = r_verts.size();
274 int edge_offset = r_edges.size();
275
276 const int vert_amount = 8 * boxes.size();
277 const int edge_amount = 12 * boxes.size();
278
279 r_verts.resize(r_verts.size() + vert_amount);
280 r_edges.resize(r_edges.size() + edge_amount);
281 boxes_to_corner_points(boxes, transform, r_verts.as_mutable_span().take_back(vert_amount));
282
283 for (int i = 0; i < boxes.size(); i++) {
284 for (int j = 0; j < 12; j++) {
285 r_edges[edge_offset + j] = {vert_offset + box_edges[j][0], vert_offset + box_edges[j][1]};
286 }
287 vert_offset += 8;
288 edge_offset += 12;
289 }
290}
291
292static void boxes_to_cube_mesh(blender::Span<openvdb::CoordBBox> boxes,
293 const openvdb::math::Transform &transform,
295 blender::Vector<std::array<int, 3>> &r_tris)
296{
297 const int box_tris[12][3] = {
298 {0, 1, 4},
299 {4, 1, 5},
300 {0, 2, 1},
301 {1, 2, 3},
302 {1, 3, 5},
303 {5, 3, 7},
304 {6, 4, 5},
305 {7, 5, 6},
306 {2, 0, 4},
307 {2, 4, 6},
308 {3, 7, 2},
309 {6, 2, 7},
310 };
311
312 int vert_offset = r_verts.size();
313 int tri_offset = r_tris.size();
314
315 const int vert_amount = 8 * boxes.size();
316 const int tri_amount = 12 * boxes.size();
317
318 r_verts.resize(r_verts.size() + vert_amount);
319 r_tris.resize(r_tris.size() + tri_amount);
320 boxes_to_corner_points(boxes, transform, r_verts.as_mutable_span().take_back(vert_amount));
321
322 for (int i = 0; i < boxes.size(); i++) {
323 for (int j = 0; j < 12; j++) {
324 r_tris[tri_offset + j] = {vert_offset + box_tris[j][0],
325 vert_offset + box_tris[j][1],
326 vert_offset + box_tris[j][2]};
327 }
328 vert_offset += 8;
329 tri_offset += 12;
330 }
331}
332
333#endif
334
336 const blender::bke::VolumeGridData *volume_grid,
338 void *cb_userdata)
339{
340 if (volume->display.wireframe_type == VOLUME_WIREFRAME_NONE) {
341 cb(cb_userdata, nullptr, nullptr, 0, 0);
342 return;
343 }
344
345#ifdef WITH_OPENVDB
346 blender::bke::VolumeTreeAccessToken tree_token;
347 const openvdb::GridBase &grid = volume_grid->grid(tree_token);
348
349 if (volume->display.wireframe_type == VOLUME_WIREFRAME_BOUNDS) {
350 /* Bounding box. */
351 openvdb::CoordBBox box;
354 if (grid.baseTree().evalLeafBoundingBox(box)) {
355 boxes_to_edge_mesh({box}, grid.transform(), verts, edges);
356 }
357 cb(cb_userdata,
358 (float(*)[3])verts.data(),
359 (int(*)[2])edges.data(),
360 verts.size(),
361 edges.size());
362 }
363 else {
364 blender::Vector<openvdb::CoordBBox> boxes = get_bounding_boxes(
365 volume_grid->grid_type(),
366 grid,
367 volume->display.wireframe_detail == VOLUME_WIREFRAME_COARSE);
368
371
372 if (volume->display.wireframe_type == VOLUME_WIREFRAME_POINTS) {
373 verts.resize(boxes.size());
374 boxes_to_center_points(boxes, grid.transform(), verts);
375 }
376 else {
377 boxes_to_edge_mesh(boxes, grid.transform(), verts, edges);
378 }
379
380 cb(cb_userdata,
381 (float(*)[3])verts.data(),
382 (int(*)[2])edges.data(),
383 verts.size(),
384 edges.size());
385 }
386
387#else
388 UNUSED_VARS(volume, volume_grid);
389 cb(cb_userdata, nullptr, nullptr, 0, 0);
390#endif
391}
392
393#ifdef WITH_OPENVDB
394static void grow_triangles(blender::MutableSpan<blender::float3> verts,
395 blender::Span<std::array<int, 3>> tris,
396 const float factor)
397{
398 /* Compute the offset for every vertex based on the connected edges.
399 * This formula simply tries increases the length of all edges. */
400 blender::Array<blender::float3> offsets(verts.size(), {0, 0, 0});
401 blender::Array<float> weights(verts.size(), 0.0f);
402 for (const std::array<int, 3> &tri : tris) {
403 offsets[tri[0]] += factor * (2 * verts[tri[0]] - verts[tri[1]] - verts[tri[2]]);
404 offsets[tri[1]] += factor * (2 * verts[tri[1]] - verts[tri[0]] - verts[tri[2]]);
405 offsets[tri[2]] += factor * (2 * verts[tri[2]] - verts[tri[0]] - verts[tri[1]]);
406 weights[tri[0]] += 1.0;
407 weights[tri[1]] += 1.0;
408 weights[tri[2]] += 1.0;
409 }
410 /* Apply the computed offsets. */
411 for (const int i : verts.index_range()) {
412 if (weights[i] > 0.0f) {
413 verts[i] += offsets[i] / weights[i];
414 }
415 }
416}
417#endif /* WITH_OPENVDB */
418
420 const blender::bke::VolumeGridData *volume_grid,
422 void *cb_userdata)
423{
424#ifdef WITH_OPENVDB
425 blender::bke::VolumeTreeAccessToken tree_token;
426 const openvdb::GridBase &grid = volume_grid->grid(tree_token);
427 blender::Vector<openvdb::CoordBBox> boxes = get_bounding_boxes(
428 volume_grid->grid_type(), grid, true);
429
432 boxes_to_cube_mesh(boxes, grid.transform(), verts, tris);
433
434 /* By slightly scaling the individual boxes up, we can avoid some artifacts when drawing the
435 * selection outline. */
436 const float offset_factor = 0.01f;
437 grow_triangles(verts, tris, offset_factor);
438
439 cb(cb_userdata, (float(*)[3])verts.data(), (int(*)[3])tris.data(), verts.size(), tris.size());
440#else
441 UNUSED_VARS(volume_grid);
442 cb(cb_userdata, nullptr, nullptr, 0, 0);
443#endif
444}
445
446/* Render */
447
448float BKE_volume_density_scale(const Volume *volume, const float matrix[4][4])
449{
450 if (volume->render.space == VOLUME_SPACE_OBJECT) {
451 float unit[3] = {1.0f, 1.0f, 1.0f};
452 normalize_v3(unit);
453 mul_mat3_m4_v3(matrix, unit);
454 return 1.0f / len_v3(unit);
455 }
456
457 return 1.0f;
458}
VolumeGridType
@ VOLUME_GRID_VECTOR_FLOAT
@ VOLUME_GRID_MASK
@ VOLUME_GRID_VECTOR_DOUBLE
@ VOLUME_GRID_VECTOR_INT
@ VOLUME_GRID_UNKNOWN
@ VOLUME_GRID_DOUBLE
@ VOLUME_GRID_BOOLEAN
@ VOLUME_GRID_INT
@ VOLUME_GRID_INT64
@ VOLUME_GRID_POINTS
@ VOLUME_GRID_FLOAT
Volume data-block rendering and viewport drawing utilities.
void(*)( void *userdata, const float(*verts)[3], const int(*edges)[2], int totvert, int totedge) BKE_volume_wireframe_cb
void(*)(void *userdata, float(*verts)[3], int(*tris)[3], int totvert, int tottris) BKE_volume_selection_surface_cb
#define BLI_assert(a)
Definition BLI_assert.h:50
void mul_m4_m4m4(float R[4][4], const float A[4][4], const float B[4][4])
void size_to_mat4(float R[4][4], const float size[3])
void mul_mat3_m4_v3(const float mat[4][4], float r[3])
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE void copy_v3_v3_int(int r[3], const int a[3])
MINLINE float normalize_v3(float n[3])
MINLINE float len_v3(const float a[3]) ATTR_WARN_UNUSED_RESULT
#define UNUSED_VARS(...)
#define UNPACK3(a)
@ VOLUME_SPACE_OBJECT
@ VOLUME_WIREFRAME_NONE
@ VOLUME_WIREFRAME_POINTS
@ VOLUME_WIREFRAME_BOUNDS
@ VOLUME_WIREFRAME_COARSE
Read Guarded memory(de)allocation.
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 producing a negative Combine Generate a color from its and blue channels(Deprecated)") DefNode(ShaderNode
SIMD_FORCE_INLINE btVector3 operator()(const btVector3 &x) const
Return the transform of the vector.
Definition btTransform.h:90
constexpr int64_t size() const
Definition BLI_span.hh:494
constexpr int64_t size() const
Definition BLI_span.hh:253
int64_t size() const
void append(const T &value)
void resize(const int64_t new_size)
MutableSpan< T > as_mutable_span()
draw_view in_light_buf[] float
static float verts[][3]
void *(* MEM_malloc_arrayN)(size_t len, size_t size, const char *str)
Definition mallocn.cc:45
void MEM_freeN(void *vmemh)
Definition mallocn.cc:105
int get_channels_num(VolumeGridType type)
void memory_bandwidth_bound_task(const int64_t approximate_bytes_touched, const Function &function)
Definition BLI_task.hh:243
VecBase< float, 3 > float3
__int64 int64_t
Definition stdint.h:89
void BKE_volume_dense_float_grid_clear(DenseFloatVolumeGrid *dense_grid)
bool BKE_volume_grid_dense_floats(const Volume *volume, const blender::bke::VolumeGridData *volume_grid, DenseFloatVolumeGrid *r_dense_grid)
float BKE_volume_density_scale(const Volume *volume, const float matrix[4][4])
void BKE_volume_grid_selection_surface(const Volume *, const blender::bke::VolumeGridData *volume_grid, BKE_volume_selection_surface_cb cb, void *cb_userdata)
void BKE_volume_grid_wireframe(const Volume *volume, const blender::bke::VolumeGridData *volume_grid, BKE_volume_wireframe_cb cb, void *cb_userdata)