Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement PCSS & Revert graphics level 8 #5300

Merged
merged 9 commits into from
Feb 16, 2025
2 changes: 1 addition & 1 deletion data/gui/dialogs/custom_video_settings.stkgui
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<div layout="horizontal-row" proportion="1" height="fit">
<label text="Shadows" I18N="Video settings"/>
<spacer width="10" height="10"/>
<gauge id="shadows" min_value="0" max_value="4" proportion="1"/>
<gauge id="shadows" min_value="0" max_value="3" proportion="1"/>
</div>
</div>

Expand Down
1 change: 1 addition & 0 deletions data/gui/dialogs/ghost_replay_info_dialog.stkgui
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
<spacer width="1%"/>
<label proportion="1" id="compare-ghost-text" height="100%" text_align="left" I18N="Ghost replay info action" text="Compare to another ghost"/>
</div>
<bubble id="info" width="100%" proportion="1" word_wrap="true"/>
</div>

<div width="95%" height="40%" align="center">
Expand Down
46 changes: 35 additions & 11 deletions data/shaders/sunlightshadow.frag
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ uniform float splitmax;
uniform float shadow_res;
uniform float overlap_proportion;

uniform vec3 box0;
uniform vec3 box1;
uniform vec3 box2;
uniform vec3 box3;

uniform vec3 sundirection;
uniform vec3 sun_color;

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

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

vec2 uv = shadowtexcoord * shadow_res;
vec2 base_uv = floor(uv + 0.5);
Expand Down Expand Up @@ -83,13 +88,26 @@ float blend_start(float x) {
return x * (1.0 - overlap_proportion);
}

vec3 getXcYcZc(int x, int y, float zC)
{
// We use perspective symetric projection matrix hence P(0,2) = P(1, 2) = 0
float xC= (2. * (float(x)) / u_screen.x - 1.) * zC / u_projection_matrix[0][0];
float yC= (2. * (float(y)) / u_screen.y - 1.) * zC / u_projection_matrix[1][1];
return vec3(xC, yC, zC);
}

