Blender V4.3
eevee_subsurface.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2021 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
9#include "BLI_vector.hh"
10
11#include "eevee_instance.hh"
12#include "eevee_subsurface.hh"
13
14#include <iostream>
15
16namespace blender::eevee {
17
18/* -------------------------------------------------------------------- */
23{
24 data_.sample_len = 16;
25
26 {
27 PassSimple &pass = setup_ps_;
28 pass.init();
29 pass.state_set(DRW_STATE_NO_DRAW);
30 pass.shader_set(inst_.shaders.static_shader_get(SUBSURFACE_SETUP));
31 pass.bind_resources(inst_.gbuffer);
32 pass.bind_texture("depth_tx", &inst_.render_buffers.depth_tx);
33 pass.bind_image("direct_light_img", &direct_light_tx_);
34 pass.bind_image("indirect_light_img", &indirect_light_tx_);
35 pass.bind_image("object_id_img", &object_id_tx_);
36 pass.bind_image("radiance_img", &radiance_tx_);
37 pass.bind_ssbo("convolve_tile_buf", &convolve_tile_buf_);
38 pass.bind_ssbo("convolve_dispatch_buf", &convolve_dispatch_buf_);
40 pass.dispatch(&setup_dispatch_size_);
41 }
42 {
43 /* Clamping to border color allows to always load ID 0 for out of view samples and discard
44 * their influence. Also disable filtering to avoid light bleeding between different objects
45 * and loading invalid interpolated IDs. */
51
52 PassSimple &pass = convolve_ps_;
53 pass.init();
54 pass.state_set(DRW_STATE_NO_DRAW);
55 pass.shader_set(inst_.shaders.static_shader_get(SUBSURFACE_CONVOLVE));
56 pass.bind_resources(inst_.uniform_data);
57 pass.bind_resources(inst_.gbuffer);
58 pass.bind_texture("radiance_tx", &radiance_tx_, sampler);
59 pass.bind_texture("depth_tx", &inst_.render_buffers.depth_tx, sampler);
60 pass.bind_texture("object_id_tx", &object_id_tx_, sampler);
61 pass.bind_image("out_direct_light_img", &direct_light_tx_);
62 pass.bind_image("out_indirect_light_img", &indirect_light_tx_);
63 pass.bind_ssbo("tiles_coord_buf", &convolve_tile_buf_);
65 pass.dispatch(convolve_dispatch_buf_);
66 }
67}
68
69void SubsurfaceModule::render(GPUTexture *direct_diffuse_light_tx,
70 GPUTexture *indirect_diffuse_light_tx,
71 eClosureBits active_closures,
72 View &view)
73{
74 if (!(active_closures & CLOSURE_SSS)) {
75 return;
76 }
77
78 precompute_samples_location();
79
80 int2 render_extent = inst_.film.render_extent_get();
81 setup_dispatch_size_ = int3(math::divide_ceil(render_extent, int2(SUBSURFACE_GROUP_SIZE)), 1);
82
83 const int convolve_tile_count = setup_dispatch_size_.x * setup_dispatch_size_.y;
84 convolve_tile_buf_.resize(ceil_to_multiple_u(convolve_tile_count, 512));
85
86 direct_light_tx_ = direct_diffuse_light_tx;
87 indirect_light_tx_ = indirect_diffuse_light_tx;
88
90 object_id_tx_.acquire(render_extent, SUBSURFACE_OBJECT_ID_FORMAT, usage);
91 radiance_tx_.acquire(render_extent, SUBSURFACE_RADIANCE_FORMAT, usage);
92
93 convolve_dispatch_buf_.clear_to_zero();
94
95 inst_.manager->submit(setup_ps_, view);
96 inst_.manager->submit(convolve_ps_, view);
97
98 object_id_tx_.release();
99 radiance_tx_.release();
100}
101
102void SubsurfaceModule::precompute_samples_location()
103{
104 /* Precompute sample position with white albedo. */
105 float d = burley_setup(float3(1.0f), float3(1.0f)).x;
106
107 float rand_u = inst_.sampling.rng_get(SAMPLING_SSS_U);
108 float rand_v = inst_.sampling.rng_get(SAMPLING_SSS_V);
109
110 /* Find minimum radius that we can represent because we are only sampling the largest radius. */
111 data_.min_radius = 1.0f;
112
113 double golden_angle = M_PI * (3.0 - sqrt(5.0));
114 for (auto i : IndexRange(data_.sample_len)) {
115 float theta = golden_angle * i + M_PI * 2.0f * rand_u;
116 float x = (rand_v + i) / data_.sample_len;
117 float r = SubsurfaceModule::burley_sample(d, x);
118 data_.min_radius = min_ff(data_.min_radius, r);
119 data_.samples[i].x = cosf(theta) * r;
120 data_.samples[i].y = sinf(theta) * r;
121 data_.samples[i].z = 1.0f / burley_pdf(d, r);
122 }
123 /* Avoid float imprecision.*/
124 data_.min_radius = max_ff(data_.min_radius, 1e-4f);
125
127}
128
131/* -------------------------------------------------------------------- */
139float SubsurfaceModule::burley_sample(float d, float x_rand)
140{
141 x_rand *= SSS_BURLEY_TRUNCATE_CDF;
142
143 const float tolerance = 1e-6;
144 const int max_iteration_count = 10;
145 /* Do initial guess based on manual curve fitting, this allows us to reduce
146 * number of iterations to maximum 4 across the [0..1] range. We keep maximum
147 * number of iteration higher just to be sure we didn't miss root in some
148 * corner case.
149 */
150 float r;
151 if (x_rand <= 0.9) {
152 r = exp(x_rand * x_rand * 2.4) - 1.0;
153 }
154 else {
155 /* TODO(sergey): Some nicer curve fit is possible here. */
156 r = 15.0;
157 }
158 /* Solve against scaled radius. */
159 for (int i = 0; i < max_iteration_count; i++) {
160 float exp_r_3 = exp(-r / 3.0);
161 float exp_r = exp_r_3 * exp_r_3 * exp_r_3;
162 float f = 1.0 - 0.25 * exp_r - 0.75 * exp_r_3 - x_rand;
163 float f_ = 0.25 * exp_r + 0.25 * exp_r_3;
164
165 if (abs(f) < tolerance || f_ == 0.0) {
166 break;
167 }
168
169 r = r - f / f_;
170 if (r < 0.0) {
171 r = 0.0;
172 }
173 }
174
175 return r * d;
176}
177
178float SubsurfaceModule::burley_eval(float d, float r)
179{
180 if (r >= SSS_BURLEY_TRUNCATE * d) {
181 return 0.0;
182 }
183 /* Slide 33. */
184 float exp_r_3_d = expf(-r / (3.0f * d));
185 float exp_r_d = exp_r_3_d * exp_r_3_d * exp_r_3_d;
186 return (exp_r_d + exp_r_3_d) / (8.0f * float(M_PI) * d);
187}
188
189float SubsurfaceModule::burley_pdf(float d, float r)
190{
191 return burley_eval(d, r) / SSS_BURLEY_TRUNCATE_CDF;
192}
193
196} // namespace blender::eevee
MINLINE uint ceil_to_multiple_u(uint a, uint b)
MINLINE float max_ff(float a, float b)
MINLINE float min_ff(float a, float b)
#define M_PI
@ GPU_BARRIER_SHADER_STORAGE
Definition GPU_state.hh:48
@ GPU_BARRIER_TEXTURE_FETCH
Definition GPU_state.hh:37
@ GPU_BARRIER_SHADER_IMAGE_ACCESS
Definition GPU_state.hh:35
@ GPU_SAMPLER_CUSTOM_COMPARE
@ GPU_SAMPLER_STATE_TYPE_PARAMETERS
eGPUTextureUsage
@ GPU_TEXTURE_USAGE_SHADER_READ
@ GPU_TEXTURE_USAGE_SHADER_WRITE
@ GPU_SAMPLER_EXTEND_MODE_CLAMP_TO_BORDER
@ GPU_SAMPLER_FILTERING_DEFAULT
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
void submit(PassSimple &pass, View &view)
void acquire(int2 extent, eGPUTextureFormat format, eGPUTextureUsage usage=GPU_TEXTURE_USAGE_GENERAL)
int2 render_extent_get() const
UniformDataModule uniform_data
float rng_get(eSamplingDimension dimension) const
GPUShader * static_shader_get(eShaderType shader_type)
local_group_size(16, 16) .push_constant(Type local_group_size(16, 16) .push_constant(Type input_tx sampler(1, ImageType::FLOAT_2D, "matte_tx") .image(0
#define sinf(x)
#define cosf(x)
#define expf(x)
@ DRW_STATE_NO_DRAW
Definition draw_state.hh:27
#define SUBSURFACE_RADIANCE_FORMAT
#define SUBSURFACE_GROUP_SIZE
#define SUBSURFACE_OBJECT_ID_FORMAT
draw_view in_light_buf[] float
#define SSS_BURLEY_TRUNCATE
#define SSS_BURLEY_TRUNCATE_CDF
static float3 burley_setup(float3 radius, float3 albedo)
T sqrt(const T &a)
VecBase< T, Size > divide_ceil(const VecBase< T, Size > &a, const VecBase< T, Size > &b)
T exp(const T &x)
T abs(const T &a)
VecBase< int32_t, 2 > int2
VecBase< int32_t, 3 > int3
VecBase< float, 3 > float3
void render(GPUTexture *direct_diffuse_light_tx, GPUTexture *indirect_diffuse_light_tx, eClosureBits active_closures, View &view)