Blender V4.3
eevee_lightprobe.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
12
14#include "WM_api.hh"
15
16#include "eevee_instance.hh"
17#include "eevee_lightprobe.hh"
18
19#include "draw_debug.hh"
20
21#include <iostream>
22
23namespace blender::eevee {
24
25/* -------------------------------------------------------------------- */
28
30{
31 /* Initialize the world probe. */
32 world_sphere_.clipping_distances = float2(1.0f, 10.0f);
33 world_sphere_.world_to_probe_transposed = float3x4::identity();
34 world_sphere_.influence_shape = SHAPE_ELIPSOID;
35 world_sphere_.parallax_shape = SHAPE_ELIPSOID;
36 /* Full influence. */
37 world_sphere_.influence_scale = 0.0f;
38 world_sphere_.influence_bias = 1.0f;
39 world_sphere_.parallax_distance = 1e10f;
40 /* In any case, the world must always be up to valid and used for render. */
41 world_sphere_.use_for_render = true;
42}
43
45{
46 switch (resolution) {
47 case 128:
49 case 256:
51 case 512:
53 case 1024:
55 case 2048:
57 case 4096:
59 }
62}
63
65{
66 const SceneEEVEE &sce_eevee = inst_.scene->eevee;
67 sphere_object_resolution_ = resolution_to_probe_resolution_enum(sce_eevee.gi_cubemap_resolution);
68}
69
71{
72 auto_bake_enabled_ = inst_.is_viewport() &&
73 (inst_.scene->eevee.flag & SCE_EEVEE_GI_AUTOBAKE) != 0;
74}
75
76void LightProbeModule::sync_volume(const Object *ob, ObjectHandle &handle)
77{
78 VolumeProbe &grid = volume_map_.lookup_or_add_default(handle.object_key);
79 grid.used = true;
80 if (handle.recalc != 0 || grid.initialized == false) {
81 const ::LightProbe *lightprobe = static_cast<const ::LightProbe *>(ob->data);
82
83 grid.initialized = true;
84 grid.updated = true;
85 grid.surfel_density = static_cast<const ::LightProbe *>(ob->data)->grid_surfel_density;
86 grid.object_to_world = ob->object_to_world();
87 grid.cache = ob->lightprobe_cache;
88
91
92 grid.normal_bias = lightprobe->grid_normal_bias;
93 grid.view_bias = lightprobe->grid_view_bias;
94 grid.facing_bias = lightprobe->grid_facing_bias;
95
96 grid.validity_threshold = lightprobe->grid_validity_threshold;
97 grid.dilation_threshold = lightprobe->grid_dilation_threshold;
98 grid.dilation_radius = lightprobe->grid_dilation_radius;
99 grid.intensity = lightprobe->intensity;
100
101 const bool has_valid_cache = grid.cache && grid.cache->grid_static_cache;
102 grid.viewport_display = has_valid_cache && (lightprobe->flag & LIGHTPROBE_FLAG_SHOW_DATA);
103 if (grid.viewport_display) {
104 int3 cache_size = grid.cache->grid_static_cache->size;
105 float3 scale = math::transform_direction(ob->object_to_world(),
106 1.0f / float3(cache_size + 1));
107 grid.viewport_display_size = math::reduce_min(scale) * lightprobe->data_display_size;
108 }
109
110 /* Force reupload. */
111 inst_.volume_probes.bricks_free(grid.bricks);
112 }
113}
114
115void LightProbeModule::sync_sphere(const Object *ob, ObjectHandle &handle)
116{
117 SphereProbe &cube = sphere_map_.lookup_or_add_default(handle.object_key);
118 cube.used = true;
119 if (handle.recalc != 0 || cube.initialized == false) {
120 const ::LightProbe &light_probe = *(::LightProbe *)ob->data;
121
122 cube.initialized = true;
123 cube.updated = true;
124 cube.do_render = true;
125
126 SphereProbeModule &probe_module = inst_.sphere_probes;
127 eLightProbeResolution probe_resolution = sphere_object_resolution_;
128 int subdivision_lvl = probe_module.subdivision_level_get(probe_resolution);
129
130 if (cube.atlas_coord.subdivision_lvl != subdivision_lvl) {
131 cube.atlas_coord.free();
132 cube.atlas_coord = find_empty_atlas_region(subdivision_lvl);
133 SphereProbeData &cube_data = *static_cast<SphereProbeData *>(&cube);
134 /* Update gpu data sampling coordinates. */
135 cube_data.atlas_coord = cube.atlas_coord.as_sampling_coord();
136 /* Coordinates have changed. Area might contain random data. Do not use for rendering. */
137 cube.use_for_render = false;
138 }
139
140 bool use_custom_parallax = (light_probe.flag & LIGHTPROBE_FLAG_CUSTOM_PARALLAX) != 0;
141 float influence_distance = light_probe.distinf;
142 float influence_falloff = light_probe.falloff;
143 float parallax_distance = light_probe.distpar;
144 parallax_distance = use_custom_parallax ? max_ff(parallax_distance, influence_distance) :
145 influence_distance;
146
147 auto to_eevee_shape = [](int bl_shape_type) {
148 return (bl_shape_type == LIGHTPROBE_SHAPE_BOX) ? SHAPE_CUBOID : SHAPE_ELIPSOID;
149 };
150 cube.influence_shape = to_eevee_shape(light_probe.attenuation_type);
151 cube.parallax_shape = to_eevee_shape(use_custom_parallax ? light_probe.parallax_type :
152 light_probe.attenuation_type);
153
154 float4x4 object_to_world = math::scale(ob->object_to_world(), float3(influence_distance));
155 cube.location = object_to_world.location();
156 cube.volume = math::abs(math::determinant(object_to_world));
157 cube.world_to_probe_transposed = float3x4(math::transpose(math::invert(object_to_world)));
158 cube.influence_scale = 1.0 / max_ff(1e-8f, influence_falloff);
159 cube.influence_bias = cube.influence_scale;
160 cube.parallax_distance = parallax_distance / influence_distance;
161 cube.clipping_distances = float2(light_probe.clipsta, light_probe.clipend);
162
163 float3 scale = influence_distance * math::to_scale(ob->object_to_world());
164 cube.viewport_display = light_probe.flag & LIGHTPROBE_FLAG_SHOW_DATA;
165 cube.viewport_display_size = light_probe.data_display_size * math::reduce_add(scale / 3.0f);
166 }
167}
168
169void LightProbeModule::sync_planar(const Object *ob, ObjectHandle &handle)
170{
171 PlanarProbe &plane = planar_map_.lookup_or_add_default(handle.object_key);
172 plane.used = true;
173 if (handle.recalc != 0 || plane.initialized == false) {
174 const ::LightProbe *light_probe = (::LightProbe *)ob->data;
175
176 plane.initialized = true;
177 plane.updated = true;
178 plane.plane_to_world = ob->object_to_world();
179 plane.plane_to_world.z_axis() = math::normalize(plane.plane_to_world.z_axis()) *
180 light_probe->distinf;
181 plane.world_to_plane = math::invert(plane.plane_to_world);
182 plane.clipping_offset = light_probe->clipsta;
183 plane.viewport_display = (light_probe->flag & LIGHTPROBE_FLAG_SHOW_DATA) != 0;
184 }
185}
186
188{
189 const ::LightProbe *lightprobe = static_cast<const ::LightProbe *>(ob->data);
190 switch (lightprobe->type) {
192 sync_sphere(ob, handle);
193 return;
195 sync_planar(ob, handle);
196 return;
198 sync_volume(ob, handle);
199 return;
200 }
202}
203
204void LightProbeModule::sync_world(const ::World *world, bool has_update)
205{
206 const eLightProbeResolution probe_resolution = static_cast<eLightProbeResolution>(
207 world->probe_resolution);
208
209 SphereProbeModule &sph_module = inst_.sphere_probes;
210 int subdivision_lvl = sph_module.subdivision_level_get(probe_resolution);
211
212 if (subdivision_lvl != world_sphere_.atlas_coord.subdivision_lvl) {
213 world_sphere_.atlas_coord.free();
214 world_sphere_.atlas_coord = find_empty_atlas_region(subdivision_lvl);
215 SphereProbeData &world_data = *static_cast<SphereProbeData *>(&world_sphere_);
216 world_data.atlas_coord = world_sphere_.atlas_coord.as_sampling_coord();
217 has_update = true;
218 }
219
220 if (has_update) {
221 world_sphere_.do_render = true;
222 }
223}
224
226{
227 /* Check for deleted or updated grid. */
228 volume_update_ = false;
229 volume_map_.remove_if([&](const Map<ObjectKey, VolumeProbe>::MutableItem &item) {
230 VolumeProbe &grid = item.value;
231 bool remove_grid = !grid.used;
232 if (grid.updated || remove_grid) {
233 volume_update_ = true;
234 }
235 grid.updated = false;
236 grid.used = false;
237 return remove_grid;
238 });
239
240 /* Check for deleted or updated cube. */
241 sphere_update_ = false;
242 sphere_map_.remove_if([&](const Map<ObjectKey, SphereProbe>::MutableItem &item) {
243 SphereProbe &cube = item.value;
244 bool remove_cube = !cube.used;
245 if (cube.updated || remove_cube) {
246 sphere_update_ = true;
247 }
248 cube.updated = false;
249 cube.used = false;
250 return remove_cube;
251 });
252
253 /* Check for deleted or updated plane. */
254 planar_update_ = false;
255 planar_map_.remove_if([&](const Map<ObjectKey, PlanarProbe>::MutableItem &item) {
256 PlanarProbe &plane = item.value;
257 bool remove_plane = !plane.used;
258 if (plane.updated || remove_plane) {
259 planar_update_ = true;
260 }
261 plane.updated = false;
262 plane.used = false;
263 return remove_plane;
264 });
265}
266
267SphereProbeAtlasCoord LightProbeModule::find_empty_atlas_region(int subdivision_level) const
268{
269 int layer_count = sphere_layer_count();
270 SphereProbeAtlasCoord::LocationFinder location_finder(layer_count, subdivision_level);
271
272 location_finder.mark_space_used(world_sphere_.atlas_coord);
273 for (const SphereProbe &probe : sphere_map_.values()) {
274 location_finder.mark_space_used(probe.atlas_coord);
275 }
276 return location_finder.first_free_spot();
277}
278
279int LightProbeModule::sphere_layer_count() const
280{
281 int max_layer = world_sphere_.atlas_coord.atlas_layer;
282 for (const SphereProbe &probe : sphere_map_.values()) {
283 max_layer = max_ii(max_layer, probe.atlas_coord.atlas_layer);
284 }
285 int layer_count = max_layer + 1;
286 return layer_count;
287}
288
290
291/* -------------------------------------------------------------------- */
294
296 int subdivision_level)
297{
298 subdivision_level_ = subdivision_level;
299 areas_per_dimension_ = 1 << subdivision_level_;
300 areas_per_layer_ = square_i(areas_per_dimension_);
301 /* Always add an additional layer to make sure that there is always a free area.
302 * If this area is chosen the atlas will grow. */
303 int area_len = (allocated_layer_count + 1) * areas_per_layer_;
304 areas_occupancy_.resize(area_len, false);
305}
306
308{
309 if (coord.atlas_layer == -1) {
310 /* Coordinate not allocated yet. */
311 return;
312 }
313 /* The input probe data can be stored in a different subdivision level and should tag all areas
314 * of the target subdivision level. Shift right if subdivision is higher, left if lower. */
315 const int shift_right = max_ii(coord.subdivision_lvl - subdivision_level_, 0);
316 const int shift_left = max_ii(subdivision_level_ - coord.subdivision_lvl, 0);
317 const int2 pos_in_location_finder = (coord.area_location() >> shift_right) << shift_left;
318 /* Tag all areas this probe overlaps. */
319 const int layer_offset = coord.atlas_layer * areas_per_layer_;
320 const int areas_overlapped_per_dim = 1 << shift_left;
321 for (const int y : IndexRange(areas_overlapped_per_dim)) {
322 for (const int x : IndexRange(areas_overlapped_per_dim)) {
323 const int2 pos = pos_in_location_finder + int2(x, y);
324 const int area_index = pos.x + pos.y * areas_per_dimension_;
325 areas_occupancy_[area_index + layer_offset].set();
326 }
327 }
328}
329
331{
333 result.subdivision_lvl = subdivision_level_;
334 for (int index : areas_occupancy_.index_range()) {
335 if (!areas_occupancy_[index]) {
336 result.atlas_layer = index / areas_per_layer_;
337 result.area_index = index % areas_per_layer_;
338 return result;
339 }
340 }
341 /* There should always be a free area. See constructor. */
343 return result;
344}
345
347{
348 std::ostream &os = std::cout;
349 int layer = 0, row = 0, column = 0;
350 os << "subdivision " << subdivision_level_ << "\n";
351 for (bool spot_taken : areas_occupancy_) {
352 if (row == 0 && column == 0) {
353 os << "layer " << layer << "\n";
354 }
355 os << (spot_taken ? 'X' : '-');
356 column++;
357 if (column == areas_per_dimension_) {
358 os << "\n";
359 column = 0;
360 row++;
361 }
362 if (row == areas_per_dimension_) {
363 row = 0;
364 layer++;
365 }
366 }
367}
368
370
371} // namespace blender::eevee
#define BLI_assert_unreachable()
Definition BLI_assert.h:97
MINLINE float max_ff(float a, float b)
MINLINE int square_i(int a)
MINLINE int max_ii(int a, int b)
@ LIGHTPROBE_FLAG_SHOW_DATA
@ LIGHTPROBE_FLAG_CUSTOM_PARALLAX
@ LIGHTPROBE_TYPE_PLANE
@ LIGHTPROBE_TYPE_VOLUME
@ LIGHTPROBE_TYPE_SPHERE
struct LightProbe LightProbe
@ LIGHTPROBE_SHAPE_BOX
struct Object Object
@ SCE_EEVEE_GI_AUTOBAKE
eLightProbeResolution
@ LIGHT_PROBE_RESOLUTION_128
@ LIGHT_PROBE_RESOLUTION_512
@ LIGHT_PROBE_RESOLUTION_4096
@ LIGHT_PROBE_RESOLUTION_256
@ LIGHT_PROBE_RESOLUTION_2048
@ LIGHT_PROBE_RESOLUTION_1024
in reality light always falls off quadratically Particle Retrieve the data of the particle that spawned the object for example to give variation to multiple instances of an object Point Retrieve information about points in a point cloud Retrieve the edges of an object as it appears to Cycles topology will always appear triangulated Convert a blackbody temperature to an RGB value Normal Generate a perturbed normal from an RGB normal map image Typically used for faking highly detailed surfaces Generate an OSL shader from a file or text data block Image Sample an image file as a texture Gabor Generate Gabor noise Gradient Generate interpolated color and intensity values based on the input vector Magic Generate a psychedelic color texture Voronoi Generate Worley noise based on the distance to random points Typically used to generate textures such as or biological cells Brick Generate a procedural texture producing bricks Texture Retrieve multiple types of texture coordinates nTypically used as inputs for texture nodes Vector Convert a or normal between world
MutableMapItem< Key, Value > MutableItem
Definition BLI_map.hh:133
A running instance of the engine.
VolumeProbeModule volume_probes
void sync_world(const ::World *world, bool has_update)
void sync_probe(const Object *ob, ObjectHandle &handle)
void mark_space_used(const SphereProbeAtlasCoord &coord)
LocationFinder(int allocated_layer_count, int subdivision_level)
void bricks_free(Vector< IrradianceBrickPacked > &bricks)
Simple API to draw debug shapes and log in the viewport.
static eLightProbeResolution resolution_to_probe_resolution_enum(int resolution)
MatBase< T, NumCol, NumRow > transpose(const MatBase< T, NumRow, NumCol > &mat)
MatBase< T, NumCol, NumRow > scale(const MatBase< T, NumCol, NumRow > &mat, const VectorT &scale)
T reduce_min(const VecBase< T, Size > &a)
CartesianBasis invert(const CartesianBasis &basis)
MatBase< T, NumCol, NumRow > normalize(const MatBase< T, NumCol, NumRow > &a)
VecBase< T, 3 > to_scale(const MatBase< T, NumCol, NumRow > &mat)
T abs(const T &a)
VecBase< T, 3 > transform_direction(const MatBase< T, 3, 3 > &mat, const VecBase< T, 3 > &direction)
T reduce_add(const VecBase< T, Size > &a)
T determinant(const MatBase< T, Size, Size > &mat)
MatBase< float, 4, 4 > float4x4
MatBase< float, 3, 4 > float3x4
VecBase< int32_t, 2 > int2
VecBase< float, 2 > float2
VecBase< int32_t, 3 > int3
MatBase< float, 3, 3 > float3x3
VecBase< float, 3 > float3
struct LightProbeGridCacheFrame * grid_static_cache
struct LightProbeObjectCache * lightprobe_cache
SphereProbeAtlasCoord atlas_coord
Vector< IrradianceBrickPacked > bricks
const LightProbeObjectCache * cache