EEVEE-Next: Refactor light data packing

This allows to use unions on the C++ side and safe type
casting on the GPU side.

The type casting functions are statically verified at
compile time in C++.

This PR doesn't change the size of the light struct
but removes the need of packing floats in the `object_mat`.
The matrix will be changed to a `float4x3` in another PR
and will reduce the struct by 16 bytes.

This remove the need for the light parameters macros and
reveals the padding members that could be used for future
features for each type.

After this, all accesses to light type dependent data in
the shaders should be done using:
- `LightLocalData light_local_data_get(LightData light)`
- `LightSpotData light_spot_data_get(LightData light)`
- `LightAreaData light_area_data_get(LightData light)`
- `LightSunData light_sun_data_get(LightData light)`

Note that these functions are simple passthrough for Metal
since it supports `union` (but enforce for error checking
if option is enabled).

The error check on GPU is a bit costly so it is disabled
by default.

Pull Request: https://projects.blender.org/blender/blender/pulls/119713
This commit is contained in:
Clément Foucault 2024-03-23 08:16:12 +01:00 committed by Clément Foucault
parent c7afb06ed0
commit 4d502ab51d
15 changed files with 643 additions and 275 deletions

View File

@ -47,32 +47,31 @@ static eLightType to_light_type(short blender_light_type,
void Light::sync(ShadowModule &shadows, const Object *ob, float threshold)
{
using namespace blender::math;
const ::Light *la = (const ::Light *)ob->data;
float scale[3];
float max_power = max_fff(la->r, la->g, la->b) * fabsf(la->energy / 100.0f);
float surface_max_power = max_ff(la->diff_fac, la->spec_fac) * max_power;
float volume_max_power = la->volume_fac * max_power;
float influence_radius_surface = attenuation_radius_get(la, threshold, surface_max_power);
float influence_radius_volume = attenuation_radius_get(la, threshold, volume_max_power);
this->influence_radius_max = max_ff(influence_radius_surface, influence_radius_volume);
this->influence_radius_invsqr_surface = 1.0f / square_f(max_ff(influence_radius_surface, 1e-8f));
this->influence_radius_invsqr_volume = 1.0f / square_f(max_ff(influence_radius_volume, 1e-8f));
eLightType new_type = to_light_type(la->type, la->area_shape, la->mode & LA_USE_SOFT_FALLOFF);
if (assign_if_different(this->type, new_type)) {
shadow_discard_safe(shadows);
}
this->color = float3(&la->r) * la->energy;
normalize_m4_m4_ex(this->object_mat.ptr(), ob->object_to_world().ptr(), scale);
float3 scale;
this->object_mat = ob->object_to_world();
this->object_mat.view<3, 3>() = normalize_and_get_size(this->object_mat.view<3, 3>(), scale);
/* Make sure we have consistent handedness (in case of negatively scaled Z axis). */
float3 cross = math::cross(float3(this->_right), float3(this->_up));
if (math::dot(cross, float3(this->_back)) < 0.0f) {
float3 back = cross(float3(this->_right), float3(this->_up));
if (dot(back, float3(this->_back)) < 0.0f) {
negate_v3(this->_up);
}
shape_parameters_set(la, scale);
shape_parameters_set(la, scale, threshold);
float shape_power = shape_radiance_get(la);
float point_power = point_radiance_get(la);
float shape_power = shape_radiance_get();
float point_power = point_radiance_get();
this->power[LIGHT_DIFFUSE] = la->diff_fac * shape_power;
this->power[LIGHT_TRANSMIT] = la->diff_fac * point_power;
this->power[LIGHT_SPECULAR] = la->spec_fac * shape_power;
@ -80,20 +79,12 @@ void Light::sync(ShadowModule &shadows, const Object *ob, float threshold)
this->pcf_radius = la->shadow_filter_radius;
float resolution_scale = std::clamp(la->shadow_resolution_scale, 0.0f, 2.0f);
if (resolution_scale < 1.0) {
this->lod_bias = -log2(resolution_scale);
}
else {
this->lod_bias = -(resolution_scale - 1.0f);
}
/* TODO(fclem): Cleanup: Move that block to `ShadowPunctual::end_sync()` and
* `ShadowDirectional::end_sync()`. */
float resolution_scale = clamp(la->shadow_resolution_scale, 0.0f, 2.0f);
this->lod_bias = (resolution_scale < 1.0) ? -log2(resolution_scale) : -(resolution_scale - 1.0f);
this->lod_bias += shadows.get_global_lod_bias();
eLightType new_type = to_light_type(la->type, la->area_shape, la->mode & LA_USE_SOFT_FALLOFF);
if (assign_if_different(this->type, new_type)) {
shadow_discard_safe(shadows);
}
if (la->mode & LA_SHADOW) {
shadow_ensure(shadows);
if (is_sun_light(this->type)) {
@ -105,7 +96,7 @@ void Light::sync(ShadowModule &shadows, const Object *ob, float threshold)
else {
/* Reuse shape radius as near clip plane. */
/* This assumes `shape_parameters_set` has already set `radius_squared`. */
float radius = math::sqrt(this->radius_squared);
float radius = math::sqrt(this->local.radius_squared);
float shadow_radius = la->shadow_softness_factor * radius;
if (ELEM(la->type, LA_LOCAL, LA_SPOT)) {
/* `shape_parameters_set` can increase the radius of point and spot lights to ensure a
@ -120,7 +111,7 @@ void Light::sync(ShadowModule &shadows, const Object *ob, float threshold)
this->object_mat,
la->spotsize,
radius,
this->influence_radius_max,
this->local.influence_radius_max,
la->shadow_softness_factor,
shadow_radius);
}
@ -156,10 +147,6 @@ void Light::shadow_ensure(ShadowModule &shadows)
float Light::attenuation_radius_get(const ::Light *la, float light_threshold, float light_power)
{
if (la->type == LA_SUN) {
return (light_power > 1e-5f) ? 1e16f : 0.0f;
}
if (la->mode & LA_CUSTOM_ATTENUATION) {
return la->att_dist;
}
@ -169,102 +156,139 @@ float Light::attenuation_radius_get(const ::Light *la, float light_threshold, fl
return sqrtf(light_power / light_threshold);
}
void Light::shape_parameters_set(const ::Light *la, const float scale[3])
void Light::shape_parameters_set(const ::Light *la, const float3 &scale, float threshold)
{
if (la->type == LA_AREA) {
float area_size_y = ELEM(la->area_shape, LA_AREA_RECT, LA_AREA_ELLIPSE) ? la->area_sizey :
la->area_size;
_area_size_x = max_ff(0.003f, la->area_size * scale[0] * 0.5f);
_area_size_y = max_ff(0.003f, area_size_y * scale[1] * 0.5f);
/* For volume point lighting. */
radius_squared = max_ff(0.001f, hypotf(_area_size_x, _area_size_y) * 0.5f);
radius_squared = square_f(radius_squared);
}
else {
if (la->type == LA_SPOT) {
/* Spot size & blend */
spot_size_inv[0] = scale[2] / scale[0];
spot_size_inv[1] = scale[2] / scale[1];
float spot_size = cosf(la->spotsize * 0.5f);
float spot_blend = (1.0f - spot_size) * la->spotblend;
_spot_mul = 1.0f / max_ff(1e-8f, spot_blend);
_spot_bias = -spot_size * _spot_mul;
spot_tan = tanf(min_ff(la->spotsize * 0.5f, M_PI_2 - 0.0001f));
}
using namespace blender::math;
if (la->type == LA_SUN) {
_area_size_x = tanf(min_ff(la->sun_angle, DEG2RADF(179.9f)) / 2.0f);
if (is_sun_light(this->type)) {
this->sun.radius = tanf(min_ff(la->sun_angle, DEG2RADF(179.9f)) / 2.0f);
/* Clamp to minimum value before float imprecision artifacts appear. */
this->sun.radius = max(0.001f, this->sun.radius);
}
else if (is_area_light(this->type)) {
const bool is_irregular = ELEM(la->area_shape, LA_AREA_RECT, LA_AREA_ELLIPSE);
this->area.size = float2(la->area_size, is_irregular ? la->area_sizey : la->area_size);
/* Scale and clamp to minimum value before float imprecision artifacts appear. */
this->area.size = max(float2(0.003f), this->area.size * scale.xy() / 2.0f);
/* For volume point lighting. */
this->local.radius_squared = square(max(0.001f, length(this->area.size) / 2.0f));
}
else if (is_point_light(this->type)) {
/* Spot size & blend */
if (is_spot_light(this->type)) {
const float spot_size = cosf(la->spotsize * 0.5f);
const float spot_blend = (1.0f - spot_size) * la->spotblend;
this->spot.spot_size_inv = scale.z / max(scale.xy(), float2(1e-8f));
this->spot.spot_mul = 1.0f / max(1e-8f, spot_blend);
this->spot.spot_bias = -spot_size * this->spot.spot_mul;
this->spot.spot_tan = tanf(min(la->spotsize * 0.5f, float(M_PI_2 - 0.0001f)));
}
else {
/* Ensure a minimum radius/energy ratio to avoid harsh cut-offs. (See 114284) */
float min_radius = la->energy * 2e-05f;
_area_size_x = std::max(la->radius, min_radius);
/* Point light could access it. Make sure to avoid Undefined Behavior.
* In practice it is only ever used. */
this->spot.spot_size_inv = float2(1.0f);
this->spot.spot_mul = 0.0f;
this->spot.spot_bias = 1.0f;
this->spot.spot_tan = 0.0f;
}
_area_size_y = _area_size_x = max_ff(0.001f, _area_size_x);
radius_squared = square_f(_area_size_x);
this->spot.radius = la->radius;
/* Ensure a minimum radius/energy ratio to avoid harsh cut-offs. (See 114284) */
this->spot.radius = max(this->spot.radius, la->energy * 2e-05f);
/* Clamp to minimum value before float imprecision artifacts appear. */
this->spot.radius = max(0.001f, this->spot.radius);
/* For volume point lighting. */
this->local.radius_squared = square(this->spot.radius);
}
if (is_local_light(this->type)) {
const float max_power = reduce_max(float3(&la->r)) * fabsf(la->energy / 100.0f);
const float surface_max_power = max(la->diff_fac, la->spec_fac) * max_power;
const float volume_max_power = la->volume_fac * max_power;
float influence_radius_surface = attenuation_radius_get(la, threshold, surface_max_power);
float influence_radius_volume = attenuation_radius_get(la, threshold, volume_max_power);
this->local.influence_radius_max = max(influence_radius_surface, influence_radius_volume);
this->local.influence_radius_invsqr_surface = safe_rcp(square(influence_radius_surface));
this->local.influence_radius_invsqr_volume = safe_rcp(square(influence_radius_volume));
}
}
float Light::shape_radiance_get(const ::Light *la)
float Light::shape_radiance_get()
{
using namespace blender::math;
/* Make illumination power constant. */
switch (la->type) {
case LA_AREA: {
switch (this->type) {
case LIGHT_RECT:
case LIGHT_ELLIPSE: {
/* Rectangle area. */
float area = (_area_size_x * 2.0f) * (_area_size_y * 2.0f);
float area = this->area.size.x * this->area.size.y * 4.0f;
/* Scale for the lower area of the ellipse compared to the surrounding rectangle. */
if (ELEM(la->area_shape, LA_AREA_DISK, LA_AREA_ELLIPSE)) {
if (this->type == LIGHT_ELLIPSE) {
area *= M_PI / 4.0f;
}
/* Convert radiant flux to radiance. */
return float(M_1_PI) / area;
}
case LA_SPOT:
case LA_LOCAL: {
case LIGHT_OMNI_SPHERE:
case LIGHT_OMNI_DISK:
case LIGHT_SPOT_SPHERE:
case LIGHT_SPOT_DISK: {
/* Sphere area. */
float area = 4.0f * float(M_PI) * square_f(_radius);
float area = float(4.0f * M_PI) * this->spot.radius_squared;
/* Convert radiant flux to radiance. */
return 1.0f / (area * float(M_PI));
}
default:
case LA_SUN: {
float inv_sin_sq = 1.0f + 1.0f / square_f(_radius);
case LIGHT_SUN_ORTHO:
case LIGHT_SUN: {
float inv_sin_sq = 1.0f + 1.0f / square(this->sun.radius);
/* Convert irradiance to radiance. */
return float(M_1_PI) * inv_sin_sq;
}
}
BLI_assert_unreachable();
return 0.0f;
}
float Light::point_radiance_get(const ::Light *la)
float Light::point_radiance_get()
{
/* Volume light is evaluated as point lights. */
switch (la->type) {
case LA_AREA: {
switch (this->type) {
case LIGHT_RECT:
case LIGHT_ELLIPSE: {
/* This corrects for area light most representative point trick.
* The fit was found by reducing the average error compared to cycles. */
float area = (_area_size_x * 2.0) * (_area_size_y * 2.0f);
float area = this->area.size.x * this->area.size.y * 4.0f;
float tmp = M_PI_2 / (M_PI_2 + sqrtf(area));
/* Lerp between 1.0 and the limit (1 / pi). */
float mrp_scaling = tmp + (1.0f - tmp) * M_1_PI;
return float(M_1_PI) * mrp_scaling;
}
case LA_SPOT:
case LA_LOCAL: {
case LIGHT_OMNI_SPHERE:
case LIGHT_OMNI_DISK:
case LIGHT_SPOT_SPHERE:
case LIGHT_SPOT_DISK: {
/* Convert radiant flux to intensity. */
/* Inverse of sphere solid angle. */
return 0.25f * float(M_1_PI);
return float(1.0 / (4.0 * M_PI));
}
default:
case LA_SUN: {
case LIGHT_SUN_ORTHO:
case LIGHT_SUN: {
return 1.0f;
}
}
BLI_assert_unreachable();
return 0.0f;
}
void Light::debug_draw()
{
#ifndef NDEBUG
drw_debug_sphere(float3(_position), influence_radius_max, float4(0.8f, 0.3f, 0.0f, 1.0f));
drw_debug_sphere(float3(_position), local.influence_radius_max, float4(0.8f, 0.3f, 0.0f, 1.0f));
#endif
}

View File

@ -85,9 +85,9 @@ struct Light : public LightData, NonCopyable {
private:
float attenuation_radius_get(const ::Light *la, float light_threshold, float light_power);
void shape_parameters_set(const ::Light *la, const float scale[3]);
float shape_radiance_get(const ::Light *la);
float point_radiance_get(const ::Light *la);
void shape_parameters_set(const ::Light *la, const float3 &scale, float threshold);
float shape_radiance_get();
float point_radiance_get();
};
/** \} */

View File

@ -11,7 +11,9 @@
#ifndef USE_GPU_SHADER_CREATE_INFO
# pragma once
# include "BLI_math_bits.h"
# include "BLI_memory_utils.hh"
# include "DRW_gpu_wrapper.hh"
# include "draw_manager.hh"
@ -32,6 +34,13 @@ constexpr GPUSamplerState no_filter = GPUSamplerState::default_sampler();
constexpr GPUSamplerState with_filter = {GPU_SAMPLER_FILTERING_LINEAR};
#endif
/* __cplusplus is true when compiling with MSL, so ensure we are not inside a shader. */
#ifdef GPU_SHADER
# define IS_CPP 0
#else
# define IS_CPP 1
#endif
#define UBO_MIN_MAX_SUPPORTED_SIZE 1 << 14
/* -------------------------------------------------------------------- */
@ -741,6 +750,11 @@ static inline bool is_area_light(eLightType type)
return type >= LIGHT_RECT;
}
static inline bool is_point_light(eLightType type)
{
return type >= LIGHT_OMNI_SPHERE && type <= LIGHT_SPOT_DISK;
}
static inline bool is_spot_light(eLightType type)
{
return type == LIGHT_SPOT_SPHERE || type == LIGHT_SPOT_DISK;
@ -751,23 +765,139 @@ static inline bool is_sphere_light(eLightType type)
return type == LIGHT_SPOT_SPHERE || type == LIGHT_OMNI_SPHERE;
}
static inline bool is_oriented_disk_light(eLightType type)
{
return type == LIGHT_SPOT_DISK || type == LIGHT_OMNI_DISK;
}
static inline bool is_sun_light(eLightType type)
{
return type < LIGHT_OMNI_SPHERE;
}
static inline bool is_local_light(eLightType type)
{
return type >= LIGHT_OMNI_SPHERE;
}
/* Using define because GLSL doesn't have inheritance, and encapsulation forces us to add some
* unneeded padding. */
#define LOCAL_LIGHT_COMMON \
/** Special radius factor for point lighting (volume). */ \
float radius_squared; \
/** Maximum influence radius. Used for culling. Equal to clip far distance. */ \
float influence_radius_max; \
/** Influence radius (inverted and squared) adjusted for Surface / Volume power. */ \
float influence_radius_invsqr_surface; \
float influence_radius_invsqr_volume; \
/** --- Shadow Data --- */ \
/** Other parts of the perspective matrix. Assumes symmetric frustum. */ \
float clip_side; \
/** Scaling factor to the light shape for shadow ray casting. */ \
float shadow_scale; \
/** Shift to apply to the light origin to get the shadow projection origin. */ \
float shadow_projection_shift; \
/** Number of allocated tilemap for this local light. */ \
int tilemaps_count;
/* Untyped local light data. Gets reinterpreted to LightSpotData and LightAreaData.
* Allow access to local light common data without casting. */
struct LightLocalData {
LOCAL_LIGHT_COMMON
/** Padding reserved for when shadow_projection_shift will become a vec3. */
float _pad0_reserved;
float _pad1_reserved;
float _pad1;
float _pad2;
float2 _pad3;
float _pad4;
float _pad5;
};
BLI_STATIC_ASSERT_ALIGN(LightLocalData, 16)
/* Despite the name, is also used for omni light. */
struct LightSpotData {
LOCAL_LIGHT_COMMON
/** Padding reserved for when shadow_projection_shift will become a vec3. */
float _pad0_reserved;
float _pad1_reserved;
/** Sphere light radius. */
float radius;
/** Scale and bias to spot equation parameter. Used for adjusting the falloff. */
float spot_mul;
/** Inverse spot size (in X and Y axes). */
float2 spot_size_inv;
/** Spot angle tangent. */
float spot_tan;
float spot_bias;
};
BLI_STATIC_ASSERT(sizeof(LightSpotData) == sizeof(LightLocalData), "Data size must match")
struct LightAreaData {
LOCAL_LIGHT_COMMON
/** Padding reserved for when shadow_projection_shift will become a vec3. */
float _pad0_reserved;
float _pad1_reserved;
float _pad2;
float _pad3;
/** Shape size. */
float2 size;
float _pad5;
float _pad6;
};
BLI_STATIC_ASSERT(sizeof(LightAreaData) == sizeof(LightLocalData), "Data size must match")
struct LightSunData {
float radius;
float _pad0;
float _pad1;
float _pad2;
float _pad3;
float _pad4;
float _pad5;
float _pad6;
/** --- Shadow Data --- */
/** Offset of the LOD min in LOD min tile units. */
int2 clipmap_base_offset;
/** Angle covered by the light shape for shadow ray casting. */
float shadow_angle;
/** Trace distance around the shading point. */
float shadow_trace_distance;
/** Offset to convert from world units to tile space of the clipmap_lod_max. */
float2 clipmap_origin;
/** Clip-map LOD range to avoid sampling outside of valid range. */
int clipmap_lod_min;
int clipmap_lod_max;
};
BLI_STATIC_ASSERT(sizeof(LightSunData) == sizeof(LightLocalData), "Data size must match")
/* Enable when debugging. This is quite costly. */
#define SAFE_UNION_ACCESS 0
#if IS_CPP
/* C++ always uses union. */
# define USE_LIGHT_UNION 1
#elif defined(GPU_BACKEND_METAL) && !SAFE_UNION_ACCESS
/* Metal supports union, but force usage of the getters if SAFE_UNION_ACCESS is enabled. */
# define USE_LIGHT_UNION 1
#else
/* Use getter functions on GPU if not supported or if SAFE_UNION_ACCESS is enabled. */
# define USE_LIGHT_UNION 0
#endif
struct LightData {
/** Normalized object matrix. Last column contains data accessible using the following macros. */
/** Normalized object to world matrix. */
/* TODO(fclem): Use float4x3. */
float4x4 object_mat;
/** Packed data in the last column of the object_mat. */
#define _area_size_x object_mat[0][3]
#define _area_size_y object_mat[1][3]
#define _radius _area_size_x
#define _spot_mul object_mat[2][3]
#define _spot_bias object_mat[3][3]
/** Scale to convert from world units to tile space of the clipmap_lod_max. */
#define _clipmap_origin_x object_mat[2][3]
#define _clipmap_origin_y object_mat[3][3]
/** Aliases for axes. */
#ifndef USE_GPU_SHADER_CREATE_INFO
# define _right object_mat[0]
@ -780,54 +910,213 @@ struct LightData {
# define _back object_mat[2].xyz
# define _position object_mat[3].xyz
#endif
/** Power depending on shader type. Referenced by LightingType. */
float4 power;
/** Light Color. */
packed_float3 color;
/** Light Type. */
eLightType type;
/** Inverse spot size (in X and Y axes). Aligned to size of float2. */
float2 spot_size_inv;
/** Punctual : Influence radius (inverted and squared) adjusted for Surface / Volume power. */
float influence_radius_invsqr_surface;
float influence_radius_invsqr_volume;
/** Punctual : Maximum influence radius. Used for culling. Equal to clip far distance. */
float influence_radius_max;
/** Special radius factor for point lighting. */
float radius_squared;
/** Spot angle tangent. */
float spot_tan;
/** --- Shadow Data --- */
/** Near clip distances. Float stored as int for atomic operations. */
/** Near clip distances. Float stored as orderedIntBitsToFloat for atomic operations. */
int clip_near;
int clip_far;
/** Directional : Clip-map LOD range to avoid sampling outside of valid range. */
int clipmap_lod_min;
int clipmap_lod_max;
/** Index of the first tile-map. */
/** Index of the first tile-map. Set to LIGHT_NO_SHADOW if light is not casting shadow. */
int tilemap_index;
/** Directional : Offset of the LOD min in LOD min tile units. */
int2 clipmap_base_offset;
/** Punctual: Other parts of the perspective matrix. */
float clip_side;
/** Punctual: Shift to apply to the light origin to get the shadow projection origin. */
float shadow_projection_shift;
/** Scaling factor to the light shape for shadow ray casting. */
float shadow_shape_scale_or_angle;
/** Trace distance for directional lights. */
float shadow_trace_distance;
/* Radius in pixels for shadow filtering. */
float pcf_radius;
/* Shadow Map resolution bias. */
float lod_bias;
float _pad0;
float _pad1;
float _pad2;
#if USE_LIGHT_UNION
union {
LightLocalData local;
LightSpotData spot;
LightAreaData area;
LightSunData sun;
};
#else
/* Use `light_*_data_get(light)` to access typed data. */
LightLocalData do_not_access_directly;
#endif
};
BLI_STATIC_ASSERT_ALIGN(LightData, 16)
#ifdef GPU_SHADER
# define CHECK_TYPE_PAIR(a, b)
# define CHECK_TYPE(a, b)
# define FLOAT_AS_INT floatBitsToInt
# define TYPECAST_NOOP
#else /* C++ */
# define FLOAT_AS_INT float_as_int
# define TYPECAST_NOOP
#endif
/* In addition to the static asserts that verify correct member assignment, also verify access on
* the GPU so that only lights of a certain type can read for the appropriate union member.
* Return cross platform garbage data as some platform can return cleared memory if we early exit.
*/
#ifdef SAFE_UNION_ACCESS
# ifdef GPU_SHADER
# define DATA_MEMBER do_not_access_directly
/* Should result in a beautiful zebra pattern on invalid load. */
# if defined(GPU_FRAGMENT_SHADER)
# define GARBAGE_VALUE sin(gl_FragCoord.x + gl_FragCoord.y)
# elif defined(GPU_COMPUTE_SHADER)
# define GARBAGE_VALUE \
sin(float(gl_GlobalInvocationID.x + gl_GlobalInvocationID.y + gl_GlobalInvocationID.z))
# else
# define GARBAGE_VALUE sin(float(gl_VertexID))
# endif
/* Can be set to zero if zebra creates out-of-bound accesses and crashes. At least avoid UB. */
// # define GARBAGE_VALUE 0.0
# else /* C++ */
# define GARBAGE_VALUE 0.0f
# define DATA_MEMBER local
# endif
# define SAFE_BEGIN(data_type, check) \
data_type data; \
bool _validity_check = check; \
float _garbage = GARBAGE_VALUE;
/* Assign garbage value if the light type check fails. */
# define SAFE_ASSIGN_LIGHT_TYPE_CHECK(_type, _value) \
(_validity_check ? (_value) : _type(_garbage))
#else
# define SAFE_BEGIN(data_type, check) data_type data;
# define SAFE_ASSIGN_LIGHT_TYPE_CHECK(_type, _value) _value
#endif
#define ERROR_OFS(a, b) "Offset of " STRINGIFY(a) " mismatch offset of " STRINGIFY(b)
/* This is a dangerous process, make sure to static assert every assignment. */
#define SAFE_ASSIGN(a, reinterpret_fn, in_type, b) \
CHECK_TYPE_PAIR(data.a, reinterpret_fn(light.DATA_MEMBER.b)); \
data.a = reinterpret_fn(SAFE_ASSIGN_LIGHT_TYPE_CHECK(in_type, light.DATA_MEMBER.b)); \
BLI_STATIC_ASSERT(offsetof(decltype(data), a) == offsetof(LightLocalData, b), ERROR_OFS(a, b))
#define SAFE_ASSIGN_FLOAT(a, b) SAFE_ASSIGN(a, TYPECAST_NOOP, float, b);
#define SAFE_ASSIGN_FLOAT2(a, b) SAFE_ASSIGN(a, TYPECAST_NOOP, float2, b);
#define SAFE_ASSIGN_INT(a, b) SAFE_ASSIGN(a, TYPECAST_NOOP, int, b);
#define SAFE_ASSIGN_FLOAT_AS_INT(a, b) SAFE_ASSIGN(a, FLOAT_AS_INT, float, b);
#define SAFE_ASSIGN_FLOAT_AS_INT2_COMBINE(a, b, c) \
SAFE_ASSIGN_FLOAT_AS_INT(a.x, b); \
SAFE_ASSIGN_FLOAT_AS_INT(a.y, c);
#if !USE_LIGHT_UNION || IS_CPP
/* These functions are not meant to be used in C++ code. They are only defined on the C++ side for
* static assertions. Hide them. */
# if IS_CPP
namespace do_not_use {
# endif
static inline LightSpotData light_local_data_get(LightData light)
{
SAFE_BEGIN(LightSpotData, is_local_light(light.type))
SAFE_ASSIGN_FLOAT(radius_squared, radius_squared)
SAFE_ASSIGN_FLOAT(influence_radius_max, influence_radius_max)
SAFE_ASSIGN_FLOAT(influence_radius_invsqr_surface, influence_radius_invsqr_surface)
SAFE_ASSIGN_FLOAT(influence_radius_invsqr_volume, influence_radius_invsqr_volume)
SAFE_ASSIGN_FLOAT(clip_side, clip_side)
SAFE_ASSIGN_FLOAT(shadow_scale, shadow_scale)
SAFE_ASSIGN_FLOAT(shadow_projection_shift, shadow_projection_shift)
SAFE_ASSIGN_INT(tilemaps_count, tilemaps_count)
return data;
}
static inline LightSpotData light_spot_data_get(LightData light)
{
SAFE_BEGIN(LightSpotData, is_spot_light(light.type) || is_point_light(light.type))
SAFE_ASSIGN_FLOAT(radius_squared, radius_squared)
SAFE_ASSIGN_FLOAT(influence_radius_max, influence_radius_max)
SAFE_ASSIGN_FLOAT(influence_radius_invsqr_surface, influence_radius_invsqr_surface)
SAFE_ASSIGN_FLOAT(influence_radius_invsqr_volume, influence_radius_invsqr_volume)
SAFE_ASSIGN_FLOAT(clip_side, clip_side)
SAFE_ASSIGN_FLOAT(shadow_scale, shadow_scale)
SAFE_ASSIGN_FLOAT(shadow_projection_shift, shadow_projection_shift)
SAFE_ASSIGN_INT(tilemaps_count, tilemaps_count)
SAFE_ASSIGN_FLOAT(radius, _pad1)
SAFE_ASSIGN_FLOAT(spot_mul, _pad2)
SAFE_ASSIGN_FLOAT2(spot_size_inv, _pad3)
SAFE_ASSIGN_FLOAT(spot_tan, _pad4)
SAFE_ASSIGN_FLOAT(spot_bias, _pad5)
return data;
}
static inline LightAreaData light_area_data_get(LightData light)
{
SAFE_BEGIN(LightAreaData, is_area_light(light.type))
SAFE_ASSIGN_FLOAT(radius_squared, radius_squared)
SAFE_ASSIGN_FLOAT(influence_radius_max, influence_radius_max)
SAFE_ASSIGN_FLOAT(influence_radius_invsqr_surface, influence_radius_invsqr_surface)
SAFE_ASSIGN_FLOAT(influence_radius_invsqr_volume, influence_radius_invsqr_volume)
SAFE_ASSIGN_FLOAT(clip_side, clip_side)
SAFE_ASSIGN_FLOAT(shadow_scale, shadow_scale)
SAFE_ASSIGN_FLOAT(shadow_projection_shift, shadow_projection_shift)
SAFE_ASSIGN_INT(tilemaps_count, tilemaps_count)
SAFE_ASSIGN_FLOAT2(size, _pad3)
return data;
}
static inline LightSunData light_sun_data_get(LightData light)
{
SAFE_BEGIN(LightSunData, is_sun_light(light.type))
SAFE_ASSIGN_FLOAT(radius, radius_squared)
SAFE_ASSIGN_FLOAT_AS_INT2_COMBINE(clipmap_base_offset, _pad0_reserved, _pad1_reserved)
SAFE_ASSIGN_FLOAT(shadow_angle, _pad1)
SAFE_ASSIGN_FLOAT(shadow_trace_distance, _pad2)
SAFE_ASSIGN_FLOAT2(clipmap_origin, _pad3)
SAFE_ASSIGN_FLOAT_AS_INT(clipmap_lod_min, _pad4)
SAFE_ASSIGN_FLOAT_AS_INT(clipmap_lod_max, _pad5)
return data;
}
# if IS_CPP
} // namespace do_not_use
# endif
#endif
#if USE_LIGHT_UNION
# define light_local_data_get(light) light.local
# define light_spot_data_get(light) light.spot
# define light_area_data_get(light) light.area
# define light_sun_data_get(light) light.sun
#endif
#undef DATA_MEMBER
#undef GARBAGE_VALUE
#undef FLOAT_AS_INT
#undef TYPECAST_NOOP
#undef SAFE_BEGIN
#undef SAFE_ASSIGN_LIGHT_TYPE_CHECK
#undef ERROR_OFS
#undef SAFE_ASSIGN
#undef SAFE_ASSIGN_FLOAT
#undef SAFE_ASSIGN_FLOAT2
#undef SAFE_ASSIGN_INT
#undef SAFE_ASSIGN_FLOAT_AS_INT
#undef SAFE_ASSIGN_FLOAT_AS_INT2_COMBINE
static inline int light_tilemap_max_get(LightData light)
{
/* This is not something we need in performance critical code. */
return light.tilemap_index + (light.clipmap_lod_max - light.clipmap_lod_min);
if (is_sun_light(light.type)) {
return light.tilemap_index +
(light_sun_data_get(light).clipmap_lod_max - light_sun_data_get(light).clipmap_lod_min);
}
return light.tilemap_index + light_local_data_get(light).tilemaps_count - 1;
}
/** \} */
@ -1521,8 +1810,7 @@ BLI_STATIC_ASSERT_ALIGN(UniformData, 16)
#define UTIL_DISK_INTEGRAL_LAYER UTIL_SSS_TRANSMITTANCE_PROFILE_LAYER
#define UTIL_DISK_INTEGRAL_COMP 3
/* __cplusplus is true when compiling with MSL, so include if inside a shader. */
#if !defined(__cplusplus) || defined(GPU_SHADER)
#ifdef GPU_SHADER
# if defined(GPU_FRAGMENT_SHADER)
# define UTIL_TEXEL vec2(gl_FragCoord.xy)
@ -1582,8 +1870,7 @@ float4 utility_tx_sample_lut(sampler2DArray util_tx, float cos_theta, float roug
/** \} */
/* __cplusplus is true when compiling with MSL, so ensure we are not inside a shader. */
#if defined(__cplusplus) && !defined(GPU_SHADER)
#if IS_CPP
using AOVsInfoDataBuf = draw::StorageBuffer<AOVsInfoData>;
using CameraDataBuf = draw::UniformBuffer<CameraData>;

View File

@ -242,10 +242,6 @@ void ShadowPunctual::sync(eLightType light_type,
light_radius_ = min_ff(light_shape_radius, max_distance_ - 1e-4f);
light_type_ = light_type;
/* Keep custom data. */
size_x_ = _area_size_x;
size_y_ = _area_size_y;
position_ = float3(object_mat[3]);
softness_factor_ = softness_factor;
shadow_radius_ = shadow_radius;
@ -372,10 +368,7 @@ void ShadowPunctual::end_sync(Light &light, float lod_bias)
light.tilemap_index = tilemap_pool.tilemaps_data.size();
/* A bit weird give we are inside a punctual shadow, but this is
* in order to make light_tilemap_max_get() work. */
light.clipmap_lod_min = 0;
light.clipmap_lod_max = tilemaps_needed_ - 1;
light.local.tilemaps_count = tilemaps_needed_;
/* TODO(fclem): `as_uint()`. */
union {
float f;
@ -385,9 +378,9 @@ void ShadowPunctual::end_sync(Light &light, float lod_bias)
light.clip_near = as_int.i;
as_int.f = far;
light.clip_far = as_int.i;
light.clip_side = side;
light.shadow_projection_shift = shift;
light.shadow_shape_scale_or_angle = softness_factor_;
light.local.clip_side = side;
light.local.shadow_projection_shift = shift;
light.local.shadow_scale = softness_factor_;
for (ShadowTileMap *tilemap : tilemaps_) {
/* Add shadow tile-maps grouped by lights to the GPU buffer. */
@ -502,7 +495,7 @@ void ShadowDirectional::cascade_tilemaps_distribution(Light &light, const Camera
/* Offset in tiles between the first and the last tile-maps. */
int2 offset_vector = int2(round(farthest_tilemap_center / tile_size));
light.clipmap_base_offset = (offset_vector * (1 << 16)) / max_ii(levels_range.size() - 1, 1);
light.sun.clipmap_base_offset = (offset_vector * (1 << 16)) / max_ii(levels_range.size() - 1, 1);
/* \note: cascade_level_range starts the range at the unique LOD to apply to all tile-maps. */
int level = levels_range.first();
@ -510,7 +503,8 @@ void ShadowDirectional::cascade_tilemaps_distribution(Light &light, const Camera
ShadowTileMap *tilemap = tilemaps_[i];
/* Equal spacing between cascades layers since we want uniform shadow density. */
int2 level_offset = origin_offset + shadow_cascade_grid_offset(light.clipmap_base_offset, i);
int2 level_offset = origin_offset +
shadow_cascade_grid_offset(light.sun.clipmap_base_offset, i);
tilemap->sync_orthographic(object_mat_, level_offset, level, 0.0f, SHADOW_PROJECTION_CASCADE);
/* Add shadow tile-maps grouped by lights to the GPU buffer. */
@ -518,20 +512,19 @@ void ShadowDirectional::cascade_tilemaps_distribution(Light &light, const Camera
tilemap->set_updated();
}
light._clipmap_origin_x = origin_offset.x * tile_size;
light._clipmap_origin_y = origin_offset.y * tile_size;
light.sun.clipmap_origin = float2(origin_offset * tile_size);
light.type = LIGHT_SUN_ORTHO;
/* Not really clip-maps, but this is in order to make #light_tilemap_max_get() work and determine
* the scaling. */
light.clipmap_lod_min = levels_range.first();
light.clipmap_lod_max = levels_range.last();
light.sun.clipmap_lod_min = levels_range.first();
light.sun.clipmap_lod_max = levels_range.last();
/* The bias is applied in cascade_level_range().
* Using clipmap_lod_min here simplify code in shadow_directional_level().
* Minus 1 because of the ceil(). */
light.lod_bias = light.clipmap_lod_min - 1;
light.lod_bias = light.sun.clipmap_lod_min - 1;
}
/************************************************************************
@ -605,7 +598,7 @@ void ShadowDirectional::clipmap_tilemaps_distribution(Light &light,
/* Compressing to a single value to save up storage in light data. Number of levels is limited to
* 16 by `clipmap_level_range()` for this reason. */
light.clipmap_base_offset = pos_offset | (neg_offset << 16);
light.sun.clipmap_base_offset = pos_offset | (neg_offset << 16);
float tile_size_max = ShadowDirectional::tile_size_get(levels_range.last());
int2 level_offset_max = tilemaps_[levels_range.size() - 1]->grid_offset;
@ -615,11 +608,10 @@ void ShadowDirectional::clipmap_tilemaps_distribution(Light &light,
/* Used for selecting the clipmap level. */
light.object_mat.location() = camera.position() * float3x3(object_mat_.view<3, 3>());
/* Used as origin for the clipmap_base_offset trick. */
light._clipmap_origin_x = level_offset_max.x * tile_size_max;
light._clipmap_origin_y = level_offset_max.y * tile_size_max;
light.sun.clipmap_origin = float2(level_offset_max * tile_size_max);
light.clipmap_lod_min = levels_range.first();
light.clipmap_lod_max = levels_range.last();
light.sun.clipmap_lod_min = levels_range.first();
light.sun.clipmap_lod_max = levels_range.last();
light.lod_bias = lod_bias;
}
@ -692,9 +684,8 @@ void ShadowDirectional::end_sync(Light &light, const Camera &camera, float lod_b
light.tilemap_index = tilemap_pool.tilemaps_data.size();
light.clip_near = 0x7F7FFFFF; /* floatBitsToOrderedInt(FLT_MAX) */
light.clip_far = int(0xFF7FFFFFu ^ 0x7FFFFFFFu); /* floatBitsToOrderedInt(-FLT_MAX) */
light.shadow_trace_distance = trace_distance_;
/* This stores the disk radius directly. */
light.shadow_shape_scale_or_angle = disk_shape_angle_;
light.sun.shadow_trace_distance = trace_distance_;
light.sun.shadow_angle = disk_shape_angle_;
if (directional_distribution_type_get(camera) == SHADOW_PROJECTION_CASCADE) {
cascade_tilemaps_distribution(light, camera);

View File

@ -394,8 +394,6 @@ class ShadowPunctual : public NonCopyable, NonMovable {
ShadowModule &shadows_;
/** Tile-map for each cube-face needed (in eCubeFace order). */
Vector<ShadowTileMap *> tilemaps_;
/** Area light size. */
float size_x_, size_y_;
/** Shape type. */
eLightType light_type_;
/** Light position. */
@ -468,7 +466,7 @@ class ShadowDirectional : public NonCopyable, NonMovable {
float4x4 object_mat_;
/** Current range of clip-map / cascades levels covered by this shadow. */
IndexRange levels_range;
/** Radius of the shadowed light shape. Might be scaled compared to the shading disk. */
/** Angle of the shadowed light shape. Might be scaled compared to the shading disk. */
float disk_shape_angle_;
/** Maximum distance a shadow map ray can be travel. */
float trace_distance_;

View File

@ -19,11 +19,6 @@ void main()
LightData light = in_light_buf[l_idx];
/* Do not select 0 power lights. */
if (light.influence_radius_max < 1e-8) {
return;
}
/* Sun lights are packed at the end of the array. Perform early copy. */
if (is_sun_light(light.type)) {
/* NOTE: We know the index because sun lights are packed at the start of the input buffer. */
@ -31,27 +26,34 @@ void main()
return;
}
/* Do not select 0 power lights. */
if (light_local_data_get(light).influence_radius_max < 1e-8) {
return;
}
Sphere sphere;
switch (light.type) {
case LIGHT_SPOT_SPHERE:
case LIGHT_SPOT_DISK:
case LIGHT_SPOT_DISK: {
LightSpotData spot = light_spot_data_get(light);
/* Only for < ~170 degree Cone due to plane extraction precision. */
if (light.spot_tan < 10.0) {
if (spot.spot_tan < 10.0) {
Pyramid pyramid = shape_pyramid_non_oblique(
light._position,
light._position - light._back * light.influence_radius_max,
light._right * light.influence_radius_max * light.spot_tan / light.spot_size_inv.x,
light._up * light.influence_radius_max * light.spot_tan / light.spot_size_inv.y);
light._position - light._back * spot.influence_radius_max,
light._right * spot.influence_radius_max * spot.spot_tan / spot.spot_size_inv.x,
light._up * spot.influence_radius_max * spot.spot_tan / spot.spot_size_inv.y);
if (!intersect_view(pyramid)) {
return;
}
}
}
case LIGHT_RECT:
case LIGHT_ELLIPSE:
case LIGHT_OMNI_SPHERE:
case LIGHT_OMNI_DISK:
sphere.center = light._position;
sphere.radius = light.influence_radius_max;
sphere.radius = light_local_data_get(light).influence_radius_max;
break;
default:
break;

View File

@ -152,25 +152,27 @@ void main()
vec3 v_right = drw_normal_world_to_view(light._right);
vec3 v_up = drw_normal_world_to_view(light._up);
vec3 v_back = drw_normal_world_to_view(light._back);
float radius = light.influence_radius_max;
float radius = light_local_data_get(light).influence_radius_max;
Sphere sphere = shape_sphere(vP, radius);
bool intersect_tile = intersect(tile, sphere);
switch (light.type) {
case LIGHT_SPOT_SPHERE:
case LIGHT_SPOT_DISK:
case LIGHT_SPOT_DISK: {
LightSpotData spot = light_spot_data_get(light);
/* Only for < ~170 degree Cone due to plane extraction precision. */
if (light.spot_tan < 10.0) {
if (spot.spot_tan < 10.0) {
Pyramid pyramid = shape_pyramid_non_oblique(
vP,
vP - v_back * radius,
v_right * radius * light.spot_tan / light.spot_size_inv.x,
v_up * radius * light.spot_tan / light.spot_size_inv.y);
v_right * radius * spot.spot_tan / spot.spot_size_inv.x,
v_up * radius * spot.spot_tan / spot.spot_size_inv.y);
intersect_tile = intersect_tile && intersect(tile, pyramid);
break;
}
/* Fall-through to the hemispheric case. */
}
case LIGHT_RECT:
case LIGHT_ELLIPSE: {
vec3 v000 = vP - v_right * radius - v_up * radius;

View File

@ -35,9 +35,10 @@ void main()
if (index >= light_cull_buf.visible_count) {
continue;
}
vec3 P = light_buf[index]._position;
LightData light = light_buf[index];
vec3 P = light._position;
/* TODO(fclem): Could have better bounds for spot and area lights. */
float radius = light_buf[index].influence_radius_max;
float radius = light_local_data_get(light).influence_radius_max;
float z_dist = dot(drw_view_forward(), P) - dot(drw_view_forward(), drw_view_position());
int z_min = culling_z_to_zbin(
light_cull_buf.zbin_scale, light_cull_buf.zbin_bias, z_dist + radius);

View File

@ -40,10 +40,11 @@ LightVector light_vector_get(LightData light, const bool is_directional, vec3 P)
LightVector light_shape_vector_get(LightData light, const bool is_directional, vec3 P)
{
if (!is_directional && is_area_light(light.type)) {
LightAreaData area = light_area_data_get(light);
vec3 L = P - light._position;
vec2 closest_point = vec2(dot(light._right, L), dot(light._up, L));
vec2 max_pos = vec2(light._area_size_x, light._area_size_y);
closest_point /= max_pos;
closest_point /= area.size;
if (light.type == LIGHT_ELLIPSE) {
closest_point /= max(1.0, length(closest_point));
@ -51,7 +52,7 @@ LightVector light_shape_vector_get(LightData light, const bool is_directional, v
else {
closest_point = clamp(closest_point, -1.0, 1.0);
}
closest_point *= max_pos;
closest_point *= area.size;
vec3 L_prime = light._right * closest_point.x + light._up * closest_point.y;
@ -96,9 +97,10 @@ float light_influence_attenuation(float dist, float inv_sqr_influence)
float light_spot_attenuation(LightData light, vec3 L)
{
LightSpotData spot = light_spot_data_get(light);
vec3 lL = light_world_to_local(light, L);
float ellipse = inversesqrt(1.0 + length_squared(lL.xy * light.spot_size_inv / lL.z));
float spotmask = smoothstep(0.0, 1.0, ellipse * light._spot_mul + light._spot_bias);
float ellipse = inversesqrt(1.0 + length_squared(lL.xy * spot.spot_size_inv / lL.z));
float spotmask = smoothstep(0.0, 1.0, ellipse * spot.spot_mul + spot.spot_bias);
return spotmask * step(0.0, -dot(L, -light._back));
}
@ -126,11 +128,14 @@ float light_attenuation_common(LightData light, const bool is_directional, vec3
vec2 light_attenuation_facing(LightData light, vec3 L, float distance_to_light, vec3 Ng)
{
float radius;
if (is_area_light(light.type)) {
radius = length(vec2(light._area_size_x, light._area_size_y));
if (is_sun_light(light.type)) {
radius = light_sun_data_get(light).radius;
}
else if (is_area_light(light.type)) {
radius = length(light_area_data_get(light).size);
}
else {
radius = light._radius;
radius = light_spot_data_get(light).radius;
}
/* Sine of angle between light center and light edge. */
float sin_solid_angle = radius / distance_to_light;
@ -144,15 +149,23 @@ vec2 light_attenuation_facing(LightData light, vec3 L, float distance_to_light,
vec2 light_attenuation_surface(LightData light, const bool is_directional, vec3 Ng, LightVector lv)
{
return light_attenuation_common(light, is_directional, lv.L) *
light_attenuation_facing(light, lv.L, lv.dist, Ng) *
light_influence_attenuation(lv.dist, light.influence_radius_invsqr_surface);
vec2 result = light_attenuation_facing(light, lv.L, lv.dist, Ng);
result *= light_attenuation_common(light, is_directional, lv.L);
if (!is_directional) {
result *= light_influence_attenuation(
lv.dist, light_local_data_get(light).influence_radius_invsqr_surface);
}
return result;
}
float light_attenuation_volume(LightData light, const bool is_directional, LightVector lv)
{
return light_attenuation_common(light, is_directional, lv.L) *
light_influence_attenuation(lv.dist, light.influence_radius_invsqr_volume);
float result = light_attenuation_common(light, is_directional, lv.L);
if (!is_directional) {
result *= light_influence_attenuation(
lv.dist, light_local_data_get(light).influence_radius_invsqr_volume);
}
return result;
}
/* Cheaper alternative than evaluating the LTC.
@ -167,7 +180,7 @@ float light_point_light(LightData light, const bool is_directional, LightVector
* http://www.cemyuksel.com/research/pointlightattenuation/
*/
float d_sqr = square(lv.dist);
float r_sqr = light.radius_squared;
float r_sqr = light_local_data_get(light).radius_squared;
/* Using reformulation that has better numerical precision. */
float power = 2.0 / (d_sqr + r_sqr + lv.dist * sqrt(d_sqr + r_sqr));
@ -193,15 +206,17 @@ float light_sphere_disk_radius(float sphere_radius, float distance_to_sphere)
float light_ltc(
sampler2DArray utility_tx, LightData light, vec3 N, vec3 V, LightVector lv, vec4 ltc_mat)
{
if (is_sphere_light(light.type) && lv.dist < light._radius) {
if (is_sphere_light(light.type) && lv.dist < light_spot_data_get(light).radius) {
/* Inside the sphere light, integrate over the hemisphere. */
return 1.0;
}
if (light.type == LIGHT_RECT) {
LightAreaData area = light_area_data_get(light);
vec3 corners[4];
corners[0] = light._right * light._area_size_x + light._up * -light._area_size_y;
corners[1] = light._right * light._area_size_x + light._up * light._area_size_y;
corners[0] = light._right * area.size.x + light._up * -area.size.y;
corners[1] = light._right * area.size.x + light._up * area.size.y;
corners[2] = -corners[0];
corners[3] = -corners[1];
@ -226,15 +241,18 @@ float light_ltc(
vec2 size;
if (is_sphere_light(light.type)) {
/* Spherical omni or spot light. */
size = vec2(light_sphere_disk_radius(light._radius, lv.dist));
size = vec2(light_sphere_disk_radius(light_spot_data_get(light).radius, lv.dist));
}
else if (light.type == LIGHT_OMNI_DISK || light.type == LIGHT_SPOT_DISK) {
else if (is_oriented_disk_light(light.type)) {
/* View direction-aligned disk. */
size = vec2(light._radius);
size = vec2(light_spot_data_get(light).radius);
}
else if (is_sun_light(light.type)) {
size = vec2(light_sun_data_get(light).radius);
}
else {
/* Sun light and elliptical area light. */
size = vec2(light._area_size_x, light._area_size_y);
/* Area light. */
size = vec2(light_area_data_get(light).size);
}
vec3 points[3];

View File

@ -127,7 +127,7 @@ mat4 shadow_punctual_projection_perspective(LightData light)
/* Face Local (View) Space > Clip Space. */
float clip_far = intBitsToFloat(light.clip_far);
float clip_near = intBitsToFloat(light.clip_near);
float clip_side = light.clip_side;
float clip_side = light_local_data_get(light).clip_side;
return shadow_projection_perspective(clip_side, clip_near, clip_far);
}
@ -136,7 +136,7 @@ mat4 shadow_punctual_projection_perspective_inverse(LightData light)
/* Face Local (View) Space > Clip Space. */
float clip_far = intBitsToFloat(light.clip_far);
float clip_near = intBitsToFloat(light.clip_near);
float clip_side = light.clip_side;
float clip_side = light_local_data_get(light).clip_side;
return shadow_projection_perspective_inverse(clip_side, clip_near, clip_far);
}
@ -205,12 +205,13 @@ ShadowDirectionalSampleInfo shadow_directional_sample_info_get(LightData light,
int level = shadow_directional_level(light, lP - light._position);
/* This difference needs to be less than 32 for the later shift to be valid.
* This is ensured by ShadowDirectional::clipmap_level_range(). */
info.level_relative = level - light.clipmap_lod_min;
info.lod_relative = (light.type == LIGHT_SUN_ORTHO) ? light.clipmap_lod_min : level;
info.level_relative = level - light_sun_data_get(light).clipmap_lod_min;
info.lod_relative = (light.type == LIGHT_SUN_ORTHO) ? light_sun_data_get(light).clipmap_lod_min :
level;
info.clipmap_offset = shadow_decompress_grid_offset(
light.type, light.clipmap_base_offset, info.level_relative);
info.clipmap_origin = vec2(light._clipmap_origin_x, light._clipmap_origin_y);
light.type, light_sun_data_get(light).clipmap_base_offset, info.level_relative);
info.clipmap_origin = light_sun_data_get(light).clipmap_origin;
return info;
}

View File

@ -36,7 +36,8 @@ void shadow_tag_usage_tilemap_directional_at_level(uint l_idx, vec3 P, int level
vec3 lP = light_world_to_local(light, P);
level = clamp(level, light.clipmap_lod_min, light.clipmap_lod_max);
level = clamp(
level, light_sun_data_get(light).clipmap_lod_min, light_sun_data_get(light).clipmap_lod_max);
ShadowCoordinates coord = shadow_directional_coordinates_at_level(light, lP, level);
shadow_tag_usage_tile(light, coord.tile_coord, 0, coord.tilemap_index);
@ -90,13 +91,13 @@ void shadow_tag_usage_tilemap_punctual(
vec3 lP = light_world_to_local(light, P - light._position);
float dist_to_light = max(length(lP) - radius, 1e-5);
if (dist_to_light > light.influence_radius_max) {
if (dist_to_light > light_local_data_get(light).influence_radius_max) {
return;
}
if (is_spot_light(light.type)) {
/* Early out if out of cone. */
float angle_tan = length(lP.xy / dist_to_light);
if (angle_tan > light.spot_tan) {
if (angle_tan > light_spot_data_get(light).spot_tan) {
return;
}
}
@ -108,7 +109,7 @@ void shadow_tag_usage_tilemap_punctual(
}
/* TODO(fclem): 3D shift for jittered soft shadows. */
lP += vec3(0.0, 0.0, -light.shadow_projection_shift);
lP += vec3(0.0, 0.0, -light_local_data_get(light).shadow_projection_shift);
float footprint_ratio = shadow_punctual_footprint_ratio(
light, P, drw_view_is_perspective(), dist_to_cam, tilemap_proj_ratio);

View File

@ -15,17 +15,50 @@
#define TEST(a, b) if (true)
void set_clipmap_data(inout LightData light,
int clipmap_lod_min,
int clipmap_lod_max,
float clipmap_origin_x,
float clipmap_origin_y)
{
/* WATCH: Can get out of sync with light_sun_data_get(). */
light.do_not_access_directly._pad3 = vec2(clipmap_origin_x, clipmap_origin_y);
light.do_not_access_directly._pad4 = intBitsToFloat(clipmap_lod_min);
light.do_not_access_directly._pad5 = intBitsToFloat(clipmap_lod_max);
}
void set_clipmap_base_offset(inout LightData light, ivec2 clipmap_base_offset)
{
/* WATCH: Can get out of sync with light_sun_data_get(). */
light.do_not_access_directly._pad0_reserved = intBitsToFloat(clipmap_base_offset.x);
light.do_not_access_directly._pad1_reserved = intBitsToFloat(clipmap_base_offset.y);
}
void main()
{
TEST(eevee_shadow, DirectionalMemberSet)
{
LightData light;
/* Test the setter functions define in this file to make sure
* that they are not out of sync with `light_sun_data_get`. */
set_clipmap_data(light, 1, 2, 3.0, 4.0);
set_clipmap_base_offset(light, ivec2(5, 6));
EXPECT_EQ(light_sun_data_get(light).clipmap_lod_min, 1);
EXPECT_EQ(light_sun_data_get(light).clipmap_lod_max, 2);
EXPECT_EQ(light_sun_data_get(light).clipmap_origin, vec2(3.0, 4.0));
EXPECT_EQ(light_sun_data_get(light).clipmap_base_offset, ivec2(5, 6));
}
TEST(eevee_shadow, DirectionalClipmapLevel)
{
LightData light;
light.type = LIGHT_SUN;
light.clipmap_lod_min = -5;
light.clipmap_lod_max = 8;
set_clipmap_data(light, -5, 8, 0.0, 0.0);
light.lod_bias = 0.0;
float fac = float(SHADOW_TILEMAP_RES - 1) / float(SHADOW_TILEMAP_RES);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 0.0)), light.clipmap_lod_min);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 0.0)),
light_sun_data_get(light).clipmap_lod_min);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 0.49)), 1);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 0.5)), 1);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 0.51)), 1);
@ -37,30 +70,31 @@ void main()
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 15.9999)), 6);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 16.0)), 6);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 16.00001)), 6);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 5000.0)), light.clipmap_lod_max);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 5000.0)),
light_sun_data_get(light).clipmap_lod_max);
/* Produces NaN / Inf, Undefined behavior. */
// EXPECT_EQ(shadow_directional_level(light, vec3(FLT_MAX)), light.clipmap_lod_max);
// EXPECT_EQ(shadow_directional_level(light, vec3(FLT_MAX)),
// light_sun_data_get(light).clipmap_lod_max);
}
TEST(eevee_shadow, DirectionalCascadeLevel)
{
LightData light;
light.type = LIGHT_SUN_ORTHO;
light.clipmap_lod_min = 2;
light.clipmap_lod_max = 8;
light._clipmap_origin_x = 0.0;
light._clipmap_origin_y = 0.0;
float half_size = exp2(float(light.clipmap_lod_min - 1));
light.lod_bias = light.clipmap_lod_min - 1;
set_clipmap_data(light, 2, 8, 0.0, 0.0);
float half_size = exp2(float(light_sun_data_get(light).clipmap_lod_min - 1));
light.lod_bias = light_sun_data_get(light).clipmap_lod_min - 1;
float fac = float(SHADOW_TILEMAP_RES - 1) / float(SHADOW_TILEMAP_RES);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * half_size * 0.0, 0.0, 0.0)), 2);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * half_size * 0.5, 0.0, 0.0)), 2);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * half_size * 1.0, 0.0, 0.0)), 3);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * half_size * 1.5, 0.0, 0.0)), 3);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * half_size * 2.0, 0.0, 0.0)), 4);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 5000.0)), light.clipmap_lod_max);
EXPECT_EQ(shadow_directional_level(light, vec3(fac * 5000.0)),
light_sun_data_get(light).clipmap_lod_max);
/* Produces NaN / Inf, Undefined behavior. */
// EXPECT_EQ(shadow_directional_level(light, vec3(FLT_MAX)), light.clipmap_lod_max);
// EXPECT_EQ(shadow_directional_level(light, vec3(FLT_MAX)),
// light_sun_data_get(light).clipmap_lod_max);
}
TEST(eevee_shadow, DirectionalClipmapCoordinates)
@ -70,21 +104,22 @@ void main()
LightData light;
light.type = LIGHT_SUN;
light.clipmap_lod_min = 0; /* Range [-0.5..0.5]. */
light.clipmap_lod_max = 2; /* Range [-2..2]. */
light.tilemap_index = light.clipmap_lod_min;
// clipmap_lod_min = 0; /* Range [-0.5..0.5]. */
// clipmap_lod_max = 2; /* Range [-2..2]. */
set_clipmap_data(light, 0, 2, 0.0, 0.0);
light.tilemap_index = light_sun_data_get(light).clipmap_lod_min;
light._position = vec3(0.0);
light._clipmap_origin_x = 0.0;
light._clipmap_origin_y = 0.0;
light.lod_bias = 0;
float lod_min_tile_size = exp2(float(light.clipmap_lod_min)) / float(SHADOW_TILEMAP_RES);
float lod_max_half_size = exp2(float(light.clipmap_lod_max)) / 2.0;
float lod_min_tile_size = exp2(float(light_sun_data_get(light).clipmap_lod_min)) /
float(SHADOW_TILEMAP_RES);
float lod_max_half_size = exp2(float(light_sun_data_get(light).clipmap_lod_max)) / 2.0;
camera_lP = vec3(0.0, 0.0, 0.0);
/* Follows ShadowDirectional::end_sync(). */
light.clipmap_base_offset = ivec2(round(camera_lP.xy / lod_min_tile_size));
EXPECT_EQ(light.clipmap_base_offset, ivec2(0));
set_clipmap_base_offset(light, ivec2(round(camera_lP.xy / lod_min_tile_size)));
EXPECT_EQ(light_sun_data_get(light).clipmap_base_offset, ivec2(0));
/* Test UVs and tile mapping. */
@ -116,8 +151,8 @@ void main()
camera_lP = vec3(2.0, 2.0, 0.0);
/* Follows ShadowDirectional::end_sync(). */
light.clipmap_base_offset = ivec2(round(camera_lP.xy / lod_min_tile_size));
EXPECT_EQ(light.clipmap_base_offset, ivec2(32));
set_clipmap_base_offset(light, ivec2(round(camera_lP.xy / lod_min_tile_size)));
EXPECT_EQ(light_sun_data_get(light).clipmap_base_offset, ivec2(32));
lP = vec3(2.00001, 2.00001, 0.0);
coords = shadow_directional_coordinates(light, lP);
@ -145,7 +180,7 @@ void main()
/* Test clip-map offset. */
light.clipmap_base_offset = ivec2(31, 1);
set_clipmap_base_offset(light, ivec2(31, 1));
lP = vec3(2.0001, 0.0001, 0.0);
coords = shadow_directional_coordinates(light, lP);
@ -162,7 +197,7 @@ void main()
/* Test clip-map negative offsets. */
light.clipmap_base_offset = ivec2(-31, -1);
set_clipmap_base_offset(light, ivec2(-31, -1));
lP = vec3(-2.0001, -0.0001, 0.0);
coords = shadow_directional_coordinates(light, lP);
@ -185,22 +220,23 @@ void main()
LightData light;
light.type = LIGHT_SUN_ORTHO;
light.clipmap_lod_min = 0; /* Range [-0.5..0.5]. */
light.clipmap_lod_max = 2; /* 3 tile-maps. */
// clipmap_lod_min = 0; /* Range [-0.5..0.5]. */
// clipmap_lod_max = 2; /* 3 tile-maps. */
set_clipmap_data(light, 0, 2, 0.0, 0.0);
light.tilemap_index = 1;
light._position = vec3(0.0);
light.lod_bias = light.clipmap_lod_min - 1;
light._clipmap_origin_x = 0.0;
light._clipmap_origin_y = 0.0;
float lod_tile_size = exp2(float(light.clipmap_lod_min)) / float(SHADOW_TILEMAP_RES);
float lod_half_size = exp2(float(light.clipmap_lod_min)) / 2.0;
light.lod_bias = light_sun_data_get(light).clipmap_lod_min - 1;
float lod_tile_size = exp2(float(light_sun_data_get(light).clipmap_lod_min)) /
float(SHADOW_TILEMAP_RES);
float lod_half_size = exp2(float(light_sun_data_get(light).clipmap_lod_min)) / 2.0;
float narrowing = float(SHADOW_TILEMAP_RES - 1) / float(SHADOW_TILEMAP_RES);
camera_lP = vec3(0.0, 0.0, 0.0);
int level_range_size = light.clipmap_lod_max - light.clipmap_lod_min + 1;
int level_range_size = light_sun_data_get(light).clipmap_lod_max -
light_sun_data_get(light).clipmap_lod_min + 1;
vec2 farthest_tilemap_center = vec2(lod_half_size * float(level_range_size - 1), 0.0);
light.clipmap_base_offset = floatBitsToInt(
vec2(lod_half_size / float(level_range_size - 1), 0.0));
set_clipmap_base_offset(
light, floatBitsToInt(vec2(lod_half_size / float(level_range_size - 1), 0.0)));
/* Test UVs and tile mapping. */
@ -241,8 +277,8 @@ void main()
// camera_lP = vec3(2.0, 2.0, 0.0);
/* Follows ShadowDirectional::end_sync(). */
// light.clipmap_base_offset = ivec2(round(camera_lP.xy / lod_min_tile_size));
// EXPECT_EQ(light.clipmap_base_offset, ivec2(32));
// set_clipmap_base_offset(light, ivec2(round(camera_lP.xy / lod_min_tile_size)));
// EXPECT_EQ(light_sun_data_get(light).clipmap_base_offset, ivec2(32));
// lP = vec3(2.00001, 2.00001, 0.0);
// coords = shadow_directional_coordinates(light, lP);
@ -270,7 +306,7 @@ void main()
/* Test clip-map offset. */
// light.clipmap_base_offset = ivec2(31, 1);
// set_clipmap_base_offset(light, ivec2(31, 1));
// lP = vec3(2.0001, 0.0001, 0.0);
// coords = shadow_directional_coordinates(light, lP);
@ -287,7 +323,7 @@ void main()
/* Test clip-map negative offsets. */
// light.clipmap_base_offset = ivec2(-31, -1);
// set_clipmap_base_offset(light, ivec2(-31, -1));
// lP = vec3(-2.0001, -0.0001, 0.0);
// coords = shadow_directional_coordinates(light, lP);

