mirror of
https://bitbucket.org/CPMADevs/cnq3
synced 2024-11-10 06:31:48 +00:00
fixed several OIT issues
- all: fixed depth test (yet another reverse Z pitfall...) - VL: fixed output color mismatch when a low-impact fragment is added - VL: fixed next closer fragment search ignoring the depth test
This commit is contained in:
parent
c937948f8f
commit
7c217a313d
4 changed files with 141 additions and 84 deletions
|
@ -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))
|
||||
|
|
|
@ -21,6 +21,56 @@ along with Challenge Quake 3. If not, see <https://www.gnu.org/licenses/>.
|
|||
// 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)
|
||||
float4 closerScatterData = FindCloserScatterData(i + 1, dstDepth, input, volumeSize);
|
||||
float3 inScattering = scatterData.rgb - closerScatterData.rgb;
|
||||
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;
|
||||
}
|
||||
|
||||
#if defined(VOLUMETRIC_LIGHT)
|
||||
float4 FindCloserScatterData(uint startIndex, float dstDepth, VOut input, float3 volumeSize)
|
||||
{
|
||||
float4 closerScatterData = float4(0, 0, 0, 1);
|
||||
for(uint j = startIndex; j < fragmentCount; ++j)
|
||||
{
|
||||
// @TODO: fix this loop to account for the depth test
|
||||
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;
|
||||
}
|
||||
|
||||
float froxelDepth01 = scene.FroxelViewDepthToZ01(fragDepth, volumeSize.z);
|
||||
float3 scatterTC = float3(input.texCoords, froxelDepth01);
|
||||
closerScatterData = scatterTexture.SampleLevel(scatterSampler, scatterTC, 0);
|
||||
break;
|
||||
}
|
||||
float3 inScattering = scatterData.rgb - closerScatterData.rgb;
|
||||
float transmittance = scatterData.a / max(closerScatterData.a, 0.000001);
|
||||
color.rgb = color.rgb * transmittance + inScattering;
|
||||
scatterData = closerScatterData;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveInvisible()
|
||||
{
|
||||
uint newCount = 0;
|
||||
for(uint i = 0; i < fragmentCount; ++i)
|
||||
{
|
||||
if(invisible[i])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if(newCount != i)
|
||||
{
|
||||
sorted[newCount] = sorted[i];
|
||||
}
|
||||
newCount++;
|
||||
}
|
||||
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<float4> 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;
|
||||
|
|
|
@ -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 );
|
||||
|
||||
/*
|
||||
====================================================================
|
||||
|
|
|
@ -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 "";
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue