95.4501
Comp 4501
Wilf LaLonde ©2012
How Do I Draw a Pixel?
Shadows volumes: Used in Doom (Expensive)
From lights, build a shadow shape for occluders.
If it’s OUTSIDE a shadow shape, draw lit.
If it’s INSIDE a shadow shape, draw dark.
Internally nested shadow shapes possible
Shadow maps: Most popular Technique
Build a z-buffer texture for what light can see.
If light CAN SEE it (at z-buffer), draw normally.
If light CAN’T SEE it (behind z-buffer), draw dark
Occluders don’t need to be known…
Comp 4501
Wilf LaLonde ©2012
95.4501
Comp 4501
Wilf LaLonde ©2012
Seeing Nested Shadow Volumes
inside green and
red volumes
inside green
volume only
inside NO
volumes
Look at Demo
Comp 4501
Wilf LaLonde ©2012
Basic Idea
• Clear the stencil buffer (a pixel buffer of counters)
and turn color buffer off (so it really doesn’t draw).
• Extract the shadow caster’s silhouette generated
by the light and draw the volume to infinity.
• As each triangle is drawn, in stencil buffer
Increment front facing ones (if pixel drawn)
Decrement back facing ones (if pixel drawn)
• When done,
if stencil counter = 0, it’s NOT in shadow
if stencil counter = n, it’s in n shadows…
Comp 4501
Wilf LaLonde ©2012
Counting Via Stencil Buffer
Suppose drawing order is
4 Shadow casters
zero
1 2 3 4
1
+1, -1 Both faces in front of
2
+1, 0 One face in front of
3
+1, 0 One face in front of
4
0, 0 Both faces behind
Camera
Final result +2 for
Final result 0 for
Comp 4501
Wilf LaLonde ©2012
95.4501
Comp 4501
Wilf LaLonde ©2012
Shadows Maps: Steps Involved Per Light
•
•
•
•
•
Move the camera to the light’s viewpoint.
Draw the world with the color buffer off.
Grab the z-buffer as a texture (need frame
buffers to do this),
Move the camera back
Draw the world normally using a shader
that draws dark when in shade; i.e. dark
when pixel’s z in light space > the light zbuffer’s z.
Works best for spot light
(since need location and orientation)
Comp 4501
Wilf LaLonde ©2012
Shadow Maps
• From an ATI Demo…. (draw world from light’s
position as a camera TO GET light’s z-buffer)
Look at Demo
Not visible (behind light’s z-buffer)
Visible (in front of light’s z-buffer)
Comp 4501
Wilf LaLonde ©2012
What the Special Shader Does
•
•
•
Let L place the light in the world and P be the
lights’s projection matrix.
Let shadow map be the light’s z-buffer texture.
Let p be the LOCAL position of the pixel
BEFORE transforming to light coordinates.
q = p*(LP) //pixel in light coordinates
q /= q.w; //Because of P, q.w is NOT 1.
depthSeenByLight = shadow map at (q.x, q.y)
if (q.z > depthSeenByLight )
draw dark
else draw normally
Comp 4501
Wilf LaLonde ©2012
Detailed Topic
Comp 4501
Wilf LaLonde ©2012
Variations
Convolution Shadow Maps
Percentage Closer Filtering
Variance Shadow Maps
Hierarchical Shadow Maps
Cascaded Shadow Maps
Exponential Shadow Maps
Omnidirectional Shadow Maps
Tiled Shadow Maps
Midpoint Shadow Maps
Forward Shadow Maps
Perspective Shadow Maps
Light Space Perspective Shadow Maps
Comp 4501
Volumetric Shadow Maps
Wilf LaLonde ©2012
Shadow Mapping
PDF available too
From “Real-time shadows” Book,
Eisemann et al, CRC Press, 2012, page 33
Comp 4501
Wilf LaLonde ©2012
Given pixel position pLPS (after dividing by w) in
light projection space (also called clip space)
• [pLPSx, pLPSy] is the clip space position of the
pixel in the depth map where the pixel would
project when seen by the light.
• pLPSz is the distance from the pixel to the light.
pLPSz > texture at [pLPSx, pLPSy]  the pixel is hidden
depth map
not normally accessed via clip coordinates
Hidden  in shadow
Comp 4501
Wilf LaLonde ©2012
Clip to Texture Coordinates
• Clip coordinates c range from [-1,+1].
• Texture coordinates t range from [0,1].
Transform via t = c * 0.5 + 0.5
Why? Consider
c
ranges from -1 to +1
c*0.5
ranges from -0.5 to +0.5
c*0.5+0.5 ranges from 0 to 1.0
Comp 4501
Wilf LaLonde ©2012
Problems with Shadow Maps
• Magnification artifacts: occur when
projected shadow-map texels cover a large
area in screen space.
jaggies or aliasing
• Minification artifacts: occur when several
shadow map texels are mapped to the same
screen-space pixel.
shadow acne or z-fighting (shadows
where none should be)
How can you both magnify and shrink with a
single transformation to perform the mapping?
Comp 4501
Wilf LaLonde ©2012
An example of Aliasing
Comp 4501
Wilf LaLonde ©2012
An Example of Acne
Comp 4501
Wilf LaLonde ©2012
Solutions
• Reduce areas of extreme mag- and minifications (increase resolution or change the
engine architecture)
Perspective shadow maps => warp the
shadow space to the view space
Cascaded shadow maps => use different
shadow maps in different parts of the
frustum.
• Remove sampling errors (changes to the
shaders)
Filtering
Comp 4501
Wilf LaLonde ©2012
Percentage-Closer Filtering
• Invented because bilinear, trilinear and
anisotropic filtering; i,e., hardware texture
filtering is INAPPLICABLE to standard
shadows maps.
Hardware filtering example:
an average of depths d1 and d2
Type of filtering needed:
an average of “in shadow at d1”
and “not in shadow at d2”
Key insight: Correct filtering needs to filter the
results of the depth comparisons, not the depths
Comp 4501
Wilf LaLonde ©2012
95.4501
Comp 4501
Wilf LaLonde ©2012
Where Do We Start
• The June 2010 DX SDK has a good DirectX9 demo called
“ShadowMap” from 2004.
• Made a copy of the demo in “C:\School\4501.2012\From June
2010 DX SDK – Wilf”.
• Made changes to the software (camera + trackball) to use a
RIGHT-HANDED SYSTEM; e.g.,
D3DXMatrixLookAtRH (instead of LH)
D3DXVECTOR3 vLookatPt =
D3DXVECTOR3( 0.0f, 0.0f, -1.0f ); //Not +1 for camera
• Add logging facility: clearLog () and void log (char *message, ...)
so we can say; e.g., ::log (“\nI am %d years old.”, 23);
Search “wilf” for a list of changes.
Comp 4501
Wilf LaLonde ©2012
Preliminaries
• A 1 channel 32F texture “g_pShadowMap” is
obtained to contain the depthBuffer data.
window size is 640 x 480
SHADOWMAP_SIZE is 1024
pd3dDevice->CreateTexture (|
SHADOWMAP_SIZE, SHADOWMAP_SIZE, 1,
D3DUSAGE_RENDERTARGET, D3DFMT_R32F, D3DPOOL_DEFAULT,
&g_pShadowMap, NULL);
Actually used as a DRAW texture rather than a DEPTH-BUFFER texture
• A frame buffer “g_pDSShadow” is obtained
to draw into the depthBuffer into.
pd3dDevice->CreateDepthStencilSurface (
SHADOWMAP_SIZE, SHADOWMAP_SIZE,
d3dSettings.d3d9.pp.AutoDepthStencilFormat,
D3DMULTISAMPLE_NONE, 0, TRUE,
&g_pDSShadow, NULL);
See callback OnResetDevice for details
Comp 4501
Wilf LaLonde ©2012
Rendering Performs 2 Passes
void CALLBACK OnFrameRender (IDirect3DDevice9* pd3dDevice, double fTime,
float fElapsedTime, void* pUserContext) {
//Draw the scene as seen by the light
See later slide
//Draw the scene as seen by the camera.
See later slide
}
Comp 4501
Wilf LaLonde ©2012
Draw the scene seen by the light (without error checks)
LPDIRECT3DSURFACE9 pOldRT = NULL;
pd3dDevice->GetRenderTarget (0, &pOldRT);
LPDIRECT3DSURFACE9 pShadowSurf;
Get old drawing target (back buffer)
and replace by the shadow map
texture. AFTER, restore it
g_pShadowMap->GetSurfaceLevel (0, &pShadowSurf);
pd3dDevice->SetRenderTarget (0, pShadowSurf);
SAFE_RELEASE (pShadowSurf);
LPDIRECT3DSURFACE9 pOldDS = NULL;
Get old frame buffer (full screen)
and replace by the shadow map
frame buffer. AFTER, restore it
pd3dDevice->GetDepthStencilSurface (&pOldDS);
pd3dDevice->SetDepthStencilSurface (g_pDSShadow);
RenderScene (pd3dDevice, true, fElapsedTime, &mLightView, &g_mShadowProj);
pd3dDevice->SetDepthStencilSurface (pOldDS);
pOldDS->Release ();
pd3dDevice->SetRenderTarget (0, pOldRT);
bRenderShadow
SAFE_RELEASE (pOldRT);
Comp 4501
Wilf LaLonde ©2012
Draw the scene seen by the camera (without error checks)
in camera’s model view space
in model space
code on next slide
in light’s model view space
in light’s clip space
//Setup 2 shader parameters…
g_pEffect->SetTexture ("g_txShadow", g_pShadowMap);
g_pEffect->SetMatrix ("g_mViewToLightProj", &...); // CMV-1 * LMV * LP
RenderScene (pd3dDevice, false, fElapsedTime, g_VCamera.GetViewMatrix(),
g_VCamera.GetProjMatrix ());
g_pEffect->SetTexture ("g_txShadow", NULL);
Comp 4501
bRenderShadow
Wilf LaLonde ©2012
Camera model view TO light clip space
in camera’s model view space
in model space
code on next slide
in light’s model view space
in light’s clip space
// CMV-1 * LMV * LP
//Compute the matrix to transform from view space to light projection space. This
//consists of the inverse of view matrix * view matrix of light * light projection matrix
Easier to understand if you call it M.
D3DXMATRIXA16 mViewToLightProj;
mViewToLightProj = *pmView;
CMV
D3DXMatrixInverse (&mViewToLightProj, NULL, &mViewToLightProj );
CMV-1
D3DXMatrixMultiply (&mViewToLightProj, &mViewToLightProj, &mLightView );
LMV
D3DXMatrixMultiply (&mViewToLightProj, &mViewToLightProj, &g_mShadowProj );
LP
V (g_pEffect->SetMatrix ("g_mViewToLightProj", &mViewToLightProj ))
Comp 4501
Wilf LaLonde ©2012
Uses 1 shader file “shadowMap.fx”
• We duplicated it as shadowMapOriginal.fx”.
• It contains 3 techniques (3 shaders):
RenderShadow for drawing the scene from
the point of view of the light (pass 1).
RenderScene for drawing the lit scene with
shadows (pass 2)
RenderLight for drawing the “spotlight
mesh (looks like a lamp shade)” (this is
irrelevant and part of pass 1).
Comp 4501
Wilf LaLonde ©2012
The RenderScene Routine
void RenderScene (IDirect3DDevice9* pd3dDevice, bool bRenderShadow, float
fElapsedTime, const D3DXMATRIX* pmView, const D3DXMATRIX* pmProj) {
//Setup shader variables g_mProj, g_vLightPos, g_vLightDir…
See later slide
//Clear the frame buffer.
pd3dDevice->Clear (0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
0x000000ff, 1.0f, 0L ) );
//Choose whether drawing from perspective of light or camera.
g_pEffect->SetTechnique (bRenderShadow ? "RenderShadow" : "RenderScene";
//Draw the scene…
pd3dDevice->BeginScene ();
//Draw the scene objects.
See later slide
//Not shown: if not rendering shadow, code for rendering "lamp shade object",
// text, and User interface stuff.
pd3dDevice->EndScene ();
Comp 4501
Wilf LaLonde ©2012
Do We Get “toLight” or “fromLight”?
• The light is actually a “CFirstPersonCamera”.
D3DXMATRIX * GetWorldMatrix () {return &m_mCameraWorld;}
const D3DXVECTOR3* GetWorldAhead () const {//toLight (via back)
return (D3DXVECTOR3 *) &m_mCameraWorld._31;}
[_31,_32,_33] = [0,0,1] *
_11
_21
_31
_12
_22
_32
_13
_23
_33
[0,0,-1]
is ahead in right-handed system
fromLight would actually be [0,0,-1] transformed
[0,0,1]
is back in right-handed system
toLight would actually be [0,0,1] transformed
I didn’t want to change the code to avoid breaking their stuff…
Comp 4501
We get a toLight…
Wilf LaLonde ©2012
Setup shader variables g_mProj, g_vLightPos, g_vLightDir…
//Setup the projection matrix...
g_pEffect->SetMatrix ("g_mProj", pmProj); //Parameter that came in…
the light
//Setup the light position (in camera or view space)...
D3DXVECTOR3 v = *g_LCamera.GetEyePt ();
D3DXVECTOR4 v4; D3DXVec3Transform (&v4, &v, pmView); //Transform to CS.
g_pEffect->SetVector ("g_vLightPos", &v4);
//Setup the light direction (in camera or view space)...
*(D3DXVECTOR3 *) &v4 = *g_LCamera.GetWorldAhead(); //toLight
v4.w = 0.0f; //Set w to 0 so that the translation part doesn't come to play.
D3DXVec4Transform (&v4, &v4, pmView); //Transform toLight to CS
D3DXVec3Normalize ((D3DXVECTOR3 *) &v4, (D3DXVECTOR3 *) &v4);
g_pEffect->SetVector ("g_vLightDir", &v4);
Comp 4501
Wilf LaLonde ©2012
Draw the Scene Objects
//Render the objects in the scene..
for (int obj = 0; obj < NUM_OBJ; ++obj) {
D3DXMATRIXA16 mWorldView = g_Obj[obj].m_mWorld;
D3DXMatrixMultiply (&mWorldView, &mWorldView, pmView);
g_pEffect->SetMatrix ("g_mWorldView", &mWorldView);
LPD3DXMESH pMesh = g_Obj[obj].m_Mesh.GetMesh();
UINT cPass; g_
pEffect->Begin (&cPass, 0); //It will return 1.
for (UINT p = 0; p < cPass; ++p ) {
g_pEffect->BeginPass (p);
for (DWORD i = 0; i < g_Obj[obj].m_Mesh.m_dwNumMaterials; ++i) {
D3DXVECTOR4 vDif (
g_Obj[obj].m_Mesh.m_pMaterials[i].Diffuse.r,
g_Obj[obj].m_Mesh.m_pMaterials[i].Diffuse.g,
g_Obj[obj].m_Mesh.m_pMaterials[i].Diffuse.b,
g_Obj[obj].m_Mesh.m_pMaterials[i].Diffuse.a);
g_pEffect->SetVector ("g_vMaterial", &vDif);
if (g_Obj[obj].m_Mesh.m_pTextures[i] )
g_pEffect->SetTexture ("g_txScene", g_Obj[obj].m_Mesh.m_pTextures[i];
else
g_pEffect->SetTexture ("g_txScene", g_pTexDef ) ) //It's NULL.
g_pEffect->CommitChanges (); pMesh->DrawSubset (i);
}
g_pEffect->EndPass ();
}
g_pEffect->End ();
}
Comp 4501
Wilf LaLonde ©2012
Where Are We?
• We have MOST of the “.cpp” code worked
out.
• We need to look at the shader (RenderShadow)
for drawing the scene from the light’s point of
view (this is RELATIVELY simple).
• We need to develop the shader (RenderScene)
for drawing the scene from the camera’s
point of view (this is COMPLEX; we will
develop several variations)…
Comp 4501
Wilf LaLonde ©2012
The Effect File “ShadowMap.fx”
//Copyright (c) Microsoft Corporation. All rights reserved.
#define SMAP_SIZE 512
#define SHADOW_EPSILON 0.00005f
float4x4 g_mWorldView;
float4x4 g_mProj;
float4x4 g_mViewToLightProj; //Transform from view to light projection space
float4 g_vMaterial;
texture g_txScene;
texture g_txShadow;
float3 g_vLightPos; //Light position in view space (wilf: lightPositionCS)
float3 g_vLightDir; //Light direction in view space (wilf: toLightCS)
float4 g_vLightDiffuse = float4 ( 1.0f, 1.0f, 1.0f, 1.0f ); //Light diffuse color
float4 g_vLightAmbient = float4 ( 0.3f, 0.3f, 0.3f, 1.0f ); //Use an ambient light of 0.3
float g_fCosTheta; //Cosine of theta of the spot light (wilf: coneAngleAsCosine)
sampler2D g_samScene = sampler_state {Texture = <g_txScene>; …};
sampler2D g_samShadow = sampler_state {Texture = <g_txShadow>; …}
Comp 4501
Wilf LaLonde ©2012
The Effect File “ShadowMap.fx” (continued)
void VertShadow (float4 Pos : POSITION, float3 Normal : NORMAL,
out float4 oPos : POSITION, out float2 Depth : TEXCOORD0) {
…
}
void PixShadow (float2 Depth : TEXCOORD0, out float4 Color : COLOR) {
…
}
…
technique RenderShadow {
pass p0 {
VertexShader = compile vs_2_0 VertShadow();
PixelShader = compile ps_2_0 PixShadow();
}
}
Comp 4501
Wilf LaLonde ©2012
The RenderShadow Shader Details
void VertShadow (float4 Pos : POSITION, float3 Normal : NORMAL,
out float4 oPos : POSITION, out float2 Depth : TEXCOORD0) {
//Convert to light clip coordinates (light projection space);
oPos = mul (Pos, g_mWorldView); //Pos * g_mWorldView (so far)
oPos = mul (oPos, g_mProj); //Pos * g_mWorldView * g_mProj
//Store z and w in our spare texcoord…
Depth.xy = oPos.zw;
}
void PixShadow (float2 Depth : TEXCOORD0, out float4 Color : COLOR) {
//Compute depth which is z / w
Color = Depth.x / Depth.y; //All 4 components rgba get the same value.
}
All it does is draw the depth AS IF IT WERE A COLOR.
Comp 4501
Wilf LaLonde ©2012
95.4501
Comp 4501
Wilf LaLonde ©2012
Instrumenting the Original Shader
• We want to perform major experiments via
minor changes to the original shader...
• Structure of original “ShadowMap.tx”
Shader variables
Shader VertScene, PixScene //Use by technique RenderScene
Shader VertShadow, PixShadow //Used by technique RenderShadow
technique RenderScene {
pass p0 {
VertexShader = compile vs_2_0 VertScene ();
PixelShader = compile ps_2_0 PixScene ();
}
}
Comp 4501
Wilf LaLonde ©2012
New Shader Structure
#include “WilfShadowExperiment.choice”
Shader variables
#include “WilfShadowLibrary.all”
#include “WilfShadowRenderer.shader”
Shader VertScene, PixScene
Shader VertShadow, PixShadow
technique RenderScene {
pass p0 {
#if (experiment == ORIGINAL_VERSION)
VertexShader = compile vs_2_0 VertScene ();
PixelShader = compile ps_2_0 PixScene ();
#else
VertexShader = compile vs_2_0 WilfsDrawSceneVS ();
PixelShader = compile ps_2_0 WilfsDrawScenePS ();
#endif //experiment
}
}
Comp 4501
Wilf LaLonde ©2012
Preliminary Setup
• WilfShadowExperiment.choice
#define ORIGINAL_VERSION 0
#define experiment ORIGINAL_VERSION
• WilfShadowLibrary.all
ALL NON-AMBIENT
#define clamp01 saturate
//Define the ambient/diffuse/specular specifics for this application....
float4 Ka () {return g_vMaterial * g_vLightAmbient;}
float4 Kd () {return g_vMaterial * (1 - g_vLightAmbient);}
float4 Ks () {return float4 (0,0,0,0);}
float g_fSpecularExponent = 60.0f;
//Functions from lighting for dummies; i.e. inSpotLight, combinedColor...
//Create 2 variations of combinedColor : 1 without specular color; 1 unlit.
Comp 4501
Wilf LaLonde ©2012
Preliminary Setup
• WilfShadowRenderer.shader switched to
using structs instead of parameter lists...
//Notation for spaces: CS camera, CPS camera projection, LPS light projection.
struct VS_INPUT {
float4 position : POSITION;
float3 normal : NORMAL;
float2 textureCoordinate : TEXCOORD0;
};
struct VS_OUTPUT {
float4 positionCPS : POSITION; //This disappears from PS_INPUT...
float2 textureCoordinate : TEXCOORD0;
float4 positionCS : TEXCOORD1;
float3 normalCS : TEXCOORD2;
float4 positionLPS : TEXCOORD3;
};
VS_OUTPUT WilfsDrawSceneVS (VS_INPUT In) {
VS_OUTPUT Out; Out.positionCS = ...
return Out;
}
Comp 4501
Wilf LaLonde ©2012
Preliminary Setup
• The Vertex Shader...
//Notation for spaces: CS camera, CPS camera projection, LPS light projection.
struct VS_INPUT {
float4 position : POSITION;
float3 normal : NORMAL;
float2 textureCoordinate : TEXCOORD0;
};
struct VS_OUTPUT {
float4 positionCPS : POSITION; //This disappears from PS_INPUT...
float2 textureCoordinate : TEXCOORD0;
float4 positionCS : TEXCOORD1;
float3 normalCS : TEXCOORD2;
float4 positionLPS : TEXCOORD3;
};
Not clear why
we compute
all these
variables yet,
VS_OUTPUT WilfsDrawSceneVS (VS_INPUT In) {
VS_OUTPUT Out;
Out.positionCS = mul (In.position, g_mWorldView);
Out.normalCS = mul (In.normal, (float3x3) g_mWorldView);
Out.textureCoordinate = In.textureCoordinate; //Propagate.
Out.positionCPS = mul (Out.positionCS, g_mProj);
Out.positionLPS = mul (Out.positionCS, g_mViewToLightProj);
return Out;
}
Comp 4501
Wilf LaLonde ©2012
Preliminary Setup
• The Pixel Shader...
//Notation for spaces: CS camera, CPS camera projection, LPS light projection.
struct VS_OUTPUT {
float4 positionCPS : POSITION; //This disappears from PS_INPUT...
float2 textureCoordinate : TEXCOORD0;
float4 positionCS : TEXCOORD1;
positionCPS
float3 normalCS : TEXCOORD2;
float4 positionLPS : TEXCOORD3;
};
is gone
struct PS_INPUT {
float2 textureCoordinate : TEXCOORD0;
float4 positionCS : TEXCOORD1;
float3 normalCS : TEXCOORD2;
float4 positionLPS : TEXCOORD3;
};
float4 WilfsDrawScenePS (PS_INPUT In) : COLOR0 {
#if (experiment == drawRed)
return float4 (1,0,0,1);
#endif //experiment
}
Comp 4501
Wilf LaLonde ©2012
95.4501
Comp 4501
Wilf LaLonde ©2012
Lets Perform Experiment 1
• WilfShadowExperiment.choice
#define ORIGINAL_VERSION 0
#define drawRed 1
//#define experiment ORIGINAL_VERSION
#define experiment drawRed
• Yup... It draws a screen full of red....
Comp 4501
Wilf LaLonde ©2012
Lets Perform Experiment 2
• WilfShadowExperiment.choice
#define drawBrightTexture 2
#define experiment drawBrightTexture
• And the pixel shader....
float4 WilfsDrawScenePS (PS_INPUT In) : COLOR0 {
#elif (experiment == drawBrightTexture)
return tex2D (g_samScene, In.textureCoordinate);
#endif //experiment
}
Comp 4501
Wilf LaLonde ©2012
Lets Perform Experiment 3
• WilfShadowExperiment.choice
#define drawDarkTexture 3
#define experiment drawDarkTexture
• And the pixel shader....
float4 WilfsDrawScenePS (PS_INPUT In) : COLOR0 {
#elif (experiment == drawDarkTexture)
return tex2D (g_samScene, In.textureCoordinate) * (Ka ());
#endif //experiment
}
Comp 4501
Wilf LaLonde ©2012
Lets Perform Experiment 4
• WilfShadowExperiment.choice
#define drawLitTextureIgnoringShadows 4
#define experiment drawLitTextureIgnoringShadows
• Two issues to deal with if we are to be LIT.
• Are we in the cone of the light?
• If so, are we facing the light?
Comp 4501
Wilf LaLonde ©2012
What Do We Have To Work With LIGHTS IN CAMERA SPACE?
• Shader global variables and parameters
//We have the following renamed globals.
float3 lightPositionCS;
float3 toLightCS;
float coneAngleAsCosine;
struct PS_INPUT {
float2 textureCoordinate : TEXCOORD0;
float4 positionCS : TEXCOORD1;
float3 normalCS : TEXCOORD2;
float4 positionLPS : TEXCOORD3;
};
• Existing Spotlight Routine
bool inSpotLight (float3 normalizedPixelToLight, float3 normalizedToLightVector, float coneAngleAsCosine) {
//Negating each of the above unit directions A and B makes them point away from the light.
//Note: -A.-B = A.B = |A||B| cos angle = cos angle. cos 45° = 0.707, cos 0° = 1
return dot (normalizedPixelToLight , normalizedToLightVector ) > coneAngleAsCosine;
}
• Easy to Compute First Parameter...
float3 pixelToLightCS = normalize (lightPositionCS - In.positionCS.xyz);
Just make sure all parameters are in CS?
Comp 4501
Wilf LaLonde ©2012
Utility Routines
• Utility Routines so we can ask “Is the pixel normal
aligned with the pixelToLight vector”...
bool alignedWith (float3 a, float3 b) {//Vectors pointing in the same direction.
//Since A.B = |A||B|cos angle and cos (0 to 90 degrees) is positive; otherwise negative.
//In degrees, cos (0) = 1, cos (45) = 0.707, cos (90) = 0, cos (135) = -0.707, cos (180) = -1.
return dot (a, b) > 0.0;
}
bool alignedAgainst (float3 a, float3 b) {return !alignedWith (a, b);}
• And finally

<0
>0
bool canSeeLight (ifloat3 pixelToLightCS, float3 pixelNormalCS,
float3 normalizedToLightVector, float coneAngleAsCosine) {
//Ignoring blockers, RETURN TRUE if in the spot light's cone angle and facing the light...
return
inSpotLight (pixelToLightCS, normalizedToLightVector, coneAngleAsCosine) &&
alignedWith (pixelToLightCS, pixelNormalCS);
}
Are We Facing the Light?
Comp 4501
Wilf LaLonde ©2012
Putting It All Together
• WilfShadowExperiment.choice
#define drawLitTextureIgnoringShadows 4
#define experiment drawLitTextureIgnoringShadows
• And the pixel shader....
float4 WilfsDrawScenePS (PS_INPUT In) : COLOR0 {
#elif (experiment == drawLitTextureIgnoringShadows)
In.normalCS = normalize (In.normalCS); //Interpolation changes length.
float3 pixelToLightCS = normalize (lightPositionCS - In.positionCS.xyz);
float shadowBrightness =
canSeeLight (pixelToLightCS, In.normalCS, toLightCS, coneAngleAsCosine)
? 1.0
: 0.0;
return combinedColor (tex2D (g_samScene, In.textureCoordinate),
In.normalCS, pixelToLightCS, shadowBrightness);
#endif //experiment
}
Comp 4501
Wilf LaLonde ©2012
And it Looks Like
Comp 4501
Wilf LaLonde ©2012
Finally, Lets Perform Experiment 5
• WilfShadowExperiment.choice
#define drawMostBasicLitTextureWithShadows 5
#define experiment drawMostBasicLitTextureWithShadows
float4 WilfsDrawScenePS (PS_INPUT In) : COLOR0 {
#elif (experiment == drawMostBasicLitTextureWithShadows)
In.normalCS = normalize (In.normalCS); //Interpolation changes length.
float3 pixelToLightCS = normalize (lightPositionCS - In.positionCS.xyz);
float shadowBrightness =
canSeeLight (pixelToLightCS, In.normalCS, toLightCS, coneAngleAsCosine) &&
!isInShadow (...)
? 1.0
: 0.0;
return combinedColor (tex2D (g_samScene, In.textureCoordinate),
In.normalCS, pixelToLightCS, shadowBrightness);
#endif //experiment
}
Comp 4501
Wilf LaLonde ©2012
What Do We Have and What Do We Need?
• We have shadow map “g_txShadow” from which we
can get
shadow map depth
• We have
struct PS_INPUT {
float2 textureCoordinate : TEXCOORD0;
float4 positionCS : TEXCOORD1;
float3 normalCS : TEXCOORD2;
float4 positionLPS : TEXCOORD3;};
position [x,y,z,w] in light clip (projection) space
which allows us to get
clip space texture coordinates
clip space depth
in shadow if “clip space depth” > “shadow map depth”
Comp 4501
Wilf LaLonde ©2012
Recall: Clip to Texture Coordinates
• Clip coordinates c range from [-1,+1].
• Texture coordinates t range from [0,1].
Transform via t = c * 0.5 + 0.5
Why? Consider
c
ranges from -1 to +1
c*0.5
ranges from -0.5 to +0.5
c*0.5+0.5 ranges from 0 to 1.0
#define usingDirectX true
float2 asShadowMapTextureCoordinates (float4 positionLPS) {
float2 uv = 0.5 * (positionLPS.xy / positionLPS.w) + 0.5;
if (usingDirectX) uv.y = 1.0 - uv.y; //Y goes down...
return uv;
}
Comp 4501
Wilf LaLonde ©2012
Finally, isInShadow
bool isInShadow (sampler2D shadowMap, float4 positionLPS) {
//Remember: depth 0 is near, depth 1 is far...
float pixelDepth = positionLPS.z / positionLPS.w;
float2 uv = asShadowMapTextureCoordinates (positionLPS);
float mapDepth = tex2D (shadowMap, uv).r;
return pixelDepth > mapDepth; //Pixel is blocked by something...
}
Comp 4501
Wilf LaLonde ©2012
This is Called Surface Acne or Z-Fighting
Comp 4501
Wilf LaLonde ©2012
Eliminating Acne
Note: For the same pixel in the
shadow map, some parts of the
scene are above the pixel height
and some are below. FORCE
MORE ABOVE WITH A BIAS.
Doesn’t always work.
From “Real-time shadows” handouts, precursor to
Book, Eisemann et al, CRC Press, 2012, page 34
Comp 4501
Wilf LaLonde ©2012
Which Way Should We Pull Away?
• Let d be pixel depth and sd be the depth sampled
from the texture; z is perspective space depth...
Far (z = 1)
d
sd
Near (z = 0)
The 2 values are rarely equal; suppose equal to 10-128.
Comp 4501
Wilf LaLonde ©2012
Which Way Should We Pull Away By EPSILON E?
• Wrong Way (EVERY pixel points in shadow)
sd
Move sd nearer.
d
sd
Far (z = 1)
ADD E to bigger number
E
sd-E
SUBTRACT E get smaller number
Near (z = 0)
• Right Way (spread d and sd apart)
d
d
sd
E
E
sd
OR
d-E
d
Move d nearer.
Comp 4501
sd+E
sd
Move sd further.
Wilf LaLonde ©2012
Finally, isInShadow (Experiment 6)
• WilfShadowExperiment.choice
#define drawLitTextureWithBias 6
#define experiment drawLitTextureWithBias
• And the library
#define SHADOW_EPSILON 0.00005
bool isInShadow (sampler2D shadowMap, float4 positionLPS, float epsilon) {
//Remember: depth 0 is near, depth 1 is far...
float pixelDepth = positionLPS.z / positionLPS.w;
float2 uv = asShadowMapTextureCoordinates (positionLPS);
float mapDepth = tex2D (shadowMap, uv).r + epsilon;
return pixelDepth > mapDepth; //Pixel is blocked by something...
}
WORKED (No picture supplied)
Comp 4501
Wilf LaLonde ©2012
But Constant Offset (Bias) Doesn’t Always Work
surface defined
by depth map
(in yellow)
surface being
rendered
constant offset
gets pulled out to
inside
Comp 4501
Wilf LaLonde ©2012
But Constant Offset (Bias) Doesn’t Always Work
surface defined
by depth map
(in yellow)
original
constant
offset
surface being
rendered
constant offset
(needs to be
larger here)
Comp 4501
Wilf LaLonde ©2012
Normal Offset Shadows: Daniel Holbert
• shadowPosition = position + normal * epsilon
See poster
this is called
peter-panning
this is called
shadow acne
perfect shadows
constant bias
slope-scale
normal offset
Comp 4501
Wilf LaLonde ©2012
Holbert’s GDC Poster Session Code
if (bScaleNormalOffsetByShadowDepth) {
float4 Plv = mul (Pw, gLampViewXf);
float shadowFOVFactor = max (gLampProjX [0].x, gLampProjX [1].y);
shadowMapTexelSize *= abs (Plv.z) * shadowFOVFactor;
SOME
}
float4 Plp;
float3 ToLight = normalize (gSpotLamp0Pos - Pw);
float cosLightAngle = dot (ToLight, WorldSpaceNormal);
VARIABLES NOT
INITIALIZED
float NormalOffsetScale = bNormalOffsetSlopeScale ? saturate (1 - cosLightAngle) : 1.0;
NormalOffsetScale *= ShadowlNormalOffset * shadowMapTexelSize;
float ShadowOffset = float4 (WorldSpaceNormal.xyz * NormalOffsetScale, 0);
if (bOnlyUVNormalOffset) {
Plp = mul (Pw, ShadowViewProjXf); //"P" in light coordinates.
float4 shadowPwUVOnly = Pw + ShadowOffset;
float4 UVOffsetPlp = mul (shadowPwUVOnly, ShadowViewProjXf); //”P” in light coor.
Plp.xy = UVOffsetPlp.xy;
} else {
float4 shadowPW = Pw + ShadowOffset;
Plp = mul (shadowPw, ShadowViewProjxf); //"P" in light coordinates.
}
Comp 4501
Wilf LaLonde ©2012
Using Some of Holbert’s Ideas
float variableLengthShadowEpsilon (float3 normalCS, float4 positionLPS) {
//Compute how long to make the normal offset in projection space (0 to 1);
//longer if close, smaller if far...
//Note: In logarithmic space, close up, z varies fast (use large epsilon);
//far away, z varies very little (use small)..
float offsetLength = lerp (0.00001, 0.000001, positionLPS.z / positionLPS.w);
//At grazing angles (along a wall), the normal is almost perpendicular and
//needs a longer offset...
float t = dot (normalCS, toLightCS); //t = 0 if perpendicular, 1 if parallel)...
offsetLength *= lerp (10.0, 1.0, t); //10 if perpendicular, 1 if parallel...
return offsetLength;
}
Holbert’s code worked in camera space (demo was in
small room; z depth is accurate when near).
By working in projection space, more likely to handle
large depths of 1000 meters...
Comp 4501
Wilf LaLonde ©2012
With Fixed Versus Variable Length
The Peter Panning Artifact
variable length epsilon
epsilon of 0.00001
Essentially the same
Comp 4501
Wilf LaLonde ©2012
95.4501
Comp 4501
Wilf LaLonde ©2012
What Percentage-Closer Filtering (PCF) is?
• Given a filter and a position to sample, choose
a reference depth for that position; e.g., the
pixel depth.
• For each filter sample, determine if it is closer
than the reference (a boolean result).
• Determine the percentage of pixels closer than
the reference value.
• Use this percentage to attenuate the light.
100% closer  white (in light)
0% closer  black (in shadow)
p% closer  gray (interpolate via p as a
fraction from black to white)
Comp 4501
Wilf LaLonde ©2012
An Example 2x2 Filter
• 4 probes for sample [x,y]...
Comp 4501
Weight
0.25
[x, y+1]
Weight
0.25
[x+1, y+1]
Weight
0.25
[x, y]
Weight
0.25
[x+1, y]
Wilf LaLonde ©2012
Using a 2x2 PCF Filter
float lightIntensityVia2x2PCF (
//1 if visible; 0 if in shadow; in between if partially in shadow...
//4 canSeeLight parameters...
float3 pixelToLightCS, float3 pixelNormalCS, float3 normalizedToLightVector, float coneAngleAsCosine,
//3 isInShadow parameters...
sampler2D shadowMap, float4 positionLPS, float epsilon) {
//Percentage closer filtering averages the boolean inShadow results (not the depths).
if (!canSeeLight (pixelToLightCS, pixelNormalCS, normalizedToLightVector, coneAngleAsCosine)) return 0.0;
float2 uv = asShadowMapTextureCoordinates (positionLPS);
float pixelDepth = positionLPS.z / positionLPS.w;
float dt = 1.0 / SMAP_SIZE;
float mapDepth0 = tex2D (g_samShadow, uv).r + epsilon;
float mapDepth1 = tex2D (g_samShadow, clamp01 (uv+float2(dt,0))).r + epsilon;
float mapDepth2 = tex2D (g_samShadow, clamp01 (uv+float2(0,dt))).r + epsilon;
float mapDepth3 = tex2D (g_samShadow, clamp01 (uv+float2(dt,dt))).r + epsilon;
//Note: mapDepth0 > pixelDepth means it's further back (so reference pixel is VISIBLY in front).
return
0.25 * (mapDepth0 > pixelDepth) +
0.25 * (mapDepth1 > pixelDepth) +
0.25 * (mapDepth2 > pixelDepth) +
0.25 * (mapDepth3 > pixelDepth);
}
Comp 4501
Wilf LaLonde ©2012
Results
1 Sample
Comp 4501
4 Sample PCF
Wilf LaLonde ©2012
How About Interpolating The Results
float lightIntensityVia2x2InterpolatedPCF (
... SAME AS BEFORE ...
//Interpolate 0/1 results in terms of where uv is inside the pixel...
float2 t = frac (uv * SMAP_SIZE); //T-value for where we are inside the pixel (for x, 0=>left, 1=>right; similar for y).
return
lerp (
note
lerp ((mapDepth0 > pixelDepth), (mapDepth1 > pixelDepth), t.x), //x interpolate
lerp ((mapDepth2 > pixelDepth), (mapDepth3 > pixelDepth), t.x), //x interpolate
t.y); //y interpolate
}
mapDepth2
float2 t
mapDepth0
Comp 4501
mapDepth3
Weight
0.25
[x, y+1]
Weight
0.25
[x+1, y+1]
Weight
0.25
[x, y]
Weight
0.25
[x+1, y]
mapDepth1
Wilf LaLonde ©2012
Results
4 Sample Interpolated PCF
Comp 4501
Wilf LaLonde ©2012
How About a 5 probe Symmetric Filter
• 5 probes for sample [x,y]... different weights
Weight
1/6
[x, y+1]
Weight
1/6
[x-1, y]
Weight
2/6
[x, y]
Weight
1/6
[x+1, y]
Weight
1/6
[x, y-1]
Comp 4501
Wilf LaLonde ©2012
How About Interpolating The Results
float lightIntensityVia2x2InterpolatedPCF (
...
#define useSymmetricProbes 0
#if (useSymmetricProbes)
float mapDepthCenter = tex2D (g_samShadow, uv).r + epsilon;
float mapDepth0 = tex2D (g_samShadow, clamp01 (uv+float2(-dt,0))).r + epsilon;
float mapDepth1 = tex2D (g_samShadow, clamp01 (uv+float2(dt,0))).r + epsilon;
float mapDepth2 = tex2D (g_samShadow, clamp01 (uv+float2(0,-dt))).r + epsilon;
float mapDepth3 = tex2D (g_samShadow, clamp01 (uv+float2(0,dt))).r + epsilon;
#else
float mapDepth0 = tex2D (g_samShadow, uv).r + epsilon;
float mapDepth1 = tex2D (g_samShadow, clamp01 (uv+float2(dt,0))).r + epsilon;
float mapDepth2 = tex2D (g_samShadow, clamp01 (uv+float2(0,dt))).r + epsilon;
float mapDepth3 = tex2D (g_samShadow, clamp01 (uv+float2(dt,dt))).r + epsilon;
#endif //useSymmetricProbes
...
Weight
1/6
[x, y+1]
Weight Weight
1/6
2/6
[x-1, y]
[x, y]
Weight
1/6
[x, y-1]
Weight
1/6
[x+1, y]
#if (useSymmetricProbes)
return (
lerp ((mapDepth0 > pixelDepth), (mapDepth1 > pixelDepth), t.x) + //x interpolate
lerp ((mapDepth2 > pixelDepth), (mapDepth3 > pixelDepth), t.y) + //y interpolate
2 * (mapDepthCenter > pixelDepth)) //Double weight for center
* 0.16; //Divide by 6 (1/6 = 0.16)
#else
return
lerp (
lerp ((mapDepth0 > pixelDepth), (mapDepth1 > pixelDepth), t.x), //x interpolate
lerp ((mapDepth2 > pixelDepth), (mapDepth3 > pixelDepth), t.x), //x interpolate
t.y); //y interpolate
#endif //useSymmetricProbes
}
Comp 4501
Wilf LaLonde ©2012
Results
5 Probe Symmetric PCF
Comp 4501
Wilf LaLonde ©2012
95.4501
Comp 4501
Wilf LaLonde ©2012
Variance Shadow Maps
• Supports hardware filtering (mipmapping +
anisotropic filtering).
• Probabilistically inspired and so difficult to
understand.
• Claims to reduce aliasing.
• Requires no changes to the game engine
architecture…
1-Sample VarianceMap is Equivalent to
Many-Sample PCF with large filters
Comp 4501
Wilf LaLonde ©2012
Variance Shadow Maps
• Instead of rendering just the depth as seen
by a light into a shadow texture, render both
depth and squared depth.
• Allow the card to anti-alias or blur the
shadow map texture.
• If the depth test for a pixel indicates that it is
in shadow, say it’s black just like PCF.
• If it is not in shadow, compute the
“percentage in shadow” from the filtered
data (from this ONE SAMPLE). Few people
understand this part…
Comp 4501
Wilf LaLonde ©2012
Gaussian Filters
• In terms of mean  and variance 2.
Comp 4501
Wilf LaLonde ©2012
Chebychev’s Inequality
• Let x be a random variable drawn from a
distribution with mean  and variance 2.
Then for t > 
P(x  t)  pmax(t) =
2
2 + (t-)2
 is what you expect x to be; i.e. E(x)
2 is how you expect x to change; i.e. E(x2) - E(x)2
We want to re-interpret this in terms of
pixel depth d; i.e. t = d.
Comp 4501
Wilf LaLonde ©2012
For depth
samples sd and
Chebychev’s
Inequality
2
sd and pixel depth d
• Let x be a random variable drawn from a
distribution with mean  and variance 2.
Then for t > 
pixel is partially shadowed
pixel is behind
2
 t)  pmax(t) =
