Skip to content

Commit

Permalink
*Switched to higher quality PCF filter for softer shadows
Browse files Browse the repository at this point in the history
*Fixed some artifacts which occured when blending between shadow map cascades
*Added some more shader utility functions (namely, PCF filters)
  • Loading branch information
iftodebogdan committed Apr 26, 2015
1 parent 0088906 commit 124f264
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 60 deletions.
4 changes: 2 additions & 2 deletions GITechDemo/Code/AppMain/GITechDemo/GITechDemo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ Matrix44f worldViewProjMat;
const bool GBUFFER_Z_PREPASS = false;

#define MAX_NUM_CASCADES (9)
#define PCF_BLUR_SIZE (4)
#define PCF_BLUR_SIZE (16)
const bool DEBUG_CASCADES = false;
const unsigned int NUM_CASCADES = 4;
const float CASCADE_SPLIT_FACTOR = 0.75f;
const float CASCADE_SPLIT_FACTOR = 0.7f;
const float CASCADE_MAX_VIEW_DEPTH = 3000.f;
const float CASCADE_BLEND_SIZE = 50.f;

Expand Down
64 changes: 47 additions & 17 deletions GITechDemo/Data/shaders/DeferredLightDir.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ static const int nCascadeCount = 4; // number of cascades
static const int nCascadesPerRow = 2; // number of cascades per row, i.e. ceil(sqrt(nCascadeCount))
static const float fCascadeNormSize = 0.5f; // normalized size of a cascade, i.e. 1.f / nCascadesPerRow

// PCF method
#define PCF_SAMPLE PCF4x4Poisson

