diff --git a/code/renderer/shaders/crp/transp_draw.hlsl b/code/renderer/shaders/crp/transp_draw.hlsl
index 1ca2dcf..2398ca7 100644
--- a/code/renderer/shaders/crp/transp_draw.hlsl
+++ b/code/renderer/shaders/crp/transp_draw.hlsl
@@ -85,7 +85,7 @@ VOut vs(VIn input)
bool IsFragmentUseless(uint blendBits, float4 color)
{
- const float epsilon = 1.0 / 255.0;
+ const float epsilon = 1.0 / 1024.0;
if(blendBits == GLS_BLEND_ADDITIVE &&
all(color.rgb < epsilon.xxx))
diff --git a/code/renderer/shaders/crp/transp_resolve.hlsl b/code/renderer/shaders/crp/transp_resolve.hlsl
index 8ed7a2a..3fc1aca 100644
--- a/code/renderer/shaders/crp/transp_resolve.hlsl
+++ b/code/renderer/shaders/crp/transp_resolve.hlsl
@@ -21,6 +21,56 @@ along with Challenge Quake 3. If not, see .
// reads per-pixel fragment linked lists into arrays, sorts them and composites them
+/*
+OIT integration with volumetric lighting
+
+Each texel has the final accumulated in-scattering (RGB) and transmittance (A)
+from the near clip plane to the current voxel's depth.
+The in-scattered light and transmittance values for any given depth range are therefore:
+inScattering = farScatter - nearScatter
+transmittance = farTrans / nearTrans
+
+Definitions:
+B : opaque/background surface color
+C : color accumulator
+D : color accumulator #2
+T : transmittance from near plane to opaque surface
+S : in-scattered light from near plane to opaque surface
+T': transmittance from near plane to fragment
+S': in-scattered light from near plane to fragment
+
+If we use a single color accumulator C and apply transmittance T and in-scattering S post-blend,
+we run the following logic for the opaque surface and every fragment:
+C = Blend(C, ...)
+C = CT + S
+Doing this creates artifacts along the geometric edges of explosions (additive blend, black borders). But why?
+Suppose we have no fragment, the final color F is therefore:
+F = BT + S
+What if we add a single fragment that contributes nothing (e.g. black and additive blended)?
+C = BT/T' + S - S'
+C = Blend(C, ...) // doesn't change C
+F = (BT/T' + S - S') * T'/1 + S' - 0
+F = BT + ST' - S'T' + S'
+It's mismatched: we get (ST' - S'T' + S') instead of (S).
+
+Let's try a separate color accumulator D just for the in-scattered light like so:
+C = Blend(C, ...)
+C = CT
+D = D + S
+F = C + D
+No fragment case:
+F = C + D == BT + S
+Single useless fragment case:
+C = BT/T'
+D = S - S'
+C = Blend(C, ...) // doesn't change C
+C = CT'/1 == BT/T' * T'/1 == BT
+D = D + S' - 0 == S - S' + S' - 0 == S
+F = C + D == BT + S
+Now it matches perfectly.
+*/
+
+
#include "common.hlsli"
#include "fullscreen.hlsli"
#include "oit.h.hlsli"
@@ -97,24 +147,25 @@ float4 DepthFadeFragmentColor(float4 color, OIT_Fragment fragment, float opaqueV
return dst;
}
-float FragmentImpact(float4 fragColor, uint blendBits)
+float3 BlendInScatteredLight(float4 srcColor, float3 dstColor, uint blendBits)
{
- if(blendBits == GLS_BLEND_ADDITIVE)
+ // source blend must not double source contributions, so we can't call BlendSource
+ uint srcBlend = blendBits & GLS_SRCBLEND_BITS;
+ float3 srcContrib = float3(0, 0, 0);
+ if(srcBlend == GLS_SRCBLEND_DST_COLOR)
{
- return Brightness(fragColor.rgb);
+ srcContrib = dstColor * (float3(1, 1, 1) - dstColor);
+ }
+ else if(srcBlend == GLS_SRCBLEND_ONE_MINUS_DST_COLOR)
+ {
+ srcContrib = dstColor * (float3(1, 1, 1) - dstColor);
}
- if(blendBits == GLS_BLEND_STD_ALPHA || blendBits == GLS_BLEND_PMUL_ALPHA)
- {
- return fragColor.a;
- }
+ uint dstBlend = blendBits & GLS_DSTBLEND_BITS;
+ float3 dstContrib = BlendDest(srcColor, float4(dstColor, 1), dstBlend).rgb;
+ float3 result = srcContrib + dstContrib;
- if(blendBits == GLS_BLEND_FILTER || blendBits == GLS_BLEND_FILTER_V2)
- {
- return abs(1.0 - Brightness(fragColor.rgb));
- }
-
- return 1.0;
+ return result;
}
struct OIT_Resolve
@@ -153,7 +204,6 @@ struct OIT_Resolve
{
sorted[fragmentCount] = fragments[fragmentIndex];
fragmentIndex = sorted[fragmentCount].next;
- invisible[fragmentCount] = false;
++fragmentCount;
}
@@ -172,12 +222,12 @@ struct OIT_Resolve
}
}
- void Resolve(VOut input, float impactThreshold)
+ float4 Resolve(VOut input)
{
- color = opaqueColor;
- smallestImpact = 666.0;
+ float4 color = saturate(opaqueColor);
#if defined(VOLUMETRIC_LIGHT)
+ float3 inScatterAccum = float3(0, 0, 0);
// initialize volume traversal
float3 volumeSize = GetTextureSize(scatterTexture);
float opaqueFroxelDepth01 = scene.FroxelViewDepthToZ01(opaqueViewDepth, volumeSize.z);
@@ -186,27 +236,18 @@ struct OIT_Resolve
float3 scatterTC = float3(input.texCoords, opaqueFroxelDepth01);
float4 scatterData = scatterTexture.SampleLevel(scatterSampler, scatterTC, 0);
{
- float4 closerScatterData = float4(0, 0, 0, 1);
- for(uint i = 0; i < fragmentCount; ++i)
- {
- // @TODO: fix this loop to account for the depth test
- OIT_Fragment frag = sorted[i];
- float fragDepth = frag.depth;
- float froxelDepth01 = scene.FroxelViewDepthToZ01(fragDepth, volumeSize.z);
- float3 scatterTC = float3(input.texCoords, froxelDepth01);
- closerScatterData = scatterTexture.SampleLevel(scatterSampler, scatterTC, 0);
- break;
- }
+ float4 closerScatterData = FindCloserScatterData(0, -1.0, input, volumeSize);
float3 inScattering = scatterData.rgb - closerScatterData.rgb;
- float transmittance = scatterData.a / max(closerScatterData.a, 0.000001);
- color.rgb = color.rgb * transmittance + inScattering;
+ float transmittance = min(scatterData.a / max(closerScatterData.a, 0.000001), 1.0);
+ inScatterAccum = inScattering;
+ color.rgb = color.rgb * transmittance;
scatterData = closerScatterData;
}
#endif
// blend the results
lastFragmentIndex = -1;
- float dstDepth = 1.0;
+ float dstDepth = -1.0;
for(uint i = 0; i < fragmentCount; ++i)
{
OIT_Fragment frag = sorted[i];
@@ -221,9 +262,10 @@ struct OIT_Resolve
float4 fragColor = UnpackColor(frag.color);
float4 prevColor = color;
fragColor = DepthFadeFragmentColor(fragColor, frag, opaqueViewDepth);
- color = Blend(fragColor, color, frag.stateBits);
+ color = Blend(fragColor, color, stateBits);
+ color = saturate(color);
if((stateBits & GLS_DEPTHMASK_TRUE) != 0u &&
- fragDepth < dstDepth)
+ fragDepth != dstDepth)
{
dstDepth = fragDepth;
}
@@ -235,45 +277,46 @@ struct OIT_Resolve
}
#if defined(VOLUMETRIC_LIGHT)
- float fragmentImpact = FragmentImpact(fragColor, stateBits & GLS_BLEND_BITS);
- invisible[i] = fragmentImpact < impactThreshold;
- smallestImpact = min(smallestImpact, fragmentImpact);
- float4 closerScatterData = float4(0, 0, 0, 1);
- for(uint j = i + 1; j < fragmentCount; ++j)
- {
- // @TODO: fix this loop to account for the depth test
- OIT_Fragment frag = sorted[j];
- float fragDepth = frag.depth;
- float froxelDepth01 = scene.FroxelViewDepthToZ01(fragDepth, volumeSize.z);
- float3 scatterTC = float3(input.texCoords, froxelDepth01);
- closerScatterData = scatterTexture.SampleLevel(scatterSampler, scatterTC, 0);
- break;
- }
+ float4 closerScatterData = FindCloserScatterData(i + 1, dstDepth, input, volumeSize);
float3 inScattering = scatterData.rgb - closerScatterData.rgb;
- float transmittance = scatterData.a / max(closerScatterData.a, 0.000001);
- color.rgb = color.rgb * transmittance + inScattering;
+ float transmittance = min(scatterData.a / max(closerScatterData.a, 0.000001), 1.0);
+ inScatterAccum = inScattering + BlendInScatteredLight(fragColor, inScatterAccum, stateBits & GLS_BLEND_BITS);
+ color.rgb *= transmittance;
scatterData = closerScatterData;
#endif
}
+
+#if defined(VOLUMETRIC_LIGHT)
+ color.rgb += inScatterAccum;
+#endif
+
+ return color;
}
- void RemoveInvisible()
+#if defined(VOLUMETRIC_LIGHT)
+ float4 FindCloserScatterData(uint startIndex, float dstDepth, VOut input, float3 volumeSize)
{
- uint newCount = 0;
- for(uint i = 0; i < fragmentCount; ++i)
+ float4 closerScatterData = float4(0, 0, 0, 1);
+ for(uint j = startIndex; j < fragmentCount; ++j)
{
- if(invisible[i])
+ OIT_Fragment frag = sorted[j];
+ uint stateBits = frag.stateBits;
+ float fragDepth = frag.depth;
+ if((stateBits & (GLS_DEPTHFUNC_EQUAL | GLS_DEPTHTEST_DISABLE)) == GLS_DEPTHFUNC_EQUAL &&
+ fragDepth != dstDepth)
{
continue;
}
- if(newCount != i)
- {
- sorted[newCount] = sorted[i];
- }
- newCount++;
+
+ float froxelDepth01 = scene.FroxelViewDepthToZ01(fragDepth, volumeSize.z);
+ float3 scatterTC = float3(input.texCoords, froxelDepth01);
+ closerScatterData = scatterTexture.SampleLevel(scatterSampler, scatterTC, 0);
+ break;
}
- fragmentCount = newCount;
+
+ return closerScatterData;
}
+#endif
void WriteShaderID(VOut input)
{
@@ -300,13 +343,10 @@ struct OIT_Resolve
Texture2D renderTarget;
int3 tcPx;
float4 opaqueColor;
- float4 color;
OIT_Fragment sorted[OIT_MAX_FRAGMENTS_PER_PIXEL];
- bool invisible[OIT_MAX_FRAGMENTS_PER_PIXEL];
uint fragmentCount;
int lastFragmentIndex;
float opaqueViewDepth;
- float smallestImpact;
#if defined(VOLUMETRIC_LIGHT)
Texture3D scatterTexture;
SamplerState scatterSampler;
@@ -322,27 +362,7 @@ float4 ps(VOut input) : SV_Target
}
resolve.Init(input);
-
-#if defined(VOLUMETRIC_LIGHT)
- // To fight off discontinuities between adjacent pixels,
- // we interpolate between the result computed normally
- // and computed by rejecting low-impact fragments
- // using a t value that only depends on the low-impact fragments.
- // It's far from perfect but once we get rid of most sprites
- // in favor of particles, we should be fine.
- const float VL_ImpactThreshold = 4.0 / 255.0;
- resolve.Resolve(input, VL_ImpactThreshold);
- float diff = resolve.smallestImpact;
- float4 color = resolve.color;
- resolve.RemoveInvisible();
- resolve.Resolve(input, 0.0);
- float4 color2 = resolve.color;
- color = lerp(color2, color, saturate(diff / VL_ImpactThreshold));
-#else
- resolve.Resolve(input, 666.0);
- float4 color = resolve.color;
-#endif
-
+ float4 color = resolve.Resolve(input);
resolve.WriteShaderID(input);
return color;
diff --git a/code/renderer/tr_local.h b/code/renderer/tr_local.h
index eaaea71..869509f 100644
--- a/code/renderer/tr_local.h
+++ b/code/renderer/tr_local.h
@@ -1269,6 +1269,8 @@ void R_CompleteShaderName_f( int startArg, int compArg );
const char* R_GetShaderPath( const shader_t* shader );
qbool R_EditShader( shader_t* sh, const shader_t* original, const char* shaderText );
void R_SetShaderData( shader_t* sh, const shader_t* original );
+const char* R_GetSourceBlendName( unsigned int stateBits );
+const char* R_GetDestBlendName( unsigned int stateBits );
/*
====================================================================
diff --git a/code/renderer/tr_shader.cpp b/code/renderer/tr_shader.cpp
index 62dab03..e1dca70 100644
--- a/code/renderer/tr_shader.cpp
+++ b/code/renderer/tr_shader.cpp
@@ -3273,3 +3273,38 @@ const char* R_GetShaderPath( const shader_t* sh )
return va( "scripts/%s", fileName );
}
+
+
+const char* R_GetSourceBlendName( unsigned int stateBits )
+{
+ switch ( stateBits & GLS_SRCBLEND_BITS ) {
+ case GLS_SRCBLEND_ZERO: return "0";
+ case GLS_SRCBLEND_ONE: return "1";
+ case GLS_SRCBLEND_DST_COLOR: return "dst.rgb";
+ case GLS_SRCBLEND_ONE_MINUS_DST_COLOR: return "(1 - dst.rgb)";
+ case GLS_SRCBLEND_SRC_ALPHA: return "src.a";
+ case GLS_SRCBLEND_ONE_MINUS_SRC_ALPHA: return "(1 - src.a)";
+ case GLS_SRCBLEND_DST_ALPHA: return "dst.a";
+ case GLS_SRCBLEND_ONE_MINUS_DST_ALPHA: return "(1 - dst.a)";
+ case GLS_SRCBLEND_ALPHA_SATURATE: return "min(src.a, 1 - dst.a)";
+ case 0: return "1";
+ default: Q_assert( !"Invalid source blend bits" ); return "";
+ }
+}
+
+
+const char* R_GetDestBlendName( unsigned int stateBits )
+{
+ switch ( stateBits & GLS_DSTBLEND_BITS ) {
+ case GLS_DSTBLEND_ZERO: return "0";
+ case GLS_DSTBLEND_ONE: return "1";
+ case GLS_DSTBLEND_SRC_COLOR: return "src.rgb";
+ case GLS_DSTBLEND_ONE_MINUS_SRC_COLOR: return "(1 - src.rgb)";
+ case GLS_DSTBLEND_SRC_ALPHA: return "src.a";
+ case GLS_DSTBLEND_ONE_MINUS_SRC_ALPHA: return "(1 - src.a)";
+ case GLS_DSTBLEND_DST_ALPHA: return "dst.a";
+ case GLS_DSTBLEND_ONE_MINUS_DST_ALPHA: return "(1 - dst.a)";
+ case 0: return "0";
+ default: Q_assert( !"Invalid dest blend bits" ); return "";
+ }
+}