Skip to content

Commit 174a534

Browse files
committed
Add translucent & alpha-tested shadow support
Implement transparency-aware shadow mapping: add alpha-tested and hashed alpha casters plus an experimental translucent (moment-based) shadow accumulation path. New and updated GLSL programs provide caster/receiver support for projected and point lights (alpha-tested casters, hashed alpha coverage, and translucent moment packing), and receiver shaders sample translucent moment maps and blend them with existing shadowing. Renderer bindings and initialization were updated (src/renderer/*) to expose control CVars and bind new shadow textures, and documentation/README were added/updated (docs-user/docs-dev) describing the feature, CVars, and usage. This change enables optional translucent shadowing while preserving existing CSM and PCF-based shadow behavior.
1 parent ba751ae commit 174a534

32 files changed

+3200
-162
lines changed

.install/openq4/glprogs/openq4_shadow_interaction.fs

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
#version 110
2+
13
uniform sampler2D uBumpMap;
24
uniform sampler2D uLightFalloffMap;
35
uniform sampler2D uLightProjectionMap;
46
uniform sampler2D uDiffuseMap;
57
uniform sampler2D uSpecularMap;
68
uniform sampler2D uShadowMap;
9+
uniform sampler2D uTranslucentShadowMap;
710

811
uniform vec4 uDiffuseColor;
912
uniform vec4 uSpecularColor;
@@ -17,6 +20,8 @@ uniform float uShadowCascadeBiasScale[4];
1720
uniform int uShadowCascadeCount;
1821
uniform float uShadowCascadeBlend;
1922
uniform float uShadowDebugMode;
23+
uniform float uTranslucentShadowEnabled;
24+
uniform float uTranslucentShadowDensity;
2025

2126
varying vec2 vBumpTexCoord;
2227
varying vec2 vDiffuseTexCoord;
@@ -41,9 +46,12 @@ const float kShadowDebugProjectedUV = 3.0;
4146
const float kShadowDebugProjectedDepth = 4.0;
4247
const float kShadowDebugProjectedW = 5.0;
4348
const float kShadowDebugInvalidMask = 6.0;
49+
const float kTranslucentMomentMinVariance = 1.0e-5;
4450

4551
float gShadowDebugState = 0.0;
4652

53+
bool ProjectShadowCoord( vec4 shadowCoord, out vec2 localUv, out float depth );
54+
4755
bool ShadowCoordComponentInvalid( float value ) {
4856
return value != value || abs( value ) > kShadowCoordMaxMagnitude;
4957
}
@@ -56,6 +64,35 @@ vec3 SafeNormalize( vec3 value ) {
5664
return value * inversesqrt( max( dot( value, value ), 1.0e-8 ) );
5765
}
5866

67+
float ApproxErf( float x ) {
68+
float s = sign( x );
69+
float ax = abs( x );
70+
float t = 1.0 / ( 1.0 + 0.3275911 * ax );
71+
float y = 1.0 - ( ( ( ( ( 1.061405429 * t - 1.453152027 ) * t ) + 1.421413741 ) * t - 0.284496736 ) * t + 0.254829592 ) * t * exp( -ax * ax );
72+
return s * y;
73+
}
74+
75+
float NormalCdf( float x ) {
76+
return 0.5 * ( 1.0 + ApproxErf( x * 0.70710678 ) );
77+
}
78+
79+
float ResolveTranslucentShadowMoments( vec4 moments, float depth ) {
80+
if ( uTranslucentShadowEnabled < 0.5 ) {
81+
return 1.0;
82+
}
83+
84+
float totalTau = max( moments.x, 0.0 );
85+
if ( totalTau <= 1.0e-4 ) {
86+
return 1.0;
87+
}
88+
89+
float mean = moments.y / totalTau;
90+
float variance = max( moments.z / totalTau - mean * mean, kTranslucentMomentMinVariance );
91+
float sigma = sqrt( variance );
92+
float tau = totalTau * clamp( NormalCdf( ( depth - mean ) / sigma ), 0.0, 1.0 );
93+
return exp( -min( tau * max( uTranslucentShadowDensity, 0.0 ), 16.0 ) );
94+
}
95+
5996
float CascadeBiasScale( int cascadeIndex ) {
6097
if ( cascadeIndex <= 0 ) {
6198
return uShadowCascadeBiasScale[0];
@@ -135,6 +172,34 @@ vec4 SampleShadowCascade( vec4 shadowCoord, vec4 atlasRect, int cascadeIndex ) {
135172
return vec4( shadow * ( 1.0 / 13.0 ), localUv.x, localUv.y, depth );
136173
}
137174

175+
float SampleTranslucentShadowCascade( vec4 shadowCoord, vec4 atlasRect ) {
176+
if ( uTranslucentShadowEnabled < 0.5 ) {
177+
return 1.0;
178+
}
179+
180+
vec2 localUv;
181+
float depth;
182+
if ( !ProjectShadowCoord( shadowCoord, localUv, depth ) ) {
183+
return 1.0;
184+
}
185+
if ( localUv.x <= 0.0 || localUv.x >= 1.0 || localUv.y <= 0.0 || localUv.y >= 1.0 ) {
186+
return 1.0;
187+
}
188+
if ( depth <= 0.0 || depth >= 1.0 ) {
189+
return 1.0;
190+
}
191+
192+
vec2 atlasMin = atlasRect.xy;
193+
vec2 atlasMax = atlasRect.zw;
194+
vec2 uv = atlasMin + localUv * ( atlasMax - atlasMin );
195+
vec2 clampMin = atlasMin + uShadowTexelSize * 0.5;
196+
vec2 clampMax = atlasMax - uShadowTexelSize * 0.5;
197+
clampMin = min( clampMin, clampMax );
198+
uv = clamp( uv, clampMin, clampMax );
199+
200+
return ResolveTranslucentShadowMoments( texture2D( uTranslucentShadowMap, uv ), depth );
201+
}
202+
138203
float CascadeSplitDepth( int index ) {
139204
if ( index <= 0 ) {
140205
return uShadowSplitDepths[0];
@@ -197,7 +262,7 @@ bool ProjectShadowCoord( vec4 shadowCoord, out vec2 localUv, out float depth ) {
197262
}
198263

199264
int SelectCascade( float viewDepth ) {
200-
int interiorSplitCount = max( uShadowCascadeCount - 1, 0 );
265+
int interiorSplitCount = ( uShadowCascadeCount > 1 ) ? ( uShadowCascadeCount - 1 ) : 0;
201266
if ( interiorSplitCount <= 0 || viewDepth < uShadowSplitDepths[0] ) {
202267
return 0;
203268
}
@@ -279,7 +344,8 @@ vec4 ShadowDebugOutput( vec4 shadowInfo ) {
279344
}
280345
vec4 cascadeColor = CascadeDebugColor( float( cascadeIndex ) );
281346
if ( shadowInfo.z > 0.0 ) {
282-
cascadeColor.rgb = mix( cascadeColor.rgb, CascadeDebugColor( float( min( cascadeIndex + 1, uShadowCascadeCount - 1 ) ) ).rgb, shadowInfo.z );
347+
int nextCascadeIndex = ( cascadeIndex + 1 < uShadowCascadeCount ) ? ( cascadeIndex + 1 ) : ( uShadowCascadeCount - 1 );
348+
cascadeColor.rgb = mix( cascadeColor.rgb, CascadeDebugColor( float( nextCascadeIndex ) ).rgb, shadowInfo.z );
283349
}
284350
return cascadeColor;
285351
}
@@ -324,6 +390,45 @@ vec4 ShadowDebugOutput( vec4 shadowInfo ) {
324390
return vec4( invalidColor, 1.0 );
325391
}
326392

393+
float SampleTranslucentShadowCascadeByIndex( int index ) {
394+
if ( index <= 0 ) {
395+
return SampleTranslucentShadowCascade( vShadowCoord0, uShadowAtlasRect[0] );
396+
}
397+
if ( index == 1 ) {
398+
return SampleTranslucentShadowCascade( vShadowCoord1, uShadowAtlasRect[1] );
399+
}
400+
if ( index == 2 ) {
401+
return SampleTranslucentShadowCascade( vShadowCoord2, uShadowAtlasRect[2] );
402+
}
403+
return SampleTranslucentShadowCascade( vShadowCoord3, uShadowAtlasRect[3] );
404+
}
405+
406+
float SampleTranslucentShadow() {
407+
if ( uTranslucentShadowEnabled < 0.5 ) {
408+
return 1.0;
409+
}
410+
411+
int cascadeIndex = SelectCascade( vViewDepth );
412+
float shadow = SampleTranslucentShadowCascadeByIndex( cascadeIndex );
413+
int lastInteriorIndex = uShadowCascadeCount - 2;
414+
415+
if ( cascadeIndex > lastInteriorIndex || uShadowCascadeBlend <= 0.0 ) {
416+
return shadow;
417+
}
418+
419+
float previousSplit = ( cascadeIndex == 0 ) ? 0.0 : CascadeSplitDepth( cascadeIndex - 1 );
420+
float currentSplit = CascadeSplitDepth( cascadeIndex );
421+
float blendWidth = max( 1.0, ( currentSplit - previousSplit ) * uShadowCascadeBlend );
422+
float blendStart = currentSplit - blendWidth;
423+
if ( vViewDepth <= blendStart ) {
424+
return shadow;
425+
}
426+
427+
float nextShadow = SampleTranslucentShadowCascadeByIndex( cascadeIndex + 1 );
428+
float blend = clamp( ( vViewDepth - blendStart ) / blendWidth, 0.0, 1.0 );
429+
return mix( shadow, nextShadow, blend );
430+
}
431+
327432
void main() {
328433
vec4 bumpSample = texture2D( uBumpMap, vBumpTexCoord );
329434
vec3 localNormal = vec3( bumpSample.a, bumpSample.g, bumpSample.b ) * 2.0 - 1.0;
@@ -339,6 +444,7 @@ void main() {
339444
light *= texture2DProj( uLightProjectionMap, vLightProjectionTexCoord ).rgb;
340445
vec4 shadowInfo = SampleShadow();
341446
light *= shadowInfo.x;
447+
light *= SampleTranslucentShadow();
342448

343449
vec3 diffuse = texture2D( uDiffuseMap, vDiffuseTexCoord ).rgb * uDiffuseColor.rgb;
344450

.install/openq4/glprogs/openq4_shadow_interaction.vs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#version 110
2+
13
attribute vec2 attr_TexCoord0;
24
attribute vec3 attr_Tangent;
35
attribute vec3 attr_Bitangent;
Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,48 @@
1+
#version 110
2+
13
uniform float uPointShadowFar;
4+
uniform sampler2D uAlphaMap;
5+
uniform float uAlphaRef;
6+
uniform float uAlphaScale;
7+
uniform float uAlphaTestEnabled;
8+
uniform float uAlphaHashEnabled;
29

310
varying vec3 vPointShadowVector;
11+
varying vec2 vAlphaTexCoord;
412

513
vec2 PackDepth16( float depth ) {
614
vec2 enc = fract( vec2( 1.0, 255.0 ) * clamp( depth, 0.0, 1.0 ) );
715
enc.x -= enc.y * ( 1.0 / 255.0 );
816
return enc;
917
}
1018

19+
float AlphaHashThreshold( vec2 fragmentCoord ) {
20+
vec2 texel = floor( fragmentCoord );
21+
return fract( 52.9829189 * fract( texel.x * 0.06711056 + texel.y * 0.00583715 ) );
22+
}
23+
24+
float AlphaCoverage( float alpha ) {
25+
float alphaRef = clamp( uAlphaRef, 0.0, 0.9999 );
26+
float scaledAlpha = clamp( alpha, 0.0, 1.0 );
27+
return clamp( ( scaledAlpha - alphaRef ) / max( 1.0 - alphaRef, 1.0e-4 ), 0.0, 1.0 );
28+
}
29+
1130
void main() {
31+
if ( uAlphaTestEnabled > 0.5 ) {
32+
float alpha = texture2D( uAlphaMap, vAlphaTexCoord ).a * uAlphaScale;
33+
if ( uAlphaHashEnabled > 0.5 ) {
34+
float coverage = AlphaCoverage( alpha );
35+
if ( coverage <= 0.0 || coverage <= AlphaHashThreshold( gl_FragCoord.xy ) ) {
36+
discard;
37+
}
38+
} else {
39+
if ( alpha <= uAlphaRef ) {
40+
discard;
41+
}
42+
}
43+
}
44+
1245
float depth = clamp( length( vPointShadowVector ) / uPointShadowFar, 0.0, 1.0 );
13-
vec2 packed = PackDepth16( depth );
14-
gl_FragColor = vec4( packed, 0.0, 1.0 );
46+
vec2 packedDepth = PackDepth16( depth );
47+
gl_FragColor = vec4( packedDepth, 0.0, 1.0 );
1548
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
1+
#version 110
2+
13
uniform vec4 uModelMatrixRow0;
24
uniform vec4 uModelMatrixRow1;
35
uniform vec4 uModelMatrixRow2;
46
uniform vec4 uGlobalLightOrigin;
7+
uniform vec4 uAlphaTexCoordS;
8+
uniform vec4 uAlphaTexCoordT;
59

610
varying vec3 vPointShadowVector;
11+
varying vec2 vAlphaTexCoord;
712

813
void main() {
914
vec4 position = gl_Vertex;
15+
vec4 alphaTexCoord = vec4( gl_MultiTexCoord0.xy, 0.0, 1.0 );
1016
vec3 worldPos = vec3(
1117
dot( position, uModelMatrixRow0 ),
1218
dot( position, uModelMatrixRow1 ),
1319
dot( position, uModelMatrixRow2 ) );
1420
vPointShadowVector = worldPos - uGlobalLightOrigin.xyz;
21+
vAlphaTexCoord = vec2( dot( alphaTexCoord, uAlphaTexCoordS ), dot( alphaTexCoord, uAlphaTexCoordT ) );
1522
gl_Position = ftransform();
1623
}

.install/openq4/glprogs/openq4_shadow_point_interaction.fs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
#version 110
2+
13
uniform sampler2D uBumpMap;
24
uniform sampler2D uLightFalloffMap;
35
uniform sampler2D uLightProjectionMap;
46
uniform sampler2D uDiffuseMap;
57
uniform sampler2D uSpecularMap;
68
uniform samplerCube uPointShadowMap;
9+
uniform samplerCube uPointTranslucentShadowMap;
710

811
uniform vec4 uDiffuseColor;
912
uniform vec4 uSpecularColor;
@@ -12,6 +15,10 @@ uniform float uShadowBias;
1215
uniform float uShadowNormalBias;
1316
uniform float uShadowFilterRadius;
1417
uniform float uPointShadowTexelScale;
18+
uniform float uTranslucentShadowEnabled;
19+
uniform float uTranslucentShadowDensity;
20+
21+
const float kTranslucentMomentMinVariance = 1.0e-5;
1522

1623
varying vec2 vBumpTexCoord;
1724
varying vec2 vDiffuseTexCoord;
@@ -28,6 +35,35 @@ vec3 SafeNormalize( vec3 value ) {
2835
return value * inversesqrt( max( dot( value, value ), 1.0e-8 ) );
2936
}
3037

38+
float ApproxErf( float x ) {
39+
float s = sign( x );
40+
float ax = abs( x );
41+
float t = 1.0 / ( 1.0 + 0.3275911 * ax );
42+
float y = 1.0 - ( ( ( ( ( 1.061405429 * t - 1.453152027 ) * t ) + 1.421413741 ) * t - 0.284496736 ) * t + 0.254829592 ) * t * exp( -ax * ax );
43+
return s * y;
44+
}
45+
46+
float NormalCdf( float x ) {
47+
return 0.5 * ( 1.0 + ApproxErf( x * 0.70710678 ) );
48+
}
49+
50+
float ResolveTranslucentShadowMoments( vec4 moments, float depth ) {
51+
if ( uTranslucentShadowEnabled < 0.5 ) {
52+
return 1.0;
53+
}
54+
55+
float totalTau = max( moments.x, 0.0 );
56+
if ( totalTau <= 1.0e-4 ) {
57+
return 1.0;
58+
}
59+
60+
float mean = moments.y / totalTau;
61+
float variance = max( moments.z / totalTau - mean * mean, kTranslucentMomentMinVariance );
62+
float sigma = sqrt( variance );
63+
float tau = totalTau * clamp( NormalCdf( ( depth - mean ) / sigma ), 0.0, 1.0 );
64+
return exp( -min( tau * max( uTranslucentShadowDensity, 0.0 ), 16.0 ) );
65+
}
66+
3167
float ShadowReceiverBias() {
3268
float lightCos = clamp( vShadowLightCos, 0.0, 1.0 );
3369
float slopeBias = sqrt( max( 1.0 - lightCos * lightCos, 0.0 ) );
@@ -81,6 +117,20 @@ float SamplePointShadow() {
81117
return shadow * ( 1.0 / 13.0 );
82118
}
83119

120+
float SamplePointTranslucentShadow() {
121+
if ( uTranslucentShadowEnabled < 0.5 || uPointShadowFar <= 0.0 ) {
122+
return 1.0;
123+
}
124+
125+
float depth = length( vPointShadowVector ) / uPointShadowFar;
126+
if ( depth <= 0.0 || depth >= 1.0 ) {
127+
return 1.0;
128+
}
129+
130+
vec3 direction = SafeNormalize( vPointShadowVector );
131+
return ResolveTranslucentShadowMoments( textureCube( uPointTranslucentShadowMap, direction ), depth );
132+
}
133+
84134
void main() {
85135
vec4 bumpSample = texture2D( uBumpMap, vBumpTexCoord );
86136
vec3 localNormal = vec3( bumpSample.a, bumpSample.g, bumpSample.b ) * 2.0 - 1.0;
@@ -93,6 +143,7 @@ void main() {
93143
light *= texture2DProj( uLightFalloffMap, vLightFalloffTexCoord ).rgb;
94144
light *= texture2DProj( uLightProjectionMap, vLightProjectionTexCoord ).rgb;
95145
light *= SamplePointShadow();
146+
light *= SamplePointTranslucentShadow();
96147

97148
vec3 diffuse = texture2D( uDiffuseMap, vDiffuseTexCoord ).rgb * uDiffuseColor.rgb;
98149

.install/openq4/glprogs/openq4_shadow_point_interaction.vs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#version 110
2+
13
attribute vec2 attr_TexCoord0;
24
attribute vec3 attr_Tangent;
35
attribute vec3 attr_Bitangent;
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#version 110
2+
3+
uniform float uPointShadowFar;
4+
uniform sampler2D uAlphaMap;
5+
uniform float uAlphaScale;
6+
uniform float uOpacitySourceMode;
7+
uniform float uTranslucentMinAlpha;
8+
9+
varying vec3 vPointShadowVector;
10+
varying vec2 vAlphaTexCoord;
11+
varying float vVertexAlpha;
12+
13+
float OpticalDepth( float alpha ) {
14+
return -log( max( 1.0 - clamp( alpha, 0.0, 0.999 ), 1.0e-4 ) );
15+
}
16+
17+
float StageOpacity() {
18+
if ( uOpacitySourceMode > 1.5 ) {
19+
return clamp( uAlphaScale * vVertexAlpha, 0.0, 0.999 );
20+
}
21+
22+
vec4 sampleColor = texture2D( uAlphaMap, vAlphaTexCoord );
23+
float opacity = sampleColor.a;
24+
if ( uOpacitySourceMode > 0.5 ) {
25+
opacity = max( opacity, dot( sampleColor.rgb, vec3( 0.2126, 0.7152, 0.0722 ) ) );
26+
}
27+
28+
return clamp( opacity * uAlphaScale * vVertexAlpha, 0.0, 0.999 );
29+
}
30+
31+
void main() {
32+
float alpha = StageOpacity();
33+
if ( alpha <= uTranslucentMinAlpha ) {
34+
discard;
35+
}
36+
37+
float tau = OpticalDepth( alpha );
38+
float depth = clamp( length( vPointShadowVector ) / uPointShadowFar, 0.0, 1.0 );
39+
float depth2 = depth * depth;
40+
gl_FragColor = vec4( tau, tau * depth, tau * depth2, tau * depth2 * depth );
41+
}

0 commit comments

Comments
 (0)