struct PSOut
{
float4 colorOut : SV_TARGET;
Expand Down Expand Up @@ -110,7 +113,7 @@ void psmain(VSOut input, out PSOut output)
float2(fCascadeNormSize * fmod(nValidCascade, nCascadesPerRow), fCascadeNormSize * floor(nValidCascade / nCascadesPerRow));

// PCF Poisson disc shadow sampling
float fPercentLit = PCF4x4Poisson(texShadowMap, f2OneOverShadowMapSize, f3CascadeTexCoord.xy, f3CascadeTexCoord.z - fShadowDepthBias);
float fPercentLit = PCF_SAMPLE(texShadowMap, f2OneOverShadowMapSize, f3CascadeTexCoord.xy, f3CascadeTexCoord.z - fShadowDepthBias);
//float fPercentLit = tex2D(texShadowMap, f3CascadeTexCoord.xy).r > f3CascadeTexCoord.z - fShadowDepthBias;

// if required, blend between cascade seams
Expand All @@ -119,29 +122,56 @@ void psmain(VSOut input, out PSOut output)
if (nValidCascade != nCascadeCount - 1)
{
// the blend amount depends on the position of the point inside the blend band of the current cascade
float fBlendAmount = (f2CascadeBoundsMin[nValidCascade].x + fCascadeBlendSize) - f4LightViewPos.x;
fBlendAmount = max(fBlendAmount, (f2CascadeBoundsMin[nValidCascade].y + fCascadeBlendSize) - f4LightViewPos.y);
fBlendAmount = max(fBlendAmount, f4LightViewPos.x - (f2CascadeBoundsMax[nValidCascade].x - fCascadeBlendSize));
fBlendAmount = max(fBlendAmount, f4LightViewPos.y - (f2CascadeBoundsMax[nValidCascade].y - fCascadeBlendSize));
float fScaledBlendSize = fCascadeBlendSize * (nValidCascade * nValidCascade + 1);
float fBlendAmount = 0.f;

// partition the cascade into 4 parts and only allow blending in the parts furthest from the camera
// (i.e. the part that is closest to the camera is not adjacent to any valid cascade, because they
// fit very tightly around the view frustum, so blending in that zone would result in artifacts)
// Disclaimer: there probably exists an easier way to do this, but I'm just lazy
float2 NE = float2(0.707107f, 0.707107f); //normalize(float2(0.5f, 0.5f));
float2 SE = float2(0.707107f, -0.707107f); //normalize(float2(0.5f, -0.5f));
float2 SW = float2(-0.707107f, -0.707107f); //normalize(float2(-0.5f, -0.5f));
float2 NW = float2(-0.707107f, 0.707107f); //normalize(float2(-0.5f, 0.5f));

float2 f2CascadeSpaceViewDir = normalize(mul(f44CascadeProjMat[0], f4LightViewPos).xy);
float4 f4LightSpaceCameraDir = mul(f44CascadeProjMat[0], mul(f44ScreenToLightViewMat, float4(0.f, 0.f, 1.f, 1.f)));
bool dotNE = dot(f2CascadeSpaceViewDir, NE) > 0.f;
bool dotSE = dot(f2CascadeSpaceViewDir, SE) > 0.f;
bool dotSW = dot(f2CascadeSpaceViewDir, SW) > 0.f;
bool dotNW = dot(f2CascadeSpaceViewDir, NW) > 0.f;
bool dotCamDirNE = dot(f4LightSpaceCameraDir.xy, NE) > 0.f;
bool dotCamDirSE = dot(f4LightSpaceCameraDir.xy, SE) > 0.f;
bool dotCamDirSW = dot(f4LightSpaceCameraDir.xy, SW) > 0.f;
bool dotCamDirNW = dot(f4LightSpaceCameraDir.xy, NW) > 0.f;

if (dotSW && dotNW && (dotCamDirSW || dotCamDirNW))
fBlendAmount = max(fBlendAmount, (f2CascadeBoundsMin[nValidCascade].x + fScaledBlendSize) - f4LightViewPos.x);
if (dotSW && dotSE && (dotCamDirSW || dotCamDirSE))
fBlendAmount = max(fBlendAmount, (f2CascadeBoundsMin[nValidCascade].y + fScaledBlendSize) - f4LightViewPos.y);
if (dotSE && dotNE && (dotCamDirSE || dotCamDirNE))
fBlendAmount = max(fBlendAmount, f4LightViewPos.x - (f2CascadeBoundsMax[nValidCascade].x - fScaledBlendSize));
if (dotNW && dotNE && (dotCamDirNW || dotCamDirNE))
fBlendAmount = max(fBlendAmount, f4LightViewPos.y - (f2CascadeBoundsMax[nValidCascade].y - fScaledBlendSize));

fBlendAmount /= fScaledBlendSize;

// if our point is inside the blend band, we can continue with blending
if (fBlendAmount > 0.f)
{
// calculate texture coordinates for sampling from the cascade one order
// higher than the cascade from which we sampled earlier
float3 f3LQCascadeTexCoord = mul(f44CascadeProjMat[nValidCascade + 1], f4LightViewPos).xyz;
if (f3LQCascadeTexCoord.x > 0.f && f3LQCascadeTexCoord.x < 1.f && f3LQCascadeTexCoord.y > 0.f && f3LQCascadeTexCoord.y < 1.f)
{
fBlendAmount /= fCascadeBlendSize;
f3LQCascadeTexCoord.xy =
(f3LQCascadeTexCoord.xy * float2(0.5f, -0.5f) + float2(0.5f, 0.5f)) *
fCascadeNormSize +
float2(fCascadeNormSize * fmod(nValidCascade + 1, nCascadesPerRow), fCascadeNormSize * floor((nValidCascade + 1) / nCascadesPerRow));

// sample from the lower quality cascade and blend between samples appropriately
float fPercentLitLQ = PCF4x4Poisson(texShadowMap, f2OneOverShadowMapSize, f3LQCascadeTexCoord.xy, f3LQCascadeTexCoord.z - fShadowDepthBias);
fPercentLit = fPercentLit * (1.f - fBlendAmount) + fPercentLitLQ * fBlendAmount;
}
f3LQCascadeTexCoord.xy =
(f3LQCascadeTexCoord.xy * float2(0.5f, -0.5f) + float2(0.5f, 0.5f)) *
fCascadeNormSize +
float2(fCascadeNormSize * fmod(nValidCascade + 1, nCascadesPerRow), fCascadeNormSize * floor((nValidCascade + 1) / nCascadesPerRow));

// sample from the lower quality cascade and blend between samples appropriately
float fPercentLitLQ = PCF_SAMPLE(texShadowMap, f2OneOverShadowMapSize, f3LQCascadeTexCoord.xy, f3LQCascadeTexCoord.z - fShadowDepthBias);
//float fPercentLitLQ = tex2D(texShadowMap, f3LQCascadeTexCoord.xy).r > f3LQCascadeTexCoord.z - fShadowDepthBias;
if (fPercentLitLQ > 0.f && fPercentLitLQ < 1.f) // only blend at shadow edges
fPercentLit = lerp(fPercentLit, fPercentLitLQ, fBlendAmount);
}
}
}
Expand Down
225 changes: 184 additions & 41 deletions GITechDemo/Data/shaders/Utils.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -29,73 +29,197 @@ float3 DecodeNormal(float4 enc)
// Percentage Closer Filtering variations //
//////////////////////////////////////////////

