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
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/* -------------------------------------------------------------------- */
30{
31 /* Initialize the world probe. */
32 world_sphere_.clipping_distances = float2(1.0f, 10.0f);
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
89 grid.world_to_object = float4x4(
90 math::normalize(math::transpose(float3x3(grid.object_to_world))));
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
291/* -------------------------------------------------------------------- */
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
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_SHAPE_BOX
@ LIGHTPROBE_FLAG_SHOW_DATA
@ LIGHTPROBE_FLAG_CUSTOM_PARALLAX
@ LIGHTPROBE_TYPE_PLANE
@ LIGHTPROBE_TYPE_VOLUME
@ LIGHTPROBE_TYPE_SPHERE
@ 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
void resize(const int64_t new_size_in_bits, const bool value=false)
A running instance of the engine.
VolumeProbeModule volume_probes
SphereProbeModule sphere_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
MatBase< float, 3, 3 > float3x3
struct LightProbeObjectCache * lightprobe_cache
struct SceneEEVEE eevee
SphereProbeUvArea as_sampling_coord() const
SphereProbeAtlasCoord atlas_coord