Skip to content

Commit a7c1cd7

Browse files
committed
Add shadow contact hardening via PCSS blocker search
The shadow-mapped path's penumbra width was a fixed per-cascade value (shadow_smoothness_scale()'s ceiling), so every occluder blurred its shadow by the same amount regardless of how close it actually was to the receiver -- unlike the raytraced path, which gets that for free from traceShadowRayCone()'s real occluder distance. Close that gap with PCSS: search nearby texels for occluders, then scale the filter radius by how far away they were. The blocker search (pcssBlockerSearch() in shadows.sdr) needs raw, uncompared depth reads, which the existing PCF sampler can't do -- it's bound with a compare mode for the hardware shadow-compare instructions the filtered sampling itself relies on. So shadow maps now get a second sampler, shadow_map_raw, bound to the same texture with compare mode off; on OpenGL that needs a second sampler object and GL 3.3 (shadow_contact_hardening_supported()), Vulkan can always bind it. Where the raw sampler isn't available, or the new Graphics.ShadowContactHardening option is off, shadows_start_render() sets Shadow_penumbra_scale[cascade] to -1 as a sentinel and pcssPenumbraRadius() (shadows.sdr) falls straight back to the fixed radius, bit-identical to the pre-contact-hardening behavior and skipping the extra texture reads. When it is on, shadows_start_render() computes each cascade's penumbra scale from its own frustum extents and the sun's tanθ; the shader turns that plus the blocker search's average depth into a per-pixel radius, clamped to [1 texel, the old fixed ceiling]. A blocker search that finds nothing doesn't fall back to "no shadow" -- a sparse 8-tap search can miss a thin occluder, so it instead runs the full filter at the hardest (1-texel) radius, trading a touch of extra blur for not popping a real shadow to nothing. Drops RT_SHADOW_SUN_SIZE_CALIBRATION accordingly: that constant existed only to fake a blocker-distance-driven penumbra on the shadow-mapped path by pre-scaling the raytraced cone to match it, and the shadow-mapped path now has a real blocker search of its own, so the raytraced cone goes back to its physically correct size.
1 parent 311f15a commit a7c1cd7

27 files changed

Lines changed: 436 additions & 111 deletions

code/def_files/data/effects/deferred-f.sdr

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ layout(set = 1, binding = 1) uniform sampler2D sTextures[16];
1818
#define SpecBuffer sTextures[3]
1919

2020
layout(set = 0, binding = 2) uniform sampler2DArrayShadow shadow_map;
21+
layout(set = 0, binding = 7) uniform sampler2DArray shadow_map_raw;
2122

2223
#ifdef ENV_MAP
2324
layout(set = 0, binding = 3) uniform samplerCube sEnvmap;
@@ -33,6 +34,7 @@ uniform sampler2D NormalBuffer;
3334
uniform sampler2D PositionBuffer;
3435
uniform sampler2D SpecBuffer;
3536
uniform sampler2DArrayShadow shadow_map;
37+
uniform sampler2DArray shadow_map_raw;
3638

