350 lines
9.0 KiB
HLSL
350 lines
9.0 KiB
HLSL
#include "screenspace.hlsl"
|
|
|
|
// tweakables
|
|
#define SSAO_NUM_PAIRS 8
|
|
#define SSAO_SPHERE_RAD 2.0f // world-space
|
|
#define SSAO_MIN_PIXEL_RANGE 10.0f
|
|
#define SSAO_MAX_PIXEL_RANGE 100.0f
|
|
#define BLUR_DEPTH_DELTA 0.4f
|
|
|
|
#define COMPOSITE_DEPTH_DELTA 0.02f
|
|
#define COMPOSITE_DEPTH_DELTA2 0.4f
|
|
|
|
TEX_DECLARE2D(depthBuffer, 0);
|
|
TEX_DECLARE2D(randMap, 1);
|
|
TEX_DECLARE2D(map, 2);
|
|
TEX_DECLARE2D(geomMap, 3);
|
|
TEX_DECLARE2D(colorMap, 4);
|
|
|
|
VertexOutput_4uv ssaoDepthDown_vs(float4 p: POSITION)
|
|
{
|
|
float2 uv = convertUv(p);
|
|
|
|
VertexOutput_4uv OUT;
|
|
OUT.p = convertPosition(p, 2);
|
|
OUT.uv = uv;
|
|
|
|
float2 uvOffset = TextureSize.zw * 0.25f;
|
|
|
|
OUT.uv12.xy = uv + uvOffset * float2(-1, -1);
|
|
OUT.uv12.zw = uv + uvOffset * float2(+1, -1);
|
|
OUT.uv34.xy = uv + uvOffset * float2(-1, +1);
|
|
OUT.uv34.zw = uv + uvOffset * float2(+1, +1);
|
|
|
|
return OUT;
|
|
}
|
|
|
|
// used for ssao blurring passes
|
|
VertexOutput_8uv ssaoBlur_vs(float4 position, float2 uvOffset)
|
|
{
|
|
float2 uv = convertUv(position);
|
|
|
|
VertexOutput_8uv OUT;
|
|
OUT.p = convertPosition(position, 2);
|
|
OUT.uv = uv;
|
|
|
|
OUT.uv12.xy = uv + 1 * uvOffset;
|
|
OUT.uv12.zw = uv + 2 * uvOffset;
|
|
OUT.uv34.xy = uv + 3 * uvOffset;
|
|
OUT.uv34.zw = uv + 4 * uvOffset;
|
|
|
|
OUT.uv56.xy = uv - 1 * uvOffset;
|
|
OUT.uv56.zw = uv - 2 * uvOffset;
|
|
OUT.uv78.xy = uv - 3 * uvOffset;
|
|
OUT.uv78.zw = uv - 4 * uvOffset;
|
|
|
|
return OUT;
|
|
}
|
|
|
|
VertexOutput_8uv ssaoBlurX_vs(float4 p: POSITION)
|
|
{
|
|
return ssaoBlur_vs(p, float2(TextureSize.z * 1, 0));
|
|
}
|
|
|
|
VertexOutput_8uv ssaoBlurY_vs(float4 p: POSITION)
|
|
{
|
|
return ssaoBlur_vs(p, float2(0, TextureSize.w * 1));
|
|
}
|
|
|
|
float unpackDepth( TEXTURE_IN_2D(s), float2 uv )
|
|
{
|
|
float4 geomTex = tex2D(s, uv);
|
|
float d = geomTex.z * (1.0f/256.0f) + geomTex.w;
|
|
return d;
|
|
}
|
|
|
|
float getDepth( TEXTURE_IN_2D(s), float2 uv )
|
|
{
|
|
return (float)tex2D(s,uv).r;
|
|
}
|
|
|
|
#define NUM_PAIRS SSAO_NUM_PAIRS
|
|
#define RANGE 60.0/1024.0
|
|
|
|
#define pi 3.14159265359
|
|
#define RAD(X) ( (X) * (pi/180) )
|
|
|
|
float2 GetRotatedSample(float i)
|
|
{
|
|
return (i+1) / (NUM_PAIRS+2) * float2(cos( RAD(45) + i / NUM_PAIRS * 2 * pi ), sin( RAD(45) + i / NUM_PAIRS * 2 * pi ) );
|
|
}
|
|
|
|
#define NUM_SAMPLES NUM_PAIRS*2+1
|
|
|
|
float4 ssao_ps(
|
|
VertexOutput IN): COLOR0
|
|
{
|
|
float2 mapSize = TextureSize.xy;
|
|
|
|
float baseDepth = getDepth( TEXTURE(depthBuffer), IN.uv );
|
|
|
|
float4 noiseTex = tex2D(randMap, IN.uv*mapSize/4) * 2 - 1;
|
|
|
|
float2x2 rotation =
|
|
{
|
|
{ noiseTex.y, noiseTex.x },
|
|
{ -noiseTex.x, noiseTex.y }
|
|
};
|
|
|
|
float2 OFFSETS1[NUM_PAIRS] =
|
|
{
|
|
GetRotatedSample(0),
|
|
GetRotatedSample(1),
|
|
GetRotatedSample(2),
|
|
GetRotatedSample(3),
|
|
GetRotatedSample(4),
|
|
GetRotatedSample(5),
|
|
#if NUM_PAIRS > 6
|
|
GetRotatedSample(6),
|
|
GetRotatedSample(7),
|
|
#if NUM_PAIRS > 8
|
|
GetRotatedSample(8),
|
|
GetRotatedSample(9),
|
|
GetRotatedSample(10),
|
|
GetRotatedSample(11),
|
|
#endif
|
|
#endif
|
|
};
|
|
|
|
float occ = 1;
|
|
|
|
float sphereRadiusZB = (float) ( 2.0f / GBUFFER_MAX_DEPTH );
|
|
|
|
#define MINPIXEL SSAO_MIN_PIXEL_RANGE
|
|
#define MAXPIXEL SSAO_MAX_PIXEL_RANGE
|
|
|
|
float radiusTex = (float)clamp( 0.5*sphereRadiusZB / baseDepth, MINPIXEL / mapSize.x, MAXPIXEL / mapSize.y);
|
|
|
|
float numSamples = 2;
|
|
|
|
for(int i = 0; i < NUM_PAIRS; i++)
|
|
{
|
|
float2 offset1 = mul(rotation, OFFSETS1[i]);
|
|
|
|
float2 offseted1 = IN.uv + offset1 * radiusTex;
|
|
float2 offseted2 = IN.uv - offset1 * radiusTex;
|
|
|
|
float2 offsetDepth;
|
|
offsetDepth.x = getDepth( TEXTURE(depthBuffer), offseted1 );
|
|
offsetDepth.y = getDepth( TEXTURE(depthBuffer), offseted2 );
|
|
|
|
float2 diff = offsetDepth - baseDepth.xx;
|
|
|
|
float normalizedOffsetLen = (float)(i+1)/(NUM_PAIRS+2);
|
|
|
|
float segmentDiff = (float) ( 1.5f*sphereRadiusZB*sqrt(1-normalizedOffsetLen*normalizedOffsetLen) );
|
|
|
|
float2 normalizedDiff = (diff / segmentDiff) + 0.5;
|
|
|
|
float minDiff = min(normalizedDiff.x, normalizedDiff.y);
|
|
|
|
// At 0, full sample
|
|
// At -1, zero sample, zero weight
|
|
|
|
float sampleadd = (float) saturate(1+minDiff);
|
|
|
|
float a = (float)(saturate(normalizedDiff.x) + saturate(normalizedDiff.y))*sampleadd;
|
|
occ += a;
|
|
numSamples += 2 * sampleadd;
|
|
}
|
|
|
|
occ = occ / numSamples;
|
|
|
|
float finalocc = (float)saturate(occ*2);
|
|
|
|
if(baseDepth - (1.0f-1/256.0f) > 0)
|
|
finalocc += 1;
|
|
|
|
return float4(finalocc, finalocc, finalocc, 1);
|
|
}
|
|
|
|
// this function estimates depth discrepancy tolerance for the blur filter
|
|
float depthTolerance( float baseDepth, float sphereRadiusZB )
|
|
{
|
|
float ramp = 80; // tweak
|
|
return ( clamp( sphereRadiusZB * (baseDepth * ramp) , 0.1f * sphereRadiusZB, 40*sphereRadiusZB ) );
|
|
}
|
|
|
|
float ssaoBlur(
|
|
float2 uv,
|
|
|
|
float4 uv12,
|
|
float4 uv34,
|
|
float4 uv56,
|
|
float4 uv78,
|
|
|
|
TEXTURE_IN_2D(map),
|
|
TEXTURE_IN_2D(depthBuffer)
|
|
)
|
|
{
|
|
float sphereRadiusZB = BLUR_DEPTH_DELTA / GBUFFER_MAX_DEPTH;
|
|
float4 i = { 1, 2, 3, 4 };
|
|
float4 iw = 4-i;
|
|
float4 denom = 1;
|
|
|
|
|
|
float4 sum = tex2D(map, uv).rrrr * denom;
|
|
|
|
float baseDepth = getDepth( TEXTURE(depthBuffer), uv );
|
|
|
|
float4 newDepth, delta, ssample, coef;
|
|
|
|
newDepth.x = getDepth( TEXTURE(depthBuffer), uv12.xy );
|
|
newDepth.y = getDepth( TEXTURE(depthBuffer), uv12.zw );
|
|
newDepth.z = getDepth( TEXTURE(depthBuffer), uv34.xy );
|
|
newDepth.w = getDepth( TEXTURE(depthBuffer), uv34.zw );
|
|
|
|
delta = (newDepth - baseDepth.xxxx);
|
|
coef = iw * ( abs(delta) < depthTolerance( baseDepth, sphereRadiusZB ).xxxx );
|
|
|
|
|
|
ssample.x = tex2D( map, uv12.xy ).r;
|
|
ssample.y = tex2D( map, uv12.zw ).r;
|
|
ssample.z = tex2D( map, uv34.xy ).r;
|
|
ssample.w = tex2D( map, uv34.zw ).r;
|
|
|
|
sum += ssample * coef;
|
|
denom += coef;
|
|
|
|
////////////////////////////////////////
|
|
|
|
newDepth.x = getDepth( TEXTURE(depthBuffer), uv56.xy );
|
|
newDepth.y = getDepth( TEXTURE(depthBuffer), uv56.zw );
|
|
newDepth.z = getDepth( TEXTURE(depthBuffer), uv78.xy );
|
|
newDepth.w = getDepth( TEXTURE(depthBuffer), uv78.zw );
|
|
|
|
delta = newDepth - baseDepth.xxxx;
|
|
coef = iw * ( abs(delta) < depthTolerance( baseDepth, sphereRadiusZB ).xxxx );
|
|
|
|
ssample.x = tex2D( map, uv56.xy ).r;
|
|
ssample.y = tex2D( map, uv56.zw ).r;
|
|
ssample.z = tex2D( map, uv78.xy ).r;
|
|
ssample.w = tex2D( map, uv78.zw ).r;
|
|
|
|
sum += ssample * coef;
|
|
denom += coef;
|
|
|
|
return dot( sum, float4(1,1,1,1) ) / dot( denom, float4(1,1,1,1) );
|
|
}
|
|
|
|
|
|
float4 ssaoBlurX_ps(VertexOutput_8uv IN): COLOR0
|
|
{
|
|
float ssaoTerm = ssaoBlur( IN.uv, IN.uv12, IN.uv34, IN.uv56, IN.uv78, TEXTURE(map), TEXTURE(depthBuffer));
|
|
|
|
return float4(ssaoTerm.xxx, 1);
|
|
}
|
|
|
|
#define SPECULAR_WEIGHT 3
|
|
|
|
|
|
float4 ssaoBlurY_ps(VertexOutput_8uv IN): COLOR0
|
|
{
|
|
float ssaoTerm = ssaoBlur( IN.uv, IN.uv12, IN.uv34, IN.uv56, IN.uv78, TEXTURE(map), TEXTURE(depthBuffer));
|
|
|
|
float4 geom = tex2D(geomMap, IN.uv);
|
|
|
|
float specular = geom.x;
|
|
float diffuse = geom.y + 0.001;
|
|
|
|
// Making specular kill SSAO faster, so it doesn't get capped by 1
|
|
return (SPECULAR_WEIGHT*specular + diffuse * ssaoTerm) / (SPECULAR_WEIGHT*specular + diffuse);
|
|
}
|
|
|
|
|
|
|
|
float4 ssaoDepthDown_ps( VertexOutput_4uv IN ) : COLOR0
|
|
{
|
|
|
|
float4 d;
|
|
d.x = unpackDepth( TEXTURE(depthBuffer), IN.uv12.xy );
|
|
d.y = unpackDepth( TEXTURE(depthBuffer), IN.uv12.zw );
|
|
d.z = unpackDepth( TEXTURE(depthBuffer), IN.uv34.xy );
|
|
d.w = unpackDepth( TEXTURE(depthBuffer), IN.uv34.zw );
|
|
|
|
float2 tmp = min( d.xy, d.zw );
|
|
return min( tmp.x, tmp.y ).x;
|
|
}
|
|
|
|
VertexOutput_4uv ssaoComposit_vs(float4 p: POSITION)
|
|
{
|
|
float2 uv = convertUv(p);
|
|
|
|
VertexOutput_4uv OUT;
|
|
OUT.p = convertPosition(p, 1);
|
|
OUT.uv = uv;
|
|
|
|
float2 uvOffset = TextureSize.zw * 2;
|
|
|
|
OUT.uv12.xy = uv + float2(uvOffset.x, 0);
|
|
OUT.uv12.zw = uv - float2(uvOffset.x, 0);
|
|
OUT.uv34.xy = uv + float2(0, uvOffset.y);
|
|
OUT.uv34.zw = uv - float2(0, uvOffset.y);
|
|
|
|
return OUT;
|
|
}
|
|
|
|
#define CMP_LESS(X,Y) ( (X) < (Y) )
|
|
|
|
float4 ssaoComposit_ps(VertexOutput_4uv IN): COLOR0
|
|
{
|
|
//return float4(1,0,0,0.5);
|
|
float depth_range = COMPOSITE_DEPTH_DELTA / GBUFFER_MAX_DEPTH;
|
|
float depth_range2 = COMPOSITE_DEPTH_DELTA2 / GBUFFER_MAX_DEPTH;
|
|
|
|
// we're here
|
|
float baseDepth = unpackDepth( TEXTURE(geomMap), IN.uv );
|
|
float ssaoTerm = 1.0f;
|
|
|
|
float depth = getDepth( TEXTURE(depthBuffer), IN.uv );
|
|
float diff = abs( depth - baseDepth );
|
|
ssaoTerm = tex2D( map, IN.uv ).x;
|
|
|
|
float chk1 = CMP_LESS( depth_range, diff ); // can we trust the base depth? 0 - yes, 1 - no
|
|
float4 ssaoTermNew = 0, chk2, depth2, diff2;
|
|
|
|
depth2.x = getDepth( TEXTURE(depthBuffer), IN.uv12.xy );
|
|
depth2.y = getDepth( TEXTURE(depthBuffer), IN.uv12.zw );
|
|
depth2.z = getDepth( TEXTURE(depthBuffer), IN.uv34.xy );
|
|
depth2.w = getDepth( TEXTURE(depthBuffer), IN.uv34.zw );
|
|
|
|
ssaoTermNew.x = tex2D( map, IN.uv12.xy ).x;
|
|
ssaoTermNew.y = tex2D( map, IN.uv12.zw ).x;
|
|
ssaoTermNew.z = tex2D( map, IN.uv34.xy ).x;
|
|
ssaoTermNew.w = tex2D( map, IN.uv34.zw ).x;
|
|
|
|
diff2 = abs( depth2 - baseDepth.xxxx );
|
|
chk2 = CMP_LESS( diff2, depth_range2.xxxx );
|
|
|
|
ssaoTermNew *= chk2;
|
|
float den = dot( chk2, 1 ); // + 1e-5f; - TODO: add this if we encounter glitches; //
|
|
ssaoTermNew.x = dot( ssaoTermNew, 1 ) / den;
|
|
|
|
// the final decision: pick the base sample or its estimate, if base depth in unauthorative
|
|
ssaoTerm = saturate(den*chk1) ? ssaoTermNew.x : ssaoTerm;
|
|
|
|
//return float4(ssaoTermNew.rgb,1);
|
|
float4 colorMapSample = tex2D(colorMap, IN.uv);
|
|
return float4(colorMapSample.rgb * ssaoTerm, colorMapSample.a);
|
|
}
|