float PCF3x3(sampler2D shadowMap, float2 oneOverShadowMapSize, float2 texCoord, float depthCompare)
float PCF2x2Poisson(sampler2D shadowMap, float2 oneOverShadowMapSize, float2 texCoord, float depthCompare)
{
float2 poissonDisk[4] =
{
float2(-0.94201624, -0.39906216),
float2(0.94558609, -0.76890725),
float2(-0.094184101, -0.92938870),
float2(0.34495938, 0.29387760)
};
float percentLit = 0.f;

for (int i = 0; i < 4; i++)
{
bool isLit =
tex2D(
shadowMap,
texCoord +
poissonDisk[i] * oneOverShadowMapSize
).r > depthCompare;
percentLit += isLit * 0.25f;
}

return percentLit;
}

float PCF3x3Poisson(sampler2D shadowMap, float2 oneOverShadowMapSize, float2 texCoord, float depthCompare)
{
float2 poissonDisk[9] =
{
float2(0.4677864f, 1.0492188f),
float2(0.8965628f, -0.3094058f),
float2(-0.3828112f, 1.6226606f),
float2(-0.214711f, -0.7530054f),
float2(-0.6589904f, 0.4324332f),
float2(1.480888f, 1.2347982f),
float2(1.9457676f, -0.13916496f),
float2(1.1015856f, -1.5681816f),
float2(0.06198422f, -1.7864658f)
};
float percentLit = 0.f;

for (int i = 0; i < 9; i++)
{
bool isLit =
tex2D(
shadowMap,
texCoord +
poissonDisk[i] * oneOverShadowMapSize
).r > depthCompare;
percentLit += isLit * 0.1111111f;
}

return percentLit;
}

float PCF12TapPoisson(sampler2D shadowMap, float2 oneOverShadowMapSize, float2 texCoord, float depthCompare)
{
float2 poissonDisk[12] =
{
float2(-0.5946302f, 0.6008242f),
float2(-1.762928f, 0.5199542f),
float2(-1.56506f, -0.8692048f),
float2(0.14760224f, 1.7710094f),
float2(0.7065646f, 0.04265878f),
float2(-0.5888886f, -0.429245f),
float2(1.024129f, 1.1201256f),
float2(-1.118836f, 1.6018892f),
float2(-0.9557046f, -1.7215218f),
float2(0.02812326f, -1.4003752f),
float2(1.6575236f, -0.287287f),
float2(1.3867802f, -1.375697f)
};
float percentLit = 0.f;

for (int i = 0; i < 12; i++)
{
bool isLit =
tex2D(
shadowMap,
texCoord +
poissonDisk[i] * oneOverShadowMapSize
).r > depthCompare;
percentLit += isLit * 0.0833333f;
}

return percentLit;
}