If d > sdP(x
, theorem
2 + (t-)2

applies
 is what you expect x to be; i.e. E(x)
2 is how you expect x to change; i.e. E(x2) - E(x)2
We want to re-interpret this in terms of pixel depth d; i.e. t = d.
Comp 4501
Wilf LaLonde ©2012
For depth
samples sd and
Chebychev’s
Inequality
2
sd and pixel depth d
• Let x be a random variable drawn from a
distribution with mean
 andthat
variance
2is.
Probability
random depth
greater than d; i.e., of d being in
Then for t > 
front; i.e. of pixel being visible
d > sd
percentage visible
2
P(x  t)  pmax(t) =
 = sd
2 + (t-)2
pixel behind

(but with some probability)
 is what you expect x dto be; i.e. E(x)
2 is how you expect x to change; i.e. E(x2) - E(x)2
2 = sd - 2
2
We want to re-interpret this in terms of pixel depth d ; i.e. t = d.
Comp 4501
Wilf LaLonde ©2012
Chebychev’s Inequality
• For depth samples sd and sd2 and pixel depth
d, the percentage visible is
2
2 + (d-)2
where  = sd and 2 = sd - 2
2
visible means unaccluded
Comp 4501
Wilf LaLonde ©2012
To validate Chebychev’s inequality, consider
• An occluder at depth d1 that casts a shadow on
a surface at depth d2 where p is the percentage
that is visible (unoccluded).
p
d1
Top view with
p unoccluded
d2
2
2 + (d2-)2
Let’s compute everything from 1st principles
and see if Chebychev’s inequality gives us p?
Comp 4501
Wilf LaLonde ©2012
First compute  and 2 (average depth + depth variance).
p
d1
1-p
p unaccluded, 1- p occluded
d2
Expected depth is d2 with some probability
and d1 with some probability
 = E(x) = p d2 + (1-p) d1
E(x2) = p d22 + (1-p) d12
2 = E(x2) - E(x)2 = p d22 + (1-p) d12 - (p d2 + (1-p) d1)2
Comp 4501
Wilf LaLonde ©2012
Simplify 2
2 = p d22 + (1-p) d12 - (p d2 + (1-p) d1)2
= p d22 + (1-p) d12 - (p2d22 + (1-p)2d12 + 2p(1-p)d1d2)
= (p - p2) d22 + ((1-p) - (1-p)2) d12 - 2(p-p2)d1d2)
= (p - p2) d22 + (p - p2) d12 - 2(p - p2)d1d2)
= (p - p2) (d22 + d12 - 2d1d2)
= (p -
p2)
(d2 - d1
)2
= p(1-p) (d2 - d1)2
(1-p) - (1-p)2
= 1-p - (1-2p+p2)
= 1-p -1+2p-p2
= p-p2
Comp 4501
Wilf LaLonde ©2012
Also, compute and simplify (d2-)2 (needed too)
(d2-)2
1-2p+p2 = (1-p)2
= (d2 2 - 2d2 -  2)
= d22 - 2d2 (pd2 + (1-p)d1)
+ p2d22 + 2p d2(1-p) d1 + (1-p)2d12
= d22 - 2pd22 - 2(1-p)d1d2
+ p2d22 + 2p d2(1-p) d1 + (1-p)2d12
= d22 (1-2p+p2) - 2d2d1 (1-p - p(1-p)) + d12(1-p)2
= (d22 - 2d2d1 + d12) (1-p)2
= (d2 - d1)2 (1-p)2 = (1-p)2 (d2 - d1)2
Comp 4501
Wilf LaLonde ©2012
Percentage visible via Chebychev’s inequality
Since 2 = p(1-p)(d2-d1)2, and (d2-)2 = (1-p)2(d2-d1)2,
percentage visible
Cancel (1-p)
2

