Skip to content

Commit e3d8c6c

Browse files
Implement PCSS & Revert graphics level 8 (#5300)
* Avoid iterating appear animation node when it's invisible * Support Ghost Replay Info * Implement PCSS & Revert graphics level 8 * Add missing file * Order the effects & etc. * Use fixed kernel & tune shadow height & remove translation string * Improve shadow bias * Tune
1 parent dc467cf commit e3d8c6c

18 files changed

+446
-72
lines changed

data/gui/dialogs/custom_video_settings.stkgui

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<div layout="horizontal-row" proportion="1" height="fit">
3030
<label text="Shadows" I18N="Video settings"/>
3131
<spacer width="10" height="10"/>
32-
<gauge id="shadows" min_value="0" max_value="4" proportion="1"/>
32+
<gauge id="shadows" min_value="0" max_value="3" proportion="1"/>
3333
</div>
3434
</div>
3535

data/gui/dialogs/ghost_replay_info_dialog.stkgui

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
<spacer width="1%"/>
3535
<label proportion="1" id="compare-ghost-text" height="100%" text_align="left" I18N="Ghost replay info action" text="Compare to another ghost"/>
3636
</div>
37+
<bubble id="info" width="100%" proportion="1" word_wrap="true"/>
3738
</div>
3839

3940
<div width="95%" height="40%" align="center">

data/shaders/sunlightshadow.frag

+35-11
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ uniform float splitmax;
1313
uniform float shadow_res;
1414
uniform float overlap_proportion;
1515

16+
uniform vec3 box0;
17+
uniform vec3 box1;
18+
uniform vec3 box2;
19+
uniform vec3 box3;
20+
1621
uniform vec3 sundirection;
1722
uniform vec3 sun_color;
1823

@@ -32,12 +37,12 @@ out vec4 Spec;
3237
#stk_include "utils/SunMRP.frag"
3338

3439
// https://web.archive.org/web/20230210095515/http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1
35-
float getShadowFactor(vec3 pos, int index, float bias)
40+
float getShadowFactor(vec3 pos, int index)
3641
{
3742
vec4 shadowcoord = (u_shadow_projection_view_matrices[index] * u_inverse_view_matrix * vec4(pos, 1.0));
3843
shadowcoord.xy /= shadowcoord.w;
3944
vec2 shadowtexcoord = shadowcoord.xy * 0.5 + 0.5;
40-
float d = .5 * shadowcoord.z + .5 - bias;
45+
float d = .5 * shadowcoord.z + .5;
4146

4247
vec2 uv = shadowtexcoord * shadow_res;
4348
vec2 base_uv = floor(uv + 0.5);
@@ -83,13 +88,26 @@ float blend_start(float x) {
8388
return x * (1.0 - overlap_proportion);
8489
}
8590

91+
vec3 getXcYcZc(int x, int y, float zC)
92+
{
93+
// We use perspective symetric projection matrix hence P(0,2) = P(1, 2) = 0
94+
float xC= (2. * (float(x)) / u_screen.x - 1.) * zC / u_projection_matrix[0][0];
95+
float yC= (2. * (float(y)) / u_screen.y - 1.) * zC / u_projection_matrix[1][1];
96+
return vec3(xC, yC, zC);
97+
}
98+
8699
void main() {
87100
vec2 uv = gl_FragCoord.xy / u_screen;
88101
float z = texture(dtex, uv).x;
89102
vec4 xpos = getPosFromUVDepth(vec3(uv, z), u_inverse_projection_matrix);
90103

104+
// get the normal of current fragment
105+
vec3 ddx = dFdx(xpos.xyz);
106+
vec3 ddy = dFdy(xpos.xyz);
107+
vec3 geo_norm = normalize(cross(ddy, ddx));
108+
91109
vec3 norm = DecodeNormal(texture(ntex, uv).xy);
92-
float roughness =texture(ntex, uv).z;
110+
float roughness = texture(ntex, uv).z;
93111
vec3 eyedir = -normalize(xpos.xyz);
94112

95113
vec3 Lightdir = SunMRP(norm, eyedir);
@@ -100,24 +118,30 @@ void main() {
100118

101119
// Shadows
102120
float factor;
103-
float bias = max(1.0 - NdotL, .2) / shadow_res;
121+
vec3 lbias = 40. * Lightdir / shadow_res; // Scale with blur kernel size
122+
vec3 nbias = geo_norm * (1.0 - max(dot(-geo_norm, Lightdir), 0.)) / shadow_res;
123+
nbias -= Lightdir * dot(nbias, Lightdir); // Slope-scaled normal bias
124+
104125
if (xpos.z < split0) {
105-
factor = getShadowFactor(xpos.xyz, 0, bias);
126+
factor = getShadowFactor(xpos.xyz + lbias + nbias * max(box0.x, box0.y), 0);
106127
if (xpos.z > blend_start(split0)) {
107-
factor = mix(factor, getShadowFactor(xpos.xyz, 1, bias), (xpos.z - blend_start(split0)) / split0 / overlap_proportion);
128+
factor = mix(factor, getShadowFactor(xpos.xyz + lbias + nbias * max(box1.x, box1.y), 1),
129+
(xpos.z - blend_start(split0)) / split0 / overlap_proportion);
108130
}
109131
} else if (xpos.z < split1) {
110-
factor = getShadowFactor(xpos.xyz, 1, bias);
132+
factor = getShadowFactor(xpos.xyz + lbias + nbias * max(box1.x, box1.y), 1);
111133
if (xpos.z > blend_start(split1)) {
112-
factor = mix(factor, getShadowFactor(xpos.xyz, 2, bias), (xpos.z - blend_start(split1)) / split1 / overlap_proportion);
134+
factor = mix(factor, getShadowFactor(xpos.xyz + lbias + nbias * max(box2.x, box2.y), 2),
135+
(xpos.z - blend_start(split1)) / split1 / overlap_proportion);
113136
}
114137
} else if (xpos.z < split2) {
115-
factor = getShadowFactor(xpos.xyz, 2, bias);
138+
factor = getShadowFactor(xpos.xyz + lbias + nbias * max(box2.x, box2.y), 2);
116139
if (xpos.z > blend_start(split2)) {
117-
factor = mix(factor, getShadowFactor(xpos.xyz, 3, bias), (xpos.z - blend_start(split2)) / split2 / overlap_proportion);
140+
factor = mix(factor, getShadowFactor(xpos.xyz + lbias + nbias * max(box3.x, box3.y), 3),
141+
(xpos.z - blend_start(split2)) / split2 / overlap_proportion);
118142
}
119143
} else if (xpos.z < splitmax) {
120-
factor = getShadowFactor(xpos.xyz, 3, bias);
144+
factor = getShadowFactor(xpos.xyz + lbias + nbias * max(box3.x, box3.y), 3);
121145
if (xpos.z > blend_start(splitmax)) {
122146
factor = mix(factor, 1.0, (xpos.z - blend_start(splitmax)) / splitmax / overlap_proportion);
123147
}

data/shaders/sunlightshadowpcss.frag

+247
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
// Implementation from https://github.com/google/filament/blob/main/shaders/src/surface_shadowing.fs
2+
uniform sampler2D ntex;
3+
#if defined(GL_ES) && defined(GL_FRAGMENT_PRECISION_HIGH)
4+
uniform highp sampler2D dtex;
5+
#else
6+
uniform sampler2D dtex;
7+
#endif
8+
uniform sampler2DArray shadowtexdepth;
9+
uniform sampler2DArrayShadow shadowtex;
10+
11+
uniform float split0;
12+
uniform float split1;
13+
uniform float split2;
14+
uniform float splitmax;
15+
uniform float shadow_res;
16+
uniform float overlap_proportion;
17+
18+
uniform vec3 box0;
19+
uniform vec3 box1;
20+
uniform vec3 box2;
21+
uniform vec3 box3;
22+
23+
uniform vec3 sundirection;
24+
uniform vec3 sun_color;
25+
26+
in vec2 uv;
27+
#ifdef GL_ES
28+
layout (location = 0) out vec4 Diff;
29+
layout (location = 1) out vec4 Spec;
30+
#else
31+
out vec4 Diff;
32+
out vec4 Spec;
33+
#endif
34+
35+
#stk_include "utils/decodeNormal.frag"
36+
#stk_include "utils/SpecularBRDF.frag"
37+
#stk_include "utils/DiffuseBRDF.frag"
38+
#stk_include "utils/getPosFromUVDepth.frag"
39+
#stk_include "utils/SunMRP.frag"
40+
41+
// PCF with Vogel Disk Sampling
42+
// From https://drdesten.github.io/web/tools/vogel_disk/
43+
vec2 vogel_disk_16[16] = vec2[](
44+
vec2(0.18993645671348536, 0.027087114076591513),
45+
vec2(-0.21261242652069953, 0.23391293246949066),
46+
vec2(0.04771781344140756, -0.3666840644525993),
47+
vec2(0.297730981239584, 0.398259878229082),
48+
vec2(-0.509063425827436, -0.06528681462854097),
49+
vec2(0.507855152944665, -0.2875976005206389),
50+
vec2(-0.15230616564632418, 0.6426121151781916),
51+
vec2(-0.30240170651828074, -0.5805072900736001),
52+
vec2(0.6978019230005561, 0.2771173334141519),
53+
vec2(-0.6990963248129052, 0.3210960724922725),
54+
vec2(0.3565142601623699, -0.7066415061851589),
55+
vec2(0.266890002328106, 0.8360191043249159),
56+
vec2(-0.7515861305520581, -0.41609876195815027),
57+
vec2(0.9102937449894895, -0.17014527555321657),
58+
vec2(-0.5343471434373126, 0.8058593459499529),
59+
vec2(-0.1133270115046468, -0.9490025827627441)
60+
);
61+
62+
// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_standard_multisample_quality_levels?redirectedfrom=MSDN
63+
vec2 sample_point_pos[8] = vec2[](
64+
vec2( 0.125, -0.375),
65+
vec2(-0.125, 0.375),
66+
vec2( 0.625, 0.125),
67+
vec2(-0.375, -0.625),
68+
vec2(-0.625, 0.625),
69+
vec2(-0.875, -0.125),
70+
vec2( 0.375, 0.875),
71+
vec2( 0.875, -0.875)
72+
);
73+
74+
float interleavedGradientNoise(vec2 w)
75+
{
76+
vec3 m = vec3(0.06711056, 0.00583715, 52.9829189);
77+
return fract(m.z * fract(dot(w, m.xy)));
78+
}
79+
80+
vec2 computeReceiverPlaneDepthBias(vec3 position)
81+
{
82+
// see: GDC '06: Shadow Mapping: GPU-based Tips and Techniques
83+
// Chain rule to compute dz/du and dz/dv
84+
// |dz/du| |du/dx du/dy|^-T |dz/dx|
85+
// |dz/dv| = |dv/dx dv/dy| * |dz/dy|
86+
vec3 duvz_dx = dFdx(position);
87+
vec3 duvz_dy = dFdy(position);
88+
vec2 dz_duv = inverse(transpose(mat2(duvz_dx.xy, duvz_dy.xy))) * vec2(duvz_dx.z, duvz_dy.z);
89+
return dz_duv;
90+
}
91+
92+
mat2 getRandomRotationMatrix(vec2 fragCoord)
93+
{
94+
// rotate the vogel disk randomly
95+
float randomAngle = interleavedGradientNoise(fragCoord) * 2.0 * 3.14159;
96+
vec2 randomBase = vec2(cos(randomAngle), sin(randomAngle));
97+
mat2 R = mat2(randomBase.x, randomBase.y, -randomBase.y, randomBase.x);
98+
return R;
99+
}
100+
101+
void blockerSearchAndFilter(out float occludedCount, out float z_occSum,
102+
vec2 uv, float z_rec, uint layer, vec2 filterRadii, vec2 dz_duv)
103+
{
104+
// Make sure no light leaking
105+
float z_occ = texture(shadowtexdepth, vec3(uv, float(layer))).r;
106+
float dz = z_rec - z_occ;
107+
float occluded = 0.01 * step(0.5 / shadow_res, dz);
108+
occludedCount = occluded;
109+
z_occSum = z_occ * occluded;
110+
111+
for (uint i = 0u; i < 8u; i++)
112+
{
113+
vec2 duv = sample_point_pos[i] * filterRadii;
114+
vec2 tc = clamp(uv + duv, vec2(0.), vec2(1.));
115+
// receiver plane depth bias
116+
float z_bias = dot(dz_duv, duv);
117+
118+
float z_occ = texture(shadowtexdepth, vec3(tc, float(layer))).r;
119+
float dz = z_rec - z_occ; // dz>0 when blocker is between receiver and light
120+
float occluded = step(z_bias, dz);
121+
occludedCount += occluded;
122+
z_occSum += z_occ * occluded;
123+
}
124+
}
125+
126+
float filterPCSS(vec2 uv, float z_rec, uint layer,
127+
vec2 filterRadii, mat2 R, vec2 dz_duv)
128+
{
129+
float occludedCount = 0.0; // must be to workaround a spirv-tools issue
130+
for (uint i = 0u; i < 16u; i++)
131+
{
132+
vec2 duv = R * (vogel_disk_16[i] * filterRadii);
133+
vec2 tc = clamp(uv + duv, vec2(0.), vec2(1.));
134+
135+
// receiver plane depth bias
136+
float z_bias = dot(dz_duv, duv);
137+
occludedCount += texture(shadowtex, vec4(tc, float(layer), z_rec + z_bias));
138+
}
139+
return occludedCount * (1.0 / 16.0);
140+
}
141+
142+
float getShadowFactor(vec3 position, vec3 bbox, vec2 dz_duv, uint layer)
143+
{
144+
float penumbra = tan(3.14 * sun_angle / 360.) * bbox.z * position.z;
145+
146+
// rotate the poisson disk randomly
147+
mat2 R = getRandomRotationMatrix(gl_FragCoord.xy);
148+
149+
float occludedCount = 0.0;
150+
float z_occSum = 0.0;
151+
152+
blockerSearchAndFilter(occludedCount, z_occSum, position.xy, position.z, layer, penumbra / bbox.xy, dz_duv);
153+
154+
// early exit if there is no occluders at all, also avoids a divide-by-zero below.
155+
if (z_occSum == 0.0) {
156+
return 1.0;
157+
}
158+
159+
float penumbraRatio = 1.0 - z_occSum / occludedCount / position.z;
160+
vec2 radius = max(penumbra / bbox.xy * penumbraRatio, vec2(0.5 / shadow_res));
161+
162+
float percentageOccluded = filterPCSS(position.xy, position.z, layer, radius, R, dz_duv);
163+
164+
return percentageOccluded;
165+
}
166+
167+
float blend_start(float x) {
168+
return x * (1.0 - overlap_proportion);
169+
}
170+
171+
void main() {
172+
vec2 uv = gl_FragCoord.xy / u_screen;
173+
float z = texture(dtex, uv).x;
174+
vec4 xpos = getPosFromUVDepth(vec3(uv, z), u_inverse_projection_matrix);
175+
176+
vec3 norm = DecodeNormal(texture(ntex, uv).xy);
177+
float roughness =texture(ntex, uv).z;
178+
vec3 eyedir = -normalize(xpos.xyz);
179+
180+
vec3 Lightdir = SunMRP(norm, eyedir);
181+
float NdotL = clamp(dot(norm, Lightdir), 0., 1.);
182+
183+
vec3 Specular = SpecularBRDF(norm, eyedir, Lightdir, vec3(1.), roughness);
184+
vec3 Diffuse = DiffuseBRDF(norm, eyedir, Lightdir, vec3(1.), roughness);
185+
186+
// Shadows
187+
// Calculate all shadow positions to prevent bug of dFdx
188+
vec4 position = (u_shadow_projection_view_matrices[0] * u_inverse_view_matrix * vec4(xpos.xyz, 1.0));
189+
vec3 position1 = position.xyz * (1.0 / position.w) * 0.5 + 0.5;
190+
191+
position = (u_shadow_projection_view_matrices[1] * u_inverse_view_matrix * vec4(xpos.xyz, 1.0));
192+
vec3 position2 = position.xyz * (1.0 / position.w) * 0.5 + 0.5;
193+
194+
position = (u_shadow_projection_view_matrices[2] * u_inverse_view_matrix * vec4(xpos.xyz, 1.0));
195+
vec3 position3 = position.xyz * (1.0 / position.w) * 0.5 + 0.5;
196+
197+
position = (u_shadow_projection_view_matrices[3] * u_inverse_view_matrix * vec4(xpos.xyz, 1.0));
198+
vec3 position4 = position.xyz * (1.0 / position.w) * 0.5 + 0.5;
199+
200+
// We need to use the shadow receiver plane depth bias to combat shadow acne due to the
201+
// large kernel.
202+
vec2 dz_duv1 = computeReceiverPlaneDepthBias(position1);
203+
vec2 dz_duv2 = computeReceiverPlaneDepthBias(position2);
204+
vec2 dz_duv3 = computeReceiverPlaneDepthBias(position3);
205+
vec2 dz_duv4 = computeReceiverPlaneDepthBias(position4);
206+
207+
float factor;
208+
if (xpos.z < split0) {
209+
float factor2 = getShadowFactor(position1, box0, dz_duv1, 0);
210+
factor = factor2;
211+
}
212+
if (blend_start(split0) < xpos.z && xpos.z < split1) {
213+
float factor2 = getShadowFactor(position2, box1, dz_duv2, 1);
214+
if (xpos.z < split0) {
215+
factor = mix(factor, factor2, (xpos.z - blend_start(split0)) / split0 / overlap_proportion);
216+
} else {
217+
factor = factor2;
218+
}
219+
}
220+
if (blend_start(split1) < xpos.z && xpos.z < split2) {
221+
float factor2 = getShadowFactor(position3, box2, dz_duv3, 2);
222+
if (xpos.z < split1) {
223+
factor = mix(factor, factor2, (xpos.z - blend_start(split1)) / split1 / overlap_proportion);
224+
} else {
225+
factor = factor2;
226+
}
227+
}
228+
if (blend_start(split2) < xpos.z && xpos.z < splitmax) {
229+
float factor2 = getShadowFactor(position4, box3, dz_duv4, 3);
230+
if (xpos.z < split2) {
231+
factor = mix(factor, factor2, (xpos.z - blend_start(split2)) / split2 / overlap_proportion);
232+
} else {
233+
factor = factor2;
234+
}
235+
}
236+
if (blend_start(splitmax) < xpos.z) {
237+
float factor2 = 1.;
238+
if (xpos.z < splitmax) {
239+
factor = mix(factor, factor2, (xpos.z - blend_start(splitmax)) / splitmax / overlap_proportion);
240+
} else {
241+
factor = factor2;
242+
}
243+
}
244+
245+
Diff = vec4(factor * NdotL * Diffuse * sun_color, 1.);
246+
Spec = vec4(factor * NdotL * Specular * sun_color, 1.);
247+
}

src/config/user_config.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -1017,6 +1017,10 @@ namespace UserConfigParams
10171017
PARAM_DEFAULT( IntUserConfigParam(0,
10181018
"shadows_resolution", &m_graphics_quality,
10191019
"Shadow resolution (0 = disabled") );
1020+
PARAM_PREFIX IntUserConfigParam m_pcss_threshold
1021+
PARAM_DEFAULT( IntUserConfigParam(2048,
1022+
"pcss_threshold", &m_graphics_quality,
1023+
"Enable Percentage Closer Soft Shadows when shadow resolution is higher than this value") );
10201024
PARAM_PREFIX BoolUserConfigParam m_degraded_IBL
10211025
PARAM_DEFAULT(BoolUserConfigParam(true,
10221026
"Degraded_IBL", &m_graphics_quality,

0 commit comments

Comments
 (0)