float PCF4x4Poisson(sampler2D shadowMap, float2 oneOverShadowMapSize, float2 texCoord, float depthCompare)
{
float2 poissonDisk[16] =
{
float2(2.688109f, 0.686785f),
float2(3.0212565f, -1.1432755f),
float2(1.9640215f, 1.5282315f),
float2(3.293479f, 1.5044285f),
float2(3.7991725f, 0.47122805f),
float2(1.211579f, 0.587791f),
float2(1.8459345f, -0.8353495f),
float2(4.1637455f, -0.639698f),
float2(2.3383775f, 2.6021615f),
float2(4.58543f, 1.3013345f),
float2(3.9300595f, 2.3949865f),
float2(3.537996f, 3.44172f),
float2(1.055484f, 2.1200395f),
float2(1.9478455f, 3.7591355f),
float2(1.0448675f, 3.2125375f),
float2(-0.25313345f, 2.309396f)
};
float percentLit = 0.f;

for (int i = 0; i < 16; i++)
{
bool isLit =
tex2D(
shadowMap,
texCoord +
poissonDisk[i] * oneOverShadowMapSize
).r > depthCompare;
percentLit += isLit * 0.0625f;
}

return percentLit;
}

float PCF3x3Dithered(sampler2D shadowMap, float2 oneOverShadowMapSize, float2 texCoord, float depthCompare)
{
// use modulo to vary the sample pattern
float2 offset = floor(texCoord.xy) % 3.0;

float percentLit = 0.f;
for(int x = -1; x <= 1; x++)
for(int y = -1; y <= 1; y++)
{
bool isLit =
tex2D(
shadowMap,
texCoord +
float2(x * oneOverShadowMapSize.x, y * oneOverShadowMapSize.y)
float2(x, y) * oneOverShadowMapSize +
offset
).r > depthCompare;
percentLit += isLit;
}

return percentLit * 0.111111f;
}

float PCF4x4Poisson(sampler2D shadowMap, float2 oneOverShadowMapSize, float2 texCoord, float depthCompare)
float PCF5x5Dithered(sampler2D shadowMap, float2 oneOverShadowMapSize, float2 texCoord, float depthCompare)
{
float2 poissonDisk[4] =
{
float2( -0.94201624, -0.39906216 ),
float2( 0.94558609, -0.76890725 ),
float2( -0.094184101, -0.92938870 ),
float2( 0.34495938, 0.29387760 )
};
float percentLit = 0.f;
// use modulo to vary the sample pattern
float2 offset = floor(texCoord.xy) % 5.0;

for(int i = 0; i < 4; i++)
{
bool isLit =
tex2D(
shadowMap,
texCoord +
poissonDisk[i] * oneOverShadowMapSize
).r > depthCompare;
percentLit += isLit * 0.2f;
}
float percentLit = 0.f;
for(int x = -2; x <= 2; x++)
for(int y = -2; y <= 2; y++)
{
bool isLit =
tex2D(
shadowMap,
texCoord +
float2(x, y) * oneOverShadowMapSize +
offset
).r > depthCompare;
percentLit += isLit;
}

return percentLit;
return percentLit * 0.04f;
}

float PCF4x4Dithered(sampler2D shadowMap, float2 oneOverShadowMapSize, float2 texCoord, float depthCompare)
float PCF8x8Dithered(sampler2D shadowMap, float2 oneOverShadowMapSize, float2 texCoord, float depthCompare)
{
// use modulo to vary the sample pattern
float2 offset = floor(texCoord.xy) % 2.0;
float2 offset = floor(texCoord.xy) % 8.0;

float percentLit = 0.f;
percentLit += tex2D(shadowMap, texCoord + (float2(-1.5, 1.5) + offset) * oneOverShadowMapSize).r > depthCompare;
percentLit += tex2D(shadowMap, texCoord + (float2(0.5, 1.5) + offset) * oneOverShadowMapSize).r > depthCompare;
percentLit += tex2D(shadowMap, texCoord + (float2(-1.5, -0.5) + offset) * oneOverShadowMapSize).r > depthCompare;
percentLit += tex2D(shadowMap, texCoord + (float2(0.5, -0.5) + offset) * oneOverShadowMapSize).r > depthCompare;

return percentLit * 0.25 ;
float x, y, percentLit = 0.f;
for (y = -3.5f; y <= 3.5f; y += 1.0f)
for (x = -3.5f; x <= 3.5f; x += 1.0f)
percentLit += tex2D(shadowMap, texCoord + float2(x, y) * oneOverShadowMapSize + offset).r > depthCompare;

return percentLit * 0.015625f;
}