Cancel (d2 - d1)2
=
2 + (d2-)2
=
p(1-p) (d2 - d1)2
p(1-p)(d2 - d1)2 + (1-p)2 (d2 - d1)2
p
=
= p
p + (1-p)
Chebychev’s inequality works!
Comp 4501
Wilf LaLonde ©2012
Programming Chebychev’s Inequality
• Let x be a random variable drawn from a
distribution with mean  and variance 2.
Then for t > 
P(x  t)  pmax(t) =
2
2 + (t-)2
 is what you expect x to be; i.e. E(x)
2 is how you expect x to change; i.e. E(x2) - E(x)2
With t = d, denote E(x) by z, and E(x2) by zSquared
Comp 4501
Wilf LaLonde ©2012
Programming Chebychev’s Inequality
• Let x be a random variable drawn from a
distribution with mean  and variance 2.
Then for t > 
P(x  t)  pmax(t) =
2
2 + (d-z)2
 is what you expect x to be; i.e. z
2 is how you expect x to change; i.e. zSquared - z2
With t = d, denote E(x) by z, and E(x2) by zSquared
Comp 4501
Wilf LaLonde ©2012
Variance Shadow Maps
• Start with a pixel at depth d with mean and
variance [z, zSquared] retrieved from the 2component shadow map (because of harware
anti-aliasing or blurring, zSquared  z2; i.e., the 2
components average to values that don’t match).
• If the depth test for a pixel indicates that it is
visible (d  z), return 100% visible.
• If not visible (d > z), compute percentage visible
p from the following.
If (d > z) p =
Comp 4501
2
2 + (d-z)2
2 = zSquared - z2
Wilf LaLonde ©2012
Changing What We Can Write Into ShadowMap
//Create the shadow map texture...
V_RETURN ( pd3dDevice->CreateTexture (
SHADOWMAP_SIZE, SHADOWMAP_SIZE,
1, D3DUSAGE_RENDERTARGET,
D3DFMT_R32F, D3DPOOL_DEFAULT,
&g_pShadowMap, NULL ) );
const bool usingVarianceShadowMap = experiment == drawVarianceShadow1Sample;
usingVarianceShadowMap ? D3DFMT_A32B32G32R32F : D3DFMT_R32F
Allowing the texture to contains MORE than just a depth
Comp 4501
Wilf LaLonde ©2012
Time for an Experiment (Experiment 8)
• WilfShadowExperiment.choice
#define drawVarianceShadow1Sample 8
#define experiment drawVarianceShadow1Sample
• Add to “ShadowMap.cpp” file.
#include "WilfShadowExperiment.choice"
• Changes to the ShadowMap Writing Shader...
void PixShadow (float2 Depth : TEXCOORD0, out float4 Color : COLOR) {
//Depth is z / w
#if (experiment == drawVarianceShadow1Sample)
float pixelDepth = Depth.x / Depth.y;
Color = float4 (pixelDepth, pixelDepth * pixelDepth, 0, 0);
#else
Color = Depth.x / Depth.y;
#endif //experiment
}
Comp 4501
Wilf LaLonde ©2012
Time for an Experiment (Experiment 8)
• Changes to Get Hardware Anti-Aliasing...
sampler2D g_samShadow = sampler_state {
Texture = <g_txShadow>;
#if (experiment == drawVarianceShadow1Sample)
MinFilter = Anisotropic;
MagFilter = Anisotropic;
MipFilter = Anisotropic;
Filter = Anisotropic;
MaxAnisotropy = 16;
#else
MinFilter = Point;
MagFilter = Point;
MipFilter = Point;
#endif //experiment
AddressU = Clamp;
AddressV = Clamp;
};
Comp 4501
Wilf LaLonde ©2012
Simplifying a bit
float lightIntensityViaVariance (
//1 if visible; 0 if in shadow; in between if partially in shadow...
//4 canSeeLight parameters...
float3 pixelToLightCS, float3 pixelNormalCS, float3 normalizedToLightVector, float coneAngleAsCosine,
//3 isInShadow parameters...
sampler2D shadowMap, float2 textureCoordinateSS, in float4 positionLPS, float epsilon) {
//Immediately deal with the situations where variance does NOT apply...
if (!canSeeLight (pixelToLightCS, pixelNormalCS, normalizedToLightVector, coneAngleAsCosine)) return 0.0;
still there
float2 uv = asShadowMapTextureCoordinates (positionLPS);
float pixelDepth = positionLPS.z / positionLPS.w - epsilon;
float2 sample = tex2D (g_samShadow, uv).rg; float z = sample.r; float zSquared = sample.g;
if (pixelDepth <= z) return 1.0; //Completely visible...
//Variance applies...
float varianceSquared = zSquared - z * z;
float depthDelta = pixelDepth – z;
float probabilityOfBeingVisible = varianceSquared / (varianceSquared + depthDelta * depthDelta );
return probabilityOfBeingVisible;
}
2
If (d > z) p =
Comp 4501
2 + (d-z)2
2 = zSquared - z2
Wilf LaLonde ©2012
Looks A Little Strange (Pixelation Visible)
Comp 4501
Wilf LaLonde ©2012
Taking Suggestions Into Account (Even worse)
return pow (probabilityOfBeingVisible, 4); //Suggestion by a number of papers...
Comp 4501
Wilf LaLonde ©2012
Another Addition (Many other failures tried)
float varianceSquared = zSquared - z * z; varianceSquared = abs (varianceSquared);
Comp 4501
Wilf LaLonde ©2012
Search of the Literature Also Reveals
• Implementers typically use the linearized
depth (i.e., depth in meters) rather than the
clip coordinate depth which is in the range 0
to 1 (not a very big range when subtracting
numbers and squaring them).
Just Need the position in camera (view) space when drawing
the shadowMap via shaders VertShadow and PixShadow.
Comp 4501
Wilf LaLonde ©2012
Will Using Linear Depth Fix It
• WilfShadowExperiment.choice
#define drawVarianceShadow1SampleViaLinearDepth 9
#define experiment drawVarianceShadow1SampleViaLinearDepth
• Other details left for the reader.
Details for the reader (NOT DONE)
Comp 4501
Wilf LaLonde ©2012
Nvidia SDK 10 has a Demo But It’s Using 9x9 VSM
Also using MSAA
Comp 4501
Wilf LaLonde ©2012
Perhaps We Need to Look Elsewhere
• There are soft shadow demos that might be
simpler and more effective...
all visible
69% visible
0% visible
By determining “how much” of a filter rectangle around the pixel is visible
3x3 filter
Comp 4501
Wilf LaLonde ©2012
95.4501
Comp 4501
Wilf LaLonde ©2012
Soft Shadows
Comp 4501
Wilf LaLonde ©2012
Fuzzy Compared To Hard Shadows
partial darkness
total darkness
Comp 4501
Wilf LaLonde ©2012
Nvidia DX10 SDK has Percentage Closer Soft
Shadows (25x25 Poison Filter)
Let’s skip this one
Comp 4501
Wilf LaLonde ©2012
Inspired By SoftShadows Demo of Nvidia SDK 10
Fast Version Works Well
Accurate technique implements
Real-Time Soft Shadow Mapping by Back Projection, Guennebaud et all
Comp 4501
Wilf LaLonde ©2012
Recall: PCF Shadows From Point Lights
•
•
•
•
Hard shadows  1x1 filter
(0 or 1 value)
Softer shadows  2x2 filter  in range [0,1]
Even softer shadows  16x16 filter
Softer shadows yet  64x64 filter
Per Pixel Large filters  very slow shaders…
Comp 4501
Wilf LaLonde ©2012
But We Can Be Efficient...
•
•
•
•
•
•
If we use a 32x32 filter only where its needed
Otherwise, a 16x16 filter if its needed
Otherwise, a 8x8 filter if its needed
Otherwise, a 4x4 filter if its needed
Otherwise, a 2x2 filter if its needed
Otherwise, a 1x1 filter if its needed
How Do We Decide? Key Insight: a MIN/MAX shadow map
Comp 4501
Wilf LaLonde ©2012
Consider Lower Detail Pixels (1 red/blue = 4 black)
Min (low detail depth)
High detail depth
Max (low detail depth)
Technically, a low resolution pixel is divided into 4 higher resolution pixels...
Comp 4501
Wilf LaLonde ©2012
Basic Idea
• Build a hierarchy of shadow texture
resolutions; specifically, a min-max shadow
map hierarchy.
• Access the shadow map with a recursive
shader starting at 1-pixel version and
recursively go to lower levels if needed.
• Find a way to implement the recursive
shader NON-RECURSIVELY.
Comp 4501
Wilf LaLonde ©2012
Considering ONLY Lower Detail Pixels, What Can We Deduce?
VISIBLE (Pz  Min)
brightness = 1
DON’T KNOW
need more detail
HIDDEN (Pz  Max)
brightness = 0
Comp 4501
Wilf LaLonde ©2012
95.4501
Comp 4501
Wilf LaLonde ©2012
Where Do We Start
• The Nvidia DX SDK 10 has a good DirectX10 demo called
“SoftShadow” from 2007.
• Placed a copy of the demo in “C:\School\4501.2012\From June
2010 DX SDK – Wilf” even though it’s NOT a Microsoft demo.
• It runs either a FAST demo or an ACCURATE demo (which is
very complicated; it is based on the following paper).
Real-time soft shadow mapping by backprojection,
Guennebaud, Barthe, and Paulin, Eurographics Symposium
on Rendering, 2006.
Paper in the notes
Both Use a MIN-MAX Shadow Map Hierarchy
BUT there is a problem. I Can’t DECIPHER the FAST demo...
Wilf: see FastShadow in SoftShadowsORIGINAL.fx
Comp 4501
Wilf LaLonde ©2012
A Quick Look At The Drawing Organization
Render the shadow map from light point (whole scene)
Technique "RenderDepth“ (whole scene) //Standard z-depth
Set “DepthTex0” as input texture for Standard z-depth
Technique "ConvertDepth“ (large triangle) //Top level min-max shadow map
LOOP I FROM 1 /* SKIP 0 */ to nMips-1
Set viewport width /= 2; height /= 2
Set “DepthMip2” as input texture for min-map depth [I – 1]
Technique "CreateMip“ (large triangle) //Lower level min-map shadow map
ENDLOOP
Render whole scene from the camera just to get the camera z-buffer.
Technique “RenderDepth” (whole scene) ) //Standard z-depth
Render whole scene from the camera for textured and shadowed color
Technique "RenderFast“ //Uses min-max shadow map
Render the light object with technique "RenderNoShadows“
Render GUI...
Comp 4501
Wilf LaLonde ©2012
Important Point
• The mipmap textures 0, 1, 2, ... of DepthMip2
are accessed as texture resources. To the
shader, they look like ordinary textures.
• The code for doing this is very messy. It’s
shown on the next 2 slides but YOU
SHOULD PROBABLY SKIP IT...
IT’S ALSO WIDER THAN THE SCREEN
Comp 4501
Wilf LaLonde ©2012
Creates a texture with Mipmaps as Resources
//1. Create the texture...
m_pDepthMip2 is the texture
D3D10_TEXTURE2D_DESC rtDesc = {
DEPTH_RES, //UINT Width;
DEPTH_RES, //UINT Height;
m_pDepthMip2SRViews[im] is the
1,//UINT MipLevels;
subtexture (shader resource view)
1,//UINT ArraySize;
DXGI_FORMAT_R32_TYPELESS,//DXGI_FORMAT Format;
{1, 0}, //DXGI_SAMPLE_DESC SampleDesc;
D3D10_USAGE_DEFAULT, //D3D10_USAGE Usage;
D3D10_BIND_SHADER_RESOURCE | D3D10_BIND_DEPTH_STENCIL,//UINT BindFlags;
0,//UINT CPUAccessFlags;
m_pDepthMip2RTViews[im]
0,//UINT MiscFlags;
is the render target view
};
//The texture with subtexture resources
V(pDev10->CreateTexture2D (&rtDesc, NULL, &m_pDepthMip2));
WILF Note: MipLevels from SDK help => Use 1 for a
multisampled texture; or 0 to generate a full set of subtextures
(this creates the texture but it’s NOT finished, I think).
Comp 4501
Wilf LaLonde ©2012
Creates a texture with Mipmaps as Resources
//2Set up a description for shader resource and render target...
m_pDepthMip2 is the tex
D3D10_SHADER_RESOURCE_VIEW_DESC srViewDesc;
srViewDesc.Format = DXGI_FORMAT_R32_FLOAT;
srViewDesc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D;
srViewDesc.Texture2D.MostDetailedMip = 0;
srViewDesc.Texture2D.MipLevels = nMips;
creates the mipmaps
srViewDesc.Format = DXGI_FORMAT_R32G32_FLOAT;
V(pDev10->CreateShaderResourceView (m_pDepthMip2, &srViewDesc, &m_pDepthMip2SRV
srViewDesc.Texture2D.MipLevels = 1;
D3D10_RENDER_TARGET_VIEW_DESC rtViewDesc;
rtViewDesc.ViewDimension = D3D10_RTV_DIMENSION_TEXTURE2D;
m_pDepthMip2SR
subtexture (shade
m_pDepthMip2
is the render ta
//3. Create the view and render targets for the mipmaps...
for (int im = 0; im < nMips; ++im) {
gives you access to
srViewDesc.Texture2D.MostDetailedMip = im;
srViewDesc.Format = DXGI_FORMAT_R32G32_FLOAT;
V(pDev10->CreateShaderResourceView (m_pDepthMip2, &srViewDesc, &m_pDepthMip2
rtViewDesc.Texture2D.MipSlice = im;
same for the render targe
rtViewDesc.Format = DXGI_FORMAT_R32G32_FLOAT;
V(pDev10->CreateRenderTargetView (m_pDepthMip2, &rtViewDesc, &m_pDepthMip2RTV
}
Comp 4501
Wilf LaLonde ©2012
The Shaders
ignore
technique10 RenderNoShadows {pass P0 {RenderSceneNoShadows VS and PS}}
technique10 RenderFast {pass P0 {RenderSceneFast VS and PS}}
redo
technique10 RenderAcc {pass P0 {RenderSceneAcc VS and PS}}
technique10 RenderDepth {pass P0 {RenderDepthVS and NULL}}
technique10 ReworkDepth2 {
pass ConvertDepth {ConvertDepthVS and ConvertDepth2PS}}
pass CreateMip {ConvertDepthVS and CreateMip2PS}}
pass ConvertToBig {ConvertDepthVS and ConvertToBigPS}}
study
don’t need
}
ID3D10Effect *g_pEffect; //From D3DX10CreateEffectFromFile
ID3D10EffectTechnique *pDReworkTechnique2 =
g_pEffect >GetTechniqueByName ("ReworkDepth2");
pDReworkTechnique2->GetPassByName ("CreateMip")->Apply(0);
Comp 4501
Wilf LaLonde ©2012
95.4501
Comp 4501
Wilf LaLonde ©2012
Shader Header: Part 1 of 2
RasterizerState RStateMSAAON {MultisampleEnable = FALSE;} //MSAA too slow
Texture2D<float> DepthTex0;
Texture2D<float2> DepthMip2;
Texture2D DiffuseTex;
SamplerComparisonState DepthCompare; //WILF: NOT USED
SamplerState DepthSampler {
Filter = MIN_MAG_MIP_POINT;
AddressU = Clamp; AddressV = Clamp;
};
SamplerState DiffuseSampler {
Filter = MIN_MAG_MIP_LINEAR;
AddressU = Wrap; AddressV = Wrap;
};
Note DX10 versus DX9 differences (texture and filter)...
Comp 4501
Wilf LaLonde ©2012
Shader Header: Part 2 of 2
cbuffer cb0 : register (b0) {
float4 g_vMaterialKd;
float3 g_vLightPos; ///< light in world CS
float4 g_vLightFlux;
float g_fFilterSize, g_fDoubleFilterSizeRev;
row_major float4x4 mViewProj;
row_major float4x4 mLightView;
row_major float4x4 mLightViewProjClip2Tex;
row_major float4x4 mLightProjClip2TexInv;
bool bTextured;};
#define N_LEVELS 10
#define DEPTH_RES 1024
cbuffer cb1 : register (b1) {
float g_fResRev[N_LEVELS] = {
1./1024, 1./512, 1./256, 1./128, 1./64, 1./32, 1./16, 1./8, 1./4, 1./2};};
V(g_pEffect->GetVariableByName ("mViewProj")->AsMatrix()
->SetMatrix ((float *)&ssmap.mLightViewProj));
V(g_pEffect->GetVariableByName("DepthTex0")->AsShaderResource ()
->SetResource (m_pDepthSRView[0]));
No special code in demo to load cbuffers (done as shown above)
Comp 4501
Wilf LaLonde ©2012
Shader Technique RenderDepth
float4 RenderDepthVS (float3 vPos: POSITION) : SV_Position {
return mul (float4 (vPos, 1), mViewProj);
}
Wilf: Microsoft must mean texture units; e.g. 0-799, not clip units; e.g., -1.0-+1.0.
FROM SDK: In Direct3D 10, the SV_Position semantic (when used in the context
of a pixel shader) specifies screen space coordinates (offset by 0.5).
SV_Position can be specified as an input to a vertex shader as well as an output.
When drawn into an 800x800 texture, [x,y] ranges from
0.5, 1.5, ..., 799.5 (actuality)
rather than 0, 1, 2, 3, ..., 798, 799 (better way to remember it)
Nothing special (convert to projection (or clip) space)
Comp 4501
Wilf LaLonde ©2012
Shader Technique ConvertDepth To Create Min-Max Shadow Map
float4 ConvertDepthVS (uint iv : SV_VertexID) : SV_Position { -1
return float4((iv << 1) & 2, iv & 2, 0.5, 1) * 2 - 1;
-1
//Wilf: For 0 => (0,0,0.5,1)*2-1 = (-1,-1,0,1)
0
//Wilf: For 1 => (2,0,0.5,1)*2-1 = (3,-1,0,1) //clockwise
//Wilf: For 2 => (0,2,0.5,1)*2-1 = (-1,3,0,1)
+1
//Wilf: Triangle encompases [-1,-1,0,1] to [1,1,0,1]
}
+2
0
+1
+2
+3
+3
float2 ConvertDepth2PS (float4 vPos : SV_Position) : SV_Target0 {
float fDepth = DepthTex0.Load (uint3 (vPos.x, vPos.y, 0)); //Wilf: Depth at [x,y]
return float2 (fDepth, fDepth); //Wilf: Let Min = Max = depth...
}
How ConvertDepth Draw is done
ID3D10Buffer *pNullVBuf[] = {NULL};
unsigned pStrides[] = {0}; unsigned pOffsets[] = {0};
pDev10->IASetVertexBuffers (0, 1, pNullVBuf, pStrides, pOffsets);
pDev10->IASetInputLayout (NULL);
Since there are NO vertices,
pDev10->Draw (3, 0);
draw 3 VERTEX
Ids.©2012
Wilf LaLonde
Comp 4501
Shader Technique CreateMip
SAME ConvertDepthVS AND DRAW as previous slide
[u,v]
[2u,2v]
float2 CreateMip2PS (float4 vPos : SV_Position) : SV_Target0 {
uint3 iPos = uint3 ((int) vPos.x << 1, (int) vPos.y << 1, 0);
float2 vDepth = DepthMip2.Load (iPos), vDepth1; //Wilf: [2u,2v]
++iPos.x;
vDepth1 = DepthMip2.Load (iPos); //Wilf: [2u+1,2v]
vDepth = float2 (min (vDepth.x, vDepth1.x), max (vDepth.y, vDepth1.y));
++iPos.y;
vDepth1 = DepthMip2.Load (iPos); //Wilf: [2u+1,2v+1]
vDepth = float2 (min (vDepth.x, vDepth1.x), max (vDepth.y, vDepth1.y));
--iPos.x;
vDepth1 = DepthMip2.Load (iPos); //Wilf: [2u,2v+1]
vDepth = float2 (min (vDepth.x, vDepth1.x), max (vDepth.y, vDepth1.y));
return vDepth;
Compute MIN of the mins and MAX of the maxes...
}
Each LOAD is already MIN (x) and MAX (y)
Taking the MIN of the 4 pixels and the MAX of the 4 pixels
Comp 4501
Wilf LaLonde ©2012
RECALL: Drawing Organization
Render the shadow map from light point (whole scene)
Technique "RenderDepth“ (whole scene) //Standard z-depth
Set “DepthTex0” as input texture for Standard z-depth
Technique "ConvertDepth“ (large triangle) //Top level min-max shadow map
LOOP I FROM I to nMips-1
Set viewport width /= 2; height /= 2
Set “DepthMip2” as input texture for min-map depth [I – 1]
Technique "CreateMip“ (large triangle) //Lower level min-map shadow map
ENDLOOP
Render whole scene from the camera just to get the z-buffer.
Technique “RenderDepth” (whole scene) ) //Standard z-depth
Render whole scene from the camera for textured and shadowed color
Technique "RenderFast“ //Uses min-max shadow map
Render the light object with technique "RenderNoShadows“
Render GUI...
Comp 4501
Wilf LaLonde ©2012
95.4501
Comp 4501
Wilf LaLonde ©2012
Recall
• Goal is to determine how much of an area
around a pixel is visible...
all visible
69% visible
0% visible
So we’ll need to encode rectangles...
3x3 filter
Comp 4501
Wilf LaLonde ©2012
Basic Premise
• We will start with the map rectangle for the
entire 1024x1024 shadow and work our way
down (dividing by 4) to a 1x1 map.
• When it intersects with the sample rectangle
of the pixel, we try to determine how much of
the area is visible to get soft shadows..
map rectangle
sample rectangle
Comp 4501
Wilf LaLonde ©2012
Representing Rectangles
• Need 2 points: minimum and maximum
maximum
• float4 rectangle = float4 (0,0, 1023,1023);
#define LEFT(rectangle) rectangle.x
#define RIGHT(rectangle) rectangle.z
minimum
maximum
rectangle.xy
rectangle.zw
#define TOP(rectangle) rectangle.y
#define BOTTOM(rectangle) rectangle.w
#define MINIMUM(rectangle) rectangle.xy
#define MAXIMUM(rectangle) rectangle.zw
Comp 4501
Wilf LaLonde ©2012
Rectangle Intersection
#define CENTER(rectangle) (MINIMUM (rectangle) + MAXIMUM (rectangle)) * 0.5
#define EXTENT(rectangle) (MAXIMUM (rectangle) - MINIMUM (rectangle))
#define RECTANGLE(minimum, extent) float4 (minimum, minimum + extent)
bool areasIntersect (float4 rectangle1, float4 rectangle2, inout float intersectionArea) {
//Returns true for an intersection (also computes intersectionArea); false otherwise.
//Eliminate the cases where there is no intersection.
if (LEFT (rectangle1) >= RIGHT (rectangle2) || RIGHT (rectangle1) <= LEFT (rectangle2) ||
BOTTOM (rectangle1) <= TOP (rectangle2) || TOP (rectangle1) >= BOTTOM (rectangle2))
return false;
float4 rectangle; //Clamp rectangle1 to lie between min and max of rectangle2.
MINIMUM (rectangle) = clamp (MINIMUM (rectangle1), MINIMUM (rectangle2), MAXIMUM (rectangle2));
MAXIMUM (rectangle) = clamp (MAXIMUM (rectangle1), MINIMUM (rectangle2), MAXIMUM (rectangle2));
float2 extentOfArea = EXTENT (rectangle) ;
intersectionArea = extentOfArea.x * extentOfArea.y; return true;
}
Useful utilities
Comp 4501
Wilf LaLonde ©2012
What if a Pixel Cannot Be Seen By The Light?
bool isOutsideShadowMap (float3 uv) {
if (uv.x < 0.0 || uv.x > 1.0) return true;
if (uv.y < 0.0 || uv.y > 1.0) return true;
if (uv.z < 0.0 || uv.z > 1.0) return true;
return false;
}
float2 texel (const Texture2D<float2> texture, const SamplerState sampler,
float2 uv, float mipmapLevel) {
return texture (sampler, uv, mipmapLevel);
}
Useful utilities
Comp 4501
Wilf LaLonde ©2012
95.4501
Comp 4501
Wilf LaLonde ©2012
RECALL: Considering ONLY Lower Detail Pixels, What Can We Deduce?
VISIBLE (Pz  Min)
brightness = 1
DON’T KNOW
need more detail
OCCLUDED (Pz  Max)
brightness = 0
bool inLight (float pixelDepth, float depthMapMinimumDepth) {
return pixelDepth <= depthMapMinimumDepth;}
bool inShadow (float pixelDepth, float depthMapMaximumDepth) {
return pixelDepth >= depthMapMaximumDepth;}
Comp 4501
Wilf LaLonde ©2012
Computing If Visible (Opposite of Occluded)
float WilfPCFVisibilityUsingHierarchicalShadowMaps (float3 pixelUV) {
//Returns 1 if all visible; 0 if all occluded; anywhere in between otherwise...
//Transform from [-1,+1] projection space to [0,1] texture space...
pixelUV.xy = asShadowMapTextureCoordinates (pixelUV); //z is unchanged
if (isOutsideShadowMap (pixelUV)) return 0.0;
//Get extent (e.g., 1024x1024) from system rather than hardwired variables...
uint2 extent; uint numberOfLevels;
DepthMip2.GetDimensions (0, extent.x, extent.y, numberOfLevels);
//Build a relatively large filter around uv... in [0,1] coodinates...
float2 filterCenter = pixelUV.xy;
float2 filterExtent = float2 (7, 7) / extent; float2 h = filterExtent * 0.5;
float4 filterRectangle = float4 (filterCenter - h, filterCenter + h);
//And assume it's all visible for now...
float totalFilterArea = filterExtent.x * filterExtent.y;
float visibleArea = totalFilterArea;
//Assumes visibleArea, pixelUV, and filterRectangle are accessible.
float4 mapRectangle = RECTANGLE (float2 (0.0, 0.0), float2 (1.0, 1.0));
WilfRecursiveDecrementVisibleArea (mapRectangle, numberOfLevels - 1);
float visibilityIntensity = visibleArea / totalFilterArea;
return visibilityIntensity;
}
Comp 4501
Wilf LaLonde ©2012
WilfRecursiveDecrementVisibleArea
//Note: visibleArea, pixelUV, and filterRectangle are externally defined...
//Mipmap level 0 is the HIGHEST detail (entire map is 1024x1024 pixels) whereas
//mipmap level 9 is LOWEST detail (entire map is 1 pixel)...
void WilfRecursiveDecrementVisibleArea (float4 mapRectangle, float mipmapLevel) {
//Adjusts visibleArea starting from the map rectangle of the entire texture
//and the LOWEST detail mipmap level; mipmap is 1x1... Cuts the rectangle into 4
//and recursively processes HIGHER (more detailed) mipmap levels; SO
//mipmapLevel is decremented by recursion from 9, 8, ..., 2, 1, 0.
//Note: if mipmapLevel 0 is reached, minimum and maximum depths are equal...
//So one of inShadow or inLight will be true...
Next Slide Repeat Without Comments
}
Comp 4501
Wilf LaLonde ©2012
WilfRecursiveDecrementVisibleArea
void WilfRecursiveDecrementVisibleArea (float4 mapRectangle, float mipmapLevel) {
if (visibleArea <= 0.0) return; //Since there is nothing left to subtract...
float2 mapMinMaxDepth = texel (DepthMip2, DepthSampler,
CENTER (mapRectangle), mipmapLevel).xy;
if (inLight (pixelUV.z, mapMinMaxDepth.x)) return; //So don't decrease...
float occludedArea;
if (!areasIntersect (mapRectangle, filterRectangle, occludedArea)) return;
if (inShadow (pixelUV.z, mapMinMaxDepth.y)) {//So decrease by intersection...
visibleArea -= occludedArea; return;
}
//Otherwise, we can't tell without processing a higher detail shadow map...
float2 minimum = MINIMUM (mapRectangle);
float2 halfExtent = EXTENT (mapRectangle) * 0.5;
#define recur(min) WilfRecursiveDecrementVisibleArea ( \
RECTANGLE (min, halfExtent ), mipmapLevel - 1)
recur (minimum);
recur (minimum + float2 (halfExtent , 0));
recur (minimum + float2 (0, halfExtent ));
recur (minimum + halfExtent );
#undef recur
}
Comp 4501
Wilf LaLonde ©2012
Simulating a Stack: How Big Do We Need
• Consider the following 16 numbers
1 2 4 8 16 32 64 128 256 512
1024 2064 4096 8192 16384 32768
• Stack size 16 can handle a 32768 x 32768
texture provided there is only ONE entry
stored per mipmap level...
A simple version needs 16 * 3 + 1 entries
(4 recursive calls per level but the last
one is immediately popped)
• An entry could be
struct Stack {float4 rectangle; int level;};
Comp 4501
Wilf LaLonde ©2012
Recall: General structure
void WilfRecursiveDecrementVisibleArea (float4 mapRectangle, float mipmapLevel) {
GENERIC CODE WORKING ON mapRectangle AND mipmapLevel
There are 3 returns
#define recur(min) WilfRecursiveDecrementVisibleArea ( \
RECTANGLE (min, halfExtent ), mipmapLevel - 1)
recur (minimum);
recur (minimum + float2 (halfExtent , 0));
recur (minimum + float2 (0, halfExtent ));
recur (minimum + halfExtent );
}
Comp 4501
Wilf LaLonde ©2012
Simple Approach
#define declareStack \
struct Stack {float4 rectangle; int level}; \
Stack stack [16 * 3 + 1]; int top = -1;
#define push(a,b) top++; stack [top].rectangle = a; stack [top].level = b;
#define pop(a,b) a = stack [top].rectangle; b = stack [top].level; top--;
void WilfIterativeDecrementVisibleArea (float4 mapRectangle, float mipmapLevel) {
declareStack
push (mapRectangle, mipmapLevel)
while (top >= 0) {
pop (mapRectangle, mipmapLevel)
GENERIC CODE WORKING ON mapRectangle AND mipmapLevel
Replace 2nd and 3rd return by continue
#define PUSH(x) push (RECTANGLE (x, halfExtent ), mipmapLevel - 1)
PUSH (minimum);
PUSH (minimum + float2 (halfExtent , 0));
Since processing order
PUSH (minimum + float2 (0, halfExtent ));
does not matter
PUSH (minimum + halfExtent );
}
}
Comp 4501
Wilf LaLonde ©2012
Using a Smaller Stack
• Is there a way to limit stack size to 16 by
using the same entry 4 times? Think cases...
void WilfIterativeDecrementVisibleArea (float4 mapRectangle, float mipmapLevel) {
declareStack
push (mapRectangle, mipmapLevel)
while (top >= 0) {
pop (mapRectangle, mipmapLevel)
GENERIC CODE WORKING ON mapRectangle AND mipmapLevel
Replace 2nd and 3rd return by continue
case 0
#define PUSH(x) push (RECTANGLE (x, halfExtent ), mipmapLevel - 1)
PUSH (minimum);
PUSH (minimum + float2 (halfExtent , 0));
Since processing order
PUSH (minimum + float2 (0, halfExtent ));
does not matter
PUSH (minimum + halfExtent );
case 1
case 2
case 3
}
}
Comp 4501
Wilf LaLonde ©2012
Use 4 cases via a variable called “child”
#define declareStack struct Stack {float4 rectangle; int level; int child}; Stack stack [16]; int top = -1;
#define push(a,b,c) top++; stack [top].rectangle = a; stack [top].level = b; stack [top].child = c;
#define get(a,b,c) a = stack [top].rectangle ; b = stack [top].level; c = stack [top].child;
#define pop top--;
#define update stack [top].child++
void WilfIterativeDecrementVisibleArea (float4 mapRectangle, float mipmapLevel) {
declareStack; int child;
push (mapRectangle, mipmapLevel, 0)
while (top >= 0) {
get (mapRectangle, mipmapLevel, child)
if (child == 0) {
GENERIC CODE WORKING ON mapRectangle AND mipmapLevel
}
#define PUSH(x) push (RECTANGLE (x, halfExtent ), mipmapLevel – 1, 0)
if (child == 0) {update; PUSH (minimum); continue;}
if (child == 1) {update; PUSH (minimum + float2 (halfExtent , 0)); continue;}
if (child == 2) {update; PUSH (minimum + float2 (0, halfExtent )); continue;}
if (child == 3) {update; PUSH (minimum + halfExtent ); pop; continue;}
}
}
Comp 4501
unnecessary
Wilf LaLonde ©2012
Final Routines
• Replace WilfRecursiveDecrementVisibleArea in
WilfPCFVisibilityUsingHierarchicalShadowMaps by
the contents of WilfIterativeDecrementVisibleArea.
• See actual code for final result... Used the simple
approach rather than the more clever one...
Comp 4501
Wilf LaLonde ©2012
Tricks and Cleverness
• Perspective Shadow Maps: Care and
Feeding (subsection Tricks for Better
Shadow Maps (subsubsection Blurring))
Kozlov, GPU Gems, pp. 217-244.
• For terrain and sunlight only, draw black or
white into the depth texture. Then blur it by
redrawing over smaller depth (actually color)
map a few times (called ping-pong
rendering). Finally, draw this blurred black
and white color onto the terrain...
Comp 4501
Wilf LaLonde ©2012
Quick Review
•
•
•
•
PCF shadows.
PCF shadows with a filter.
Variance shadows.
Soft shadows using a hierarchical min-max
depth map.
• DirectX10 facilities
Mipmaps can be accessed as textures
SV_Position semantic gives you x/y tex coord.
Texture extent can be accessed by shader.
Can draw with n vertex ids and decode what
to do with it in vertex shader...
Comp 4501
Wilf LaLonde ©2012
References
• Poisson Shadow Blur, Mitchell, pp 403-409, ShaderX3, 2005.
• Percentage Closer Soft-shadows, Ferdinando, Siggraph 2005
Sketches, p. 35.
• Variance shadow maps, Donnely and Lauritzen, Proc. Of the
Symposium on Interactive 3D Graphics and Games 2006, pp.
161-165.
• Real-time soft shadow mapping by backprojection,
Guennebaud, Barthe, and Paulin, Eurographics Symposium on
Rendering, 2006.
• Fast Soft Shadows with Temporal Coherance, Scherzer,
Schwarzler, and Mattausch, pp. 243-255, , GPU Pro 2, 2011. A
Fast, Small-Radius GPU Media Filter, MgGuire, pp. 165-173,
ShaderX6, 2008. (Prince of Persia Look)
• Shadow Techniques for OpenGL Es 2.0, Feldstein, pp. 487-504,
ShaderX6, 2008.
Comp 4501
Wilf LaLonde ©2012
Related documents