Blender V4.3
volume_stack.h
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#pragma once
6
8
9#ifdef __VOLUME__
10
11/* Volumetric read/write lambda functions - default implementations */
12# ifndef VOLUME_READ_LAMBDA
13# define VOLUME_READ_LAMBDA(function_call) \
14 auto volume_read_lambda_pass = [=](const int i) { return function_call; };
15# define VOLUME_WRITE_LAMBDA(function_call) \
16 auto volume_write_lambda_pass = [=](const int i, VolumeStack entry) { function_call; };
17# endif
18
19/* Volume Stack
20 *
21 * This is an array of object/shared ID's that the current segment of the path
22 * is inside of. */
23
24template<typename StackReadOp, typename StackWriteOp>
25ccl_device void volume_stack_enter_exit(KernelGlobals kg,
26 ccl_private const ShaderData *sd,
27 StackReadOp stack_read,
28 StackWriteOp stack_write)
29{
30 /* todo: we should have some way for objects to indicate if they want the
31 * world shader to work inside them. excluding it by default is problematic
32 * because non-volume objects can't be assumed to be closed manifolds */
33 if (!(sd->flag & SD_HAS_VOLUME)) {
34 return;
35 }
36
37 if (sd->flag & SD_BACKFACING) {
38 /* Exit volume object: remove from stack. */
39 for (int i = 0;; i++) {
40 VolumeStack entry = stack_read(i);
41 if (entry.shader == SHADER_NONE) {
42 break;
43 }
44
45 if (entry.object == sd->object && entry.shader == sd->shader) {
46 /* Shift back next stack entries. */
47 do {
48 entry = stack_read(i + 1);
49 stack_write(i, entry);
50 i++;
51 } while (entry.shader != SHADER_NONE);
52
53 return;
54 }
55 }
56 }
57 else {
58 /* Enter volume object: add to stack. */
59 int i;
60 for (i = 0;; i++) {
61 VolumeStack entry = stack_read(i);
62 if (entry.shader == SHADER_NONE) {
63 break;
64 }
65
66 /* Already in the stack? then we have nothing to do. */
67 if (entry.object == sd->object && entry.shader == sd->shader) {
68 return;
69 }
70 }
71
72 /* If we exceed the stack limit, ignore. */
73 if (i >= kernel_data.volume_stack_size - 1) {
74 return;
75 }
76
77 /* Add to the end of the stack. */
78 const VolumeStack new_entry = {sd->object, sd->shader};
79 const VolumeStack empty_entry = {OBJECT_NONE, SHADER_NONE};
80 stack_write(i, new_entry);
81 stack_write(i + 1, empty_entry);
82 }
83}
84
85ccl_device void volume_stack_enter_exit(KernelGlobals kg,
87 ccl_private const ShaderData *sd)
88{
89 VOLUME_READ_LAMBDA(integrator_state_read_volume_stack(state, i))
90 VOLUME_WRITE_LAMBDA(integrator_state_write_volume_stack(state, i, entry))
91 volume_stack_enter_exit(kg, sd, volume_read_lambda_pass, volume_write_lambda_pass);
92}
93
94ccl_device void shadow_volume_stack_enter_exit(KernelGlobals kg,
96 ccl_private const ShaderData *sd)
97{
98 VOLUME_READ_LAMBDA(integrator_state_read_shadow_volume_stack(state, i))
99 VOLUME_WRITE_LAMBDA(integrator_state_write_shadow_volume_stack(state, i, entry))
100 volume_stack_enter_exit(kg, sd, volume_read_lambda_pass, volume_write_lambda_pass);
101}
102
103/* Clean stack after the last bounce.
104 *
105 * It is expected that all volumes are closed manifolds, so at the time when ray
106 * hits nothing (for example, it is a last bounce which goes to environment) the
107 * only expected volume in the stack is the world's one. All the rest volume
108 * entries should have been exited already.
109 *
110 * This isn't always true because of ray intersection precision issues, which
111 * could lead us to an infinite non-world volume in the stack, causing render
112 * artifacts.
113 *
114 * Use this function after the last bounce to get rid of all volumes apart from
115 * the world's one after the last bounce to avoid render artifacts.
116 */
117ccl_device_inline void volume_stack_clean(KernelGlobals kg, IntegratorState state)
118{
119 if (kernel_data.background.volume_shader != SHADER_NONE) {
120 /* Keep the world's volume in stack. */
121 INTEGRATOR_STATE_ARRAY_WRITE(state, volume_stack, 1, shader) = SHADER_NONE;
122 }
123 else {
124 INTEGRATOR_STATE_ARRAY_WRITE(state, volume_stack, 0, shader) = SHADER_NONE;
125 }
126}
127
128template<typename StackReadOp>
129ccl_device float volume_stack_step_size(KernelGlobals kg, StackReadOp stack_read)
130{
131 float step_size = FLT_MAX;
132
133 for (int i = 0;; i++) {
134 VolumeStack entry = stack_read(i);
135 if (entry.shader == SHADER_NONE) {
136 break;
137 }
138
139 int shader_flag = kernel_data_fetch(shaders, (entry.shader & SHADER_MASK)).flags;
140
141 bool heterogeneous = false;
142
143 if (shader_flag & SD_HETEROGENEOUS_VOLUME) {
144 heterogeneous = true;
145 }
146 else if (shader_flag & SD_NEED_VOLUME_ATTRIBUTES) {
147 /* We want to render world or objects without any volume grids
148 * as homogeneous, but can only verify this at run-time since other
149 * heterogeneous volume objects may be using the same shader. */
150 int object = entry.object;
151 if (object != OBJECT_NONE) {
152 int object_flag = kernel_data_fetch(object_flag, object);
153 if (object_flag & SD_OBJECT_HAS_VOLUME_ATTRIBUTES) {
154 heterogeneous = true;
155 }
156 }
157 }
158
159 if (heterogeneous) {
160 float object_step_size = object_volume_step_size(kg, entry.object);
161 object_step_size *= kernel_data.integrator.volume_step_rate;
162 step_size = fminf(object_step_size, step_size);
163 }
164 }
165
166 return step_size;
167}
168
169typedef enum VolumeSampleMethod {
170 VOLUME_SAMPLE_NONE = 0,
171 VOLUME_SAMPLE_DISTANCE = (1 << 0),
172 VOLUME_SAMPLE_EQUIANGULAR = (1 << 1),
173 VOLUME_SAMPLE_MIS = (VOLUME_SAMPLE_DISTANCE | VOLUME_SAMPLE_EQUIANGULAR),
174} VolumeSampleMethod;
175
176ccl_device VolumeSampleMethod volume_stack_sample_method(KernelGlobals kg, IntegratorState state)
177{
178 VolumeSampleMethod method = VOLUME_SAMPLE_NONE;
179
180 for (int i = 0;; i++) {
181 VolumeStack entry = integrator_state_read_volume_stack(state, i);
182 if (entry.shader == SHADER_NONE) {
183 break;
184 }
185
186 int shader_flag = kernel_data_fetch(shaders, (entry.shader & SHADER_MASK)).flags;
187
188 if (shader_flag & SD_VOLUME_MIS) {
189 /* Multiple importance sampling. */
190 return VOLUME_SAMPLE_MIS;
191 }
192 else if (shader_flag & SD_VOLUME_EQUIANGULAR) {
193 /* Distance + equiangular sampling -> multiple importance sampling. */
194 if (method == VOLUME_SAMPLE_DISTANCE) {
195 return VOLUME_SAMPLE_MIS;
196 }
197
198 /* Only equiangular sampling. */
199 method = VOLUME_SAMPLE_EQUIANGULAR;
200 }
201 else {
202 /* Distance + equiangular sampling -> multiple importance sampling. */
203 if (method == VOLUME_SAMPLE_EQUIANGULAR) {
204 return VOLUME_SAMPLE_MIS;
205 }
206
207 /* Distance sampling only. */
208 method = VOLUME_SAMPLE_DISTANCE;
209 }
210 }
211
212 return method;
213}
214
215#endif /* __VOLUME__*/
216
#define kernel_data
const KernelGlobalsCPU *ccl_restrict KernelGlobals
#define kernel_data_fetch(name, index)
#define ccl_device
#define ccl_private
#define ccl_device_inline
#define CCL_NAMESPACE_END
#define VOLUME_WRITE_LAMBDA(function_call)
#define fminf(x, y)
#define VOLUME_READ_LAMBDA(function_call)
ccl_device_inline float object_volume_step_size(KernelGlobals kg, int object)
@ SD_VOLUME_MIS
@ SD_VOLUME_EQUIANGULAR
@ SD_BACKFACING
@ SD_HAS_VOLUME
@ SD_NEED_VOLUME_ATTRIBUTES
@ SD_HETEROGENEOUS_VOLUME
#define SHADER_NONE
#define OBJECT_NONE
ShaderData
@ SHADER_MASK
@ SD_OBJECT_HAS_VOLUME_ATTRIBUTES
static ulong state[N]
#define INTEGRATOR_STATE_ARRAY_WRITE(state, nested_struct, array_index, member)
Definition state.h:240
IntegratorStateCPU *ccl_restrict IntegratorState
Definition state.h:228
IntegratorShadowStateCPU *ccl_restrict IntegratorShadowState
Definition state.h:230
#define FLT_MAX
Definition stdcycles.h:14