#pragma once
#include "shared.h"
static const f32 NORMAL_BIAS = 1.0f; // world normal offset, texels (sun-facing)
static const f32 NORMAL_BIAS_SLOPE = 0.25f;
static const f32 PCF_RADIUS = 3.0f;
static const int PCF_TAP_COUNT = 16;
// poisson disk samples in [-1, 1] range, gpu gems
static const vec2 POISSON_64[64] = {
vec2(0.1303, -0.4774), vec2(-0.0790, 0.1597), vec2(-0.2146, -0.7675), vec2(0.5641, 0.2770),
vec2(-0.8458, 0.0653), vec2(-0.2290, 0.5203), vec2(0.7014, -0.2814), vec2(-0.5949, -0.2615),
vec2(0.0567, 0.8423), vec2(-0.4671, -0.5259), vec2(0.9188, 0.1313), vec2(0.3152, -0.1929),
vec2(-0.6835, 0.3073), vec2(0.3916, -0.6913), vec2(-0.2006, -0.1625), vec2(0.8501, -0.4715),
vec2(-0.3950, 0.7455), vec2(0.1216, -0.0897), vec2(0.5703, 0.5782), vec2(-0.9340, -0.2388),
vec2(-0.0417, -0.4026), vec2(0.4583, -0.4611), vec2(0.1635, 0.4569), vec2(-0.5940, 0.5467),
vec2(-0.5231, -0.7638), vec2(-0.1682, 0.9132), vec2(0.7403, 0.4275), vec2(0.2742, -0.8772),
vec2(0.3046, 0.7118), vec2(-0.3307, -0.3606), vec2(0.0315, -0.6573), vec2(0.6764, -0.6494),
vec2(-0.0946, 0.3509), vec2(-0.7726, -0.4933), vec2(-0.7603, 0.6081), vec2(-0.4377, 0.0655),
vec2(0.0247, -0.2155), vec2(0.8682, -0.2073), vec2(0.4264, 0.1474), vec2(-0.2869, -0.0960),
vec2(-0.6156, -0.0669), vec2(-0.1416, -0.8762), vec2(0.4938, 0.8424), vec2(-0.8577, -0.3580),
vec2(0.9839, 0.0778), vec2(0.0538, 0.6073), vec2(0.5629, -0.1489), vec2(0.1398, -0.5184),
vec2(-0.3444, 0.2341), vec2(-0.4997, 0.4327), vec2(-0.0025, 0.9896), vec2(0.7817, -0.0236),
vec2(-0.2096, 0.6826), vec2(0.2251, -0.3476), vec2(0.6264, 0.7023), vec2(0.0367, -0.0060),
vec2(-0.9582, 0.1479), vec2(-0.6141, 0.7459), vec2(0.3188, 0.4669), vec2(0.4180, -0.3110),
vec2(-0.4167, -0.6154), vec2(-0.0996, -0.0930), vec2(0.8276, 0.2998), vec2(-0.7435, -0.6373),
};
f32 hash2(vec2 p) {
return frac(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}
u32 select_cascade_aabb(vec3 worldPos, vec3 camera_pos, ShadowCascadesGPU all_cascades) {
vec3 relPos = worldPos - camera_pos;
u32 cascade_idx = all_cascades.cascade_count - 1;
for (u32 i = 0; i < all_cascades.cascade_count; ++i) {
vec3 ls_pos = (vec4(relPos, 1.0) * all_cascades.cascade[i].view).xyz;
if (all(ls_pos >= all_cascades.cascade[i].caster_min_ls) &&
all(ls_pos <= all_cascades.cascade[i].caster_max_ls)) {
cascade_idx = i;
break;
}
}
return cascade_idx;
}
f32 sample_cascade_at(
vec3 worldPos,
vec3 normal,
f32 NdotL,
vec3 camera_pos,
vec2 screen_pos,
ShadowCascadeGPU cascade,
SamplerComparisonState cmp_samp
) {
vec3 relPos = worldPos - camera_pos;
f32 NdotL_clamped = saturate(NdotL);
f32 grazing = sqrt(max(0.0f, 1.0f - NdotL_clamped * NdotL_clamped));
f32 normal_bias = NORMAL_BIAS + grazing * NORMAL_BIAS_SLOPE;
vec3 shadow_pos = relPos + normalize(normal) * (cascade.texel_size * normal_bias);
vec4 shadow_clip = vec4(shadow_pos, 1.0f) * cascade.view_proj;
vec2 shadow_uv = vec2(shadow_clip.x, -shadow_clip.y) * 0.5f + 0.5f;
f32 offset_depth = shadow_clip.z;
if (shadow_uv.x < 0.0f || shadow_uv.x > 1.0f || shadow_uv.y < 0.0f || shadow_uv.y > 1.0f || offset_depth < 0.0f ||
offset_depth > 1.0f) {
return 1.0f;
}
u32 width;
u32 height;
Texture2D shadow_map = cascade.img;
shadow_map.GetDimensions(width, height);
vec2 texel_uv = 1.0f / vec2(width, height);
f32 angle = hash2(screen_pos) * 6.2832f;
f32 cos_a = cos(angle);
f32 sin_a = sin(angle);
f32 shadow = 0.0f;
[unroll]
for (int i = 0; i < PCF_TAP_COUNT; ++i) {
vec2 rotated;
rotated.x = POISSON_64[i].x * cos_a - POISSON_64[i].y * sin_a;
rotated.y = POISSON_64[i].x * sin_a + POISSON_64[i].y * cos_a;
vec2 offset = rotated * texel_uv * PCF_RADIUS;
shadow += shadow_map.SampleCmpLevelZero(cmp_samp, shadow_uv + offset, offset_depth);
}
return shadow / (f32)PCF_TAP_COUNT;
}
f32 sample_shadow(
vec3 world_pos,
vec3 normal,
vec2 screen_pos,
f32 NdotL,
vec3 camera_pos,
ShadowCascadesGPU cascades,
SamplerComparisonState cmp_samp
) {
u32 cascade_idx = select_cascade_aabb(world_pos, camera_pos, cascades);
ShadowCascadeGPU selected_cascade = cascades.cascade[cascade_idx];
f32 shadow = sample_cascade_at(world_pos, normal, NdotL, camera_pos, screen_pos, selected_cascade, cmp_samp);
// TODO: should do a cheaper path for this rather than just the same sampling
// blend with the next cascade near the current cascade bounds
// u32 next_idx = min(cascade_idx + 1, cascades.cascade_count - 1);
// if (next_idx != cascade_idx) {
// vec3 relPos = worldPos - camera_pos;
// vec3 ls_pos = (vec4(relPos, 1.0) * selected_cascade.view).xyz;
// vec3 edge_dist = min(ls_pos - selected_cascade.caster_min_ls, selected_cascade.caster_max_ls - ls_pos);
// f32 min_edge = min(edge_dist.x, min(edge_dist.y, edge_dist.z));
// f32 blend = 1.0f - saturate(min_edge / (selected_cascade.texel_size * 4.0f));
//
// if (blend > 0.0f) {
// f32 shadow_next = sample_cascade_at(worldPos, normal, NdotL, worldPos, screen_pos, selected_cascade);
// shadow = lerp(shadow, shadow_next, blend);
// }
// }
return shadow;
}