Blender grid rendering interprets voxel transforms in such a way that the voxel values are located at the center of a voxel. This is inconsistent with OpenVDB where the values are located at the lower corners for the purpose or sampling and related algorithms. While it is possible to offset grids when communicating with the OpenVDB library, this is also error-prone and does not add any major advantage. Every time a grid is passed to OpenVDB we currently have to take care to transform by half a voxel to ensure correct sampling weights are used that match the density displayed by the viewport rendering. This patch changes volume grid generation, conversion, and rendering code so that grid transforms match the corner-located values in OpenVDB. - The volume primitive cube node aligns the grid transform with the location of the first value, which is now also the same as min/max bounds input of the node. - Mesh<->Grid conversion does no longer require offsetting grid transform and mesh vertices respectively by 0.5 voxels. - Texture space for viewport rendering is offset by half a voxel, so that it covers the same area as before and voxel centers remain at the same texture space locations. Co-authored-by: Brecht Van Lommel <brecht@blender.org> Pull Request: https://projects.blender.org/blender/blender/pulls/138449
263 lines
7.0 KiB
C++
263 lines
7.0 KiB
C++
/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
#include "scene/image_vdb.h"
|
|
|
|
#include "util/log.h"
|
|
#include "util/nanovdb.h"
|
|
#include "util/openvdb.h"
|
|
#include "util/texture.h"
|
|
|
|
#ifdef WITH_OPENVDB
|
|
# include <openvdb/tools/Dense.h>
|
|
#endif
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
#ifdef WITH_OPENVDB
|
|
VDBImageLoader::VDBImageLoader(openvdb::GridBase::ConstPtr grid_,
|
|
const string &grid_name,
|
|
const float clipping)
|
|
: grid_name(grid_name), clipping(clipping), grid(grid_)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
VDBImageLoader::VDBImageLoader(const string &grid_name, const float clipping)
|
|
: grid_name(grid_name), clipping(clipping)
|
|
{
|
|
}
|
|
|
|
VDBImageLoader::~VDBImageLoader() = default;
|
|
|
|
bool VDBImageLoader::load_metadata(const ImageDeviceFeatures &features, ImageMetaData &metadata)
|
|
{
|
|
if (!features.has_nanovdb) {
|
|
return false;
|
|
}
|
|
|
|
#ifdef WITH_NANOVDB
|
|
load_grid();
|
|
|
|
if (!grid) {
|
|
return false;
|
|
}
|
|
|
|
/* Convert to the few float types that we know. */
|
|
grid = openvdb_convert_to_known_type(grid);
|
|
if (!grid) {
|
|
return false;
|
|
}
|
|
|
|
/* Get number of channels from type. */
|
|
metadata.channels = openvdb_num_channels(grid);
|
|
|
|
/* Convert OpenVDB to NanoVDB grid. */
|
|
nanogrid = openvdb_to_nanovdb(grid, precision, clipping);
|
|
if (!nanogrid) {
|
|
grid.reset();
|
|
return false;
|
|
}
|
|
|
|
/* Set dimensions. */
|
|
bbox = grid->evalActiveVoxelBoundingBox();
|
|
if (bbox.empty()) {
|
|
metadata.type = IMAGE_DATA_TYPE_NANOVDB_EMPTY;
|
|
metadata.byte_size = 1;
|
|
grid.reset();
|
|
return true;
|
|
}
|
|
|
|
if (metadata.channels == 1) {
|
|
if (precision == 0) {
|
|
metadata.type = IMAGE_DATA_TYPE_NANOVDB_FPN;
|
|
}
|
|
else if (precision == 16) {
|
|
metadata.type = IMAGE_DATA_TYPE_NANOVDB_FP16;
|
|
}
|
|
else {
|
|
metadata.type = IMAGE_DATA_TYPE_NANOVDB_FLOAT;
|
|
}
|
|
}
|
|
else if (metadata.channels == 3) {
|
|
metadata.type = IMAGE_DATA_TYPE_NANOVDB_FLOAT3;
|
|
}
|
|
else if (metadata.channels == 4) {
|
|
metadata.type = IMAGE_DATA_TYPE_NANOVDB_FLOAT4;
|
|
}
|
|
else {
|
|
grid.reset();
|
|
return false;
|
|
}
|
|
|
|
metadata.byte_size = nanogrid.size();
|
|
|
|
/* Set transform from object space to voxel index. */
|
|
openvdb::math::Mat4f grid_matrix = grid->transform().baseMap()->getAffineMap()->getMat4();
|
|
Transform index_to_object;
|
|
for (int col = 0; col < 4; col++) {
|
|
for (int row = 0; row < 3; row++) {
|
|
index_to_object[row][col] = (float)grid_matrix[col][row];
|
|
}
|
|
}
|
|
|
|
metadata.transform_3d = transform_inverse(index_to_object);
|
|
metadata.use_transform_3d = true;
|
|
|
|
/* Only NanoGrid needed now, free OpenVDB grid. */
|
|
grid.reset();
|
|
|
|
return true;
|
|
#else
|
|
(void)metadata;
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool VDBImageLoader::load_pixels(const ImageMetaData &metadata,
|
|
void *pixels,
|
|
const size_t /*pixels_size*/,
|
|
const bool /*associate_alpha*/)
|
|
{
|
|
#ifdef WITH_NANOVDB
|
|
if (metadata.type == IMAGE_DATA_TYPE_NANOVDB_EMPTY) {
|
|
memset(pixels, 0, metadata.byte_size);
|
|
return true;
|
|
}
|
|
if (nanogrid) {
|
|
memcpy(pixels, nanogrid.data(), nanogrid.size());
|
|
return true;
|
|
}
|
|
#else
|
|
(void)pixels;
|
|
#endif
|
|
|
|
return false;
|
|
}
|
|
|
|
string VDBImageLoader::name() const
|
|
{
|
|
return grid_name;
|
|
}
|
|
|
|
bool VDBImageLoader::equals(const ImageLoader &other) const
|
|
{
|
|
#ifdef WITH_OPENVDB
|
|
const VDBImageLoader &other_loader = (const VDBImageLoader &)other;
|
|
return grid && grid == other_loader.grid;
|
|
#else
|
|
(void)other;
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
void VDBImageLoader::cleanup()
|
|
{
|
|
#ifdef WITH_OPENVDB
|
|
/* Free OpenVDB grid memory as soon as we can. */
|
|
grid.reset();
|
|
#endif
|
|
#ifdef WITH_NANOVDB
|
|
nanogrid.reset();
|
|
#endif
|
|
}
|
|
|
|
bool VDBImageLoader::is_vdb_loader() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
#ifdef WITH_OPENVDB
|
|
openvdb::GridBase::ConstPtr VDBImageLoader::get_grid()
|
|
{
|
|
return grid;
|
|
}
|
|
|
|
template<typename GridType>
|
|
openvdb::GridBase::ConstPtr create_grid(const float *voxels,
|
|
const size_t width,
|
|
const size_t height,
|
|
const size_t depth,
|
|
Transform transform_3d,
|
|
const float clipping)
|
|
{
|
|
using ValueType = typename GridType::ValueType;
|
|
openvdb::GridBase::ConstPtr grid;
|
|
|
|
const openvdb::CoordBBox dense_bbox(0, 0, 0, width - 1, height - 1, depth - 1);
|
|
|
|
typename GridType::Ptr sparse = GridType::create(ValueType(0.0f));
|
|
if (dense_bbox.empty()) {
|
|
return sparse;
|
|
}
|
|
|
|
const openvdb::tools::Dense<const ValueType, openvdb::tools::MemoryLayout::LayoutXYZ> dense(
|
|
dense_bbox, reinterpret_cast<const ValueType *>(voxels));
|
|
|
|
openvdb::tools::copyFromDense(dense, *sparse, ValueType(clipping));
|
|
|
|
/* Compute index to world matrix. */
|
|
const float3 voxel_size = make_float3(1.0f / width, 1.0f / height, 1.0f / depth);
|
|
|
|
transform_3d = transform_inverse(transform_3d);
|
|
|
|
const 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] + voxel_size.x,
|
|
(double)transform_3d[1][3] + voxel_size.y,
|
|
(double)transform_3d[2][3] + voxel_size.z,
|
|
1.0);
|
|
|
|
const openvdb::math::Transform::Ptr index_to_world_tfm =
|
|
openvdb::math::Transform::createLinearTransform(index_to_world_mat);
|
|
|
|
sparse->setTransform(index_to_world_tfm);
|
|
|
|
return sparse;
|
|
}
|
|
#endif
|
|
|
|
void VDBImageLoader::grid_from_dense_voxels(const size_t width,
|
|
const size_t height,
|
|
const size_t depth,
|
|
const int channels,
|
|
const float *voxels,
|
|
Transform transform_3d)
|
|
{
|
|
#ifdef WITH_OPENVDB
|
|
/* TODO: Create NanoVDB grid directly? */
|
|
if (channels == 1) {
|
|
grid = create_grid<openvdb::FloatGrid>(voxels, width, height, depth, transform_3d, clipping);
|
|
}
|
|
else if (channels == 3) {
|
|
grid = create_grid<openvdb::Vec3fGrid>(voxels, width, height, depth, transform_3d, clipping);
|
|
}
|
|
else if (channels == 4) {
|
|
grid = create_grid<openvdb::Vec4fGrid>(voxels, width, height, depth, transform_3d, clipping);
|
|
}
|
|
|
|
/* Clipping already applied, no need to do it again. */
|
|
clipping = 0.0f;
|
|
#else
|
|
(void)width;
|
|
(void)height;
|
|
(void)depth;
|
|
(void)channels;
|
|
(void)voxels;
|
|
(void)transform_3d;
|
|
#endif
|
|
}
|
|
|
|
CCL_NAMESPACE_END
|