float PCF5x5Gaussian(sampler2D shadowMap, float2 oneOverShadowMapSize, float2 texCoord, float depthCompare)
{
const float GaussianKernel[5][5] =
{
0.00296901674395065, 0.013306209891014005, 0.02193823127971504, 0.013306209891014005, 0.00296901674395065,
0.013306209891014005, 0.05963429543618023, 0.09832033134884507, 0.05963429543618023, 0.013306209891014005,
0.02193823127971504, 0.09832033134884507, 0.16210282163712417, 0.09832033134884507, 0.02193823127971504,
0.013306209891014005, 0.05963429543618023, 0.09832033134884507, 0.05963429543618023, 0.013306209891014005,
0.00296901674395065, 0.013306209891014005, 0.02193823127971504, 0.013306209891014005, 0.00296901674395065
0.00296901674395065f, 0.013306209891014005f, 0.02193823127971504f, 0.013306209891014005f, 0.00296901674395065f,
0.013306209891014005f, 0.05963429543618023f, 0.09832033134884507f, 0.05963429543618023f, 0.013306209891014005f,
0.02193823127971504f, 0.09832033134884507f, 0.16210282163712417f, 0.09832033134884507f, 0.02193823127971504f,
0.013306209891014005f, 0.05963429543618023f, 0.09832033134884507f, 0.05963429543618023f, 0.013306209891014005f,
0.00296901674395065f, 0.013306209891014005f, 0.02193823127971504f, 0.013306209891014005f, 0.00296901674395065f
};
float percentLit = 0.f;

Expand All @@ -106,21 +230,40 @@ float PCF5x5Gaussian(sampler2D shadowMap, float2 oneOverShadowMapSize, float2 te
tex2D(
shadowMap,
texCoord +
float2(x * oneOverShadowMapSize.x, y * oneOverShadowMapSize.y)
float2(x, y) * oneOverShadowMapSize
).r > depthCompare;
percentLit += isLit * GaussianKernel[x+2][y+2];
}

return percentLit;
}

float PCF3x3(sampler2D shadowMap, float2 oneOverShadowMapSize, float2 texCoord, float depthCompare)
{
float percentLit = 0.f;

for (int x = -1; x <= 1; x++)
for (int y = -1; y <= 1; y++)
{
bool isLit =
tex2D(
shadowMap,
texCoord +
float2(x, y) * oneOverShadowMapSize
).r > depthCompare;
percentLit += isLit;
}

return percentLit * 0.111111f;
}

float PCF8x8(sampler2D shadowMap, float2 oneOverShadowMapSize, float2 texCoord, float depthCompare)
{
float x, y, percentLit = 0.f;
for (y = -3.5 ; y <=3.5 ; y+=1.0)
for (x = -3.5 ; x <=3.5 ; x+=1.0)
float percentLit = 0.f;
for (float y = -3.5f ; y <=3.5f ; y += 1.0f)
for (float x = -3.5f ; x <=3.5f ; x += 1.0f)
percentLit += tex2D(shadowMap, texCoord + float2(x, y) * oneOverShadowMapSize).r > depthCompare;

return percentLit / 64.0 ;
return percentLit * 0.015625f;
}
////////////////////////////////////////////////////////////////

0 comments on commit 124f264

Please sign in to comment.