View File

@ -123,11 +123,13 @@ float shadow_directional_level_fractional(LightData light, vec3 lP)
/* The narrowing need to be stronger since the tile-map position is not rounded but floored. */
const float narrowing = float(SHADOW_TILEMAP_RES) / (float(SHADOW_TILEMAP_RES) - 2.5001);
/* Since we want half of the size, bias the level by -1. */
float lod_min_half_size = exp2(float(light.clipmap_lod_min - 1));
float lod_min_half_size = exp2(float(light_sun_data_get(light).clipmap_lod_min - 1));
lod = length(lP.xy) * narrowing / lod_min_half_size;
}
float clipmap_lod = lod + light.lod_bias;
return clamp(clipmap_lod, float(light.clipmap_lod_min), float(light.clipmap_lod_max));
return clamp(clipmap_lod,
float(light_sun_data_get(light).clipmap_lod_min),
float(light_sun_data_get(light).clipmap_lod_max));
}
int shadow_directional_level(LightData light, vec3 lP)
@ -157,7 +159,8 @@ float shadow_punctual_footprint_ratio(LightData light,
/* Apply resolution ratio. */
footprint_ratio *= tilemap_projection_ratio;
/* Take the frustum padding into account. */
footprint_ratio *= light.clip_side / orderedIntBitsToFloat(light.clip_near);
footprint_ratio *= light_local_data_get(light).clip_side /
orderedIntBitsToFloat(light.clip_near);
return footprint_ratio;
}
@ -191,17 +194,18 @@ ShadowCoordinates shadow_directional_coordinates_at_level(LightData light, vec3
ShadowCoordinates ret;
/* This difference needs to be less than 32 for the later shift to be valid.
* This is ensured by `ShadowDirectional::clipmap_level_range()`. */
int level_relative = level - light.clipmap_lod_min;
int level_relative = level - light_sun_data_get(light).clipmap_lod_min;
ret.tilemap_index = light.tilemap_index + level_relative;
ret.lod_relative = (light.type == LIGHT_SUN_ORTHO) ? light.clipmap_lod_min : level;
ret.lod_relative = (light.type == LIGHT_SUN_ORTHO) ? light_sun_data_get(light).clipmap_lod_min :
level;
/* Compute offset in tile. */
ivec2 clipmap_offset = shadow_decompress_grid_offset(
light.type, light.clipmap_base_offset, level_relative);
light.type, light_sun_data_get(light).clipmap_base_offset, level_relative);
ret.uv = lP.xy - vec2(light._clipmap_origin_x, light._clipmap_origin_y);
ret.uv = lP.xy - light_sun_data_get(light).clipmap_origin;
ret.uv /= exp2(float(ret.lod_relative));
ret.uv = ret.uv * float(SHADOW_TILEMAP_RES) + float(SHADOW_TILEMAP_RES / 2);
ret.uv -= vec2(clipmap_offset);
@ -279,7 +283,7 @@ int shadow_punctual_face_index_get(vec3 lL)
ShadowCoordinates shadow_punctual_coordinates(LightData light, vec3 lP, int face_id)
{
float clip_near = intBitsToFloat(light.clip_near);
float clip_side = light.clip_side;
float clip_side = light_local_data_get(light).clip_side;
ShadowCoordinates ret;
ret.tilemap_index = light.tilemap_index + face_id;

View File

@ -210,7 +210,7 @@ ShadowRayDirectional shadow_ray_generate_directional(LightData light,
vec4 origin = vec4(lP, dist_to_near_plane / z_range);
vec3 disk_direction = sample_uniform_cone(sample_cylinder(random_2d),
light.shadow_shape_scale_or_angle);
light_sun_data_get(light).shadow_angle);
/* Light shape is 1 unit away from the shading point. */
vec4 direction = vec4(disk_direction, -1.0 / z_range);
@ -221,7 +221,8 @@ ShadowRayDirectional shadow_ray_generate_directional(LightData light,
origin += direction * thickness;
}
/* It only make sense to trace where there can be occluder. Clamp by distance to near plane. */
direction *= min(light.shadow_trace_distance, dist_to_near_plane / disk_direction.z);
direction *= min(light_sun_data_get(light).shadow_trace_distance,
dist_to_near_plane / disk_direction.z);
ShadowRayDirectional ray;
ray.origin = origin;
@ -239,17 +240,19 @@ ShadowTracingSample shadow_map_trace_sample(ShadowMapTracingState state,
int level = shadow_directional_level(ray.light, ray_pos.xyz - ray.light._position);
/* This difference needs to be less than 32 for the later shift to be valid.
* This is ensured by ShadowDirectional::clipmap_level_range(). */
int level_relative = level - ray.light.clipmap_lod_min;
int level_relative = level - light_sun_data_get(ray.light).clipmap_lod_min;
int lod_relative = (ray.light.type == LIGHT_SUN_ORTHO) ? ray.light.clipmap_lod_min : level;
int lod_relative = (ray.light.type == LIGHT_SUN_ORTHO) ?
light_sun_data_get(ray.light).clipmap_lod_min :
level;
vec2 clipmap_origin = vec2(ray.light._clipmap_origin_x, ray.light._clipmap_origin_y);
vec2 clipmap_origin = light_sun_data_get(ray.light).clipmap_origin;
vec2 clipmap_pos = ray_pos.xy - clipmap_origin;
vec2 tilemap_uv = clipmap_pos * exp2(-float(lod_relative)) + 0.5;
/* Compute offset in tile. */
ivec2 clipmap_offset = shadow_decompress_grid_offset(
ray.light.type, ray.light.clipmap_base_offset, level_relative);
ray.light.type, light_sun_data_get(ray.light).clipmap_base_offset, level_relative);
/* Translate tilemap UVs to its origin. */
tilemap_uv -= vec2(clipmap_offset) / float(SHADOW_TILEMAP_RES);
/* Clamp to avoid out of tilemap access. */
@ -296,18 +299,18 @@ ShadowRayPunctual shadow_ray_generate_punctual(LightData light,
float clip_far = intBitsToFloat(light.clip_far);
float clip_near = intBitsToFloat(light.clip_near);
float clip_side = light.clip_side;
float clip_side = light_local_data_get(light).clip_side;
/* TODO(fclem): 3D shift for jittered soft shadows. */
vec3 projection_origin = vec3(0.0, 0.0, -light.shadow_projection_shift);
vec3 projection_origin = vec3(0.0, 0.0, -light_local_data_get(light).shadow_projection_shift);
vec3 direction;
if (is_area_light(light.type)) {
random_2d *= vec2(light._area_size_x, light._area_size_y);
random_2d *= light_area_data_get(light).size;
vec3 point_on_light_shape = vec3(random_2d, 0.0);
/* Progressively blend the shape back to the projection origin. */
point_on_light_shape = mix(
-projection_origin, point_on_light_shape, light.shadow_shape_scale_or_angle);
-projection_origin, point_on_light_shape, light_local_data_get(light).shadow_scale);
direction = point_on_light_shape - lP;
r_is_above_surface = dot(direction, lNg) > 0.0;
@ -333,15 +336,15 @@ ShadowRayPunctual shadow_ray_generate_punctual(LightData light,
/* Disk rotated towards light vector. */
vec3 right, up;
make_orthonormal_basis(L, right, up);
if (is_sphere_light(light.type)) {
/* FIXME(weizhen): this is not well-defined when `dist < light._radius`. */
random_2d *= light_sphere_disk_radius(light._radius, dist);
}
else {
random_2d *= light._radius;
}
random_2d *= light.shadow_shape_scale_or_angle;
float shape_radius = light_spot_data_get(light).radius;
if (is_sphere_light(light.type)) {
/* FIXME(weizhen): this is not well-defined when `dist < light.spot.radius`. */
shape_radius = light_sphere_disk_radius(shape_radius, dist);
}
random_2d *= shape_radius;
random_2d *= light_local_data_get(light).shadow_scale;
vec3 point_on_light_shape = right * random_2d.x + up * random_2d.y;
direction = point_on_light_shape - lP;
@ -357,7 +360,7 @@ ShadowRayPunctual shadow_ray_generate_punctual(LightData light,
#endif
/* Clip the ray to not cross the light shape. */
float clip_distance = light._radius;
float clip_distance = light_spot_data_get(light).radius;
direction *= saturate((dist - clip_distance) / dist);
}

View File

@ -89,7 +89,7 @@ vec3 volume_light(LightData light, const bool is_directional, LightVector lv)
{
float power = 1.0;
if (!is_directional) {
float volume_radius_squared = light.radius_squared;
float volume_radius_squared = light_local_data_get(light).radius_squared;
float light_clamp = uniform_buf.volumes.light_clamp;
if (light_clamp != 0.0) {
/* 0.0 light clamp means it's disabled. */