Skip to content

Commit 6e6e02e

Browse files
committed
Add RGB translucent shadow moments and coverage
Implement colored (RGB) translucent-shadow moments and richer coverage handling across shaders and renderer. Shaders: split translucent shadow moments into R/G/B textures, make caster shaders output three MRT moment targets, add coverage map, stage/coverage colors, vertex color modes, coverage alpha-test and per-channel absorption/tau math. Renderer: require additional GL resources for the feature (texture units >= 9, draw buffers/color attachments >= 3), add glConfig.maxDrawBuffers and maxColorAttachments and expose them in GfxInfo, initialize and check them, and gate the feature on their availability. Render targets: support multiple cubemap color attachments in idRenderTexture::MakeCurrent and use multiple moment images for projected/point translucent shadows; update program uniform bindings and program structs to handle the new arrays and coverage-related uniforms. Docs: update shadow-mapping.md to describe layered coverage, colored transmission, and hardware requirements. Overall this improves tinted/translucent shadow fidelity while disabling the path on unsupported hardware.
1 parent 174a534 commit 6e6e02e

18 files changed

+754
-232
lines changed

.install/openq4/glprogs/openq4_shadow_interaction.fs

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ uniform sampler2D uLightProjectionMap;
66
uniform sampler2D uDiffuseMap;
77
uniform sampler2D uSpecularMap;
88
uniform sampler2D uShadowMap;
9-
uniform sampler2D uTranslucentShadowMap;
9+
uniform sampler2D uTranslucentShadowMapR;
10+
uniform sampler2D uTranslucentShadowMapG;
11+
uniform sampler2D uTranslucentShadowMapB;
1012

1113
uniform vec4 uDiffuseColor;
1214
uniform vec4 uSpecularColor;
@@ -89,7 +91,8 @@ float ResolveTranslucentShadowMoments( vec4 moments, float depth ) {
8991
float mean = moments.y / totalTau;
9092
float variance = max( moments.z / totalTau - mean * mean, kTranslucentMomentMinVariance );
9193
float sigma = sqrt( variance );
92-
float tau = totalTau * clamp( NormalCdf( ( depth - mean ) / sigma ), 0.0, 1.0 );
94+
float fraction = clamp( NormalCdf( ( depth - mean ) / sigma ), 0.0, 1.0 );
95+
float tau = totalTau * fraction;
9396
return exp( -min( tau * max( uTranslucentShadowDensity, 0.0 ), 16.0 ) );
9497
}
9598

@@ -172,21 +175,21 @@ vec4 SampleShadowCascade( vec4 shadowCoord, vec4 atlasRect, int cascadeIndex ) {
172175
return vec4( shadow * ( 1.0 / 13.0 ), localUv.x, localUv.y, depth );
173176
}
174177

