Files
test/intern/cycles/kernel/util/texture_3d.h
Weizhen Huang df496eb894 Cycles: use one-tap stochastic interpolation for volume
It has ~1.2x speed-up on CPU and ~1.5x speed-up on GPU (tested on Metal
M2 Ultra).

Individual samples are noisier, but equal time renders are mostly
better.

Note that volume emission renders differently than before.

Pull Request: https://projects.blender.org/blender/blender/pulls/144451
2025-08-14 15:22:44 +02:00

249 lines
8.1 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;
/* A -0.5 offset is used to center the cubic samples around the sample point. */
P = P - make_float3(0.5f);
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