void main() {
vec2 uv = gl_FragCoord.xy / u_screen;
float z = texture(dtex, uv).x;
vec4 xpos = getPosFromUVDepth(vec3(uv, z), u_inverse_projection_matrix);

// get the normal of current fragment
vec3 ddx = dFdx(xpos.xyz);
vec3 ddy = dFdy(xpos.xyz);
vec3 geo_norm = normalize(cross(ddy, ddx));

vec3 norm = DecodeNormal(texture(ntex, uv).xy);
float roughness =texture(ntex, uv).z;
float roughness = texture(ntex, uv).z;
vec3 eyedir = -normalize(xpos.xyz);

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

// Shadows
float factor;
float bias = max(1.0 - NdotL, .2) / shadow_res;
vec3 lbias = 40. * Lightdir / shadow_res; // Scale with blur kernel size
vec3 nbias = geo_norm * (1.0 - max(dot(-geo_norm, Lightdir), 0.)) / shadow_res;
nbias -= Lightdir * dot(nbias, Lightdir); // Slope-scaled normal bias

if (xpos.z < split0) {
factor = getShadowFactor(xpos.xyz, 0, bias);
factor = getShadowFactor(xpos.xyz + lbias + nbias * max(box0.x, box0.y), 0);
if (xpos.z > blend_start(split0)) {
factor = mix(factor, getShadowFactor(xpos.xyz, 1, bias), (xpos.z - blend_start(split0)) / split0 / overlap_proportion);
factor = mix(factor, getShadowFactor(xpos.xyz + lbias + nbias * max(box1.x, box1.y), 1),
(xpos.z - blend_start(split0)) / split0 / overlap_proportion);
}
} else if (xpos.z < split1) {
factor = getShadowFactor(xpos.xyz, 1, bias);
factor = getShadowFactor(xpos.xyz + lbias + nbias * max(box1.x, box1.y), 1);
if (xpos.z > blend_start(split1)) {
factor = mix(factor, getShadowFactor(xpos.xyz, 2, bias), (xpos.z - blend_start(split1)) / split1 / overlap_proportion);
factor = mix(factor, getShadowFactor(xpos.xyz + lbias + nbias * max(box2.x, box2.y), 2),
(xpos.z - blend_start(split1)) / split1 / overlap_proportion);
}
} else if (xpos.z < split2) {
factor = getShadowFactor(xpos.xyz, 2, bias);
factor = getShadowFactor(xpos.xyz + lbias + nbias * max(box2.x, box2.y), 2);
if (xpos.z > blend_start(split2)) {
factor = mix(factor, getShadowFactor(xpos.xyz, 3, bias), (xpos.z - blend_start(split2)) / split2 / overlap_proportion);
factor = mix(factor, getShadowFactor(xpos.xyz + lbias + nbias * max(box3.x, box3.y), 3),
(xpos.z - blend_start(split2)) / split2 / overlap_proportion);
}
} else if (xpos.z < splitmax) {
factor = getShadowFactor(xpos.xyz, 3, bias);
factor = getShadowFactor(xpos.xyz + lbias + nbias * max(box3.x, box3.y), 3);
if (xpos.z > blend_start(splitmax)) {
factor = mix(factor, 1.0, (xpos.z - blend_start(splitmax)) / splitmax / overlap_proportion);
}
Expand Down
247 changes: 247 additions & 0 deletions data/shaders/sunlightshadowpcss.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
// Implementation from https://github.com/google/filament/blob/main/shaders/src/surface_shadowing.fs
uniform sampler2D ntex;
#if defined(GL_ES) && defined(GL_FRAGMENT_PRECISION_HIGH)
uniform highp sampler2D dtex;
#else
uniform sampler2D dtex;
#endif
uniform sampler2DArray shadowtexdepth;
uniform sampler2DArrayShadow shadowtex;

uniform float split0;
uniform float split1;
uniform float split2;
uniform float splitmax;
uniform float shadow_res;
uniform float overlap_proportion;

uniform vec3 box0;
uniform vec3 box1;
uniform vec3 box2;
uniform vec3 box3;

uniform vec3 sundirection;
uniform vec3 sun_color;

in vec2 uv;
#ifdef GL_ES
layout (location = 0) out vec4 Diff;
layout (location = 1) out vec4 Spec;
#else
out vec4 Diff;
out vec4 Spec;
#endif

#stk_include "utils/decodeNormal.frag"
#stk_include "utils/SpecularBRDF.frag"
#stk_include "utils/DiffuseBRDF.frag"
#stk_include "utils/getPosFromUVDepth.frag"
#stk_include "utils/SunMRP.frag"

// PCF with Vogel Disk Sampling
// From https://drdesten.github.io/web/tools/vogel_disk/
vec2 vogel_disk_16[16] = vec2[](
vec2(0.18993645671348536, 0.027087114076591513),
vec2(-0.21261242652069953, 0.23391293246949066),
vec2(0.04771781344140756, -0.3666840644525993),
vec2(0.297730981239584, 0.398259878229082),
vec2(-0.509063425827436, -0.06528681462854097),
vec2(0.507855152944665, -0.2875976005206389),
vec2(-0.15230616564632418, 0.6426121151781916),
vec2(-0.30240170651828074, -0.5805072900736001),
vec2(0.6978019230005561, 0.2771173334141519),
vec2(-0.6990963248129052, 0.3210960724922725),
vec2(0.3565142601623699, -0.7066415061851589),
vec2(0.266890002328106, 0.8360191043249159),
vec2(-0.7515861305520581, -0.41609876195815027),
vec2(0.9102937449894895, -0.17014527555321657),
vec2(-0.5343471434373126, 0.8058593459499529),
vec2(-0.1133270115046468, -0.9490025827627441)
);

// https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_standard_multisample_quality_levels?redirectedfrom=MSDN
vec2 sample_point_pos[8] = vec2[](
vec2( 0.125, -0.375),
vec2(-0.125, 0.375),
vec2( 0.625, 0.125),
vec2(-0.375, -0.625),
vec2(-0.625, 0.625),
vec2(-0.875, -0.125),
vec2( 0.375, 0.875),
vec2( 0.875, -0.875)
);

float interleavedGradientNoise(vec2 w)
{
vec3 m = vec3(0.06711056, 0.00583715, 52.9829189);
return fract(m.z * fract(dot(w, m.xy)));
}

vec2 computeReceiverPlaneDepthBias(vec3 position)
{
// see: GDC '06: Shadow Mapping: GPU-based Tips and Techniques
// Chain rule to compute dz/du and dz/dv
// |dz/du| |du/dx du/dy|^-T |dz/dx|
// |dz/dv| = |dv/dx dv/dy| * |dz/dy|
vec3 duvz_dx = dFdx(position);
vec3 duvz_dy = dFdy(position);
vec2 dz_duv = inverse(transpose(mat2(duvz_dx.xy, duvz_dy.xy))) * vec2(duvz_dx.z, duvz_dy.z);
return dz_duv;
}

mat2 getRandomRotationMatrix(vec2 fragCoord)
{
// rotate the vogel disk randomly
float randomAngle = interleavedGradientNoise(fragCoord) * 2.0 * 3.14159;
vec2 randomBase = vec2(cos(randomAngle), sin(randomAngle));
mat2 R = mat2(randomBase.x, randomBase.y, -randomBase.y, randomBase.x);
return R;
}

void blockerSearchAndFilter(out float occludedCount, out float z_occSum,
vec2 uv, float z_rec, uint layer, vec2 filterRadii, vec2 dz_duv)
{
// Make sure no light leaking
float z_occ = texture(shadowtexdepth, vec3(uv, float(layer))).r;
float dz = z_rec - z_occ;
float occluded = 0.01 * step(0.5 / shadow_res, dz);
occludedCount = occluded;
z_occSum = z_occ * occluded;

for (uint i = 0u; i < 8u; i++)
{
vec2 duv = sample_point_pos[i] * filterRadii;
vec2 tc = clamp(uv + duv, vec2(0.), vec2(1.));
// receiver plane depth bias
float z_bias = dot(dz_duv, duv);

float z_occ = texture(shadowtexdepth, vec3(tc, float(layer))).r;
float dz = z_rec - z_occ; // dz>0 when blocker is between receiver and light
float occluded = step(z_bias, dz);
occludedCount += occluded;
z_occSum += z_occ * occluded;
}
}

float filterPCSS(vec2 uv, float z_rec, uint layer,
vec2 filterRadii, mat2 R, vec2 dz_duv)
{
float occludedCount = 0.0; // must be to workaround a spirv-tools issue
for (uint i = 0u; i < 16u; i++)
{
vec2 duv = R * (vogel_disk_16[i] * filterRadii);
vec2 tc = clamp(uv + duv, vec2(0.), vec2(1.));

// receiver plane depth bias
float z_bias = dot(dz_duv, duv);
occludedCount += texture(shadowtex, vec4(tc, float(layer), z_rec + z_bias));
}
return occludedCount * (1.0 / 16.0);
}

float getShadowFactor(vec3 position, vec3 bbox, vec2 dz_duv, uint layer)
{
float penumbra = tan(3.14 * sun_angle / 360.) * bbox.z * position.z;

// rotate the poisson disk randomly
mat2 R = getRandomRotationMatrix(gl_FragCoord.xy);

float occludedCount = 0.0;
float z_occSum = 0.0;

blockerSearchAndFilter(occludedCount, z_occSum, position.xy, position.z, layer, penumbra / bbox.xy, dz_duv);

// early exit if there is no occluders at all, also avoids a divide-by-zero below.
if (z_occSum == 0.0) {
return 1.0;
}

float penumbraRatio = 1.0 - z_occSum / occludedCount / position.z;
vec2 radius = max(penumbra / bbox.xy * penumbraRatio, vec2(0.5 / shadow_res));

float percentageOccluded = filterPCSS(position.xy, position.z, layer, radius, R, dz_duv);

return percentageOccluded;
}

float blend_start(float x) {
return x * (1.0 - overlap_proportion);
}

void main() {
vec2 uv = gl_FragCoord.xy / u_screen;
float z = texture(dtex, uv).x;
vec4 xpos = getPosFromUVDepth(vec3(uv, z), u_inverse_projection_matrix);

vec3 norm = DecodeNormal(texture(ntex, uv).xy);
float roughness =texture(ntex, uv).z;
vec3 eyedir = -normalize(xpos.xyz);

vec3 Lightdir = SunMRP(norm, eyedir);
float NdotL = clamp(dot(norm, Lightdir), 0., 1.);

vec3 Specular = SpecularBRDF(norm, eyedir, Lightdir, vec3(1.), roughness);
vec3 Diffuse = DiffuseBRDF(norm, eyedir, Lightdir, vec3(1.), roughness);

// Shadows
// Calculate all shadow positions to prevent bug of dFdx
vec4 position = (u_shadow_projection_view_matrices[0] * u_inverse_view_matrix * vec4(xpos.xyz, 1.0));
vec3 position1 = position.xyz * (1.0 / position.w) * 0.5 + 0.5;

position = (u_shadow_projection_view_matrices[1] * u_inverse_view_matrix * vec4(xpos.xyz, 1.0));
vec3 position2 = position.xyz * (1.0 / position.w) * 0.5 + 0.5;

position = (u_shadow_projection_view_matrices[2] * u_inverse_view_matrix * vec4(xpos.xyz, 1.0));
vec3 position3 = position.xyz * (1.0 / position.w) * 0.5 + 0.5;

position = (u_shadow_projection_view_matrices[3] * u_inverse_view_matrix * vec4(xpos.xyz, 1.0));
vec3 position4 = position.xyz * (1.0 / position.w) * 0.5 + 0.5;

// We need to use the shadow receiver plane depth bias to combat shadow acne due to the
// large kernel.
vec2 dz_duv1 = computeReceiverPlaneDepthBias(position1);
vec2 dz_duv2 = computeReceiverPlaneDepthBias(position2);
vec2 dz_duv3 = computeReceiverPlaneDepthBias(position3);
vec2 dz_duv4 = computeReceiverPlaneDepthBias(position4);

float factor;
if (xpos.z < split0) {
float factor2 = getShadowFactor(position1, box0, dz_duv1, 0);
factor = factor2;
}
if (blend_start(split0) < xpos.z && xpos.z < split1) {
float factor2 = getShadowFactor(position2, box1, dz_duv2, 1);
if (xpos.z < split0) {
factor = mix(factor, factor2, (xpos.z - blend_start(split0)) / split0 / overlap_proportion);
} else {
factor = factor2;
}
}
if (blend_start(split1) < xpos.z && xpos.z < split2) {
float factor2 = getShadowFactor(position3, box2, dz_duv3, 2);
if (xpos.z < split1) {
factor = mix(factor, factor2, (xpos.z - blend_start(split1)) / split1 / overlap_proportion);
} else {
factor = factor2;
}
}
if (blend_start(split2) < xpos.z && xpos.z < splitmax) {
float factor2 = getShadowFactor(position4, box3, dz_duv4, 3);
if (xpos.z < split2) {
factor = mix(factor, factor2, (xpos.z - blend_start(split2)) / split2 / overlap_proportion);
} else {
factor = factor2;
}
}
if (blend_start(splitmax) < xpos.z) {
float factor2 = 1.;
if (xpos.z < splitmax) {
factor = mix(factor, factor2, (xpos.z - blend_start(splitmax)) / splitmax / overlap_proportion);
} else {
factor = factor2;
}
}

Diff = vec4(factor * NdotL * Diffuse * sun_color, 1.);
Spec = vec4(factor * NdotL * Specular * sun_color, 1.);
}
4 changes: 4 additions & 0 deletions src/config/user_config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,10 @@ namespace UserConfigParams
PARAM_DEFAULT( IntUserConfigParam(0,
"shadows_resolution", &m_graphics_quality,
"Shadow resolution (0 = disabled") );
PARAM_PREFIX IntUserConfigParam m_pcss_threshold
PARAM_DEFAULT( IntUserConfigParam(2048,
"pcss_threshold", &m_graphics_quality,
"Enable Percentage Closer Soft Shadows when shadow resolution is higher than this value") );
PARAM_PREFIX BoolUserConfigParam m_degraded_IBL
PARAM_DEFAULT(BoolUserConfigParam(true,
"Degraded_IBL", &m_graphics_quality,
Expand Down
Loading
Loading