main sync #3

Merged
Patrick Busch merged 318 commits from blender/blender:main into main 2023-03-17 15:52:21 +01:00
7 changed files with 364 additions and 87 deletions
Showing only changes of commit 25016b56ef - Show all commits

View File

@ -722,7 +722,7 @@ void ShadowModule::begin_sync()
PassMain::Sub &sub = pass.sub("Transparent");
/* WORKAROUND: The DRW_STATE_WRITE_STENCIL is here only to avoid enabling the rasterizer
* discard inside draw manager. */
sub.state_set(DRW_STATE_DEPTH_LESS_EQUAL | DRW_STATE_WRITE_STENCIL);
sub.state_set(DRW_STATE_CULL_FRONT | DRW_STATE_WRITE_STENCIL);
sub.state_stencil(0, 0, 0);
sub.framebuffer_set(&usage_tag_fb);
sub.shader_set(inst_.shaders.static_shader_get(SHADOW_TILEMAP_TAG_USAGE_TRANSPARENT));
@ -730,6 +730,10 @@ void ShadowModule::begin_sync()
sub.bind_ssbo("tiles_buf", &tilemap_pool.tiles_data);
sub.bind_ssbo("bounds_buf", &manager.bounds_buf.current());
sub.push_constant("tilemap_projection_ratio", &tilemap_projection_ratio_);
sub.push_constant("pixel_world_radius", &pixel_world_radius_);
sub.push_constant("fb_resolution", &usage_tag_fb_resolution_);
sub.push_constant("fb_lod", &usage_tag_fb_lod_);
inst_.hiz_buffer.bind_resources(&sub);
inst_.lights.bind_resources(&sub);
box_batch_ = DRW_cache_cube_get();
@ -1092,12 +1096,17 @@ void ShadowModule::set_view(View &view)
int3 target_size = inst_.render_buffers.depth_tx.size();
dispatch_depth_scan_size_ = math::divide_ceil(target_size, int3(SHADOW_DEPTH_SCAN_GROUP_SIZE));
tilemap_projection_ratio_ = tilemap_pixel_radius() /
screen_pixel_radius(view, int2(target_size));
pixel_world_radius_ = screen_pixel_radius(view, int2(target_size));
tilemap_projection_ratio_ = tilemap_pixel_radius() / pixel_world_radius_;
usage_tag_fb_resolution_ = math::divide_ceil(int2(target_size),
int2(std::exp2(usage_tag_fb_lod_)));
usage_tag_fb.ensure(usage_tag_fb_resolution_);
usage_tag_fb.ensure(int2(target_size));
render_fb_.ensure(int2(SHADOW_TILEMAP_RES * shadow_page_size_));
inst_.hiz_buffer.update();
bool tile_update_remains = true;
while (tile_update_remains) {
DRW_stats_group_start("Shadow");

View File

@ -216,6 +216,9 @@ class ShadowModule {
int3 dispatch_depth_scan_size_;
/* Ratio between tile-map pixel world "radius" and film pixel world "radius". */
float tilemap_projection_ratio_;
float pixel_world_radius_;
int2 usage_tag_fb_resolution_;
int usage_tag_fb_lod_ = 5;
/* Statistics that are read back to CPU after a few frame (to avoid stall). */
SwapChain<ShadowStatisticsBuf, 5> statistics_buf_;

View File

@ -3,13 +3,120 @@
* Virtual shadowmapping: Usage tagging
*
* Shadow pages are only allocated if they are visible.
* This pass scan the depth buffer and tag all tiles that are needed for light shadowing as
* needed.
* This ray-marches the current fragment along the bounds depth and tags all the intersected shadow
* tiles.
*/
#pragma BLENDER_REQUIRE(eevee_shadow_tag_usage_lib.glsl)
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(common_debug_shape_lib.glsl)
float ray_aabb(vec3 ray_origin, vec3 ray_direction, vec3 aabb_min, vec3 aabb_max)
{
/* https://gdbooks.gitbooks.io/3dcollisions/content/Chapter3/raycast_aabb.html */
vec3 t_mins = (aabb_min - ray_origin) / ray_direction;
vec3 t_maxs = (aabb_max - ray_origin) / ray_direction;
float t_min = max_v3(min(t_mins, t_maxs));
float t_max = min_v3(max(t_mins, t_maxs));
/* AABB is in the opposite direction. */
if (t_max < 0.0) {
return -1.0;
}
/* No intersection. */
if (t_min > t_max) {
return -1.0;
}
/* The ray origin is inside the aabb. */
if (t_min < 0.0) {
/* For regular ray casting we would return t_max here,
* but we want to ray cast against the box volume, not just the surface. */
return 0.0;
}
return t_min;
}
float pixel_size_at(float linear_depth)
{
float pixel_size = pixel_world_radius;
bool is_persp = (ProjectionMatrix[3][3] == 0.0);
if (is_persp) {
pixel_size *= max(0.01, linear_depth);
}
return pixel_size * exp2(fb_lod);
}
void step_bounding_sphere(vec3 vs_near_plane,
vec3 vs_view_direction,
float near_t,
float far_t,
out vec3 sphere_center,
out float sphere_radius)
{
float near_pixel_size = pixel_size_at(near_t);
vec3 near_center = vs_near_plane + vs_view_direction * near_t;
float far_pixel_size = pixel_size_at(far_t);
vec3 far_center = vs_near_plane + vs_view_direction * far_t;
sphere_center = mix(near_center, far_center, 0.5);
sphere_radius = 0;
for (int x = -1; x <= 1; x += 2) {
for (int y = -1; y <= 1; y += 2) {
vec3 near_corner = near_center + (near_pixel_size * 0.5 * vec3(x, y, 0));
sphere_radius = max(sphere_radius, len_squared(near_corner - sphere_center));
vec3 far_corner = far_center + (far_pixel_size * 0.5 * vec3(x, y, 0));
sphere_radius = max(sphere_radius, len_squared(far_corner - sphere_center));
}
}
sphere_center = point_view_to_world(sphere_center);
sphere_radius = sqrt(sphere_radius);
}
void main()
{
shadow_tag_usage(interp.vP, interp.P, gl_FragCoord.xy);
vec2 screen_uv = gl_FragCoord.xy / vec2(fb_resolution);
float opaque_depth = texelFetch(hiz_tx, int2(gl_FragCoord.xy), fb_lod).r;
vec3 ws_opaque = get_world_space_from_depth(screen_uv, opaque_depth);
vec3 ws_near_plane = get_world_space_from_depth(screen_uv, 0);
vec3 ws_view_direction = normalize(interp.P - ws_near_plane);
vec3 vs_near_plane = get_view_space_from_depth(screen_uv, 0);
vec3 vs_view_direction = normalize(interp.vP - vs_near_plane);
vec3 ls_near_plane = point_world_to_object(ws_near_plane);
vec3 ls_view_direction = normalize(point_world_to_object(interp.P) - ls_near_plane);
/* TODO (Miguel Pozo): We could try to ray-cast against the non-inflated bounds first,
* and fallback to the inflated ones if theres no hit.
* The inflated bounds can cause unnecesary extra steps. */
float ls_near_box_t = ray_aabb(
ls_near_plane, ls_view_direction, interp.ls_aabb_min, interp.ls_aabb_max);
vec3 ls_near_box = ls_near_plane + ls_view_direction * ls_near_box_t;
vec3 ws_near_box = point_object_to_world(ls_near_box);
float near_box_t = distance(ws_near_plane, ws_near_box);
float far_box_t = distance(ws_near_plane, interp.P);
/* Depth test. */
far_box_t = min(far_box_t, distance(ws_near_plane, ws_opaque));
/* Ray march from the front to the back of the bbox, and tag shadow usage along the way. */
float step_size;
for (float t = near_box_t; t <= far_box_t; t += step_size) {
/* Ensure we don't get past far_box_t. */
t = min(t, far_box_t);
step_size = pixel_size_at(t);
vec3 P = ws_near_plane + (ws_view_direction * t);
float step_radius;
step_bounding_sphere(vs_near_plane, vs_view_direction, t, t + step_size, P, step_radius);
vec3 vP = point_world_to_view(P);
shadow_tag_usage(vP, P, ws_view_direction, step_radius, t, gl_FragCoord.xy * exp2(fb_lod));
}
}

View File

@ -3,8 +3,7 @@
* Virtual shadowmapping: Usage tagging
*
* Shadow pages are only allocated if they are visible.
* This pass scan the depth buffer and tag all tiles that are needed for light shadowing as
* needed.
* This contains the common logic used for tagging shadows for opaque and transparent receivers.
*/
#pragma BLENDER_REQUIRE(common_intersect_lib.glsl)
@ -14,7 +13,18 @@
#pragma BLENDER_REQUIRE(eevee_light_lib.glsl)
#pragma BLENDER_REQUIRE(eevee_shadow_lib.glsl)
void shadow_tag_usage_tilemap(uint l_idx, vec3 P, float dist_to_cam, const bool is_directional)
void shadow_tag_usage_tile(LightData light, ivec2 tile_co, int lod, int tilemap_index)
{
if (tilemap_index > light_tilemap_max_get(light)) {
return;
}
tile_co >>= lod;
int tile_index = shadow_tile_offset(tile_co, tilemaps_buf[tilemap_index].tiles_index, lod);
atomicOr(tiles_buf[tile_index], SHADOW_IS_USED);
}
void shadow_tag_usage_tilemap_directional(uint l_idx, vec3 P, vec3 V, float radius)
{
LightData light = light_buf[l_idx];
@ -22,83 +32,141 @@ void shadow_tag_usage_tilemap(uint l_idx, vec3 P, float dist_to_cam, const bool
return;
}
int lod = 0;
ivec2 tile_co;
int tilemap_index = light.tilemap_index;
if (is_directional) {
vec3 lP = shadow_world_to_local(light, P);
vec3 lP = shadow_world_to_local(light, P);
if (radius == 0) {
ShadowCoordinates coord = shadow_directional_coordinates(light, lP);
tile_co = coord.tile_coord;
tilemap_index = coord.tilemap_index;
shadow_tag_usage_tile(light, coord.tile_coord, 0, coord.tilemap_index);
}
else {
vec3 lP = light_world_to_local(light, P - light._position);
float dist_to_light = length(lP);
if (dist_to_light > light.influence_radius_max) {
return;
}
if (light.type == LIGHT_SPOT) {
/* Early out if out of cone. */
float angle_tan = length(lP.xy / dist_to_light);
if (angle_tan > light.spot_tan) {
return;
vec3 start_lP = shadow_world_to_local(light, P - V * radius);
vec3 end_lP = shadow_world_to_local(light, P + V * radius);
int min_level = shadow_directional_level(light, start_lP - light._position);
int max_level = shadow_directional_level(light, end_lP - light._position);
for (int level = min_level; level <= max_level; level++) {
ShadowCoordinates coord_min = shadow_directional_coordinates_at_level(
light, lP - vec3(radius, radius, 0), level);
ShadowCoordinates coord_max = shadow_directional_coordinates_at_level(
light, lP + vec3(radius, radius, 0), level);
for (int x = coord_min.tile_coord.x; x <= coord_max.tile_coord.x; x++) {
for (int y = coord_min.tile_coord.y; y <= coord_max.tile_coord.y; y++) {
shadow_tag_usage_tile(light, ivec2(x, y), 0, coord_min.tilemap_index);
}
}
}
else if (is_area_light(light.type)) {
/* Early out if on the wrong side. */
if (lP.z > 0.0) {
return;
}
}
/* How much a shadow map pixel covers a final image pixel.
* We project a shadow map pixel (as a sphere for simplicity) to the receiver plane.
* We then reproject this sphere onto the camera screen and compare it to the film pixel size.
* This gives a good approximation of what LOD to select to get a somewhat uniform shadow map
* resolution in screen space. */
float footprint_ratio = dist_to_light;
/* Project the radius to the screen. 1 unit away from the camera the same way
* pixel_world_radius_inv was computed. Not needed in orthographic mode. */
bool is_persp = (ProjectionMatrix[3][3] == 0.0);
if (is_persp) {
footprint_ratio /= dist_to_cam;
}
/* Apply resolution ratio. */
footprint_ratio *= tilemap_projection_ratio;
int face_id = shadow_punctual_face_index_get(lP);
lP = shadow_punctual_local_position_to_face_local(face_id, lP);
ShadowCoordinates coord = shadow_punctual_coordinates(light, lP, face_id);
tile_co = coord.tile_coord;
tilemap_index = coord.tilemap_index;
lod = int(ceil(-log2(footprint_ratio) + tilemaps_buf[tilemap_index].lod_bias));
lod = clamp(lod, 0, SHADOW_TILEMAP_LOD);
}
tile_co >>= lod;
}
if (tilemap_index > light_tilemap_max_get(light)) {
void shadow_tag_usage_tilemap_punctual(uint l_idx, vec3 P, vec3 V, float dist_to_cam, float radius)
{
LightData light = light_buf[l_idx];
if (light.tilemap_index == LIGHT_NO_SHADOW) {
return;
}
int tile_index = shadow_tile_offset(tile_co, tilemaps_buf[tilemap_index].tiles_index, lod);
atomicOr(tiles_buf[tile_index], SHADOW_IS_USED);
vec3 lP = light_world_to_local(light, P - light._position);
float dist_to_light = length(lP) - radius;
if (dist_to_light > light.influence_radius_max) {
return;
}
if (light.type == LIGHT_SPOT) {
/* Early out if out of cone. */
float angle_tan = length(lP.xy / dist_to_light);
if (angle_tan > light.spot_tan) {
return;
}
}
else if (is_area_light(light.type)) {
/* Early out if on the wrong side. */
if (lP.z - radius > 0.0) {
return;
}
}
/* How much a shadow map pixel covers a final image pixel.
* We project a shadow map pixel (as a sphere for simplicity) to the receiver plane.
* We then reproject this sphere onto the camera screen and compare it to the film pixel size.
* This gives a good approximation of what LOD to select to get a somewhat uniform shadow map
* resolution in screen space. */
float footprint_ratio = dist_to_light;
/* Project the radius to the screen. 1 unit away from the camera the same way
* pixel_world_radius_inv was computed. Not needed in orthographic mode. */
bool is_persp = (ProjectionMatrix[3][3] == 0.0);
if (is_persp) {
footprint_ratio /= dist_to_cam;
}
/* Apply resolution ratio. */
footprint_ratio *= tilemap_projection_ratio;
if (radius == 0) {
int face_id = shadow_punctual_face_index_get(lP);
lP = shadow_punctual_local_position_to_face_local(face_id, lP);
ShadowCoordinates coord = shadow_punctual_coordinates(light, lP, face_id);
int lod = int(ceil(-log2(footprint_ratio) + tilemaps_buf[coord.tilemap_index].lod_bias));
lod = clamp(lod, 0, SHADOW_TILEMAP_LOD);
shadow_tag_usage_tile(light, coord.tile_coord, lod, coord.tilemap_index);
}
else {
uint faces = 0u;
for (int x = -1; x <= 1; x += 2) {
for (int y = -1; y <= 1; y += 2) {
for (int z = -1; z <= 1; z += 2) {
vec3 _lP = lP + vec3(x, y, z) * radius;
faces |= 1u << shadow_punctual_face_index_get(_lP);
}
}
}
for (int face_id = 0; face_id < 6; face_id++) {
if ((faces & (1u << uint(face_id))) == 0u) {
continue;
}
int tilemap_index = light.tilemap_index + face_id;
int lod = int(ceil(-log2(footprint_ratio) + tilemaps_buf[tilemap_index].lod_bias));
lod = clamp(lod, 0, SHADOW_TILEMAP_LOD);
vec3 _lP = shadow_punctual_local_position_to_face_local(face_id, lP);
vec3 offset = vec3(radius, radius, 0);
ShadowCoordinates coord_min = shadow_punctual_coordinates(light, _lP - offset, face_id);
ShadowCoordinates coord_max = shadow_punctual_coordinates(light, _lP + offset, face_id);
for (int x = coord_min.tile_coord.x; x <= coord_max.tile_coord.x; x++) {
for (int y = coord_min.tile_coord.y; y <= coord_max.tile_coord.y; y++) {
shadow_tag_usage_tile(light, ivec2(x, y), lod, tilemap_index);
}
}
}
}
}
/**
* \a radius Radius of the tagging area in world space.
* Used for downsampled/ray-marched tagging, so all the shadowmap texels covered get correctly
* tagged.
*/
void shadow_tag_usage(vec3 vP, vec3 P, vec3 V, float radius, float dist_to_cam, vec2 pixel)
{
LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) {
shadow_tag_usage_tilemap_directional(l_idx, P, V, radius);
}
LIGHT_FOREACH_END
LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, pixel, vP.z, l_idx) {
shadow_tag_usage_tilemap_punctual(l_idx, P, V, dist_to_cam, radius);
}
LIGHT_FOREACH_END
}
void shadow_tag_usage(vec3 vP, vec3 P, vec2 pixel)
{
float dist_to_cam = length(vP);
LIGHT_FOREACH_BEGIN_DIRECTIONAL (light_cull_buf, l_idx) {
shadow_tag_usage_tilemap(l_idx, P, dist_to_cam, true);
}
LIGHT_FOREACH_END
LIGHT_FOREACH_BEGIN_LOCAL (light_cull_buf, light_zbin_buf, light_tile_buf, pixel, vP.z, l_idx) {
shadow_tag_usage_tilemap(l_idx, P, dist_to_cam, false);
}
LIGHT_FOREACH_END
shadow_tag_usage(vP, P, vec3(0), 0, dist_to_cam, pixel);
}

View File

@ -3,20 +3,89 @@
* Virtual shadowmapping: Usage tagging
*
* Shadow pages are only allocated if they are visible.
* This renders bounding boxes for transparent objects in order to tag the correct shadows.
* This renders the bounding boxes for transparent objects in order to tag the correct shadows.
*/
#pragma BLENDER_REQUIRE(common_view_lib.glsl)
#pragma BLENDER_REQUIRE(common_shape_lib.glsl)
#pragma BLENDER_REQUIRE(common_debug_shape_lib.glsl)
/* Inflate bounds by half a pixel as a conservative rasterization alternative,
* to ensure the tiles needed by all LOD0 pixels get tagged */
void inflate_bounds(vec3 ls_center, inout vec3 P, inout vec3 lP)
{
vec3 vP = point_world_to_view(P);
float inflate_scale = pixel_world_radius * exp2(fb_lod);
bool is_persp = (ProjectionMatrix[3][3] == 0.0);
if (is_persp) {
inflate_scale *= -vP.z;
}
/* Half-pixel. */
inflate_scale *= 0.5;
vec3 vs_inflate_vector = normal_object_to_view(sign(lP - ls_center));
vs_inflate_vector.z = 0;
/* Scale the vector so the largest axis length is 1 */
vs_inflate_vector /= max_v2(abs(vs_inflate_vector.xy));
vs_inflate_vector *= inflate_scale;
vP += vs_inflate_vector;
P = point_view_to_world(vP);
lP = point_world_to_object(P);
}
void main()
{
ObjectBounds bounds = bounds_buf[drw_ResourceID];
PASS_RESOURCE_ID
interp.P = bounds.bounding_corners[0].xyz;
interp.P += bounds.bounding_corners[1].xyz * pos.x;
interp.P += bounds.bounding_corners[2].xyz * pos.y;
interp.P += bounds.bounding_corners[3].xyz * pos.z;
const ObjectBounds bounds = bounds_buf[resource_id];
Box box = shape_box(bounds.bounding_corners[0].xyz,
bounds.bounding_corners[0].xyz + bounds.bounding_corners[1].xyz,
bounds.bounding_corners[0].xyz + bounds.bounding_corners[2].xyz,
bounds.bounding_corners[0].xyz + bounds.bounding_corners[3].xyz);
vec3 ws_aabb_min = bounds.bounding_corners[0].xyz;
vec3 ws_aabb_max = bounds.bounding_corners[0].xyz + bounds.bounding_corners[1].xyz +
bounds.bounding_corners[2].xyz + bounds.bounding_corners[3].xyz;
vec3 ls_center = point_world_to_object((ws_aabb_min + ws_aabb_max) / 2.0);
vec3 ls_conservative_min = vec3(FLT_MAX);
vec3 ls_conservative_max = vec3(-FLT_MAX);
for (int i = 0; i < 8; i++) {
vec3 P = box.corners[i];
vec3 lP = point_world_to_object(P);
inflate_bounds(ls_center, P, lP);
ls_conservative_min = min(ls_conservative_min, lP);
ls_conservative_max = max(ls_conservative_max, lP);
}
interp.ls_aabb_min = ls_conservative_min;
interp.ls_aabb_max = ls_conservative_max;
vec3 lP = mix(ls_conservative_min, ls_conservative_max, max(vec3(0), pos));
interp.P = point_object_to_world(lP);
interp.vP = point_world_to_view(interp.P);
gl_Position = point_world_to_ndc(interp.P);
#if 0
if (gl_VertexID == 0) {
Box debug_box = shape_box(
ls_conservative_min,
ls_conservative_min + (ls_conservative_max - ls_conservative_min) * vec3(1, 0, 0),
ls_conservative_min + (ls_conservative_max - ls_conservative_min) * vec3(0, 1, 0),
ls_conservative_min + (ls_conservative_max - ls_conservative_min) * vec3(0, 0, 1));
for (int i = 0; i < 8; i++) {
debug_box.corners[i] = point_object_to_world(debug_box.corners[i]);
}
drw_debug(debug_box);
}
#endif
}

View File

@ -98,7 +98,12 @@ ShadowTileData shadow_tile_load(usampler2D tilemaps_tx, ivec2 tile_co, int tilem
return shadow_tile_unpack(tile_data);
}
/* This function should be the inverse of ShadowDirectional::coverage_get(). */
/**
* This function should be the inverse of ShadowDirectional::coverage_get().
*
* \a lP shading point position in light space, relative to the to camera position snapped to
* the smallest clipmap level (`shadow_world_to_local(light, P) - light._position`).
*/
int shadow_directional_level(LightData light, vec3 lP)
{
float lod;
@ -144,14 +149,11 @@ ivec2 shadow_decompress_grid_offset(eLightType light_type, ivec2 offset, int lev
}
/**
* \a lP shading point position in light space (world unit) and translated to camera position
* snapped to smallest clipmap level.
* \a lP shading point position in light space (`shadow_world_to_local(light, P)`).
*/
ShadowCoordinates shadow_directional_coordinates(LightData light, vec3 lP)
ShadowCoordinates shadow_directional_coordinates_at_level(LightData light, vec3 lP, int level)
{
ShadowCoordinates ret;
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(). */
int level_relative = level - light.clipmap_lod_min;
@ -173,6 +175,15 @@ ShadowCoordinates shadow_directional_coordinates(LightData light, vec3 lP)
return ret;
}
/**
* \a lP shading point position in light space (`shadow_world_to_local(light, P)`).
*/
ShadowCoordinates shadow_directional_coordinates(LightData light, vec3 lP)
{
int level = shadow_directional_level(light, lP - light._position);
return shadow_directional_coordinates_at_level(light, lP, level);
}
/* Transform vector to face local coordinate. */
vec3 shadow_punctual_local_position_to_face_local(int face_id, vec3 lL)
{

View File

@ -54,7 +54,9 @@ GPU_SHADER_CREATE_INFO(eevee_shadow_tag_usage_opaque)
GPU_SHADER_INTERFACE_INFO(eevee_shadow_tag_transparent_iface, "interp")
.smooth(Type::VEC3, "P")
.smooth(Type::VEC3, "vP");
.smooth(Type::VEC3, "vP")
.flat(Type::VEC3, "ls_aabb_min")
.flat(Type::VEC3, "ls_aabb_max");
GPU_SHADER_CREATE_INFO(eevee_shadow_tag_usage_transparent)
.do_static_compilation(true)
@ -63,9 +65,17 @@ GPU_SHADER_CREATE_INFO(eevee_shadow_tag_usage_transparent)
.storage_buf(5, Qualifier::READ_WRITE, "ShadowTileMapData", "tilemaps_buf[]")
.storage_buf(6, Qualifier::READ_WRITE, "ShadowTileDataPacked", "tiles_buf[]")
.push_constant(Type::FLOAT, "tilemap_projection_ratio")
.push_constant(Type::FLOAT, "pixel_world_radius")
.push_constant(Type::IVEC2, "fb_resolution")
.push_constant(Type::INT, "fb_lod")
.vertex_out(eevee_shadow_tag_transparent_iface)
.additional_info(
"eevee_shared", "draw_view", "draw_view_culling", "draw_modelmat_new", "eevee_light_data")
.additional_info("eevee_shared",
"draw_resource_id_varying",
"draw_view",
"draw_view_culling",
"draw_modelmat_new",
"eevee_hiz_data",
"eevee_light_data")
.vertex_source("eevee_shadow_tag_usage_vert.glsl")
.fragment_source("eevee_shadow_tag_usage_frag.glsl");