From 80aad50ed3e48e6b50ca94d4beb7a30af40bd1d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Madar=C3=A1sz?= Date: Mon, 2 Sep 2024 15:45:52 +0200 Subject: [PATCH] PCSS impl --- bind/v4k.lua | 2 +- demos/09-shadows.c | 4 +- engine/art/shaderlib/light.glsl | 1 + engine/art/shaderlib/shadowmap.glsl | 155 +++++++++++++++++----------- engine/joint/v4k.h | 12 +-- engine/split/v4k_render.c | 10 +- engine/split/v4k_render.h | 2 +- engine/v4k.c | 10 +- engine/v4k.h | 2 +- 9 files changed, 119 insertions(+), 79 deletions(-) diff --git a/bind/v4k.lua b/bind/v4k.lua index 3bdf09d..2847c74 100644 --- a/bind/v4k.lua +++ b/bind/v4k.lua @@ -1166,6 +1166,7 @@ typedef struct light_t { float variance_transition; float shadow_bias; float normal_bias; + float shadow_softness; bool cached; bool processed_shadows; } light_t; @@ -1207,7 +1208,6 @@ typedef struct shadowmap_t { handle depth_texture; handle depth_texture_2d; int filter_size, window_size; - float offset_radius; handle offsets_texture; struct { int gen; diff --git a/demos/09-shadows.c b/demos/09-shadows.c index fa644c5..4277d95 100644 --- a/demos/09-shadows.c +++ b/demos/09-shadows.c @@ -90,7 +90,7 @@ int main(int argc, char** argv) { if( !initialized ) { initialized = 1; sky = skybox(flag("--mie") ? 0 : SKY_DIRS[SKY_DIR], 0); - sm = shadowmap(512, 2048); + sm = shadowmap(512, 4096); // sm.blur_pcf = 1; // sm.blur_scale mdl = model(OBJ_MDLS[OBJ_MDL], 0); @@ -113,7 +113,7 @@ int main(int argc, char** argv) { }; static unsigned mode = DIR; - if (active) { + if (!ui_active()) { if (input_down(KEY_1)) mode = POINT; if (input_down(KEY_2)) mode = SPOT; if (input_down(KEY_3)) mode = DIR; diff --git a/engine/art/shaderlib/light.glsl b/engine/art/shaderlib/light.glsl index e4b3846..fe208d1 100644 --- a/engine/art/shaderlib/light.glsl +++ b/engine/art/shaderlib/light.glsl @@ -30,6 +30,7 @@ struct light_t { float normal_bias; float min_variance; float variance_transition; + float shadow_softness; }; const int LIGHT_DIRECTIONAL = 0; diff --git a/engine/art/shaderlib/shadowmap.glsl b/engine/art/shaderlib/shadowmap.glsl index 2b2b544..5d53f74 100644 --- a/engine/art/shaderlib/shadowmap.glsl +++ b/engine/art/shaderlib/shadowmap.glsl @@ -10,62 +10,85 @@ uniform sampler2D shadowMap2D[MAX_LIGHTS * NUM_SHADOW_CASCADES]; uniform sampler3D shadow_offsets; uniform int shadow_filter_size; uniform int shadow_window_size; -uniform float shadow_offset_radius; // const float bias_modifier[NUM_SHADOW_CASCADES] = float[NUM_SHADOW_CASCADES](0.95, 0.35, 0.20, 0.1, 0.1, 0.1); const float bias_modifier[NUM_SHADOW_CASCADES] = float[NUM_SHADOW_CASCADES](1.0, 6.0, 9.0, 16.0); // const float bias_modifier[NUM_SHADOW_CASCADES] = float[NUM_SHADOW_CASCADES](0.95, 0.35, 0.20, 0.15); -//// From http://fabiensanglard.net/shadowmappingVSM/index.php -float shadow_vsm(float distance, vec3 dir, int light_index, float min_variance, float variance_transition) { - distance = distance/200; - - // Define offsets for 3x3 PCF - vec3 offsets[9] = vec3[9]( - vec3(-1, -1, 0) * 0.01, - vec3( 0, -1, 0) * 0.01, - vec3( 1, -1, 0) * 0.01, - vec3(-1, 0, 0) * 0.01, - vec3( 0, 0, 0) * 0.01, - vec3( 1, 0, 0) * 0.01, - vec3(-1, 1, 0) * 0.01, - vec3( 0, 1, 0) * 0.01, - vec3( 1, 1, 0) * 0.01 - ); - - float shadow = 0.0; - - // Perform 3x3 PCF - for (int i = 0; i < 9; i++) { - vec3 sampleDir = dir + offsets[i] * (rand(vec2(v_position_ws.x + offsets[i].x, v_position_ws.y + offsets[i].y))*1.75f + 1.25f); - vec2 moments = texture(shadowMap[light_index], sampleDir).rg; - - // If the shadow map is sampled outside of its bounds, add 1.0 - if (moments.x == 1.0 && moments.y == 1.0) { - shadow += 1.0; - continue; - } - - // Surface is fully lit if the current fragment is before the light occluder - if (distance <= moments.x) { - shadow += 1.0; - continue; - } - - // Calculate VSM for this sample - float p = step(distance, moments.x); - float variance = max(moments.y - (moments.x * moments.x), min_variance); - float d = distance - moments.x; - float p_max = linstep(variance_transition, 1.0, variance / (variance + d*d)); - - shadow += min(max(p, p_max), 1.0); - } - - // Average the results - return shadow / 9.0; +vec2 shadow_vsm_variance(vec3 dir, int light_index, float distance, float min_variance, float variance_transition) { + // Calculate the variance + vec2 moments = texture(shadowMap[light_index], dir).rg; + float variance = max(moments.y - (moments.x * moments.x), min_variance); + float d = distance - moments.x; + return vec2(linstep(variance_transition, 1.0, variance / (variance + d * d)), moments.x); } -float shadow_csm(float distance, vec3 lightDir, int light_index, float shadow_bias, float normal_bias) { +//// From http://fabiensanglard.net/shadowmappingVSM/index.php +float shadow_vsm(float distance, vec3 dir, int light_index, float min_variance, float variance_transition, float shadow_softness_raw) { + distance = distance / 200; + float shadow_softness = shadow_softness_raw * 10.0; + + // Get the offset coordinates + ivec3 ofs_coord = ivec3(0); + vec2 ofs = mod(gl_FragCoord.xy, vec2(shadow_window_size)); + ofs_coord.yz = ivec2(ofs); + float ofs_sum = 0.0; + int samples_div2 = int(shadow_filter_size * shadow_filter_size / 2.0); + vec4 sc = vec4(dir, 1.0); + sc.z = dir.z; + + vec2 texelSize = 1.0 / textureSize(shadowMap[light_index], 0); + + for (int i = 0; i < 4; i++) { + ofs_coord.x = i; + vec4 offsets = texelFetch(shadow_offsets, ofs_coord, 0) * shadow_softness; + sc.xy = dir.xy + offsets.rg * texelSize; + vec2 variance = shadow_vsm_variance(sc.xyz, light_index, distance, min_variance, variance_transition); + ofs_sum += min(max(step(distance, variance.y), variance.x), 1.0); + + sc.xy = dir.xy + offsets.ba * texelSize; + variance = shadow_vsm_variance(sc.xyz, light_index, distance, min_variance, variance_transition); + ofs_sum += min(max(step(distance, variance.y), variance.x), 1.0); + } + + float shadow_sum = ofs_sum / 8.0; + + if (shadow_sum != 0.0 && shadow_sum != 1.0) { + for (int i = 4; i < samples_div2; i++) { + ofs_coord.x = i; + vec4 offsets = texelFetch(shadow_offsets, ofs_coord, 0) * shadow_softness; + sc.xy = dir.xy + offsets.rg * texelSize; + vec2 variance = shadow_vsm_variance(sc.xyz, light_index, distance, min_variance, variance_transition); + ofs_sum += min(max(step(distance, variance.y), variance.x), 1.0); + + sc.xy = dir.xy + offsets.ba * texelSize; + variance = shadow_vsm_variance(sc.xyz, light_index, distance, min_variance, variance_transition); + ofs_sum += min(max(step(distance, variance.y), variance.x), 1.0); + } + + shadow_sum = ofs_sum / (samples_div2 * 2.0); + } + + // vec3 sampleDir = dir + (rand(vec2(v_position_ws.x, v_position_ws.y))*0.25f); + // float shadow = 0.0; + // vec2 moments = ; + + // // Calculate the variance + // float variance = max(moments.y - (moments.x * moments.x), min_variance); + // float d = distance - moments.x; + // float p_max = linstep(variance_transition, 1.0, variance / (variance + d * d)); + + return shadow_sum;//min(max(step(distance, moments.x), shadow_sum), 1.0); +} + +float shadowmap_cascade_sample(vec2 sc, int cascade_index, float blend_factor) { + float s1 = texture(shadowMap2D[cascade_index], sc).r; + // float s2 = texture(shadowMap2D[cascade_index + 1], sc).r; + // return mix(s1, s2, blend_factor); + return s1; +} + +float shadow_csm(float distance, vec3 lightDir, int light_index, float shadow_bias, float normal_bias, float shadow_softness) { // Determine which cascade to use int cascade_index = -1; int min_cascades_range = light_index * NUM_SHADOW_CASCADES; @@ -79,9 +102,25 @@ float shadow_csm(float distance, vec3 lightDir, int light_index, float shadow_bi if (cascade_index == -1) { cascade_index = max_cascades_range - 1; } + + int matrix_index = cascade_index - min_cascades_range; + + // Blend cascades using a blend region value + float blend_region = 200.0; + float blend_factor = 0.0; + if (matrix_index < NUM_SHADOW_CASCADES - 1) { + blend_factor = 0.5; + } + // float cascade_start = u_cascade_distances[cascade_index]; + // float cascade_end = u_cascade_distances[cascade_index + 1]; + // float blend_start = cascade_end - blend_region; + + // if (distance > blend_start) { + // blend_factor = smoothstep(blend_start, cascade_end, distance); + // } + // } light_t light = u_lights[light_index]; - int matrix_index = cascade_index - min_cascades_range; vec4 fragPosLightSpace = light.shadow_matrix[matrix_index] * vec4(v_position_ws, 1.0); @@ -116,13 +155,13 @@ float shadow_csm(float distance, vec3 lightDir, int light_index, float shadow_bi for (int i = 0; i < 4; i++) { ofs_coord.x = i; - vec4 offsets = texelFetch(shadow_offsets, ofs_coord, 0) * shadow_offset_radius; + vec4 offsets = texelFetch(shadow_offsets, ofs_coord, 0) * shadow_softness; sc.xy = projCoords.xy + offsets.rg * texelSize; - float csmDepth = texture(shadowMap2D[cascade_index], sc.xy).r; + float csmDepth = shadowmap_cascade_sample(sc.xy, cascade_index, blend_factor); ofs_sum += currentDepth - bias > csmDepth ? 1.0 : 0.0; sc.xy = projCoords.xy + offsets.ba * texelSize; - csmDepth = texture(shadowMap2D[cascade_index], sc.xy).r; + csmDepth = shadowmap_cascade_sample(sc.xy, cascade_index, blend_factor); ofs_sum += currentDepth - bias > csmDepth ? 1.0 : 0.0; } @@ -131,13 +170,13 @@ float shadow_csm(float distance, vec3 lightDir, int light_index, float shadow_bi if (shadow_sum != 0.0 && shadow_sum != 1.0) { for (int i = 4; i < samples_div2; i++) { ofs_coord.x = i; - vec4 offsets = texelFetch(shadow_offsets, ofs_coord, 0) * shadow_offset_radius; + vec4 offsets = texelFetch(shadow_offsets, ofs_coord, 0) * shadow_softness; sc.xy = projCoords.xy + offsets.rg * texelSize; - float csmDepth = texture(shadowMap2D[cascade_index], sc.xy).r; + float csmDepth = shadowmap_cascade_sample(sc.xy, cascade_index, blend_factor); ofs_sum += currentDepth - bias > csmDepth ? 1.0 : 0.0; sc.xy = projCoords.xy + offsets.ba * texelSize; - csmDepth = texture(shadowMap2D[cascade_index], sc.xy).r; + csmDepth = shadowmap_cascade_sample(sc.xy, cascade_index, blend_factor); ofs_sum += currentDepth - bias > csmDepth ? 1.0 : 0.0; } @@ -153,12 +192,12 @@ vec4 shadowmap(int idx, in vec4 peye, in vec4 neye) { if (light.processed_shadows) { if (light.type == LIGHT_DIRECTIONAL) { - shadowFactor = shadow_csm(-peye.z, light.dir, idx, light.shadow_bias, light.normal_bias); + shadowFactor = shadow_csm(-peye.z, light.dir, idx, light.shadow_bias, light.normal_bias, light.shadow_softness); } else if (light.type == LIGHT_POINT || light.type == LIGHT_SPOT) { vec3 light_pos = (view * vec4(light.pos, 1.0)).xyz; vec3 dir = light_pos - fragment; vec4 sc = inv_view * vec4(dir, 0.0); - shadowFactor = shadow_vsm(length(dir), -sc.xyz, idx, light.min_variance, light.variance_transition); + shadowFactor = shadow_vsm(length(dir), -sc.xyz, idx, light.min_variance, light.variance_transition, light.shadow_softness); } } diff --git a/engine/joint/v4k.h b/engine/joint/v4k.h index 6d039cc..6b4e8b5 100644 --- a/engine/joint/v4k.h +++ b/engine/joint/v4k.h @@ -17291,6 +17291,7 @@ typedef struct light_t { float variance_transition; //< VSM float shadow_bias; //< CSM float normal_bias; //< CSM + float shadow_softness; // internals bool cached; //< used by scene to invalidate cached light data @@ -17350,7 +17351,6 @@ typedef struct shadowmap_t { // shadowmap offsets texture; int filter_size, window_size; - float offset_radius; handle offsets_texture; struct { @@ -383307,10 +383307,11 @@ light_t light() { l.outerCone = 0.9f; // 25 deg l.cast_shadows = true; l.processed_shadows = false; - l.shadow_distance = 200.0f; + l.shadow_distance = 400.0f; l.shadow_near_clip = 0.01f; l.shadow_bias = 0.15f; l.normal_bias = 0.05f; + l.shadow_softness = 7.0f; l.min_variance = 0.00002f; l.variance_transition = 0.2f; return l; @@ -383393,6 +383394,7 @@ void light_update(unsigned num_lights, light_t *lv) { shader_float(va("u_lights[%d].outerCone", i), lv[i].outerCone); shader_float(va("u_lights[%d].shadow_bias", i), lv[i].shadow_bias); shader_float(va("u_lights[%d].normal_bias", i), lv[i].normal_bias); + shader_float(va("u_lights[%d].shadow_softness", i), lv[i].shadow_softness); shader_float(va("u_lights[%d].min_variance", i), lv[i].min_variance); shader_float(va("u_lights[%d].variance_transition", i), lv[i].variance_transition); shader_bool(va("u_lights[%d].processed_shadows", i), lv[i].processed_shadows); @@ -383420,6 +383422,7 @@ void ui_light(light_t *l) { ui_float("Outer Cone", &l->outerCone); ui_float_("Shadow Bias", &l->shadow_bias, 0.00005); ui_float_("Normal Bias", &l->normal_bias, 0.00005); + ui_float_("Shadow Softness", &l->shadow_softness, 0.5); ui_float_("Min Variance", &l->min_variance, 0.00005); ui_float_("Variance Transition", &l->variance_transition, 0.0005); } @@ -383468,7 +383471,7 @@ shadowmap_init_caster_vsm(shadowmap_t *s, int light_index, int texture_width) { return; } - // Create a cubemap color texture + // Create a cubemap moments texture glGenTextures(1, &s->maps[light_index].texture); glBindTexture(GL_TEXTURE_CUBE_MAP, s->maps[light_index].texture); for (int i = 0; i < 6; i++) { @@ -383526,7 +383529,6 @@ shadowmap_t shadowmap(int vsm_texture_width, int csm_texture_width) { // = 512, s.vsm_blur_scale = 0.75f; s.filter_size = 8; s.window_size = 10; - s.offset_radius = 7.0f; #if 0 s.cascade_splits[0] = 0.1f; s.cascade_splits[1] = 0.3f; @@ -384121,7 +384123,6 @@ void ui_shadowmap(shadowmap_t *s) { if (ui_collapse("Shadowmap Offsets", "shadowmap_offsets")) { ui_int("Filter Size", &s->filter_size); ui_int("Window Size", &s->window_size); - ui_float("Random Radius", &s->offset_radius); ui_collapse_end(); } @@ -386326,7 +386327,6 @@ void model_set_uniforms(model_t m, int shader, mat44 mv, mat44 proj, mat44 view, glBindTexture(GL_TEXTURE_3D, m.shadow_map->offsets_texture); shader_int("shadow_filter_size", m.shadow_map->filter_size); shader_int("shadow_window_size", m.shadow_map->window_size); - shader_float("shadow_offset_radius", m.shadow_map->offset_radius); } } else if (m.shadow_map == NULL || !m.shadow_receiver) { diff --git a/engine/split/v4k_render.c b/engine/split/v4k_render.c index 8174385..d1b78dc 100644 --- a/engine/split/v4k_render.c +++ b/engine/split/v4k_render.c @@ -1482,10 +1482,11 @@ light_t light() { l.outerCone = 0.9f; // 25 deg l.cast_shadows = true; l.processed_shadows = false; - l.shadow_distance = 200.0f; + l.shadow_distance = 400.0f; l.shadow_near_clip = 0.01f; l.shadow_bias = 0.15f; l.normal_bias = 0.05f; + l.shadow_softness = 7.0f; l.min_variance = 0.00002f; l.variance_transition = 0.2f; return l; @@ -1568,6 +1569,7 @@ void light_update(unsigned num_lights, light_t *lv) { shader_float(va("u_lights[%d].outerCone", i), lv[i].outerCone); shader_float(va("u_lights[%d].shadow_bias", i), lv[i].shadow_bias); shader_float(va("u_lights[%d].normal_bias", i), lv[i].normal_bias); + shader_float(va("u_lights[%d].shadow_softness", i), lv[i].shadow_softness); shader_float(va("u_lights[%d].min_variance", i), lv[i].min_variance); shader_float(va("u_lights[%d].variance_transition", i), lv[i].variance_transition); shader_bool(va("u_lights[%d].processed_shadows", i), lv[i].processed_shadows); @@ -1595,6 +1597,7 @@ void ui_light(light_t *l) { ui_float("Outer Cone", &l->outerCone); ui_float_("Shadow Bias", &l->shadow_bias, 0.00005); ui_float_("Normal Bias", &l->normal_bias, 0.00005); + ui_float_("Shadow Softness", &l->shadow_softness, 0.5); ui_float_("Min Variance", &l->min_variance, 0.00005); ui_float_("Variance Transition", &l->variance_transition, 0.0005); } @@ -1643,7 +1646,7 @@ shadowmap_init_caster_vsm(shadowmap_t *s, int light_index, int texture_width) { return; } - // Create a cubemap color texture + // Create a cubemap moments texture glGenTextures(1, &s->maps[light_index].texture); glBindTexture(GL_TEXTURE_CUBE_MAP, s->maps[light_index].texture); for (int i = 0; i < 6; i++) { @@ -1701,7 +1704,6 @@ shadowmap_t shadowmap(int vsm_texture_width, int csm_texture_width) { // = 512, s.vsm_blur_scale = 0.75f; s.filter_size = 8; s.window_size = 10; - s.offset_radius = 7.0f; #if 0 s.cascade_splits[0] = 0.1f; s.cascade_splits[1] = 0.3f; @@ -2296,7 +2298,6 @@ void ui_shadowmap(shadowmap_t *s) { if (ui_collapse("Shadowmap Offsets", "shadowmap_offsets")) { ui_int("Filter Size", &s->filter_size); ui_int("Window Size", &s->window_size); - ui_float("Random Radius", &s->offset_radius); ui_collapse_end(); } @@ -4501,7 +4502,6 @@ void model_set_uniforms(model_t m, int shader, mat44 mv, mat44 proj, mat44 view, glBindTexture(GL_TEXTURE_3D, m.shadow_map->offsets_texture); shader_int("shadow_filter_size", m.shadow_map->filter_size); shader_int("shadow_window_size", m.shadow_map->window_size); - shader_float("shadow_offset_radius", m.shadow_map->offset_radius); } } else if (m.shadow_map == NULL || !m.shadow_receiver) { diff --git a/engine/split/v4k_render.h b/engine/split/v4k_render.h index f9b0768..cdd3eb9 100644 --- a/engine/split/v4k_render.h +++ b/engine/split/v4k_render.h @@ -323,6 +323,7 @@ typedef struct light_t { float variance_transition; //< VSM float shadow_bias; //< CSM float normal_bias; //< CSM + float shadow_softness; // internals bool cached; //< used by scene to invalidate cached light data @@ -382,7 +383,6 @@ typedef struct shadowmap_t { // shadowmap offsets texture; int filter_size, window_size; - float offset_radius; handle offsets_texture; struct { diff --git a/engine/v4k.c b/engine/v4k.c index 23f32c5..348c5c9 100644 --- a/engine/v4k.c +++ b/engine/v4k.c @@ -18336,10 +18336,11 @@ light_t light() { l.outerCone = 0.9f; // 25 deg l.cast_shadows = true; l.processed_shadows = false; - l.shadow_distance = 200.0f; + l.shadow_distance = 400.0f; l.shadow_near_clip = 0.01f; l.shadow_bias = 0.15f; l.normal_bias = 0.05f; + l.shadow_softness = 7.0f; l.min_variance = 0.00002f; l.variance_transition = 0.2f; return l; @@ -18422,6 +18423,7 @@ void light_update(unsigned num_lights, light_t *lv) { shader_float(va("u_lights[%d].outerCone", i), lv[i].outerCone); shader_float(va("u_lights[%d].shadow_bias", i), lv[i].shadow_bias); shader_float(va("u_lights[%d].normal_bias", i), lv[i].normal_bias); + shader_float(va("u_lights[%d].shadow_softness", i), lv[i].shadow_softness); shader_float(va("u_lights[%d].min_variance", i), lv[i].min_variance); shader_float(va("u_lights[%d].variance_transition", i), lv[i].variance_transition); shader_bool(va("u_lights[%d].processed_shadows", i), lv[i].processed_shadows); @@ -18449,6 +18451,7 @@ void ui_light(light_t *l) { ui_float("Outer Cone", &l->outerCone); ui_float_("Shadow Bias", &l->shadow_bias, 0.00005); ui_float_("Normal Bias", &l->normal_bias, 0.00005); + ui_float_("Shadow Softness", &l->shadow_softness, 0.5); ui_float_("Min Variance", &l->min_variance, 0.00005); ui_float_("Variance Transition", &l->variance_transition, 0.0005); } @@ -18497,7 +18500,7 @@ shadowmap_init_caster_vsm(shadowmap_t *s, int light_index, int texture_width) { return; } - // Create a cubemap color texture + // Create a cubemap moments texture glGenTextures(1, &s->maps[light_index].texture); glBindTexture(GL_TEXTURE_CUBE_MAP, s->maps[light_index].texture); for (int i = 0; i < 6; i++) { @@ -18555,7 +18558,6 @@ shadowmap_t shadowmap(int vsm_texture_width, int csm_texture_width) { // = 512, s.vsm_blur_scale = 0.75f; s.filter_size = 8; s.window_size = 10; - s.offset_radius = 7.0f; #if 0 s.cascade_splits[0] = 0.1f; s.cascade_splits[1] = 0.3f; @@ -19150,7 +19152,6 @@ void ui_shadowmap(shadowmap_t *s) { if (ui_collapse("Shadowmap Offsets", "shadowmap_offsets")) { ui_int("Filter Size", &s->filter_size); ui_int("Window Size", &s->window_size); - ui_float("Random Radius", &s->offset_radius); ui_collapse_end(); } @@ -21355,7 +21356,6 @@ void model_set_uniforms(model_t m, int shader, mat44 mv, mat44 proj, mat44 view, glBindTexture(GL_TEXTURE_3D, m.shadow_map->offsets_texture); shader_int("shadow_filter_size", m.shadow_map->filter_size); shader_int("shadow_window_size", m.shadow_map->window_size); - shader_float("shadow_offset_radius", m.shadow_map->offset_radius); } } else if (m.shadow_map == NULL || !m.shadow_receiver) { diff --git a/engine/v4k.h b/engine/v4k.h index 21ca723..3d38b74 100644 --- a/engine/v4k.h +++ b/engine/v4k.h @@ -3358,6 +3358,7 @@ typedef struct light_t { float variance_transition; //< VSM float shadow_bias; //< CSM float normal_bias; //< CSM + float shadow_softness; // internals bool cached; //< used by scene to invalidate cached light data @@ -3417,7 +3418,6 @@ typedef struct shadowmap_t { // shadowmap offsets texture; int filter_size, window_size; - float offset_radius; handle offsets_texture; struct {