Files
test/intern/cycles/kernel/util/texture_3d.h
Lukas Tönne 12f0bc7736 Fix #138388: Use grid voxel corners as value locations like OpenVDB
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
2025-08-26 12:27:20 +02:00

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