blender/intern/cycles/scene/volume.cpp

779 lines
25 KiB
C++

/* SPDX-FileCopyrightText: 2020-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#include "scene/volume.h"
#include "scene/attribute.h"
#include "scene/image_vdb.h"
#include "scene/scene.h"
#ifdef WITH_OPENVDB
# include <openvdb/tools/Dense.h>
# include <openvdb/tools/GridTransformer.h>
# include <openvdb/tools/Morphology.h>
# include <openvdb/tools/Statistics.h>
#endif
#include "util/hash.h"
#include "util/log.h"
#include "util/openvdb.h"
#include "util/progress.h"
#include "util/types.h"
CCL_NAMESPACE_BEGIN
NODE_DEFINE(Volume)
{
NodeType *type = NodeType::add("volume", create, NodeType::NONE, Mesh::get_node_type());
SOCKET_FLOAT(clipping, "Clipping", 0.001f);
SOCKET_FLOAT(step_size, "Step Size", 0.0f);
SOCKET_BOOLEAN(object_space, "Object Space", false);
SOCKET_FLOAT(velocity_scale, "Velocity Scale", 1.0f);
return type;
}
Volume::Volume() : Mesh(get_node_type(), Geometry::VOLUME)
{
clipping = 0.001f;
step_size = 0.0f;
object_space = false;
}
void Volume::clear(bool preserve_shaders)
{
Mesh::clear(preserve_shaders, true);
}
struct QuadData {
int v0, v1, v2, v3;
float3 normal;
};
enum {
QUAD_X_MIN = 0,
QUAD_X_MAX = 1,
QUAD_Y_MIN = 2,
QUAD_Y_MAX = 3,
QUAD_Z_MIN = 4,
QUAD_Z_MAX = 5,
};
#ifdef WITH_OPENVDB
const int quads_indices[6][4] = {
/* QUAD_X_MIN */
{4, 0, 3, 7},
/* QUAD_X_MAX */
{1, 5, 6, 2},
/* QUAD_Y_MIN */
{4, 5, 1, 0},
/* QUAD_Y_MAX */
{3, 2, 6, 7},
/* QUAD_Z_MIN */
{0, 1, 2, 3},
/* QUAD_Z_MAX */
{5, 4, 7, 6},
};
const float3 quads_normals[6] = {
/* QUAD_X_MIN */
make_float3(-1.0f, 0.0f, 0.0f),
/* QUAD_X_MAX */
make_float3(1.0f, 0.0f, 0.0f),
/* QUAD_Y_MIN */
make_float3(0.0f, -1.0f, 0.0f),
/* QUAD_Y_MAX */
make_float3(0.0f, 1.0f, 0.0f),
/* QUAD_Z_MIN */
make_float3(0.0f, 0.0f, -1.0f),
/* QUAD_Z_MAX */
make_float3(0.0f, 0.0f, 1.0f),
};
static int add_vertex(int3 v,
vector<int3> &vertices,
int3 res,
unordered_map<size_t, int> &used_verts)
{
size_t vert_key = v.x + v.y * (res.x + 1) + v.z * (res.x + 1) * (res.y + 1);
unordered_map<size_t, int>::iterator it = used_verts.find(vert_key);
if (it != used_verts.end()) {
return it->second;
}
int vertex_offset = vertices.size();
used_verts[vert_key] = vertex_offset;
vertices.push_back(v);
return vertex_offset;
}
static void create_quad(int3 corners[8],
vector<int3> &vertices,
vector<QuadData> &quads,
int3 res,
unordered_map<size_t, int> &used_verts,
int face_index)
{
QuadData quad;
quad.v0 = add_vertex(corners[quads_indices[face_index][0]], vertices, res, used_verts);
quad.v1 = add_vertex(corners[quads_indices[face_index][1]], vertices, res, used_verts);
quad.v2 = add_vertex(corners[quads_indices[face_index][2]], vertices, res, used_verts);
quad.v3 = add_vertex(corners[quads_indices[face_index][3]], vertices, res, used_verts);
quad.normal = quads_normals[face_index];
quads.push_back(quad);
}
#endif
/* Create a mesh from a volume.
*
* The way the algorithm works is as follows:
*
* - The topologies of input OpenVDB grids are merged into a temporary grid.
* - Voxels of the temporary grid are dilated to account for the padding necessary for volume
* sampling.
* - Quads are created on the boundary between active and inactive leaf nodes of the temporary
* grid.
*/
class VolumeMeshBuilder {
public:
#ifdef WITH_OPENVDB
/* use a MaskGrid to store the topology to save memory */
openvdb::MaskGrid::Ptr topology_grid;
openvdb::CoordBBox bbox;
#endif
bool first_grid;
VolumeMeshBuilder();
#ifdef WITH_OPENVDB
void add_grid(openvdb::GridBase::ConstPtr grid, bool do_clipping, float volume_clipping);
#endif
void add_padding(int pad_size);
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);
void convert_object_space(const vector<int3> &vertices,
vector<float3> &out_vertices,
const float face_overlap_avoidance);
void convert_quads_to_tris(const vector<QuadData> &quads,
vector<int> &tris,
vector<float3> &face_normals);
bool empty_grid() const;
#ifdef WITH_OPENVDB
template<typename GridType>
void merge_grid(openvdb::GridBase::ConstPtr grid, bool do_clipping, float volume_clipping)
{
typename GridType::ConstPtr typed_grid = openvdb::gridConstPtrCast<GridType>(grid);
if (do_clipping) {
using ValueType = typename GridType::ValueType;
typename GridType::Ptr copy = typed_grid->deepCopy();
typename GridType::ValueOnIter iter = copy->beginValueOn();
for (; iter; ++iter) {
if (openvdb::math::Abs(iter.getValue()) < ValueType(volume_clipping)) {
iter.setValueOff();
}
}
typed_grid = copy;
}
topology_grid->topologyUnion(*typed_grid);
}
#endif
};
VolumeMeshBuilder::VolumeMeshBuilder()
{
first_grid = true;
}
#ifdef WITH_OPENVDB
void VolumeMeshBuilder::add_grid(openvdb::GridBase::ConstPtr grid,
bool do_clipping,
float volume_clipping)
{
/* set the transform of our grid from the first one */
if (first_grid) {
topology_grid = openvdb::MaskGrid::create();
topology_grid->setTransform(grid->transform().copy());
first_grid = false;
}
/* if the transforms do not match, we need to resample one of the grids so that
* its index space registers with that of the other, here we resample our mask
* grid so memory usage is kept low */
else if (topology_grid->transform() != grid->transform()) {
openvdb::MaskGrid::Ptr temp_grid = topology_grid->copyWithNewTree();
temp_grid->setTransform(grid->transform().copy());
openvdb::tools::resampleToMatch<openvdb::tools::BoxSampler>(*topology_grid, *temp_grid);
topology_grid = temp_grid;
topology_grid->setTransform(grid->transform().copy());
}
if (grid->isType<openvdb::FloatGrid>()) {
merge_grid<openvdb::FloatGrid>(grid, do_clipping, volume_clipping);
}
else if (grid->isType<openvdb::Vec3fGrid>()) {
merge_grid<openvdb::Vec3fGrid>(grid, do_clipping, volume_clipping);
}
else if (grid->isType<openvdb::Vec4fGrid>()) {
merge_grid<openvdb::Vec4fGrid>(grid, do_clipping, volume_clipping);
}
else if (grid->isType<openvdb::BoolGrid>()) {
merge_grid<openvdb::BoolGrid>(grid, do_clipping, volume_clipping);
}
else if (grid->isType<openvdb::DoubleGrid>()) {
merge_grid<openvdb::DoubleGrid>(grid, do_clipping, volume_clipping);
}
else if (grid->isType<openvdb::Int32Grid>()) {
merge_grid<openvdb::Int32Grid>(grid, do_clipping, volume_clipping);
}
else if (grid->isType<openvdb::Int64Grid>()) {
merge_grid<openvdb::Int64Grid>(grid, do_clipping, volume_clipping);
}
else if (grid->isType<openvdb::Vec3IGrid>()) {
merge_grid<openvdb::Vec3IGrid>(grid, do_clipping, volume_clipping);
}
else if (grid->isType<openvdb::Vec3dGrid>()) {
merge_grid<openvdb::Vec3dGrid>(grid, do_clipping, volume_clipping);
}
else if (grid->isType<openvdb::MaskGrid>()) {
topology_grid->topologyUnion(*openvdb::gridConstPtrCast<openvdb::MaskGrid>(grid));
}
}
#endif
void VolumeMeshBuilder::add_padding(int pad_size)
{
#ifdef WITH_OPENVDB
openvdb::tools::dilateActiveValues(
topology_grid->tree(), pad_size, openvdb::tools::NN_FACE, openvdb::tools::IGNORE_TILES);
#else
(void)pad_size;
#endif
}
void VolumeMeshBuilder::create_mesh(vector<float3> &vertices,
vector<int> &indices,
vector<float3> &face_normals,
const float face_overlap_avoidance)
{
#ifdef WITH_OPENVDB
/* We create vertices in index space (is), and only convert them to object
* space when done. */
vector<int3> vertices_is;
vector<QuadData> quads;
/* make sure we only have leaf nodes in the tree, as tiles are not handled by
* this algorithm */
topology_grid->tree().voxelizeActiveTiles();
generate_vertices_and_quads(vertices_is, quads);
convert_object_space(vertices_is, vertices, face_overlap_avoidance);
convert_quads_to_tris(quads, indices, face_normals);
#else
(void)vertices;
(void)indices;
(void)face_normals;
(void)face_overlap_avoidance;
#endif
}
#ifdef WITH_OPENVDB
static bool is_non_empty_leaf(const openvdb::MaskGrid::TreeType &tree, const openvdb::Coord coord)
{
auto *leaf_node = tree.probeLeaf(coord);
return (leaf_node && !leaf_node->isEmpty());
}
#endif
void VolumeMeshBuilder::generate_vertices_and_quads(vector<ccl::int3> &vertices_is,
vector<QuadData> &quads)
{
#ifdef WITH_OPENVDB
const openvdb::MaskGrid::TreeType &tree = topology_grid->tree();
tree.evalLeafBoundingBox(bbox);
const int3 resolution = make_int3(bbox.dim().x(), bbox.dim().y(), bbox.dim().z());
unordered_map<size_t, int> used_verts;
for (auto iter = tree.cbeginLeaf(); iter; ++iter) {
if (iter->isEmpty()) {
continue;
}
openvdb::CoordBBox leaf_bbox = iter->getNodeBoundingBox();
/* +1 to convert from exclusive to include bounds. */
leaf_bbox.max() = leaf_bbox.max().offsetBy(1);
int3 min = make_int3(leaf_bbox.min().x(), leaf_bbox.min().y(), leaf_bbox.min().z());
int3 max = make_int3(leaf_bbox.max().x(), leaf_bbox.max().y(), leaf_bbox.max().z());
int3 corners[8] = {
make_int3(min[0], min[1], min[2]),
make_int3(max[0], min[1], min[2]),
make_int3(max[0], max[1], min[2]),
make_int3(min[0], max[1], min[2]),
make_int3(min[0], min[1], max[2]),
make_int3(max[0], min[1], max[2]),
make_int3(max[0], max[1], max[2]),
make_int3(min[0], max[1], max[2]),
};
/* Only create a quad if on the border between an active and an inactive leaf.
*
* We verify that a leaf exists by probing a coordinate that is at its center,
* to do so we compute the center of the current leaf and offset this coordinate
* by the size of a leaf in each direction.
*/
static const int LEAF_DIM = openvdb::MaskGrid::TreeType::LeafNodeType::DIM;
auto center = leaf_bbox.min() + openvdb::Coord(LEAF_DIM / 2);
if (!is_non_empty_leaf(tree, openvdb::Coord(center.x() - LEAF_DIM, center.y(), center.z()))) {
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_X_MIN);
}
if (!is_non_empty_leaf(tree, openvdb::Coord(center.x() + LEAF_DIM, center.y(), center.z()))) {
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_X_MAX);
}
if (!is_non_empty_leaf(tree, openvdb::Coord(center.x(), center.y() - LEAF_DIM, center.z()))) {
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Y_MIN);
}
if (!is_non_empty_leaf(tree, openvdb::Coord(center.x(), center.y() + LEAF_DIM, center.z()))) {
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Y_MAX);
}
if (!is_non_empty_leaf(tree, openvdb::Coord(center.x(), center.y(), center.z() - LEAF_DIM))) {
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Z_MIN);
}
if (!is_non_empty_leaf(tree, openvdb::Coord(center.x(), center.y(), center.z() + LEAF_DIM))) {
create_quad(corners, vertices_is, quads, resolution, used_verts, QUAD_Z_MAX);
}
}
#else
(void)vertices_is;
(void)quads;
#endif
}
void VolumeMeshBuilder::convert_object_space(const vector<int3> &vertices,
vector<float3> &out_vertices,
const float face_overlap_avoidance)
{
#ifdef WITH_OPENVDB
/* compute the offset for the face overlap avoidance */
bbox = topology_grid->evalActiveVoxelBoundingBox();
openvdb::Coord dim = bbox.dim();
float3 cell_size = make_float3(1.0f / dim.x(), 1.0f / dim.y(), 1.0f / dim.z());
float3 point_offset = cell_size * face_overlap_avoidance;
out_vertices.reserve(vertices.size());
for (size_t i = 0; i < vertices.size(); ++i) {
openvdb::math::Vec3d p = topology_grid->indexToWorld(
openvdb::math::Vec3d(vertices[i].x, vertices[i].y, vertices[i].z));
float3 vertex = make_float3((float)p.x(), (float)p.y(), (float)p.z());
out_vertices.push_back(vertex + point_offset);
}
#else
(void)vertices;
(void)out_vertices;
(void)face_overlap_avoidance;
#endif
}
void VolumeMeshBuilder::convert_quads_to_tris(const vector<QuadData> &quads,
vector<int> &tris,
vector<float3> &face_normals)
{
int index_offset = 0;
tris.resize(quads.size() * 6);
face_normals.reserve(quads.size() * 2);
for (size_t i = 0; i < quads.size(); ++i) {
tris[index_offset++] = quads[i].v0;
tris[index_offset++] = quads[i].v2;
tris[index_offset++] = quads[i].v1;
face_normals.push_back(quads[i].normal);
tris[index_offset++] = quads[i].v0;
tris[index_offset++] = quads[i].v3;
tris[index_offset++] = quads[i].v2;
face_normals.push_back(quads[i].normal);
}
}
bool VolumeMeshBuilder::empty_grid() const
{
#ifdef WITH_OPENVDB
return !topology_grid ||
(!topology_grid->tree().hasActiveTiles() && topology_grid->tree().leafCount() == 0);
#else
return true;
#endif
}
#ifdef WITH_OPENVDB
template<typename GridType>
static openvdb::GridBase::ConstPtr openvdb_grid_from_device_texture(device_texture *image_memory,
float volume_clipping,
Transform transform_3d)
{
using ValueType = typename GridType::ValueType;
openvdb::CoordBBox dense_bbox(0,
0,
0,
image_memory->data_width - 1,
image_memory->data_height - 1,
image_memory->data_depth - 1);
typename GridType::Ptr sparse = GridType::create(ValueType(0.0f));
if (dense_bbox.empty()) {
return sparse;
}
openvdb::tools::Dense<ValueType, openvdb::tools::MemoryLayout::LayoutXYZ> dense(
dense_bbox, static_cast<ValueType *>(image_memory->host_pointer));
openvdb::tools::copyFromDense(dense, *sparse, ValueType(volume_clipping));
/* #copyFromDense will remove any leaf node that contains constant data and replace it with a
* tile, however, we need to preserve the leaves in order to generate the mesh, so re-voxelize
* the leaves that were pruned. This should not affect areas that were skipped due to the
* volume_clipping parameter. */
sparse->tree().voxelizeActiveTiles();
/* Compute index to world matrix. */
float3 voxel_size = make_float3(1.0f / image_memory->data_width,
1.0f / image_memory->data_height,
1.0f / image_memory->data_depth);
transform_3d = transform_inverse(transform_3d);
openvdb::Mat4R index_to_world_mat((double)(voxel_size.x * transform_3d[0][0]),
0.0,
0.0,
0.0,
0.0,
(double)(voxel_size.y * transform_3d[1][1]),
0.0,
0.0,
0.0,
0.0,
(double)(voxel_size.z * transform_3d[2][2]),
0.0,
(double)transform_3d[0][3],
(double)transform_3d[1][3],
(double)transform_3d[2][3],
1.0);
openvdb::math::Transform::Ptr index_to_world_tfm =
openvdb::math::Transform::createLinearTransform(index_to_world_mat);
sparse->setTransform(index_to_world_tfm);
return sparse;
}
static int estimate_required_velocity_padding(openvdb::GridBase::ConstPtr grid,
float velocity_scale)
{
/* TODO: we may need to also find outliers and clamp them to avoid adding too much padding. */
openvdb::math::Extrema extrema;
openvdb::Vec3d voxel_size;
/* External .vdb files have a vec3 type for velocity, but the Blender exporter creates a vec4. */
if (grid->isType<openvdb::Vec3fGrid>()) {
openvdb::Vec3fGrid::ConstPtr vel_grid = openvdb::gridConstPtrCast<openvdb::Vec3fGrid>(grid);
extrema = openvdb::tools::extrema(vel_grid->cbeginValueOn());
voxel_size = vel_grid->voxelSize();
}
else if (grid->isType<openvdb::Vec4fGrid>()) {
openvdb::Vec4fGrid::ConstPtr vel_grid = openvdb::gridConstPtrCast<openvdb::Vec4fGrid>(grid);
extrema = openvdb::tools::extrema(vel_grid->cbeginValueOn());
voxel_size = vel_grid->voxelSize();
}
else {
assert(0);
return 0;
}
/* We should only have uniform grids, so x = y = z, but we never know. */
const double max_voxel_size = openvdb::math::Max(voxel_size.x(), voxel_size.y(), voxel_size.z());
if (max_voxel_size == 0.0) {
return 0;
}
const double estimated_padding = extrema.max() * static_cast<double>(velocity_scale) /
max_voxel_size;
return static_cast<int>(std::ceil(estimated_padding));
}
static openvdb::FloatGrid::ConstPtr get_vdb_for_attribute(Volume *volume, AttributeStandard std)
{
Attribute *attr = volume->attributes.find(std);
if (!attr) {
return nullptr;
}
ImageHandle &handle = attr->data_voxel();
VDBImageLoader *vdb_loader = handle.vdb_loader();
if (!vdb_loader) {
return nullptr;
}
openvdb::GridBase::ConstPtr grid = vdb_loader->get_grid();
if (!grid) {
return nullptr;
}
if (!grid->isType<openvdb::FloatGrid>()) {
return nullptr;
}
return openvdb::gridConstPtrCast<openvdb::FloatGrid>(grid);
}
class MergeScalarGrids {
typedef openvdb::FloatTree ScalarTree;
openvdb::tree::ValueAccessor<const ScalarTree> m_acc_x, m_acc_y, m_acc_z;
public:
MergeScalarGrids(const ScalarTree *x_tree, const ScalarTree *y_tree, const ScalarTree *z_tree)
: m_acc_x(*x_tree), m_acc_y(*y_tree), m_acc_z(*z_tree)
{
}
MergeScalarGrids(const MergeScalarGrids &other)
: m_acc_x(other.m_acc_x), m_acc_y(other.m_acc_y), m_acc_z(other.m_acc_z)
{
}
void operator()(const openvdb::Vec3STree::ValueOnIter &it) const
{
using namespace openvdb;
const math::Coord xyz = it.getCoord();
float x = m_acc_x.getValue(xyz);
float y = m_acc_y.getValue(xyz);
float z = m_acc_z.getValue(xyz);
it.setValue(math::Vec3s(x, y, z));
}
};
static void merge_scalar_grids_for_velocity(const Scene *scene, Volume *volume)
{
if (volume->attributes.find(ATTR_STD_VOLUME_VELOCITY)) {
/* A vector grid for velocity is already available. */
return;
}
openvdb::FloatGrid::ConstPtr vel_x_grid = get_vdb_for_attribute(volume,
ATTR_STD_VOLUME_VELOCITY_X);
openvdb::FloatGrid::ConstPtr vel_y_grid = get_vdb_for_attribute(volume,
ATTR_STD_VOLUME_VELOCITY_Y);
openvdb::FloatGrid::ConstPtr vel_z_grid = get_vdb_for_attribute(volume,
ATTR_STD_VOLUME_VELOCITY_Z);
if (!(vel_x_grid && vel_y_grid && vel_z_grid)) {
return;
}
openvdb::Vec3fGrid::Ptr vecgrid = openvdb::Vec3SGrid::create(openvdb::Vec3s(0.0f));
/* Activate voxels in the vector grid based on the scalar grids to ensure thread safety during
* the merge. */
vecgrid->tree().topologyUnion(vel_x_grid->tree());
vecgrid->tree().topologyUnion(vel_y_grid->tree());
vecgrid->tree().topologyUnion(vel_z_grid->tree());
MergeScalarGrids op(&vel_x_grid->tree(), &vel_y_grid->tree(), &vel_z_grid->tree());
openvdb::tools::foreach (vecgrid->beginValueOn(), op, true, false);
/* Assume all grids have the same transformation. */
openvdb::math::Transform::Ptr transform = openvdb::ConstPtrCast<openvdb::math::Transform>(
vel_x_grid->transformPtr());
vecgrid->setTransform(transform);
/* Make an attribute for it. */
Attribute *attr = volume->attributes.add(ATTR_STD_VOLUME_VELOCITY);
ImageLoader *loader = new VDBImageLoader(vecgrid, "merged_velocity");
ImageParams params;
attr->data_voxel() = scene->image_manager->add_image(loader, params);
}
#endif
/* ************************************************************************** */
void GeometryManager::create_volume_mesh(const Scene *scene, Volume *volume, Progress &progress)
{
string msg = string_printf("Computing Volume Mesh %s", volume->name.c_str());
progress.set_status("Updating Mesh", msg);
/* Find shader and compute padding based on volume shader interpolation settings. */
Shader *volume_shader = NULL;
int pad_size = 0;
for (Node *node : volume->get_used_shaders()) {
Shader *shader = static_cast<Shader *>(node);
if (!shader->has_volume) {
continue;
}
volume_shader = shader;
if (shader->get_volume_interpolation_method() == VOLUME_INTERPOLATION_LINEAR) {
pad_size = max(1, pad_size);
}
else if (shader->get_volume_interpolation_method() == VOLUME_INTERPOLATION_CUBIC) {
pad_size = max(2, pad_size);
}
break;
}
/* Clear existing volume mesh, done here in case we early out due to
* empty grid or missing volume shader.
* Also keep the shaders to avoid infinite loops when synchronizing, as this will tag the shaders
* as having changed. */
volume->clear(true);
volume->need_update_rebuild = true;
if (!volume_shader) {
return;
}
/* Create volume mesh builder. */
VolumeMeshBuilder builder;
#ifdef WITH_OPENVDB
merge_scalar_grids_for_velocity(scene, volume);
for (Attribute &attr : volume->attributes.attributes) {
if (attr.element != ATTR_ELEMENT_VOXEL) {
continue;
}
bool do_clipping = false;
ImageHandle &handle = attr.data_voxel();
/* Try building from OpenVDB grid directly. */
VDBImageLoader *vdb_loader = handle.vdb_loader();
openvdb::GridBase::ConstPtr grid;
if (vdb_loader) {
grid = vdb_loader->get_grid();
/* If building from an OpenVDB grid, we need to manually clip the values. */
do_clipping = true;
}
/* Else fall back to creating an OpenVDB grid from the dense volume data. */
if (!grid) {
device_texture *image_memory = handle.image_memory();
if (image_memory->data_elements == 1) {
grid = openvdb_grid_from_device_texture<openvdb::FloatGrid>(
image_memory, volume->get_clipping(), handle.metadata().transform_3d);
}
else if (image_memory->data_elements == 3) {
grid = openvdb_grid_from_device_texture<openvdb::Vec3fGrid>(
image_memory, volume->get_clipping(), handle.metadata().transform_3d);
}
else if (image_memory->data_elements == 4) {
grid = openvdb_grid_from_device_texture<openvdb::Vec4fGrid>(
image_memory, volume->get_clipping(), handle.metadata().transform_3d);
}
}
if (grid) {
/* Add padding based on the maximum velocity vector. */
if (attr.std == ATTR_STD_VOLUME_VELOCITY && scene->need_motion() != Scene::MOTION_NONE) {
pad_size = max(pad_size,
estimate_required_velocity_padding(grid, volume->get_velocity_scale()));
}
builder.add_grid(grid, do_clipping, volume->get_clipping());
}
}
#else
(void)scene;
#endif
/* If nothing to build, early out. */
if (builder.empty_grid()) {
return;
}
builder.add_padding(pad_size);
/* Slightly offset vertex coordinates to avoid overlapping faces with other
* volumes or meshes. The proper solution would be to improve intersection in
* the kernel to support robust handling of multiple overlapping faces or use
* an all-hit intersection similar to shadows. */
const float face_overlap_avoidance = 0.1f *
hash_uint_to_float(hash_string(volume->name.c_str()));
/* Create mesh. */
vector<float3> vertices;
vector<int> indices;
vector<float3> face_normals;
builder.create_mesh(vertices, indices, face_normals, face_overlap_avoidance);
volume->reserve_mesh(vertices.size(), indices.size() / 3);
volume->used_shaders.clear();
volume->used_shaders.push_back_slow(volume_shader);
for (size_t i = 0; i < vertices.size(); ++i) {
volume->add_vertex(vertices[i]);
}
for (size_t i = 0; i < indices.size(); i += 3) {
volume->add_triangle(indices[i], indices[i + 1], indices[i + 2], 0, false);
}
Attribute *attr_fN = volume->attributes.add(ATTR_STD_FACE_NORMAL);
float3 *fN = attr_fN->data_float3();
for (size_t i = 0; i < face_normals.size(); ++i) {
fN[i] = face_normals[i];
}
/* Print stats. */
VLOG_WORK << "Memory usage volume mesh: "
<< ((vertices.size() + face_normals.size()) * sizeof(float3) +
indices.size() * sizeof(int)) /
(1024.0 * 1024.0)
<< "Mb.";
}
CCL_NAMESPACE_END