175-
float SampleTranslucentShadowCascade( vec4 shadowCoord, vec4 atlasRect ) {
178+
vec3 SampleTranslucentShadowCascade( vec4 shadowCoord, vec4 atlasRect ) {
176179
if ( uTranslucentShadowEnabled < 0.5 ) {
177-
return 1.0;
180+
return vec3( 1.0 );
178181
}
179182

180183
vec2 localUv;
181184
float depth;
182185
if ( !ProjectShadowCoord( shadowCoord, localUv, depth ) ) {
183-
return 1.0;
186+
return vec3( 1.0 );
184187
}
185188
if ( localUv.x <= 0.0 || localUv.x >= 1.0 || localUv.y <= 0.0 || localUv.y >= 1.0 ) {
186-
return 1.0;
189+
return vec3( 1.0 );
187190
}
188191
if ( depth <= 0.0 || depth >= 1.0 ) {
189-
return 1.0;
192+
return vec3( 1.0 );
190193
}
191194

192195
vec2 atlasMin = atlasRect.xy;
@@ -197,7 +200,10 @@ float SampleTranslucentShadowCascade( vec4 shadowCoord, vec4 atlasRect ) {
197200
clampMin = min( clampMin, clampMax );
198201
uv = clamp( uv, clampMin, clampMax );
199202

200-
return ResolveTranslucentShadowMoments( texture2D( uTranslucentShadowMap, uv ), depth );
203+
return vec3(
204+
ResolveTranslucentShadowMoments( texture2D( uTranslucentShadowMapR, uv ), depth ),
205+
ResolveTranslucentShadowMoments( texture2D( uTranslucentShadowMapG, uv ), depth ),
206+
ResolveTranslucentShadowMoments( texture2D( uTranslucentShadowMapB, uv ), depth ) );
201207
}
202208

203209
float CascadeSplitDepth( int index ) {
@@ -390,7 +396,7 @@ vec4 ShadowDebugOutput( vec4 shadowInfo ) {
390396
return vec4( invalidColor, 1.0 );
391397
}
392398

393-
float SampleTranslucentShadowCascadeByIndex( int index ) {
399+
vec3 SampleTranslucentShadowCascadeByIndex( int index ) {
394400
if ( index <= 0 ) {
395401
return SampleTranslucentShadowCascade( vShadowCoord0, uShadowAtlasRect[0] );
396402
}
@@ -403,13 +409,13 @@ float SampleTranslucentShadowCascadeByIndex( int index ) {
403409
return SampleTranslucentShadowCascade( vShadowCoord3, uShadowAtlasRect[3] );
404410
}
405411

406-
float SampleTranslucentShadow() {
412+
vec3 SampleTranslucentShadow() {
407413
if ( uTranslucentShadowEnabled < 0.5 ) {
408-
return 1.0;
414+
return vec3( 1.0 );
409415
}
410416

411417
int cascadeIndex = SelectCascade( vViewDepth );
412-
float shadow = SampleTranslucentShadowCascadeByIndex( cascadeIndex );
418+
vec3 shadow = SampleTranslucentShadowCascadeByIndex( cascadeIndex );
413419
int lastInteriorIndex = uShadowCascadeCount - 2;
414420

415421
if ( cascadeIndex > lastInteriorIndex || uShadowCascadeBlend <= 0.0 ) {
@@ -424,7 +430,7 @@ float SampleTranslucentShadow() {
424430
return shadow;
425431
}
426432

427-
float nextShadow = SampleTranslucentShadowCascadeByIndex( cascadeIndex + 1 );
433+
vec3 nextShadow = SampleTranslucentShadowCascadeByIndex( cascadeIndex + 1 );
428434
float blend = clamp( ( vViewDepth - blendStart ) / blendWidth, 0.0, 1.0 );
429435
return mix( shadow, nextShadow, blend );
430436
}

.install/openq4/glprogs/openq4_shadow_point_interaction.fs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ uniform sampler2D uLightProjectionMap;
66
uniform sampler2D uDiffuseMap;
77
uniform sampler2D uSpecularMap;
88
uniform samplerCube uPointShadowMap;
9-
uniform samplerCube uPointTranslucentShadowMap;
9+
uniform samplerCube uPointTranslucentShadowMapR;
10+
uniform samplerCube uPointTranslucentShadowMapG;
11+
uniform samplerCube uPointTranslucentShadowMapB;
1012

1113
uniform vec4 uDiffuseColor;
1214
uniform vec4 uSpecularColor;
@@ -60,7 +62,8 @@ float ResolveTranslucentShadowMoments( vec4 moments, float depth ) {
6062
float mean = moments.y / totalTau;
6163
float variance = max( moments.z / totalTau - mean * mean, kTranslucentMomentMinVariance );
6264
float sigma = sqrt( variance );
63-
float tau = totalTau * clamp( NormalCdf( ( depth - mean ) / sigma ), 0.0, 1.0 );
65+
float fraction = clamp( NormalCdf( ( depth - mean ) / sigma ), 0.0, 1.0 );
66+
float tau = totalTau * fraction;
6467
return exp( -min( tau * max( uTranslucentShadowDensity, 0.0 ), 16.0 ) );
6568
}
6669

@@ -117,18 +120,21 @@ float SamplePointShadow() {
117120
return shadow * ( 1.0 / 13.0 );
118121
}
119122

120-
float SamplePointTranslucentShadow() {
123+
vec3 SamplePointTranslucentShadow() {
121124
if ( uTranslucentShadowEnabled < 0.5 || uPointShadowFar <= 0.0 ) {
122-
return 1.0;
125+
return vec3( 1.0 );
123126
}
124127

125128
float depth = length( vPointShadowVector ) / uPointShadowFar;
126129
if ( depth <= 0.0 || depth >= 1.0 ) {
127-
return 1.0;
130+
return vec3( 1.0 );
128131
}
129132

130133
vec3 direction = SafeNormalize( vPointShadowVector );
131-
return ResolveTranslucentShadowMoments( textureCube( uPointTranslucentShadowMap, direction ), depth );
134+
return vec3(
135+
ResolveTranslucentShadowMoments( textureCube( uPointTranslucentShadowMapR, direction ), depth ),
136+
ResolveTranslucentShadowMoments( textureCube( uPointTranslucentShadowMapG, direction ), depth ),
137+
ResolveTranslucentShadowMoments( textureCube( uPointTranslucentShadowMapB, direction ), depth ) );
132138
}
133139

134140
void main() {

.install/openq4/glprogs/openq4_shadow_point_translucent_caster.fs

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,78 @@
22

33
uniform float uPointShadowFar;
44
uniform sampler2D uAlphaMap;
5-
uniform float uAlphaScale;
5+
uniform sampler2D uCoverageMap;
6+
uniform vec4 uStageColor;
7+
uniform vec4 uCoverageStageColor;
68
uniform float uOpacitySourceMode;
9+
uniform float uVertexColorMode;
10+
uniform float uCoverageSourceMode;
11+
uniform float uCoverageVertexColorMode;
12+
uniform float uCoverageAlphaTestRef;
13+
uniform float uCoverageAlphaTestEnabled;
714
uniform float uTranslucentMinAlpha;
815

916
varying vec3 vPointShadowVector;
1017
varying vec2 vAlphaTexCoord;
18+
varying vec2 vCoverageTexCoord;
19+
varying vec3 vVertexColorRgb;
1120
varying float vVertexAlpha;
21+
varying float vCoverageVertexAlpha;
1222

1323
float OpticalDepth( float alpha ) {
1424
return -log( max( 1.0 - clamp( alpha, 0.0, 0.999 ), 1.0e-4 ) );
1525
}
1626

17-
float StageOpacity() {
18-
if ( uOpacitySourceMode > 1.5 ) {
19-
return clamp( uAlphaScale * vVertexAlpha, 0.0, 0.999 );
27+
vec3 VertexColorRgb( float mode ) {
28+
if ( mode > 1.5 ) {
29+
return 1.0 - vVertexColorRgb;
30+
}
31+
if ( mode > 0.5 ) {
32+
return vVertexColorRgb;
2033
}
34+
return vec3( 1.0 );
35+
}
2136

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 ) ) );
37+
float CoverageAmount() {
38+
if ( uCoverageSourceMode < 0.5 ) {
39+
return 1.0;
2640
}
2741

28-
return clamp( opacity * uAlphaScale * vVertexAlpha, 0.0, 0.999 );
42+
vec4 coverageSample = texture2D( uCoverageMap, vCoverageTexCoord );
43+
vec3 coverageTint = clamp( coverageSample.rgb * uCoverageStageColor.rgb * VertexColorRgb( uCoverageVertexColorMode ), 0.0, 1.0 );
44+
float coverageAlpha = coverageSample.a * uCoverageStageColor.a * vCoverageVertexAlpha;
45+
if ( uCoverageAlphaTestEnabled > 0.5 && coverageAlpha <= uCoverageAlphaTestRef ) {
46+
discard;
47+
}
48+
if ( uCoverageSourceMode > 1.5 ) {
49+
coverageAlpha = max( coverageAlpha, dot( coverageTint, vec3( 0.2126, 0.7152, 0.0722 ) ) * vCoverageVertexAlpha );
50+
}
51+
return clamp( coverageAlpha, 0.0, 1.0 );
52+
}
53+
54+
vec3 StageAbsorption() {
55+
float coverage = CoverageAmount();
56+
if ( uOpacitySourceMode > 1.5 ) {
57+
vec3 transmission = clamp( uStageColor.rgb * VertexColorRgb( uVertexColorMode ), 0.0, 1.0 );
58+
return clamp( ( 1.0 - transmission ) * coverage * vVertexAlpha, 0.0, 0.999 );
59+
}
60+
61+
vec4 sampleColor = texture2D( uAlphaMap, vAlphaTexCoord );
62+
vec3 transmission = clamp( sampleColor.rgb * uStageColor.rgb * VertexColorRgb( uVertexColorMode ), 0.0, 1.0 );
63+
return clamp( ( 1.0 - transmission ) * coverage * vVertexAlpha, 0.0, 0.999 );
2964
}
3065

3166
void main() {
32-
float alpha = StageOpacity();
33-
if ( alpha <= uTranslucentMinAlpha ) {
67+
vec3 absorption = StageAbsorption();
68+
float maxAbsorption = max( absorption.r, max( absorption.g, absorption.b ) );
69+
if ( maxAbsorption <= uTranslucentMinAlpha ) {
3470
discard;
3571
}
3672

37-
float tau = OpticalDepth( alpha );
73+
vec3 tauColor = vec3( OpticalDepth( absorption.r ), OpticalDepth( absorption.g ), OpticalDepth( absorption.b ) );
3874
float depth = clamp( length( vPointShadowVector ) / uPointShadowFar, 0.0, 1.0 );
3975
float depth2 = depth * depth;
40-
gl_FragColor = vec4( tau, tau * depth, tau * depth2, tau * depth2 * depth );
76+
gl_FragData[0] = vec4( tauColor.r, tauColor.r * depth, tauColor.r * depth2, tauColor.r * depth2 * depth );
77+
gl_FragData[1] = vec4( tauColor.g, tauColor.g * depth, tauColor.g * depth2, tauColor.g * depth2 * depth );
78+
gl_FragData[2] = vec4( tauColor.b, tauColor.b * depth, tauColor.b * depth2, tauColor.b * depth2 * depth );
4179
}

.install/openq4/glprogs/openq4_shadow_point_translucent_caster.vs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,17 @@ uniform vec4 uModelMatrixRow2;
66
uniform vec4 uGlobalLightOrigin;
77
uniform vec4 uAlphaTexCoordS;
88
uniform vec4 uAlphaTexCoordT;
9+
uniform vec4 uCoverageTexCoordS;
10+
uniform vec4 uCoverageTexCoordT;
911
uniform vec2 uVertexAlphaParams;
12+
uniform vec2 uCoverageVertexAlphaParams;
1013

1114
varying vec3 vPointShadowVector;
1215
varying vec2 vAlphaTexCoord;
16+
varying vec2 vCoverageTexCoord;
17+
varying vec3 vVertexColorRgb;
1318
varying float vVertexAlpha;
19+
varying float vCoverageVertexAlpha;
1420

1521
void main() {
1622
vec4 position = gl_Vertex;
@@ -21,6 +27,9 @@ void main() {
2127
dot( position, uModelMatrixRow2 ) );
2228
vPointShadowVector = worldPos - uGlobalLightOrigin.xyz;
2329
vAlphaTexCoord = vec2( dot( alphaTexCoord, uAlphaTexCoordS ), dot( alphaTexCoord, uAlphaTexCoordT ) );
30+
vCoverageTexCoord = vec2( dot( alphaTexCoord, uCoverageTexCoordS ), dot( alphaTexCoord, uCoverageTexCoordT ) );
31+
vVertexColorRgb = gl_Color.rgb;
2432
vVertexAlpha = clamp( gl_Color.a * uVertexAlphaParams.x + uVertexAlphaParams.y, 0.0, 1.0 );
33+
vCoverageVertexAlpha = clamp( gl_Color.a * uCoverageVertexAlphaParams.x + uCoverageVertexAlphaParams.y, 0.0, 1.0 );
2534
gl_Position = ftransform();
2635
}
Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,77 @@
11
#version 110
22

33
uniform sampler2D uAlphaMap;
4-
uniform float uAlphaScale;
4+
uniform sampler2D uCoverageMap;
5+
uniform vec4 uStageColor;
6+
uniform vec4 uCoverageStageColor;
57
uniform float uOpacitySourceMode;
8+
uniform float uVertexColorMode;
9+
uniform float uCoverageSourceMode;
10+
uniform float uCoverageVertexColorMode;
11+
uniform float uCoverageAlphaTestRef;
12+
uniform float uCoverageAlphaTestEnabled;
613
uniform float uTranslucentMinAlpha;
714

815
varying vec2 vAlphaTexCoord;
16+
varying vec2 vCoverageTexCoord;
17+
varying vec3 vVertexColorRgb;
918
varying float vVertexAlpha;
19+
varying float vCoverageVertexAlpha;
1020

1121
float OpticalDepth( float alpha ) {
1222
return -log( max( 1.0 - clamp( alpha, 0.0, 0.999 ), 1.0e-4 ) );
1323
}
1424

15-
float StageOpacity() {
16-
if ( uOpacitySourceMode > 1.5 ) {
17-
return clamp( uAlphaScale * vVertexAlpha, 0.0, 0.999 );
25+
vec3 VertexColorRgb( float mode ) {
26+
if ( mode > 1.5 ) {
27+
return 1.0 - vVertexColorRgb;
28+
}
29+
if ( mode > 0.5 ) {
30+
return vVertexColorRgb;
1831
}
32+
return vec3( 1.0 );
33+
}
1934

20-
vec4 sampleColor = texture2D( uAlphaMap, vAlphaTexCoord );
21-
float opacity = sampleColor.a;
22-
if ( uOpacitySourceMode > 0.5 ) {
23-
opacity = max( opacity, dot( sampleColor.rgb, vec3( 0.2126, 0.7152, 0.0722 ) ) );
35+
float CoverageAmount() {
36+
if ( uCoverageSourceMode < 0.5 ) {
37+
return 1.0;
2438
}
2539

26-
return clamp( opacity * uAlphaScale * vVertexAlpha, 0.0, 0.999 );
40+
vec4 coverageSample = texture2D( uCoverageMap, vCoverageTexCoord );
41+
vec3 coverageTint = clamp( coverageSample.rgb * uCoverageStageColor.rgb * VertexColorRgb( uCoverageVertexColorMode ), 0.0, 1.0 );
42+
float coverageAlpha = coverageSample.a * uCoverageStageColor.a * vCoverageVertexAlpha;
43+
if ( uCoverageAlphaTestEnabled > 0.5 && coverageAlpha <= uCoverageAlphaTestRef ) {
44+
discard;
45+
}
46+
if ( uCoverageSourceMode > 1.5 ) {
47+
coverageAlpha = max( coverageAlpha, dot( coverageTint, vec3( 0.2126, 0.7152, 0.0722 ) ) * vCoverageVertexAlpha );
48+
}
49+
return clamp( coverageAlpha, 0.0, 1.0 );
50+
}
51+
52+
vec3 StageAbsorption() {
53+
float coverage = CoverageAmount();
54+
if ( uOpacitySourceMode > 1.5 ) {
55+
vec3 transmission = clamp( uStageColor.rgb * VertexColorRgb( uVertexColorMode ), 0.0, 1.0 );
56+
return clamp( ( 1.0 - transmission ) * coverage * vVertexAlpha, 0.0, 0.999 );
57+
}
58+
59+
vec4 sampleColor = texture2D( uAlphaMap, vAlphaTexCoord );
60+
vec3 transmission = clamp( sampleColor.rgb * uStageColor.rgb * VertexColorRgb( uVertexColorMode ), 0.0, 1.0 );
61+
return clamp( ( 1.0 - transmission ) * coverage * vVertexAlpha, 0.0, 0.999 );
2762
}
2863

2964
void main() {
30-
float alpha = StageOpacity();
31-
if ( alpha <= uTranslucentMinAlpha ) {
65+
vec3 absorption = StageAbsorption();
66+
float maxAbsorption = max( absorption.r, max( absorption.g, absorption.b ) );
67+
if ( maxAbsorption <= uTranslucentMinAlpha ) {
3268
discard;
3369
}
3470

35-
float tau = OpticalDepth( alpha );
71+
vec3 tauColor = vec3( OpticalDepth( absorption.r ), OpticalDepth( absorption.g ), OpticalDepth( absorption.b ) );
3672
float depth = clamp( gl_FragCoord.z, 0.0, 1.0 );
3773
float depth2 = depth * depth;
38-
gl_FragColor = vec4( tau, tau * depth, tau * depth2, tau * depth2 * depth );
74+
gl_FragData[0] = vec4( tauColor.r, tauColor.r * depth, tauColor.r * depth2, tauColor.r * depth2 * depth );
75+
gl_FragData[1] = vec4( tauColor.g, tauColor.g * depth, tauColor.g * depth2, tauColor.g * depth2 * depth );
76+
gl_FragData[2] = vec4( tauColor.b, tauColor.b * depth, tauColor.b * depth2, tauColor.b * depth2 * depth );
3977
}

.install/openq4/glprogs/openq4_shadow_proj_translucent_caster.vs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,23 @@
22

33
uniform vec4 uAlphaTexCoordS;
44
uniform vec4 uAlphaTexCoordT;
5+
uniform vec4 uCoverageTexCoordS;
6+
uniform vec4 uCoverageTexCoordT;
57
uniform vec2 uVertexAlphaParams;
8+
uniform vec2 uCoverageVertexAlphaParams;
69

710
varying vec2 vAlphaTexCoord;
11+
varying vec2 vCoverageTexCoord;
12+
varying vec3 vVertexColorRgb;
813
varying float vVertexAlpha;
14+
varying float vCoverageVertexAlpha;
915

1016
void main() {
1117
vec4 alphaTexCoord = vec4( gl_MultiTexCoord0.xy, 0.0, 1.0 );
1218
vAlphaTexCoord = vec2( dot( alphaTexCoord, uAlphaTexCoordS ), dot( alphaTexCoord, uAlphaTexCoordT ) );
19+
vCoverageTexCoord = vec2( dot( alphaTexCoord, uCoverageTexCoordS ), dot( alphaTexCoord, uCoverageTexCoordT ) );
20+
vVertexColorRgb = gl_Color.rgb;
1321
vVertexAlpha = clamp( gl_Color.a * uVertexAlphaParams.x + uVertexAlphaParams.y, 0.0, 1.0 );
22+
vCoverageVertexAlpha = clamp( gl_Color.a * uCoverageVertexAlphaParams.x + uCoverageVertexAlphaParams.y, 0.0, 1.0 );
1423
gl_Position = ftransform();
1524
}

docs-user/shadow-mapping.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,15 @@ Behavior:
153153

154154
Current limits:
155155
- Supported stages currently include old-style alpha and premultiplied-alpha stages with explicit ST texture coordinates, plus common additive `blend add` / `GL_ONE, GL_ONE` stages.
156-
- Reflective pickup-orb style `texgen reflect` cubemap stages are approximated as constant-density translucent casters so MP health and megahealth orb shells can participate.
156+
- When a translucent shell/tint stage is layered on top of a separate explicit-ST coverage stage, OpenQ4 now reuses that coverage stage, including its alpha-test threshold when present, so layered pickup-orb and similar materials can cast shaped transmitted shadows instead of only uniform blobs.
157+
- Supported translucent casters now derive colored transmission from the material inputs available to that stage: texture alpha, sampled texture RGB, stage color, and applicable vertex color.
158+
- View-dependent reflection cubemaps are treated as tinted transmissive shells instead of using the reflected sample directly, so pickup orbs can tint transmitted light without camera-dependent shadow color shifts.
159+
- The current high-quality path stores separate translucent shadow moments for red, green, and blue, so each channel resolves blocker depth independently instead of sharing one grayscale depth distribution.
157160
- GUI/subview materials are skipped.
158161
- BSE/FX particles, unusual custom stage setups, and many effect-style materials are intentionally not forced into the translucent shadow pass.
162+
- Colored transmission is still approximate rather than a full deep-shadow solution, but it is materially closer to real tinted transmission than the earlier scalar/grayscale model.
159163
- This path adds extra GPU work because eligible lights render an additional translucent caster pass.
164+
- The feature now expects enough hardware for 3 translucent MRT attachments and 3 extra texture samplers in the receiver path; if that is unavailable, OpenQ4 disables this experimental translucent-shadow feature.
160165

161166
Controls:
162167

0 commit comments

Comments
 (0)