Files
test2/intern/cycles/kernel/integrator/volume_stack.h
Weizhen Huang 2b0a1cae06 Cycles: Add an option to use ray marching for volume rendering
Null Scattering currently has performance and noise issues, and it will
take time to address them. For now add the previous Ray Marching back as
an option.

Co-authored-by: Brecht Van Lommel <brecht@blender.org>
Pull Request: https://projects.blender.org/blender/blender/pulls/146317
2025-09-26 12:14:45 +02:00

259 lines
7.6 KiB
C++

/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
*
* SPDX-License-Identifier: Apache-2.0 */
#pragma once
CCL_NAMESPACE_BEGIN
#ifdef __VOLUME__
/* Volume Stack
*
* This is an array of object/shared ID's that the current segment of the path
* is inside of. */
template<const bool shadow, typename IntegratorGenericState>
ccl_device_forceinline VolumeStack volume_stack_read(const IntegratorGenericState state,
const int i)
{
if constexpr (shadow) {
return integrator_state_read_shadow_volume_stack(state, i);
}
else {
return integrator_state_read_volume_stack(state, i);
}
# ifdef __KERNEL_GPU__
/* Silence false positive warning with some GPU compilers. */
VolumeStack stack = {};
return stack;
# endif
}
template<const bool shadow, typename IntegratorGenericState>
ccl_device_forceinline void volume_stack_write(IntegratorGenericState state,
const int i,
const VolumeStack entry)
{
if constexpr (shadow) {
integrator_state_write_shadow_volume_stack(state, i, entry);
}
else {
integrator_state_write_volume_stack(state, i, entry);
}
}
template<const bool shadow, typename IntegratorGenericState>
ccl_device void volume_stack_enter_exit(KernelGlobals kg,
IntegratorGenericState state,
const ccl_private ShaderData *sd)
{
# ifdef __KERNEL_USE_DATA_CONSTANTS__
/* If we're using data constants, this fetch disappears.
* On Apple GPUs, scenes without volumetric features can render 1 or 2% faster by dead-stripping
* this function. */
if (!(kernel_data.kernel_features & KERNEL_FEATURE_VOLUME)) {
return;
}
# endif
/* todo: we should have some way for objects to indicate if they want the
* world shader to work inside them. excluding it by default is problematic
* because non-volume objects can't be assumed to be closed manifolds */
if (!(sd->flag & SD_HAS_VOLUME)) {
return;
}
if (sd->flag & SD_BACKFACING) {
/* Exit volume object: remove from stack. */
for (int i = 0;; i++) {
VolumeStack entry = volume_stack_read<shadow>(state, i);
if (entry.shader == SHADER_NONE) {
break;
}
if (entry.object == sd->object && entry.shader == sd->shader) {
/* Shift back next stack entries. */
do {
entry = volume_stack_read<shadow>(state, i + 1);
volume_stack_write<shadow>(state, i, entry);
i++;
} while (entry.shader != SHADER_NONE);
return;
}
}
}
else {
/* Enter volume object: add to stack. */
uint i;
for (i = 0;; i++) {
const VolumeStack entry = volume_stack_read<shadow>(state, i);
if (entry.shader == SHADER_NONE) {
break;
}
/* Already in the stack? then we have nothing to do. */
if (entry.object == sd->object && entry.shader == sd->shader) {
return;
}
}
/* If we exceed the stack limit, ignore. */
if (i >= kernel_data.volume_stack_size - 1) {
return;
}
/* Add to the end of the stack. */
const VolumeStack new_entry = {sd->object, sd->shader};
const VolumeStack empty_entry = {OBJECT_NONE, SHADER_NONE};
volume_stack_write<shadow>(state, i, new_entry);
volume_stack_write<shadow>(state, i + 1, empty_entry);
}
}
/* Clean stack after the last bounce.
*
* It is expected that all volumes are closed manifolds, so at the time when ray
* hits nothing (for example, it is a last bounce which goes to environment) the
* only expected volume in the stack is the world's one. All the rest volume
* entries should have been exited already.
*
* This isn't always true because of ray intersection precision issues, which
* could lead us to an infinite non-world volume in the stack, causing render
* artifacts.
*
* Use this function after the last bounce to get rid of all volumes apart from
* the world's one after the last bounce to avoid render artifacts.
*/
ccl_device_inline void volume_stack_clean(KernelGlobals kg, IntegratorState state)
{
if (kernel_data.background.volume_shader != SHADER_NONE) {
/* Keep the world's volume in stack. */
INTEGRATOR_STATE_ARRAY_WRITE(state, volume_stack, 1, shader) = SHADER_NONE;
}
else {
INTEGRATOR_STATE_ARRAY_WRITE(state, volume_stack, 0, shader) = SHADER_NONE;
}
}
/* Check if the volume is homogeneous by checking if the shader flag is set or if volume attributes
* are needed. */
ccl_device_inline bool volume_is_homogeneous(KernelGlobals kg,
const ccl_private VolumeStack &entry)
{
const int shader_flag = kernel_data_fetch(shaders, (entry.shader & SHADER_MASK)).flags;
if (shader_flag & SD_HETEROGENEOUS_VOLUME) {
return false;
}
if (shader_flag & SD_NEED_VOLUME_ATTRIBUTES) {
const int object = entry.object;
if (object == kernel_data.background.object_index) {
/* Volume attributes for world is not supported. */
return true;
}
const int object_flag = kernel_data_fetch(object_flag, object);
if (object_flag & SD_OBJECT_HAS_VOLUME_ATTRIBUTES) {
/* If both the shader and the object needs volume attributes, the volume is heterogeneous. */
return false;
}
}
return true;
}
template<const bool shadow, typename IntegratorGenericState>
ccl_device_inline bool volume_is_homogeneous(KernelGlobals kg, const IntegratorGenericState state)
{
for (int i = 0;; i++) {
const VolumeStack entry = volume_stack_read<shadow>(state, i);
if (entry.shader == SHADER_NONE) {
return true;
}
if (!volume_is_homogeneous(kg, entry)) {
return false;
}
}
kernel_assert(false);
return false;
}
template<const bool shadow, typename IntegratorGenericState>
ccl_device float volume_stack_step_size(KernelGlobals kg, const IntegratorGenericState state)
{
kernel_assert(kernel_data.integrator.volume_ray_marching);
float step_size = FLT_MAX;
for (int i = 0;; i++) {
const VolumeStack entry = volume_stack_read<shadow>(state, i);
if (entry.shader == SHADER_NONE) {
break;
}
if (!volume_is_homogeneous(kg, entry)) {
const float object_step_size = kernel_data_fetch(volume_step_size, entry.object);
step_size = fminf(object_step_size, step_size);
}
}
return step_size;
}
enum VolumeSampleMethod {
VOLUME_SAMPLE_NONE = 0,
VOLUME_SAMPLE_DISTANCE = (1 << 0),
VOLUME_SAMPLE_EQUIANGULAR = (1 << 1),
VOLUME_SAMPLE_MIS = (VOLUME_SAMPLE_DISTANCE | VOLUME_SAMPLE_EQUIANGULAR),
};
ccl_device VolumeSampleMethod volume_stack_sample_method(KernelGlobals kg, IntegratorState state)
{
VolumeSampleMethod method = VOLUME_SAMPLE_NONE;
for (int i = 0;; i++) {
VolumeStack entry = integrator_state_read_volume_stack(state, i);
if (entry.shader == SHADER_NONE) {
break;
}
int shader_flag = kernel_data_fetch(shaders, (entry.shader & SHADER_MASK)).flags;
if (shader_flag & SD_VOLUME_MIS) {
/* Multiple importance sampling. */
return VOLUME_SAMPLE_MIS;
}
else if (shader_flag & SD_VOLUME_EQUIANGULAR) {
/* Distance + equiangular sampling -> multiple importance sampling. */
if (method == VOLUME_SAMPLE_DISTANCE) {
return VOLUME_SAMPLE_MIS;
}
/* Only equiangular sampling. */
method = VOLUME_SAMPLE_EQUIANGULAR;
}
else {
/* Distance + equiangular sampling -> multiple importance sampling. */
if (method == VOLUME_SAMPLE_EQUIANGULAR) {
return VOLUME_SAMPLE_MIS;
}
/* Distance sampling only. */
method = VOLUME_SAMPLE_DISTANCE;
}
}
return method;
}
#endif /* __VOLUME__*/
CCL_NAMESPACE_END