3739
#ifdef ENV_MAP
3840
uniform samplerCube sEnvmap;
@@ -72,6 +74,7 @@ uniform shadowCascadeParams {
7274
mat4 shadow_proj_matrix[NUM_SHADOW_CASCADES];
7375
vec4 cascade_distances[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
7476
vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
77+
vec4 penumbra_scale[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
7578
};
7679

7780
#ifdef VULKAN
@@ -331,11 +334,10 @@ void main()
331334

332335
float rtShadowBias = computeRtShadowBias(length(position), rtShadowBiasMin, rtShadowBiasMax);
333336
// Penumbra cone: for directional lights sourceRadius IS the tangent of the
334-
// sun's angular radius ($SunAngularSize), scaled to match the shadow-mapped
335-
// look (see RT_SHADOW_SUN_SIZE_CALIBRATION); for local lights the subtended
337+
// sun's angular radius ($SunAngularSize); for local lights the subtended
336338
// half-angle follows from the physical source radius and the light distance.
337339
float coneTan = (lightType == LT_DIRECTIONAL)
338-
? sourceRadius * RT_SHADOW_SUN_SIZE_CALIBRATION
340+
? sourceRadius
339341
: sourceRadius / max(lightDist, 1e-3);
340342
attenuation *= traceShadowRayCone(shadow_tlas, worldPos, worldNormal, worldLightDir, lightDist,
341343
rtShadowBias, coneTan, rtShadowSampleCount, gl_FragCoord.xy);
@@ -346,7 +348,7 @@ void main()
346348
fragShadowUV[i] = transformToShadowMap(shadow_proj_matrix[i], i, fragShadowPos);
347349
}
348350

349-
attenuation *= getShadowValue(shadow_map, -position.z, fragShadowUV, cascade_distances, smoothness_factors, cascade_offset, cascade_count);
351+
attenuation *= getShadowValue(shadow_map, shadow_map_raw, -position.z, fragShadowUV, cascade_distances, smoothness_factors, penumbra_scale, cascade_offset, cascade_count);
350352
#endif
351353
}
352354

