Blender V4.5
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 const ccl_private 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 const ccl_private 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 const ccl_private 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
128/* Check if the volume is homogeneous by checking if the shader flag is set or if volume attributes
129 * are needed. */
130ccl_device_inline bool volume_is_homogeneous(KernelGlobals kg,
131 const ccl_private VolumeStack &entry)
132{
133 const int shader_flag = kernel_data_fetch(shaders, (entry.shader & SHADER_MASK)).flags;
134
135 if (shader_flag & SD_HETEROGENEOUS_VOLUME) {
136 return false;
137 }
138
139 if (shader_flag & SD_NEED_VOLUME_ATTRIBUTES) {
140 const int object = entry.object;
141 if (object == OBJECT_NONE) {
142 /* Volume attributes for world is not supported. */
143 return true;
144 }
145
146 const int object_flag = kernel_data_fetch(object_flag, object);
147 if (object_flag & SD_OBJECT_HAS_VOLUME_ATTRIBUTES) {
148 /* If both the shader and the object needs volume attributes, the volume is heterogeneous. */
149 return false;
150 }
151 }
152
153 return true;
154}
155
156template<typename StackReadOp>
157ccl_device float volume_stack_step_size(KernelGlobals kg, StackReadOp stack_read)
158{
159 float step_size = FLT_MAX;
160
161 for (int i = 0;; i++) {
162 VolumeStack entry = stack_read(i);
163 if (entry.shader == SHADER_NONE) {
164 break;
165 }
166
167 if (!volume_is_homogeneous(kg, entry)) {
168 float object_step_size = object_volume_step_size(kg, entry.object);
169 object_step_size *= kernel_data.integrator.volume_step_rate;
170 step_size = fminf(object_step_size, step_size);
171 }
172 }
173
174 return step_size;
175}
176
177enum VolumeSampleMethod {
178 VOLUME_SAMPLE_NONE = 0,
179 VOLUME_SAMPLE_DISTANCE = (1 << 0),
180 VOLUME_SAMPLE_EQUIANGULAR = (1 << 1),
181 VOLUME_SAMPLE_MIS = (VOLUME_SAMPLE_DISTANCE | VOLUME_SAMPLE_EQUIANGULAR),
182};
183
184ccl_device VolumeSampleMethod volume_stack_sample_method(KernelGlobals kg, IntegratorState state)
185{
186 VolumeSampleMethod method = VOLUME_SAMPLE_NONE;
187
188 for (int i = 0;; i++) {
189 VolumeStack entry = integrator_state_read_volume_stack(state, i);
190 if (entry.shader == SHADER_NONE) {
191 break;
192 }
193
194 int shader_flag = kernel_data_fetch(shaders, (entry.shader & SHADER_MASK)).flags;
195
196 if (shader_flag & SD_VOLUME_MIS) {
197 /* Multiple importance sampling. */
198 return VOLUME_SAMPLE_MIS;
199 }
200 else if (shader_flag & SD_VOLUME_EQUIANGULAR) {
201 /* Distance + equiangular sampling -> multiple importance sampling. */
202 if (method == VOLUME_SAMPLE_DISTANCE) {
203 return VOLUME_SAMPLE_MIS;
204 }
205
206 /* Only equiangular sampling. */
207 method = VOLUME_SAMPLE_EQUIANGULAR;
208 }
209 else {
210 /* Distance + equiangular sampling -> multiple importance sampling. */
211 if (method == VOLUME_SAMPLE_EQUIANGULAR) {
212 return VOLUME_SAMPLE_MIS;
213 }
214
215 /* Distance sampling only. */
216 method = VOLUME_SAMPLE_DISTANCE;
217 }
218 }
219
220 return method;
221}
222
223#endif /* __VOLUME__*/
224
#define kernel_data
#define kernel_data_fetch(name, index)
#define SHADER_NONE
#define ccl_device
#define OBJECT_NONE
#define ccl_private
const ThreadKernelGlobalsCPU * KernelGlobals
#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, const int object)
@ SD_VOLUME_MIS
@ SD_VOLUME_EQUIANGULAR
@ SD_BACKFACING
@ SD_HAS_VOLUME
@ SD_NEED_VOLUME_ATTRIBUTES
@ SD_HETEROGENEOUS_VOLUME
@ SHADER_MASK
@ SD_OBJECT_HAS_VOLUME_ATTRIBUTES
static ulong state[N]
IntegratorShadowStateCPU * IntegratorShadowState
Definition state.h:230
#define INTEGRATOR_STATE_ARRAY_WRITE(state, nested_struct, array_index, member)
Definition state.h:240
IntegratorStateCPU * IntegratorState
Definition state.h:228
#define FLT_MAX
Definition stdcycles.h:14
i
Definition text_draw.cc:230