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
246 lines
8.0 KiB
C++
246 lines
8.0 KiB
C++
/* SPDX-FileCopyrightText: 2011-2025 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
#pragma once
|
|
|
|
#include "kernel/globals.h"
|
|
#include "kernel/sample/lcg.h"
|
|
|
|
#include "util/texture.h"
|
|
|
|
#if !defined(__KERNEL_METAL__) && !defined(__KERNEL_ONEAPI__)
|
|
# ifdef WITH_NANOVDB
|
|
# include "kernel/util/nanovdb.h"
|
|
# endif
|
|
#endif
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
#ifndef __KERNEL_GPU__
|
|
/* Make template functions private so symbols don't conflict between kernels with different
|
|
* instruction sets. */
|
|
namespace {
|
|
#endif
|
|
|
|
#ifdef WITH_NANOVDB
|
|
/* -------------------------------------------------------------------- */
|
|
/** Return the sample position for stochastical one-tap sampling.
|
|
* From "Stochastic Texture Filtering": https://arxiv.org/abs/2305.05810
|
|
* \{ */
|
|
ccl_device_inline float3 interp_tricubic_stochastic(const float3 P, ccl_private float3 &rand)
|
|
{
|
|
const float3 p = floor(P);
|
|
const float3 t = P - p;
|
|
|
|
/* Cubic interpolation weights. */
|
|
const float3 w[4] = {(((-1.0f / 6.0f) * t + 0.5f) * t - 0.5f) * t + (1.0f / 6.0f),
|
|
((0.5f * t - 1.0f) * t) * t + (2.0f / 3.0f),
|
|
((-0.5f * t + 0.5f) * t + 0.5f) * t + (1.0f / 6.0f),
|
|
(1.0f / 6.0f) * t * t * t};
|
|
|
|
/* For reservoir sampling, always accept the first in the stream. */
|
|
float3 total_weight = w[0];
|
|
float3 offset = make_float3(-1.0f);
|
|
|
|
for (int j = 1; j < 4; j++) {
|
|
total_weight += w[j];
|
|
const float3 thresh = w[j] / total_weight;
|
|
const auto mask = rand < thresh;
|
|
offset = select(mask, make_float3(float(j) - 1.0f), offset);
|
|
rand = select(mask, safe_divide(rand, thresh), safe_divide(rand - thresh, 1.0f - thresh));
|
|
}
|
|
|
|
return p + offset;
|
|
}
|
|
|
|
ccl_device_inline float3 interp_trilinear_stochastic(const float3 P, const float3 rand)
|
|
{
|
|
const float3 p = floor(P);
|
|
const float3 t = P - p;
|
|
return select(rand < t, p + 1.0f, p);
|
|
}
|
|
|
|
ccl_device_inline float3 interp_stochastic(const float3 P,
|
|
ccl_private InterpolationType &interpolation,
|
|
ccl_private float3 &rand)
|
|
{
|
|
float3 P_new = P;
|
|
if (interpolation == INTERPOLATION_CUBIC) {
|
|
P_new = interp_tricubic_stochastic(P, rand);
|
|
}
|
|
else if (interpolation == INTERPOLATION_LINEAR) {
|
|
P_new = interp_trilinear_stochastic(P, rand);
|
|
}
|
|
else {
|
|
kernel_assert(interpolation == INTERPOLATION_CLOSEST);
|
|
}
|
|
interpolation = INTERPOLATION_CLOSEST;
|
|
return P_new;
|
|
}
|
|
/** \} */
|
|
|
|
template<typename OutT, typename Acc>
|
|
ccl_device OutT kernel_tex_image_interp_trilinear_nanovdb(ccl_private Acc &acc, const float3 P)
|
|
{
|
|
const float3 floor_P = floor(P);
|
|
const float3 t = P - floor_P;
|
|
const int3 index = make_int3(floor_P);
|
|
|
|
const int ix = index.x;
|
|
const int iy = index.y;
|
|
const int iz = index.z;
|
|
|
|
return mix(mix(mix(OutT(acc.getValue(make_int3(ix, iy, iz))),
|
|
OutT(acc.getValue(make_int3(ix, iy, iz + 1))),
|
|
t.z),
|
|
mix(OutT(acc.getValue(make_int3(ix, iy + 1, iz + 1))),
|
|
OutT(acc.getValue(make_int3(ix, iy + 1, iz))),
|
|
1.0f - t.z),
|
|
t.y),
|
|
mix(mix(OutT(acc.getValue(make_int3(ix + 1, iy + 1, iz))),
|
|
OutT(acc.getValue(make_int3(ix + 1, iy + 1, iz + 1))),
|
|
t.z),
|
|
mix(OutT(acc.getValue(make_int3(ix + 1, iy, iz + 1))),
|
|
OutT(acc.getValue(make_int3(ix + 1, iy, iz))),
|
|
1.0f - t.z),
|
|
1.0f - t.y),
|
|
t.x);
|
|
}
|
|
|
|
template<typename OutT, typename Acc>
|
|
ccl_device OutT kernel_tex_image_interp_tricubic_nanovdb(ccl_private Acc &acc, const float3 P)
|
|
{
|
|
const float3 floor_P = floor(P);
|
|
const float3 t = P - floor_P;
|
|
const int3 index = make_int3(floor_P);
|
|
|
|
const int xc[4] = {index.x - 1, index.x, index.x + 1, index.x + 2};
|
|
const int yc[4] = {index.y - 1, index.y, index.y + 1, index.y + 2};
|
|
const int zc[4] = {index.z - 1, index.z, index.z + 1, index.z + 2};
|
|
float u[4], v[4], w[4];
|
|
|
|
/* Some helper macros to keep code size reasonable.
|
|
* Lets the compiler inline all the matrix multiplications.
|
|
*/
|
|
# define SET_CUBIC_SPLINE_WEIGHTS(u, t) \
|
|
{ \
|
|
u[0] = (((-1.0f / 6.0f) * t + 0.5f) * t - 0.5f) * t + (1.0f / 6.0f); \
|
|
u[1] = ((0.5f * t - 1.0f) * t) * t + (2.0f / 3.0f); \
|
|
u[2] = ((-0.5f * t + 0.5f) * t + 0.5f) * t + (1.0f / 6.0f); \
|
|
u[3] = (1.0f / 6.0f) * t * t * t; \
|
|
} \
|
|
(void)0
|
|
|
|
# define DATA(x, y, z) (OutT(acc.getValue(make_int3(xc[x], yc[y], zc[z]))))
|
|
# define COL_TERM(col, row) \
|
|
(v[col] * (u[0] * DATA(0, col, row) + u[1] * DATA(1, col, row) + u[2] * DATA(2, col, row) + \
|
|
u[3] * DATA(3, col, row)))
|
|
# define ROW_TERM(row) \
|
|
(w[row] * (COL_TERM(0, row) + COL_TERM(1, row) + COL_TERM(2, row) + COL_TERM(3, row)))
|
|
|
|
SET_CUBIC_SPLINE_WEIGHTS(u, t.x);
|
|
SET_CUBIC_SPLINE_WEIGHTS(v, t.y);
|
|
SET_CUBIC_SPLINE_WEIGHTS(w, t.z);
|
|
|
|
/* Actual interpolation. */
|
|
return ROW_TERM(0) + ROW_TERM(1) + ROW_TERM(2) + ROW_TERM(3);
|
|
|
|
# undef COL_TERM
|
|
# undef ROW_TERM
|
|
# undef DATA
|
|
# undef SET_CUBIC_SPLINE_WEIGHTS
|
|
}
|
|
|
|
template<typename OutT, typename T>
|
|
# if defined(__KERNEL_METAL__)
|
|
__attribute__((noinline))
|
|
# else
|
|
ccl_device_noinline
|
|
# endif
|
|
OutT kernel_tex_image_interp_nanovdb(const ccl_global TextureInfo &info,
|
|
float3 P,
|
|
const InterpolationType interp)
|
|
{
|
|
ccl_global nanovdb::NanoGrid<T> *const grid = (ccl_global nanovdb::NanoGrid<T> *)info.data;
|
|
|
|
if (interp == INTERPOLATION_CLOSEST) {
|
|
nanovdb::ReadAccessor<T> acc(grid->tree().root());
|
|
return OutT(acc.getValue(make_int3(floor(P))));
|
|
}
|
|
|
|
nanovdb::CachedReadAccessor<T> acc(grid->tree().root());
|
|
if (interp == INTERPOLATION_LINEAR) {
|
|
return kernel_tex_image_interp_trilinear_nanovdb<OutT>(acc, P);
|
|
}
|
|
|
|
return kernel_tex_image_interp_tricubic_nanovdb<OutT>(acc, P);
|
|
}
|
|
#endif /* WITH_NANOVDB */
|
|
|
|
ccl_device float4 kernel_tex_image_interp_3d(KernelGlobals kg,
|
|
ccl_private ShaderData *sd,
|
|
const int id,
|
|
float3 P,
|
|
InterpolationType interp,
|
|
const bool stochastic)
|
|
{
|
|
#ifdef WITH_NANOVDB
|
|
const ccl_global TextureInfo &info = kernel_data_fetch(texture_info, id);
|
|
|
|
if (info.use_transform_3d) {
|
|
P = transform_point(&info.transform_3d, P);
|
|
}
|
|
|
|
InterpolationType interpolation = (interp == INTERPOLATION_NONE) ?
|
|
(InterpolationType)info.interpolation :
|
|
interp;
|
|
|
|
if (stochastic) {
|
|
float3 rand = lcg_step_float3(&sd->lcg_state);
|
|
P = interp_stochastic(P, interpolation, rand);
|
|
}
|
|
|
|
const ImageDataType data_type = (ImageDataType)info.data_type;
|
|
if (data_type == IMAGE_DATA_TYPE_NANOVDB_FLOAT) {
|
|
const float f = kernel_tex_image_interp_nanovdb<float, float>(info, P, interpolation);
|
|
return make_float4(f, f, f, 1.0f);
|
|
}
|
|
if (data_type == IMAGE_DATA_TYPE_NANOVDB_FLOAT3) {
|
|
const float3 f = kernel_tex_image_interp_nanovdb<float3, packed_float3>(
|
|
info, P, interpolation);
|
|
return make_float4(f, 1.0f);
|
|
}
|
|
if (data_type == IMAGE_DATA_TYPE_NANOVDB_FLOAT4) {
|
|
return kernel_tex_image_interp_nanovdb<float4, float4>(info, P, interpolation);
|
|
}
|
|
if (data_type == IMAGE_DATA_TYPE_NANOVDB_FPN) {
|
|
const float f = kernel_tex_image_interp_nanovdb<float, nanovdb::FpN>(info, P, interpolation);
|
|
return make_float4(f, f, f, 1.0f);
|
|
}
|
|
if (data_type == IMAGE_DATA_TYPE_NANOVDB_FP16) {
|
|
const float f = kernel_tex_image_interp_nanovdb<float, nanovdb::Fp16>(info, P, interpolation);
|
|
return make_float4(f, f, f, 1.0f);
|
|
}
|
|
if (data_type == IMAGE_DATA_TYPE_NANOVDB_EMPTY) {
|
|
return zero_float4();
|
|
}
|
|
#else
|
|
(void)kg;
|
|
(void)sd;
|
|
(void)id;
|
|
(void)P;
|
|
(void)interp;
|
|
(void)stochastic;
|
|
#endif
|
|
|
|
return make_float4(
|
|
TEX_IMAGE_MISSING_R, TEX_IMAGE_MISSING_G, TEX_IMAGE_MISSING_B, TEX_IMAGE_MISSING_A);
|
|
}
|
|
|
|
#ifndef __KERNEL_GPU__
|
|
} /* Namespace. */
|
|
#endif
|
|
|
|
CCL_NAMESPACE_END
|