code/def_files/data/effects/main-f.sdr

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ uniform shadowCascadeParams {
110110
mat4 shadow_proj_matrix[NUM_SHADOW_CASCADES];
111111
vec4 cascade_distances[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
112112
vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
113+
vec4 penumbra_scale[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
113114
};
114115

115116
#ifdef VULKAN
@@ -122,6 +123,7 @@ layout(set = 1, binding = 1) uniform sampler2DArray materialTextures[16];
122123
#define sMiscmap materialTextures[6]
123124

124125
layout(set = 0, binding = 2) uniform sampler2DArrayShadow shadow_map;
126+
layout(set = 0, binding = 7) uniform sampler2DArray shadow_map_raw;
125127
layout(set = 1, binding = 5) uniform sampler2D sFramebuffer;
126128

127129
#ifdef MODEL_SDR_FLAG_RT_SHADOWS
@@ -168,6 +170,7 @@ uniform sampler2DArray sMiscmap;
168170
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_MISC
169171
#prereplace IF_FLAG_COMPILED MODEL_SDR_FLAG_SHADOWS
170172
uniform sampler2DArrayShadow shadow_map;
173+
uniform sampler2DArray shadow_map_raw;
171174
#prereplace ENDIF_FLAG_COMPILED MODEL_SDR_FLAG_SHADOWS
172175

173176
in VertexOutput {
@@ -253,10 +256,9 @@ vec3 CalculateLighting(vec3 normal, vec3 diffuseMaterial, vec3 specularMaterial,
253256
vec3 worldSunDir = normalize((invView * vec4(lights[i].position.xyz, 0.0)).xyz);
254257
float rtShadowBias = computeRtShadowBias(length(vertIn.position.xyz), rtShadowBiasMin, rtShadowBiasMax);
255258
// For directional lights ml_sourceRadius is the tangent of the sun's
256-
// angular radius ($SunAngularSize) -- 0 keeps hard shadows. Scaled to match
257-
// the shadow-mapped look, see RT_SHADOW_SUN_SIZE_CALIBRATION.
259+
// angular radius ($SunAngularSize) -- 0 keeps hard shadows.
258260
shadow = traceShadowRayCone(shadow_tlas, worldPos, worldNormal, worldSunDir, RT_SHADOW_MAX_DISTANCE,
259-
rtShadowBias, lights[i].ml_sourceRadius * RT_SHADOW_SUN_SIZE_CALIBRATION,
261+
rtShadowBias, lights[i].ml_sourceRadius,
260262
rtShadowSampleCount, gl_FragCoord.xy);
261263
++shadowedDirectionalCount;
262264
} else {
@@ -419,7 +421,7 @@ void main()
419421
// up to MAX_RT_SHADOW_LIGHTS) inside CalculateLighting()'s loop instead --
420422
// this single shared `shadow` value only matters for the CSM fallback below.
421423
#ifndef MODEL_SDR_FLAG_RT_SHADOWS
422-
shadow = getShadowValue(shadow_map, -vertIn.position.z, vertIn.shadowUV, cascade_distances, smoothness_factors, cascade_offset, cascade_count);
424+
shadow = getShadowValue(shadow_map, shadow_map_raw, -vertIn.position.z, vertIn.shadowUV, cascade_distances, smoothness_factors, penumbra_scale, cascade_offset, cascade_count);
423425
#endif
424426
#prereplace ENDIF_FLAG //MODEL_SDR_FLAG_SHADOWS
425427
baseColor.rgb = CalculateLighting(normal, baseColor.rgb, specColor.rgb, glossData, fresnelFactor, shadow, aoFactors.x);

code/def_files/data/effects/main-v.sdr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ uniform shadowCascadeParams {
124124
mat4 shadow_proj_matrix[NUM_SHADOW_CASCADES];
125125
vec4 cascade_distances[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
126126
vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
127+
vec4 penumbra_scale[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
127128
};
128129

129130
#ifdef VULKAN

code/def_files/data/effects/shadow_map-g.sdr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ layout(std140) uniform shadowCascadeParams {
2424
mat4 shadow_proj_matrix[NUM_SHADOW_CASCADES];
2525
vec4 cascade_distances[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
2626
vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
27+
vec4 penumbra_scale[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
2728
};
2829

2930
in VertexOutput {

code/def_files/data/effects/shadow_map-v.sdr

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ uniform shadowCascadeParams {
5151
mat4 shadow_proj_matrix[NUM_SHADOW_CASCADES];
5252
vec4 cascade_distances[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
5353
vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
54+
vec4 penumbra_scale[(NUM_SHADOW_CASCADES + 4 - 1) / 4];
5455
};
5556

5657
#ifdef VULKAN

code/def_files/data/effects/shadows.sdr

Lines changed: 122 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,46 @@
66
#define SAMPLES_PCSS 1
77
#endif
88

9-
float samplePoissonPCSS(sampler2DArrayShadow shadow_map, vec4 shadowUV, float maxUVOffset)
10-
{
9+
// Full 16-tap Poisson disc. Unconditional (independent of SMOOTH_PCSS) because it has three
10+
// consumers with different needs: samplePoissonPCSS() takes the first SAMPLES_PCSS of these,
11+
// pcssBlockerSearch() always wants the first 8 regardless of SAMPLES_PCSS (a blocker search
12+
// only needs a coarse estimate), and the raytraced cone/AO sampling further below always
13+
// wants the full spread. Keeping one table means none of those three can end up indexing
14+
// past a table sized for a different consumer.
15+
const vec2 poissonDisc16[16] = vec2[](
16+
vec2(-0.76275, -0.3432573),
17+
vec2(-0.5226235, -0.8277544),
18+
vec2(-0.3780261, 0.01528688),
19+
vec2(-0.7742821, 0.4245702),
20+
vec2(0.04196143, -0.02622231),
21+
vec2(-0.2974772, -0.4722782),
22+
vec2(-0.516093, 0.71495),
23+
vec2(-0.3257416, 0.3910343),
24+
vec2(0.2705966, 0.6670476),
25+
vec2(0.4918377, 0.1853267),
26+
vec2(0.4428544, -0.6251478),
27+
vec2(-0.09204347, 0.9267113),
28+
vec2(0.391505, -0.2558275),
29+
vec2(0.05605913, -0.7570801),
30+
vec2(0.81772, -0.02475523),
31+
vec2(0.6890262, 0.5191521)
32+
);
33+
34+
// Low-quality raster PCSS fallback: a single centered sample (i.e. no offset at all) rather
35+
// than poissonDisc16[0], which is just one arbitrary tap among sixteen and no better centered
36+
// than any other.
37+
const vec2 poissonDiscCenter[1] = vec2[](vec2(0.0, 0.0));
38+
39+
// samplePoissonPCSS()'s tap table: the full disc when taking all SAMPLES_PCSS == 16 of them,
40+
// or the single centered sample when SAMPLES_PCSS == 1.
1141
#if SMOOTH_PCSS
12-
vec2 poissonDisc[16] = vec2[](
13-
vec2(-0.76275, -0.3432573),
14-
vec2(-0.5226235, -0.8277544),
15-
vec2(-0.3780261, 0.01528688),
16-
vec2(-0.7742821, 0.4245702),
17-
vec2(0.04196143, -0.02622231),
18-
vec2(-0.2974772, -0.4722782),
19-
vec2(-0.516093, 0.71495),
20-
vec2(-0.3257416, 0.3910343),
21-
vec2(0.2705966, 0.6670476),
22-
vec2(0.4918377, 0.1853267),
23-
vec2(0.4428544, -0.6251478),
24-
vec2(-0.09204347, 0.9267113),
25-
vec2(0.391505, -0.2558275),
26-
vec2(0.05605913, -0.7570801),
27-
vec2(0.81772, -0.02475523),
28-
vec2(0.6890262, 0.5191521)
29-
);
42+
#define poissonDisc poissonDisc16
3043
#else
31-
vec2 poissonDisc[1] = vec2[](
32-
vec2(0.0,0.0)
33-
);
44+
#define poissonDisc poissonDiscCenter
3445
#endif
3546

47+
float samplePoissonPCSS(sampler2DArrayShadow shadow_map, vec4 shadowUV, float maxUVOffset)
48+
{
3649
float visibility = 0.0f;
3750
for (int i=0; i<SAMPLES_PCSS; i++) {
3851
vec4 uv = shadowUV;
@@ -42,8 +55,80 @@ float samplePoissonPCSS(sampler2DArrayShadow shadow_map, vec4 shadowUV, float ma
4255
return visibility * (1.0f/float(SAMPLES_PCSS));
4356
}
4457

45-
float getShadowValue(sampler2DArrayShadow shadow_map, float depth, vec4 shadowUV[NUM_SHADOW_CASCADES],
58+
// PCSS blocker search: raw (non-compare) depth taps within searchRadiusUV, using the first 8
59+
// of poissonDisc16 (always the full-spread table, not samplePoissonPCSS()'s poissonDisc --
60+
// a blocker search only needs a coarse estimate, not the final filtered result, but it needs
61+
// that estimate regardless of how many taps the raster PCSS path itself is set to take).
62+
// "Blocker" = stored < receiverDepth, matching the GL_LEQUAL/eLessOrEqual compare convention
63+
// the hardware PCF sampler already uses (visible iff Dref <= stored) -- see the compare-sampler
64+
// setup in gropengltnl.cpp / VulkanPostProcessingShadow.cpp.
65+
// A negative return means "no blockers found", which the average blocker depth (always >= 0)
66+
// can't otherwise be distinguished from "found some at depth 0".
67+
float pcssBlockerSearch(sampler2DArray shadow_map_raw, vec4 shadowUV, float searchRadiusUV)
68+
{
69+
float sum = 0.0;
70+
int blockerCount = 0;
71+
for (int i = 0; i < 8; i++) {
72+
vec2 uv = shadowUV.xy + poissonDisc16[i] * searchRadiusUV;
73+
float sampled = texture(shadow_map_raw, vec3(uv, shadowUV.z)).r;
74+
if (sampled < shadowUV.w) {
75+
sum += sampled;
76+
blockerCount++;
77+
}
78+
}
79+
return blockerCount > 0 ? (sum / float(blockerCount)) : -1.0;
80+
}
81+
82+
// Computes the PCSS penumbra radius for one cascade sample: a blocker search followed by
83+
// the orthographic-light penumbra formula (see shadow_smoothness_scale()'s doc comment in
84+
// shadows.cpp for the derivation), clamped to [1 texel, smoothnessCeiling]. penumbraScale is
85+
// Shadow_penumbra_scale[cascade] -- a negative value is the "unsupported or user-disabled"
86+
// sentinel (see shadow_contact_hardening_enabled() in shadows.cpp), in which case this
87+
// returns smoothnessCeiling immediately: bit-identical to the fixed-width behavior from
88+
// before contact hardening existed, and skips the blocker search's extra texture reads.
89+
float pcssPenumbraRadius(sampler2DArrayShadow shadow_map, sampler2DArray shadow_map_raw,
90+
vec4 shadowUV, float penumbraScale, float smoothnessCeiling)
91+
{
92+
float texelFloor = 1.0 / float(textureSize(shadow_map, 0).x);
93+
94+
if (penumbraScale < 0.0) {
95+
return smoothnessCeiling;
96+
}
97+
98+
float avgBlockerDepth = pcssBlockerSearch(shadow_map_raw, shadowUV, smoothnessCeiling);
99+
if (avgBlockerDepth < 0.0) {
100+
// The sparse 8-tap search found nothing, but that doesn't prove there's no thin
101+
// occluder nearby -- run the full PCF at the hardest radius rather than assuming
102+
// fully lit, so a missed thin blocker still reads as an edge instead of popping to
103+
// no shadow at all.
104+
return texelFloor;
105+
}
106+
107+
float deltaDepth = max(shadowUV.w - avgBlockerDepth, 0.0);
108+
return clamp(deltaDepth * penumbraScale, texelFloor, smoothnessCeiling);
109+
}
110+
111+
// PCSS-filtered shadow visibility for a single cascade: looks up its penumbra radius (see
112+
// pcssPenumbraRadius()) and samples the shadow map at that radius. Factored out of
113+
// getShadowValue() below so the cascade-index unpacking (v/i from cascade/4, cascade%4) and
114+
// the penumbra-radius lookup each appear once, whichever of getShadowValue()'s two return
115+
// paths runs.
116+
float shadowVisibilityForCascade(sampler2DArrayShadow shadow_map, sampler2DArray shadow_map_raw,
117+
vec4 shadowUV, int cascade, vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4],
118+
vec4 penumbra_scale[(NUM_SHADOW_CASCADES + 4 - 1) / 4])
119+
{
120+
int v = cascade / 4;
121+
int i = cascade % 4;
122+
123+
float radius = pcssPenumbraRadius(shadow_map, shadow_map_raw, shadowUV,
124+
penumbra_scale[v][i], smoothness_factors[v][i]);
125+
126+
return samplePoissonPCSS(shadow_map, shadowUV, radius);
127+
}
128+
129+
float getShadowValue(sampler2DArrayShadow shadow_map, sampler2DArray shadow_map_raw, float depth, vec4 shadowUV[NUM_SHADOW_CASCADES],
46130
vec4 cascade_distances[(NUM_SHADOW_CASCADES + 4 - 1) / 4], vec4 smoothness_factors[(NUM_SHADOW_CASCADES + 4 - 1) / 4],
131+
vec4 penumbra_scale[(NUM_SHADOW_CASCADES + 4 - 1) / 4],
47132
int cascade_offset, int cascade_count)
48133
{
49134
int cascade = cascade_offset + cascade_count;
@@ -52,19 +137,19 @@ float getShadowValue(sampler2DArrayShadow shadow_map, float depth, vec4 shadowUV
52137
}
53138
if (cascade >= cascade_offset + cascade_count || cascade < cascade_offset) return 1.0;
54139

55-
int cascade_v = cascade / 4;
56-
int cascade_i = cascade % 4;
57-
58140
float cascade_start = (cascade > cascade_offset) ? cascade_distances[(cascade - 1) / 4][(cascade - 1) % 4] : 0.0;
59-
float cascade_end = cascade_distances[cascade_v][cascade_i];
141+
float cascade_end = cascade_distances[cascade / 4][cascade % 4];
60142
float dist_threshold = (cascade_end - cascade_start) * 0.2;
61143

62144
if (cascade_end - dist_threshold > depth || cascade >= cascade_offset + cascade_count - 1)
63-
return samplePoissonPCSS(shadow_map, shadowUV[cascade], smoothness_factors[cascade_v][cascade_i]);
145+
return shadowVisibilityForCascade(shadow_map, shadow_map_raw, shadowUV[cascade], cascade,
146+
smoothness_factors, penumbra_scale);
64147

65148
return mix(
66-
samplePoissonPCSS(shadow_map, shadowUV[cascade], smoothness_factors[cascade_v][cascade_i]),
67-
samplePoissonPCSS(shadow_map, shadowUV[cascade + 1], smoothness_factors[(cascade + 1) / 4][(cascade + 1) % 4]),
149+
shadowVisibilityForCascade(shadow_map, shadow_map_raw, shadowUV[cascade], cascade,
150+
smoothness_factors, penumbra_scale),
151+
shadowVisibilityForCascade(shadow_map, shadow_map_raw, shadowUV[cascade + 1], cascade + 1,
152+
smoothness_factors, penumbra_scale),
68153
smoothstep(cascade_end - dist_threshold, cascade_end, depth));
69154
}
70155
// Raytraced shadow test via inline ray query against the shadow TLAS. Only
@@ -127,50 +212,13 @@ float traceShadowRay(accelerationStructureEXT tlas, vec3 worldPos, vec3 worldNor
127212
return (rayQueryGetIntersectionTypeEXT(rq, true) == gl_RayQueryCommittedIntersectionNoneEXT) ? 1.0 : 0.0;
128213
}
129214

130-
// Penumbra/AO sampling: at most this many rays per fragment per light, matching
131-
// the disc pattern size below. rtShadowSampleCount/rtaoSampleCount
132-
// (shadowCascadeParams) are clamped against it, so a bad uniform value can't
215+
// Penumbra/AO sampling: at most this many rays per fragment per light, matching the size of
216+
// poissonDisc16 above (the RT cone/AO sampling below always wants the full 16-tap spread,
217+
// independent of SAMPLES_PCSS/SMOOTH_PCSS -- see the comment on poissonDisc16). rtShadowSampleCount/
218+
// rtaoSampleCount (shadowCascadeParams) are clamped against it, so a bad uniform value can't
133219
// index out of the pattern.
134220
#define RT_SHADOW_MAX_SAMPLES 16
135221

136-
// Calibration factor applied to a *sun's* angular radius before it becomes a penumbra
137-
// cone, so that a given $SunAngularSize: reads as roughly the same softness whichever
138-
// shadow method is active. Local lights don't get it -- their penumbra is already
139-
// consistent, and they have no shadow-mapped counterpart to match.
140-
//
141-
// It exists because the two methods disagree about occluder distance. traceShadowRayCone()
142-
// uses the real one, so a Sol-sized sun gives a penumbra of only ~0.1 world units at the
143-
// 20-unit separations typical of a fighter shadowing itself. The shadow map has no blocker
144-
// search and can't: shadow_smoothness_scale() in shadows.cpp calibrates the tabled
145-
// $Shadow Smoothness Factor: values against a blocker distance pinned to the cascade's own
146-
// extent, which is a couple of hundred units out for the near cascade -- an order of
147-
// magnitude more, and a correspondingly wider penumbra. Scaling the ray cone up meets it
148-
// there.
149-
//
150-
// So this deliberately trades physical accuracy for cross-method consistency, and it is the
151-
// number to revisit if the shadow map ever grows a real blocker search -- at that point both
152-
// methods have a true occluder distance and the factor should go back to 1.
153-
const float RT_SHADOW_SUN_SIZE_CALIBRATION = 10.0;
154-
155-
const vec2 rtPoissonDisc[RT_SHADOW_MAX_SAMPLES] = vec2[](
156-
vec2(-0.76275, -0.3432573),
157-
vec2(-0.5226235, -0.8277544),
158-
vec2(-0.3780261, 0.01528688),
159-
vec2(-0.7742821, 0.4245702),
160-
vec2(0.04196143, -0.02622231),
161-
vec2(-0.2974772, -0.4722782),
162-
vec2(-0.516093, 0.71495),
163-
vec2(-0.3257416, 0.3910343),
164-
vec2(0.2705966, 0.6670476),
165-
vec2(0.4918377, 0.1853267),
166-
vec2(0.4428544, -0.6251478),
167-
vec2(-0.09204347, 0.9267113),
168-
vec2(0.391505, -0.2558275),
169-
vec2(0.05605913, -0.7570801),
170-
vec2(0.81772, -0.02475523),
171-
vec2(0.6890262, 0.5191521)
172-
);
173-
174222
// Orthonormal basis spanning the plane perpendicular to axis (a unit vector).
175223
void rtBuildOnb(vec3 axis, out vec3 tangent, out vec3 bitangent)
176224
{
@@ -225,7 +273,7 @@ float traceShadowRayCone(accelerationStructureEXT tlas, vec3 worldPos, vec3 worl
225273
int samples = min(sampleCount, RT_SHADOW_MAX_SAMPLES);
226274
float visibility = 0.0;
227275
for (int i = 0; i < samples; ++i) {
228-
vec2 p = rtPoissonDisc[i];
276+
vec2 p = poissonDisc16[i];
229277
p = vec2(p.x * cosR - p.y * sinR, p.x * sinR + p.y * cosR);
230278
vec3 sampleDir = normalize(direction + (tangent * p.x + bitangent * p.y) * coneHalfAngleTan);
231279

@@ -277,7 +325,7 @@ float traceAmbientOcclusion(accelerationStructureEXT tlas, vec3 worldPos, vec3 w
277325
int samples = min(sampleCount, RT_SHADOW_MAX_SAMPLES);
278326
float occlusion = 0.0;
279327
for (int i = 0; i < samples; ++i) {
280-
vec2 p = rtPoissonDisc[i];
328+
vec2 p = poissonDisc16[i];
281329
p = vec2(p.x * cosR - p.y * sinR, p.x * sinR + p.y * cosR);
282330
vec3 sampleDir = normalize(tangent * p.x + bitangent * p.y
283331
+ worldNormal * sqrt(max(1.0 - dot(p, p), 0.0)));

code/graphics/2d.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ gr_capability_def gr_capabilities[] = {
8383
GR_CAPABILITY_ENTRY(INSTANCED_RENDERING),
8484
GR_CAPABILITY_ENTRY(FAST_SHADOWS),
8585
GR_CAPABILITY_ENTRY(RAYTRACED_SHADOWS),
86+
GR_CAPABILITY_ENTRY(SHADOW_CONTACT_HARDENING),
8687
};
8788

8889
const size_t gr_capabilities_num = sizeof(gr_capabilities) / sizeof(gr_capabilities[0]);

code/graphics/2d.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,12 @@ enum class gr_capability {
371371
CAPABILITY_INSTANCED_RENDERING,
372372
CAPABILITY_FAST_SHADOWS,
373373
CAPABILITY_QUERIES_REUSABLE,
374-
CAPABILITY_RAYTRACED_SHADOWS
374+
CAPABILITY_RAYTRACED_SHADOWS,
375+
// A second, non-compare sampler bound to the shadow map for raw depth reads, needed by
376+
// the shadow-map PCSS blocker search. Vulkan always has this (samplers are independent
377+
// of images there); OpenGL needs GL 3.3 (glGenSamplers/glBindSampler), since the compare mode is
378+
// otherwise texture-object state shared by every sampler bound to that texture.
379+
CAPABILITY_SHADOW_CONTACT_HARDENING
375380
};
376381

377382
struct gr_capability_def {

0 commit comments

Comments
 (0)