Wilf LaLonde ©2012Comp 4501
95.450195.4501
Shadows
Wilf LaLonde ©2012Comp 4501
How Do I Draw a Pixel?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.
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
Internally nested shadow shapes possibleInternally nested shadow shapes possible
Occluders don’t need to be known…Occluders don’t need to be known…
Wilf LaLonde ©2012Comp 4501
95.450195.4501
Shadow Volumes
Wilf LaLonde ©2012Comp 4501
Seeing Nested Shadow VolumesSeeing Nested Shadow Volumes
inside green and red volumes
inside green volume only
inside NO volumes
Look at Demo
Wilf LaLonde ©2012Comp 4501
Basic IdeaBasic 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 shadowif stencil counter = n, it’s in n shadows…
Wilf LaLonde ©2012Comp 4501
Counting Via Stencil BufferCounting Via Stencil Buffer
4 Shadow casters
Camera
zero
Suppose drawing order is 1 2 3
1 +1, -1 Both faces in front of
2 +1, 0 One face in front of
3 +1, 0 One face in front of
Final result +2 for
4
4 0, 0 Both faces behind
Final result 0 for
Wilf LaLonde ©2012Comp 4501
95.450195.4501
Shadow Maps
Wilf LaLonde ©2012Comp 4501
Shadows Maps: Steps Involved Per LightShadows 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 z-buffer’s z.
Works best for spot light (since need location and orientation)
Works best for spot light (since need location and orientation)
Wilf LaLonde ©2012Comp 4501
Shadow MapsShadow Maps
• From an ATI Demo…. (draw world from light’s position as a camera TO GET light’s z-buffer)
L
Visible (in front of light’s z-buffer)
Not visible (behind light’s z-buffer)
Look at Demo
Wilf LaLonde ©2012Comp 4501
What the Special Shader DoesWhat 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 coordinatesq /= 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
Wilf LaLonde ©2012Comp 4501
Detailed TopicDetailed Topic
Shadow Maps
(Most popular approach)
Wilf LaLonde ©2012Comp 4501
VariationsVariations
Percentage Closer Filtering
Variance Shadow Maps
Cascaded Shadow MapsHierarchical Shadow Maps
Volumetric Shadow Maps
Exponential Shadow Maps
Convolution Shadow Maps
Forward Shadow Maps
Tiled Shadow Maps
Perspective Shadow Maps
Light Space Perspective Shadow Maps
Midpoint Shadow Maps
Omnidirectional Shadow Maps
Wilf LaLonde ©2012Comp 4501
Shadow MappingShadow Mapping
From “Real-time shadows” Book, Eisemann et al, CRC Press, 2012, page 33
From “Real-time shadows” Book, Eisemann et al, CRC Press, 2012, page 33
PDF available too
Wilf LaLonde ©2012Comp 4501
• [pLPSx, pLPS
y] 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.
Given pixel position pLPS (after dividing by w) in
light projection space (also called clip space)
Given pixel position pLPS (after dividing by w) in
light projection space (also called clip space)
pLPSz > texture at [pLPS
x, pLPSy] the pixel is hidden
Hidden in shadow Hidden in shadow
not normally accessed via clip coordinates depth map
Wilf LaLonde ©2012Comp 4501
• Clip coordinates c range from [-1,+1].• Texture coordinates t range from [0,1].
Transform via t = c * 0.5 + 0.5
Clip to Texture CoordinatesClip to Texture Coordinates
Why? Consider c ranges from -1 to +1 c*0.5 ranges from -0.5 to +0.5c*0.5+0.5 ranges from 0 to 1.0
Wilf LaLonde ©2012Comp 4501
• 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)
Problems with Shadow MapsProblems with Shadow Maps
How can you both magnify and shrink with a single transformation to perform the mapping?How can you both magnify and shrink with a
single transformation to perform the mapping?
Wilf LaLonde ©2012Comp 4501
An example of AliasingAn example of Aliasing
Wilf LaLonde ©2012Comp 4501
An Example of AcneAn Example of Acne
Wilf LaLonde ©2012Comp 4501
• Reduce areas of extreme mag- and mini-fications (increase resolution or change the engine architecture)
Perspective shadow maps => warp theshadow 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
SolutionsSolutions
Wilf LaLonde ©2012Comp 4501
• 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”
Percentage-Closer FilteringPercentage-Closer Filtering
Key insight: Correct filtering needs to filter the results of the depth comparisons, not the depthsKey insight: Correct filtering needs to filter the
results of the depth comparisons, not the depths
Wilf LaLonde ©2012Comp 4501
95.450195.4501
Where Do We Start
Wilf LaLonde ©2012Comp 4501
• 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);
Where Do We StartWhere Do We Start
Search “wilf” for a list of changes.
Wilf LaLonde ©2012Comp 4501
• A 1 channel 32F texture “g_pShadowMap” is obtained to contain the depthBuffer data.
PreliminariesPreliminaries
pd3dDevice->CreateTexture (|SHADOWMAP_SIZE, SHADOWMAP_SIZE, 1, D3DUSAGE_RENDERTARGET, D3DFMT_R32F, D3DPOOL_DEFAULT,&g_pShadowMap, NULL);
pd3dDevice->CreateDepthStencilSurface (SHADOWMAP_SIZE, SHADOWMAP_SIZE, d3dSettings.d3d9.pp.AutoDepthStencilFormat,D3DMULTISAMPLE_NONE, 0, TRUE,&g_pDSShadow, NULL);
• A frame buffer “g_pDSShadow” is obtained to draw into the depthBuffer into.
See callback OnResetDevice for details
window size is 640 x 480SHADOWMAP_SIZE is 1024
Actually used as a DRAW texture rather than a DEPTH-BUFFER texture
Wilf LaLonde ©2012Comp 4501
Rendering Performs 2 PassesRendering Performs 2 Passes
void CALLBACK OnFrameRender (IDirect3DDevice9* pd3dDevice, double fTime, float fElapsedTime, void* pUserContext) {
//Draw the scene as seen by the light
//Draw the scene as seen by the camera.
}
See later slide
See later slide
Wilf LaLonde ©2012Comp 4501
Draw the scene seen by the light (without error checks)Draw the scene seen by the light (without error checks)
LPDIRECT3DSURFACE9 pOldRT = NULL;
pd3dDevice->GetRenderTarget (0, &pOldRT);
LPDIRECT3DSURFACE9 pShadowSurf;
g_pShadowMap->GetSurfaceLevel (0, &pShadowSurf);
pd3dDevice->SetRenderTarget (0, pShadowSurf);
SAFE_RELEASE (pShadowSurf);
LPDIRECT3DSURFACE9 pOldDS = NULL;
pd3dDevice->GetDepthStencilSurface (&pOldDS);
pd3dDevice->SetDepthStencilSurface (g_pDSShadow);
pd3dDevice->SetDepthStencilSurface (pOldDS);
pOldDS->Release ();
pd3dDevice->SetRenderTarget (0, pOldRT);
SAFE_RELEASE (pOldRT);
Get old drawing target (back buffer)and replace by the shadow map
texture. AFTER, restore it
Get old frame buffer (full screen)and replace by the shadow map frame buffer. AFTER, restore it
RenderScene (pd3dDevice, true, fElapsedTime, &mLightView, &g_mShadowProj);
bRenderShadow
Wilf LaLonde ©2012Comp 4501
Draw the scene seen by the camera (without error checks)Draw the scene seen by the camera (without error checks)
//Setup 2 shader parameters…
g_pEffect->SetTexture ("g_txShadow", g_pShadowMap);
g_pEffect->SetMatrix ("g_mViewToLightProj", &...); // CMV-1 * LMV * LP
g_pEffect->SetTexture ("g_txShadow", NULL);
RenderScene (pd3dDevice, false, fElapsedTime, g_VCamera.GetViewMatrix(), g_VCamera.GetProjMatrix ());
bRenderShadow
in camera’s model view space
in model space
in light’s model view space
in light’s clip space
code on next slide
Wilf LaLonde ©2012Comp 4501
Camera model view TO light clip spaceCamera model view TO light 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
D3DXMATRIXA16 mViewToLightProj;
mViewToLightProj = *pmView;
D3DXMatrixInverse (&mViewToLightProj, NULL, &mViewToLightProj );
D3DXMatrixMultiply (&mViewToLightProj, &mViewToLightProj, &mLightView );
D3DXMatrixMultiply (&mViewToLightProj, &mViewToLightProj, &g_mShadowProj );
V (g_pEffect->SetMatrix ("g_mViewToLightProj", &mViewToLightProj ))
in camera’s model view space
in model space
in light’s model view space
in light’s clip space
code on next slide
CMV
CMV-1
LMV
LP
Easier to understand if you call it M.
Wilf LaLonde ©2012Comp 4501
• 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).
Uses 1 shader file “shadowMap.fx”Uses 1 shader file “shadowMap.fx”
Wilf LaLonde ©2012Comp 4501
void RenderScene (IDirect3DDevice9* pd3dDevice, bool bRenderShadow, float fElapsedTime, const D3DXMATRIX* pmView, const D3DXMATRIX* pmProj) {//Setup shader variables g_mProj, g_vLightPos, g_vLightDir…
//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.
//Not shown: if not rendering shadow, code for rendering "lamp shade object", // text, and User interface stuff.
pd3dDevice->EndScene ();
The RenderScene RoutineThe RenderScene Routine
See later slide
See later slide
Wilf LaLonde ©2012Comp 4501
• The light is actually a “CFirstPersonCamera”.D3DXMATRIX * GetWorldMatrix () {return &m_mCameraWorld;}const D3DXVECTOR3* GetWorldAhead () const {//toLight (via back)
return (D3DXVECTOR3 *) &m_mCameraWorld._31;}
Do We Get “toLight” or “fromLight”?Do We Get “toLight” or “fromLight”?
[_31,_32,_33] = [0,0,1] *_11 _12 _13_21 _22 _23_31 _32 _33
[0,0,1] is back in right-handed system
[0,0,-1] is ahead in right-handed system
fromLight would actually be [0,0,-1] transformed
toLight would actually be [0,0,1] transformed
I didn’t want to change the code to avoid breaking their stuff… We get a toLight…
Wilf LaLonde ©2012Comp 4501
//Setup the projection matrix... g_pEffect->SetMatrix ("g_mProj", pmProj); //Parameter that came in…
//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);
Setup shader variables g_mProj, g_vLightPos, g_vLightDir…Setup shader variables g_mProj, g_vLightPos, g_vLightDir…
the light
Wilf LaLonde ©2012Comp 4501
//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];
elseg_pEffect->SetTexture
("g_txScene", g_pTexDef ) ) //It's NULL.g_pEffect->CommitChanges (); pMesh-
>DrawSubset (i);}
g_pEffect->EndPass ();}
g_pEffect->End ();}
Draw the Scene ObjectsDraw the Scene Objects
Wilf LaLonde ©2012Comp 4501
• 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)…
Where Are We?Where Are We?
Wilf LaLonde ©2012Comp 4501
//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 spacefloat4 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 colorfloat4 g_vLightAmbient = float4 ( 0.3f, 0.3f, 0.3f, 1.0f ); //Use an ambient light of 0.3float 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>; …}
The Effect File “ShadowMap.fx”The Effect File “ShadowMap.fx”
Wilf LaLonde ©2012Comp 4501
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(); }}
The Effect File “ShadowMap.fx” (continued)The Effect File “ShadowMap.fx” (continued)
Wilf LaLonde ©2012Comp 4501
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 / wColor = Depth.x / Depth.y; //All 4 components rgba get the same value.
}
The RenderShadow Shader DetailsThe RenderShadow Shader Details
All it does is draw the depth AS IF IT WERE A COLOR.
Wilf LaLonde ©2012Comp 4501
95.450195.4501
Developing The
Scene Drawing
Shader That
Provides Shadows
Wilf LaLonde ©2012Comp 4501
• We want to perform major experiments via minor changes to the original shader...
• Structure of original “ShadowMap.tx”
Instrumenting the Original ShaderInstrumenting the Original Shader
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 ();
}
}
Wilf LaLonde ©2012Comp 4501
New Shader StructureNew Shader Structure
#include “WilfShadowExperiment.choice”
Shader variables
#include “WilfShadowLibrary.all”#include “WilfShadowRenderer.shader”
Shader VertScene, PixSceneShader VertShadow, PixShadow
technique RenderScene {
pass p0 {#if (experiment == ORIGINAL_VERSION)
VertexShader = compile vs_2_0 VertScene ();PixelShader = compile ps_2_0 PixScene ();
#elseVertexShader = compile vs_2_0 WilfsDrawSceneVS ();PixelShader = compile ps_2_0 WilfsDrawScenePS ();
#endif //experiment
}
}
Wilf LaLonde ©2012Comp 4501
• WilfShadowExperiment.choice
• WilfShadowLibrary.all
Preliminary SetupPreliminary Setup
#define ORIGINAL_VERSION 0
#define experiment ORIGINAL_VERSION
#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.
NOT SHOW
N
ALL NON-AMBIENT
Wilf LaLonde ©2012Comp 4501
• WilfShadowRenderer.shader switched to using structs instead of parameter lists...
Preliminary SetupPreliminary Setup
//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;
}
Wilf LaLonde ©2012Comp 4501
• The Vertex Shader...
Preliminary SetupPreliminary Setup
//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 = 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;
}
Not clear why we compute
all these variables yet,
Wilf LaLonde ©2012Comp 4501
• The Pixel Shader...
Preliminary SetupPreliminary Setup
//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;float3 normalCS : TEXCOORD2;float4 positionLPS : TEXCOORD3;
};
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
}
positionCPS is gone
Wilf LaLonde ©2012Comp 4501
95.450195.4501
Experiments
Wilf LaLonde ©2012Comp 4501
• WilfShadowExperiment.choice
• Yup... It draws a screen full of red....
Lets Perform Experiment 1Lets Perform Experiment 1
#define ORIGINAL_VERSION 0#define drawRed 1
//#define experiment ORIGINAL_VERSION
#define experiment drawRed
Wilf LaLonde ©2012Comp 4501
• WilfShadowExperiment.choice
• And the pixel shader....
Lets Perform Experiment 2Lets Perform Experiment 2
#define drawBrightTexture 2
#define experiment drawBrightTexture
float4 WilfsDrawScenePS (PS_INPUT In) : COLOR0 {#elif (experiment == drawBrightTexture)
return tex2D (g_samScene, In.textureCoordinate);#endif //experiment
}
Wilf LaLonde ©2012Comp 4501
• WilfShadowExperiment.choice
• And the pixel shader....
Lets Perform Experiment 3Lets Perform Experiment 3
#define drawDarkTexture 3
#define experiment drawDarkTexture
float4 WilfsDrawScenePS (PS_INPUT In) : COLOR0 {#elif (experiment == drawDarkTexture)
return tex2D (g_samScene, In.textureCoordinate) * (Ka ());#endif //experiment
}
Wilf LaLonde ©2012Comp 4501
• WilfShadowExperiment.choice
• 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?
Lets Perform Experiment 4Lets Perform Experiment 4
#define drawLitTextureIgnoringShadows 4
#define experiment drawLitTextureIgnoringShadows
TWO SEPARATE SLIDES
Wilf LaLonde ©2012Comp 4501
• Shader global variables and parameters
• Existing Spotlight Routine
• Easy to Compute First Parameter...
What Do We Have To Work With LIGHTS IN CAMERA SPACE?What Do We Have To Work With LIGHTS IN CAMERA SPACE?
//We have the following renamed globals.float3 lightPositionCS;float3 toLightCS;float coneAngleAsCosine;
float3 pixelToLightCS = normalize (lightPositionCS - In.positionCS.xyz);
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;
}
struct PS_INPUT {float2 textureCoordinate : TEXCOORD0;float4 positionCS : TEXCOORD1;float3 normalCS : TEXCOORD2;float4 positionLPS : TEXCOORD3;
};
Just make sure all parameters are in CS?
Wilf LaLonde ©2012Comp 4501
• Utility Routines so we can ask “Is the pixel normal aligned with the pixelToLight vector”...
• And finally
Utility Routines Utility Routines
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);}
Are We Facing the Light?
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);
}
> 0< 0
Wilf LaLonde ©2012Comp 4501
• WilfShadowExperiment.choice
• And the pixel shader....
Putting It All TogetherPutting It All Together
#define drawLitTextureIgnoringShadows 4
#define experiment drawLitTextureIgnoringShadows
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}
Wilf LaLonde ©2012Comp 4501
And it Looks LikeAnd it Looks Like
Wilf LaLonde ©2012Comp 4501
• WilfShadowExperiment.choice
Finally, Lets Perform Experiment 5Finally, Lets Perform Experiment 5
#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}
Wilf LaLonde ©2012Comp 4501
• We have shadow map “g_txShadow” from which we can get
shadow map depth• We have
position [x,y,z,w] in light clip (projection) space
which allows us to getclip space texture coordinates clip space depth
What Do We Have and What Do We Need?What Do We Have and What Do We Need?
struct PS_INPUT {float2 textureCoordinate : TEXCOORD0;float4 positionCS : TEXCOORD1;float3 normalCS : TEXCOORD2;float4 positionLPS : TEXCOORD3;};
in shadow if “clip space depth” > “shadow map depth”
Wilf LaLonde ©2012Comp 4501
• Clip coordinates c range from [-1,+1].• Texture coordinates t range from [0,1].
Transform via t = c * 0.5 + 0.5
Recall: Clip to Texture CoordinatesRecall: Clip to Texture Coordinates
Why? Consider c ranges from -1 to +1 c*0.5 ranges from -0.5 to +0.5c*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;
}
Wilf LaLonde ©2012Comp 4501
Finally, isInShadowFinally, 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...}
Wilf LaLonde ©2012Comp 4501
This is Called Surface Acne or Z-FightingThis is Called Surface Acne or Z-Fighting
Wilf LaLonde ©2012Comp 4501
Eliminating AcneEliminating Acne
From “Real-time shadows” handouts, precursor to Book, Eisemann et al, CRC Press, 2012, page 34
From “Real-time shadows” handouts, precursor to Book, Eisemann et al, CRC Press, 2012, page 34
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.
Wilf LaLonde ©2012Comp 4501
• Let d be pixel depth and sd be the depth sampled from the texture; z is perspective space depth...
Which Way Should We Pull Away?Which Way Should We Pull Away?
d sd
The 2 values are rarely equal; suppose equal to 10-128.
Near (z = 0)
Far (z = 1)
Wilf LaLonde ©2012Comp 4501
• Wrong Way (EVERY pixel points in shadow)
Which Way Should We Pull Away By EPSILON E?Which Way Should We Pull Away By EPSILON E?
d sd
OR
Esd
Near (z = 0)
Far (z = 1)
sd-E
• Right Way (spread d and sd apart)
d sd
E
d
d-Ed sd
Esd
sd+E
ADD E to bigger number
SUBTRACT E get smaller number
Move d nearer. Move sd further.
Move sd nearer.
Wilf LaLonde ©2012Comp 4501
Finally, isInShadow (Experiment 6)Finally, isInShadow (Experiment 6)
#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...}
• WilfShadowExperiment.choice
• And the library
#define drawLitTextureWithBias 6
#define experiment drawLitTextureWithBias
WORKED (No picture supplied)
Wilf LaLonde ©2012Comp 4501
But Constant Offset (Bias) Doesn’t Always WorkBut Constant Offset (Bias) Doesn’t Always Work
surface being rendered
surface definedby depth map
(in yellow)
constant offset
inside
gets pulled out to
Wilf LaLonde ©2012Comp 4501
But Constant Offset (Bias) Doesn’t Always WorkBut Constant Offset (Bias) Doesn’t Always Work
surface being rendered
surface definedby depth map
(in yellow)
constant offset
(needs to be larger here)
original constant
offset
Wilf LaLonde ©2012Comp 4501
Normal Offset Shadows: Daniel HolbertNormal Offset Shadows: Daniel Holbert
• shadowPosition = position + normal * epsilon
constant bias slope-scale normal offset
this is calledpeter-panning
this is calledshadow acne
perfect shadows
See poster
Wilf LaLonde ©2012Comp 4501
if (bScaleNormalOffsetByShadowDepth) {float4 Plv = mul (Pw, gLampViewXf);float shadowFOVFactor = max (gLampProjX [0].x, gLampProjX [1].y);shadowMapTexelSize *= abs (Plv.z) * shadowFOVFactor;
}
float4 Plp;float3 ToLight = normalize (gSpotLamp0Pos - Pw);float cosLightAngle = dot (ToLight, WorldSpaceNormal);
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.
}
Holbert’s GDC Poster Session CodeHolbert’s GDC Poster Session Code
SOME VARIABLES NOT
INITIALIZED
Wilf LaLonde ©2012Comp 4501
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;}
Using Some of Holbert’s IdeasUsing Some of Holbert’s Ideas
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...
Wilf LaLonde ©2012Comp 4501
variable length epsilon
With Fixed Versus Variable LengthWith Fixed Versus Variable Length
epsilon of 0.00001
Essentially the same
The Peter Panning Artifact
Wilf LaLonde ©2012Comp 4501
95.450195.4501
Percentage Closer
Filtering (PCF)
Wilf LaLonde ©2012Comp 4501
• 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)
What Percentage-Closer Filtering (PCF) is? What Percentage-Closer Filtering (PCF) is?
Wilf LaLonde ©2012Comp 4501
• 4 probes for sample [x,y]...
An Example 2x2 FilterAn Example 2x2 Filter
Weight 0.25
Weight 0.25
Weight 0.25
Weight 0.25
[x, y] [x+1, y]
[x, y+1] [x+1, y+1]
Wilf LaLonde ©2012Comp 4501
Using a 2x2 PCF FilterUsing 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);
}
Wilf LaLonde ©2012Comp 4501
1 Sample 4 Sample PCF
ResultsResults
Wilf LaLonde ©2012Comp 4501
How About Interpolating The ResultsHow 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 (
lerp ((mapDepth0 > pixelDepth), (mapDepth1 > pixelDepth), t.x), //x interpolate
lerp ((mapDepth2 > pixelDepth), (mapDepth3 > pixelDepth), t.x), //x interpolate
t.y); //y interpolate}
Weight 0.25
Weight 0.25
Weight 0.25
Weight 0.25
[x, y] [x+1, y]
[x, y+1] [x+1, y+1]
mapDepth0 mapDepth1
mapDepth2 mapDepth3
note
float2 t
Wilf LaLonde ©2012Comp 4501
4 Sample Interpolated PCF
ResultsResults
Wilf LaLonde ©2012Comp 4501
• 5 probes for sample [x,y]... different weights
How About a 5 probe Symmetric FilterHow About a 5 probe Symmetric Filter
Weight 1/6
Weight 2/6
Weight 1/6
[x, y] [x+1, y]
[x, y+1]
Weight 1/6
[x, y-1]
Weight 1/6
[x-1, y]
Wilf LaLonde ©2012Comp 4501
How About Interpolating The ResultsHow 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;
#elsefloat 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...#if (useSymmetricProbes)
return (lerp ((mapDepth0 > pixelDepth), (mapDepth1 > pixelDepth), t.x) + //x interpolatelerp ((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 interpolatelerp ((mapDepth2 > pixelDepth), (mapDepth3 > pixelDepth), t.x), //x interpolatet.y); //y interpolate
#endif //useSymmetricProbes}
Weight 1/6
Weight 2/6
Weight 1/6
[x, y] [x+1, y]
[x, y+1]
Weight 1/6
[x, y-1]
Weight 1/6
[x-1, y]
Wilf LaLonde ©2012Comp 4501
5 Probe Symmetric PCF
ResultsResults
Wilf LaLonde ©2012Comp 4501
95.450195.4501
Variance Shadow
Maps
Wilf LaLonde ©2012Comp 4501
• 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…
Variance Shadow MapsVariance Shadow Maps
1-Sample VarianceMap is Equivalent to Many-Sample PCF with large filters
1-Sample VarianceMap is Equivalent to Many-Sample PCF with large filters
Wilf LaLonde ©2012Comp 4501
• 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…
Variance Shadow MapsVariance Shadow Maps
Wilf LaLonde ©2012Comp 4501
Gaussian FiltersGaussian Filters
• In terms of mean and variance 2.
Wilf LaLonde ©2012Comp 4501
• Let x be a random variable drawn from a distribution with mean and variance 2. Then for t >
P(x t) pmax(t) =
Chebychev’s InequalityChebychev’s Inequality
2
2 + (t-)2
We want to re-interpret this in terms of pixel depth d; i.e. t = d.
We want to re-interpret this in terms of pixel depth d; i.e. t = d.
m 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
Wilf LaLonde ©2012Comp 4501
• Let x be a random variable drawn from a distribution with mean and variance 2. Then for t >
P(x t) pmax(t) =
Chebychev’s InequalityChebychev’s Inequality
2
2 + (t-)2
We want to re-interpret this in terms of pixel depth d; i.e. t = d.We want to re-interpret this in terms of pixel depth d; i.e. t = d.
m 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
For depth samples sd and sd
2 and pixel depth d
If d > sd, theorem applies
pixel is partially shadowed
pixel is behind
Wilf LaLonde ©2012Comp 4501
• Let x be a random variable drawn from a distribution with mean and variance 2. Then for t >
P(x t) pmax(t) =
Chebychev’s InequalityChebychev’s Inequality
2
2 + (t-)2
We want to re-interpret this in terms of pixel depth d ; i.e. t = d.We want to re-interpret this in terms of pixel depth d ; i.e. t = d.
m 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
For depth samples sd and sd
2 and pixel depth d
= sd
2 = sd2 - 2
Probability that random depth is greater than d; i.e., of d being in front; i.e. of pixel being visible
d > sd
d
percentage visible
pixel behind(but with some probability)
Wilf LaLonde ©2012Comp 4501
• For depth samples sd and sd2 and pixel depth
d, the percentage visible is
Chebychev’s InequalityChebychev’s Inequality
2
2 + (d-)2
where = sd and 2 = sd2 - 2
visible means unaccluded
Wilf LaLonde ©2012Comp 4501
• An occluder at depth d1 that casts a shadow on a surface at depth d2 where p is the percentage that is visible (unoccluded).
To validate Chebychev’s inequality, considerTo validate Chebychev’s inequality, consider
d1
d2
Top view with p unoccluded
p
Let’s compute everything from 1st principles and see if Chebychev’s inequality gives us p?
2
2 + (d2-)2
Wilf LaLonde ©2012Comp 4501
First compute and 2 (average depth + depth variance).First compute and 2 (average depth + depth variance).
d1
d2
p unaccluded, 1- p occluded
p
= E(x) = p d2 + (1-p) d1
E(x2) = p d22 + (1-p) d1
2
2 = E(x2) - E(x)2 = p d22 + (1-p) d1
2 - (p d2 + (1-p) d1)2
1-p
Expected depth is d2 with some probabilityand d1 with some probability
Wilf LaLonde ©2012Comp 4501
Simplify 2 Simplify 2
2 = p d22 + (1-p) d1
2 - (p d2 + (1-p) d1)2
= p d22 + (1-p) d1
2 - (p2d22 + (1-p)2d1
2 + 2p(1-p)d1d2)
= (p - p2) d22 + ((1-p) - (1-p)2) d1
2 - 2(p-p2)d1d2)
= (p - p2) d22 + (p - p2) d1
2 - 2(p - p2)d1d2)
= (p - p2) (d22 + d1
2 - 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
Wilf LaLonde ©2012Comp 4501
Also, compute and simplify (d2-)2 (needed too)Also, compute and simplify (d2-)2 (needed too)
(d2-)2
= (d2 2 - 2d2 - 2)
= d22 - 2d2 (pd2 + (1-p)d1)
+ p2d22 + 2p d2(1-p) d1 + (1-p)2d1
2
= d22 - 2pd2
2 - 2(1-p)d1d2
+ p2d22 + 2p d2(1-p) d1 + (1-p)2d1
2
= d22 (1-2p+p2) - 2d2d1 (1-p - p(1-p)) + d1
2(1-p)2
= (d22 - 2d2d1 + d1
2) (1-p)2
= (d2 - d1)2 (1-p)2 = (1-p)2 (d2
- d1)2
1-2p+p2 = (1-p)2
Wilf LaLonde ©2012Comp 4501
Percentage visible via Chebychev’s inequalityPercentage visible via Chebychev’s inequality
Since 2 = p(1-p)(d2-d1)2, and (d2-)2 = (1-p)2(d2-d1)2, percentage visible
2
2 + (d2-)2
p(1-p) (d2 - d1)2
p(1-p)(d2 - d1)2 + (1-p)2 (d2 - d1)2
p
p + (1-p)
=
=
= = p
Cancel (d2 - d1)2
Cancel (1-p)
Chebychev’s inequality works!
Wilf LaLonde ©2012Comp 4501
• Let x be a random variable drawn from a distribution with mean and variance 2. Then for t >
P(x t) pmax(t) =
Programming Chebychev’s InequalityProgramming Chebychev’s Inequality
2
2 + (t-)2
With t = d, denote E(x) by z, and E(x2) by zSquared
With t = d, denote E(x) by z, and E(x2) by zSquared
m 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
Wilf LaLonde ©2012Comp 4501
• Let x be a random variable drawn from a distribution with mean and variance 2. Then for t >
P(x t) pmax(t) =
Programming Chebychev’s InequalityProgramming Chebychev’s Inequality
2
2 + (d-z)2
With t = d, denote E(x) by z, and E(x2) by zSquared
With t = d, denote E(x) by z, and E(x2) by zSquared
m is what you expect x to be; i.e. z
2 is how you expect x to change; i.e. zSquared - z2
Wilf LaLonde ©2012Comp 4501
• Start with a pixel at depth d with mean and variance [z, zSquared] retrieved from the 2-component 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.
Variance Shadow MapsVariance Shadow Maps
2
2 + (d-z)2If (d > z) p = 2 = zSquared - z2
Wilf LaLonde ©2012Comp 4501
//Create the shadow map texture... V_RETURN ( pd3dDevice->CreateTexture (
SHADOWMAP_SIZE, SHADOWMAP_SIZE,1, D3DUSAGE_RENDERTARGET,D3DFMT_R32F, D3DPOOL_DEFAULT,&g_pShadowMap, NULL ) );
Changing What We Can Write Into ShadowMapChanging What We Can Write Into ShadowMap
const bool usingVarianceShadowMap = experiment == drawVarianceShadow1Sample;
usingVarianceShadowMap ? D3DFMT_A32B32G32R32F : D3DFMT_R32F
Allowing the texture to contains MORE than just a depth
Wilf LaLonde ©2012Comp 4501
Time for an Experiment (Experiment 8)Time for an Experiment (Experiment 8)
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);
#elseColor = Depth.x / Depth.y;
#endif //experiment}
• WilfShadowExperiment.choice
• Add to “ShadowMap.cpp” file.
• Changes to the ShadowMap Writing Shader...
#define drawVarianceShadow1Sample 8
#define experiment drawVarianceShadow1Sample
#include "WilfShadowExperiment.choice"
Wilf LaLonde ©2012Comp 4501
Time for an Experiment (Experiment 8)Time for an Experiment (Experiment 8)
sampler2D g_samShadow = sampler_state {
Texture = <g_txShadow>; #if (experiment == drawVarianceShadow1Sample)
MinFilter = Anisotropic;MagFilter = Anisotropic;MipFilter = Anisotropic; Filter = Anisotropic; MaxAnisotropy = 16;
#elseMinFilter = Point;MagFilter = Point;MipFilter = Point;
#endif //experiment
AddressU = Clamp;AddressV = Clamp;
};
• Changes to Get Hardware Anti-Aliasing...
Wilf LaLonde ©2012Comp 4501
Simplifying a bitSimplifying a bitfloat 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;
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
2 + (d-z)2If (d > z) p = 2 = zSquared - z2
still there
Wilf LaLonde ©2012Comp 4501
Looks A Little Strange (Pixelation Visible)Looks A Little Strange (Pixelation Visible)
Wilf LaLonde ©2012Comp 4501
Taking Suggestions Into Account (Even worse)Taking Suggestions Into Account (Even worse)return pow (probabilityOfBeingVisible, 4); //Suggestion by a number of papers...
Wilf LaLonde ©2012Comp 4501
Another Addition (Many other failures tried)Another Addition (Many other failures tried)float varianceSquared = zSquared - z * z; varianceSquared = abs (varianceSquared);
Wilf LaLonde ©2012Comp 4501
• 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).
Search of the Literature Also RevealsSearch of the Literature Also Reveals
Just Need the position in camera (view) space when drawing the shadowMap via shaders VertShadow and PixShadow.
Wilf LaLonde ©2012Comp 4501
Will Using Linear Depth Fix ItWill Using Linear Depth Fix It
Details for the reader (NOT DONE)
• WilfShadowExperiment.choice
• Other details left for the reader.
#define drawVarianceShadow1SampleViaLinearDepth 9
#define experiment drawVarianceShadow1SampleViaLinearDepth
Wilf LaLonde ©2012Comp 4501
Nvidia SDK 10 has a Demo But It’s Using 9x9 VSMNvidia SDK 10 has a Demo But It’s Using 9x9 VSM
Also using MSAA
Wilf LaLonde ©2012Comp 4501
• There are soft shadow demos that might be simpler and more effective...
Perhaps We Need to Look ElsewherePerhaps We Need to Look Elsewhere
By determining “how much” of a filter rectangle around the pixel is visible
all visible 69% visible 0% visible
3x3 filter
Wilf LaLonde ©2012Comp 4501
95.450195.4501
Soft Shadows
Wilf LaLonde ©2012Comp 4501
Soft ShadowsSoft Shadows
Wilf LaLonde ©2012Comp 4501
Fuzzy Compared To Hard ShadowsFuzzy Compared To Hard Shadows
total darkness
partial darkness
Wilf LaLonde ©2012Comp 4501
Nvidia DX10 SDK has Percentage Closer Soft
Shadows (25x25 Poison Filter)
Nvidia DX10 SDK has Percentage Closer Soft
Shadows (25x25 Poison Filter)
Let’s skip this one
Wilf LaLonde ©2012Comp 4501
Inspired By SoftShadows Demo of Nvidia SDK 10Inspired By SoftShadows Demo of Nvidia SDK 10
Fast Version Works Well
Accurate technique implementsReal-Time Soft Shadow Mapping by Back Projection, Guennebaud et all
Wilf LaLonde ©2012Comp 4501
• 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
Recall: PCF Shadows From Point LightsRecall: PCF Shadows From Point Lights
Per Pixel Large filters very slow shaders…Per Pixel Large filters very slow shaders…
Wilf LaLonde ©2012Comp 4501
• 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
But We Can Be Efficient...But We Can Be Efficient...
How Do We Decide? Key Insight: a MIN/MAX shadow mapHow Do We Decide? Key Insight: a MIN/MAX shadow map
Wilf LaLonde ©2012Comp 4501
Consider Lower Detail Pixels (1 red/blue = 4 black)Consider Lower Detail Pixels (1 red/blue = 4 black)
Min (low detail depth)
Max (low detail depth)
High detail depth
Technically, a low resolution pixel is divided into 4 higher resolution pixels...
Wilf LaLonde ©2012Comp 4501
• 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.
Basic IdeaBasic Idea
Wilf LaLonde ©2012Comp 4501
Considering ONLY Lower Detail Pixels, What Can We Deduce?Considering ONLY Lower Detail Pixels, What Can We Deduce?
VISIBLE (Pz Min)
HIDDEN (Pz Max)
DON’T KNOW
brightness = 1
brightness = 0
need more detail
Wilf LaLonde ©2012Comp 4501
95.450195.4501
Where Do We Start
Wilf LaLonde ©2012Comp 4501
• 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).
Where Do We StartWhere Do We Start
BUT there is a problem. I Can’t DECIPHER the FAST demo...
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
Wilf: see FastShadow in SoftShadowsORIGINAL.fx
Wilf LaLonde ©2012Comp 4501
A Quick Look At The Drawing OrganizationA 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-depthTechnique "ConvertDepth“ (large triangle) //Top level min-max shadow
map
LOOP I FROM 1 /* SKIP 0 */ to nMips-1
Set viewport width /= 2; height /= 2Set “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...
not interesting
understand as implemented
new implementation
DepthTex0
DepthMip2[0]
DepthMip2 [I – 1]
DepthMip2
DepthMip2 [I]
Wilf LaLonde ©2012Comp 4501
• 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...
Important PointImportant Point
IT’S ALSO WIDER THAN THE SCREEN
Wilf LaLonde ©2012Comp 4501
//1. Create the texture...D3D10_TEXTURE2D_DESC rtDesc = {
DEPTH_RES, //UINT Width;DEPTH_RES, //UINT Height;1,//UINT MipLevels;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;0,//UINT MiscFlags;
};
//The texture with subtexture resourcesV(pDev10->CreateTexture2D (&rtDesc, NULL, &m_pDepthMip2));
Creates a texture with Mipmaps as ResourcesCreates a texture with Mipmaps as Resources
m_pDepthMip2 is the texture
m_pDepthMip2SRViews[im] is the subtexture (shader resource view)
m_pDepthMip2RTViews[im] is the render target view
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).
Wilf LaLonde ©2012Comp 4501
//2Set up a description for shader resource and render target...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;srViewDesc.Format = DXGI_FORMAT_R32G32_FLOAT;V(pDev10->CreateShaderResourceView (m_pDepthMip2, &srViewDesc, &m_pDepthMip2SRView));
srViewDesc.Texture2D.MipLevels = 1;D3D10_RENDER_TARGET_VIEW_DESC rtViewDesc;rtViewDesc.ViewDimension = D3D10_RTV_DIMENSION_TEXTURE2D;
//3. Create the view and render targets for the mipmaps...for (int im = 0; im < nMips; ++im) {
srViewDesc.Texture2D.MostDetailedMip = im;srViewDesc.Format = DXGI_FORMAT_R32G32_FLOAT;V(pDev10->CreateShaderResourceView (m_pDepthMip2, &srViewDesc, &m_pDepthMip2SRViews[im]));rtViewDesc.Texture2D.MipSlice = im;rtViewDesc.Format = DXGI_FORMAT_R32G32_FLOAT;V(pDev10->CreateRenderTargetView (m_pDepthMip2, &rtViewDesc, &m_pDepthMip2RTViews[im]));
}
Creates a texture with Mipmaps as ResourcesCreates a texture with Mipmaps as Resources
m_pDepthMip2 is the texture
m_pDepthMip2SRViews[im] is the subtexture (shader resource view)
m_pDepthMip2RTViews[im] is the render target view
creates the mipmaps
gives you access to the mipmaps
same for the render targets of the mipmaps
Wilf LaLonde ©2012Comp 4501
technique10 RenderNoShadows {pass P0 {RenderSceneNoShadows VS and PS}}technique10 RenderFast {pass P0 {RenderSceneFast VS and PS}} 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}}}
The ShadersThe Shaders
don’t need
redo
study
ID3D10Effect *g_pEffect; //From D3DX10CreateEffectFromFile
ID3D10EffectTechnique *pDReworkTechnique2 = g_pEffect >GetTechniqueByName ("ReworkDepth2");
pDReworkTechnique2->GetPassByName ("CreateMip")->Apply(0);
ignore
Wilf LaLonde ©2012Comp 4501
95.450195.4501
Existing Shader Code
Wilf LaLonde ©2012Comp 4501
RasterizerState RStateMSAAON {MultisampleEnable = FALSE;} //MSAA too slow
Texture2D<float> DepthTex0;Texture2D<float2> DepthMip2;Texture2D DiffuseTex;
SamplerComparisonState DepthCompare; //WILF: NOT USEDSamplerState DepthSampler {
Filter = MIN_MAG_MIP_POINT;AddressU = Clamp; AddressV = Clamp;
};SamplerState DiffuseSampler {
Filter = MIN_MAG_MIP_LINEAR;AddressU = Wrap; AddressV = Wrap;
};
Shader Header: Part 1 of 2Shader Header: Part 1 of 2
Note DX10 versus DX9 differences (texture and filter)...
Wilf LaLonde ©2012Comp 4501
cbuffer cb0 : register (b0) {float4 g_vMaterialKd;float3 g_vLightPos; ///< light in world CSfloat4 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 1024cbuffer 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};};
Shader Header: Part 2 of 2Shader Header: Part 2 of 2
No special code in demo to load cbuffers (done as shown above)
V(g_pEffect->GetVariableByName ("mViewProj")->AsMatrix()->SetMatrix ((float *)&ssmap.mLightViewProj));
V(g_pEffect->GetVariableByName("DepthTex0")->AsShaderResource ()->SetResource (m_pDepthSRView[0]));
Wilf LaLonde ©2012Comp 4501
float4 RenderDepthVS (float3 vPos: POSITION) : SV_Position { return mul (float4 (vPos, 1), mViewProj);}
Shader Technique RenderDepthShader Technique RenderDepth
Nothing special (convert to projection (or clip) space)
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)
Wilf: Microsoft must mean texture units; e.g. 0-799, not clip units; e.g., -1.0-+1.0.
Wilf LaLonde ©2012Comp 4501
float4 ConvertDepthVS (uint iv : SV_VertexID) : SV_Position {return float4((iv << 1) & 2, iv & 2, 0.5, 1) * 2 - 1; //Wilf: For 0 => (0,0,0.5,1)*2-1 = (-1,-1,0,1)//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)//Wilf: Triangle encompases [-1,-1,0,1] to [1,1,0,1]
}
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...
}
Shader Technique ConvertDepth To Create Min-Max Shadow MapShader Technique ConvertDepth To Create Min-Max Shadow Map
ID3D10Buffer *pNullVBuf[] = {NULL};unsigned pStrides[] = {0}; unsigned pOffsets[] = {0};pDev10->IASetVertexBuffers (0, 1, pNullVBuf, pStrides, pOffsets);pDev10->IASetInputLayout (NULL);pDev10->Draw (3, 0); Since there are NO vertices,
draw 3 VERTEX Ids.
+3
-1 0 +1 +2 +3
+2
+1
0
-1
How ConvertDepth Draw is done
Wilf LaLonde ©2012Comp 4501
SAME ConvertDepthVS AND DRAW as previous slide
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;
}
Shader Technique CreateMipShader Technique CreateMip
[u,v] [2u,2v]
Taking the MIN of the 4 pixels and the MAX of the 4 pixels
Each LOAD is already MIN (x) and MAX (y)
Compute MIN of the mins and MAX of the maxes...
Wilf LaLonde ©2012Comp 4501
RECALL: Drawing OrganizationRECALL: 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-depthTechnique "ConvertDepth“ (large triangle) //Top level min-max shadow
map
LOOP I FROM I to nMips-1
Set viewport width /= 2; height /= 2Set “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...
not interesting
understand as implemented
new implementation
what’s le
ft
Wilf LaLonde ©2012Comp 4501
95.450195.4501
Preliminaries for the
new Shader
Functionality
Wilf LaLonde ©2012Comp 4501
• Goal is to determine how much of an area around a pixel is visible...
RecallRecall
So we’ll need to encode rectangles...
all visible 69% visible 0% visible
3x3 filter
Wilf LaLonde ©2012Comp 4501
• 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..
Basic PremiseBasic Premise
sample rectangle
map rectangle
Wilf LaLonde ©2012Comp 4501
• Need 2 points: minimum and maximum
Representing RectanglesRepresenting Rectangles
minimum
maximum
• float4 rectangle = float4 (0,0, 1023,1023);
rectangle.xy maximum rectangle.zw
#define LEFT(rectangle) rectangle.x
#define RIGHT(rectangle) rectangle.z
#define TOP(rectangle) rectangle.y
#define BOTTOM(rectangle) rectangle.w
#define MINIMUM(rectangle) rectangle.xy
#define MAXIMUM(rectangle) rectangle.zw
Wilf LaLonde ©2012Comp 4501
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;
}
Rectangle IntersectionRectangle 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)
Useful utilities
Wilf LaLonde ©2012Comp 4501
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); }
What if a Pixel Cannot Be Seen By The Light?What if a Pixel Cannot Be Seen By The Light?
Useful utilities
Wilf LaLonde ©2012Comp 4501
95.450195.4501
New Shader Functionality
Wilf LaLonde ©2012Comp 4501
RECALL: Considering ONLY Lower Detail Pixels, What Can We Deduce?RECALL: Considering ONLY Lower Detail Pixels, What Can We Deduce?
VISIBLE (Pz Min)
OCCLUDED (Pz Max)
DON’T KNOW
brightness = 1
brightness = 0
need more detail
bool inLight (float pixelDepth, float depthMapMinimumDepth) {return pixelDepth <= depthMapMinimumDepth;}
bool inShadow (float pixelDepth, float depthMapMaximumDepth) {return pixelDepth >= depthMapMaximumDepth;}
Wilf LaLonde ©2012Comp 4501
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
unchangedif (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;
}
Computing If Visible (Opposite of Occluded)Computing If Visible (Opposite of Occluded)
Wilf LaLonde ©2012Comp 4501
//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...
}
WilfRecursiveDecrementVisibleAreaWilfRecursiveDecrementVisibleArea
Next Slide Repeat Without Comments
Wilf LaLonde ©2012Comp 4501
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
}
WilfRecursiveDecrementVisibleAreaWilfRecursiveDecrementVisibleArea
Wilf LaLonde ©2012Comp 4501
• Consider the following 16 numbers1 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;};
Simulating a Stack: How Big Do We NeedSimulating a Stack: How Big Do We Need
Wilf LaLonde ©2012Comp 4501
void WilfRecursiveDecrementVisibleArea (float4 mapRectangle, float mipmapLevel) {
#define recur(min) WilfRecursiveDecrementVisibleArea ( \RECTANGLE (min, halfExtent ), mipmapLevel - 1)
recur (minimum);recur (minimum + float2 (halfExtent , 0));recur (minimum + float2 (0, halfExtent ));recur (minimum + halfExtent );
}
Recall: General structureRecall: General structure
GENERIC CODE WORKING ON mapRectangle AND mipmapLevel
There are 3 returns
Wilf LaLonde ©2012Comp 4501
Simple ApproachSimple Approach
void WilfIterativeDecrementVisibleArea (float4 mapRectangle, float mipmapLevel) {declareStack push (mapRectangle, mipmapLevel)while (top >= 0) {
pop (mapRectangle, mipmapLevel)
#define PUSH(x) push (RECTANGLE (x, halfExtent ), mipmapLevel - 1)
PUSH (minimum);PUSH (minimum + float2 (halfExtent , 0));PUSH (minimum + float2 (0, halfExtent ));PUSH (minimum + halfExtent );
}}
GENERIC CODE WORKING ON mapRectangle AND mipmapLevel
Replace 2nd and 3rd return by continue
Since processing order does not matter
#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--;
Wilf LaLonde ©2012Comp 4501
Using a Smaller StackUsing a Smaller Stack
void WilfIterativeDecrementVisibleArea (float4 mapRectangle, float mipmapLevel) {declareStack push (mapRectangle, mipmapLevel)while (top >= 0) {
pop (mapRectangle, mipmapLevel)
#define PUSH(x) push (RECTANGLE (x, halfExtent ), mipmapLevel - 1)
PUSH (minimum);PUSH (minimum + float2 (halfExtent , 0));PUSH (minimum + float2 (0, halfExtent ));PUSH (minimum + halfExtent );
}}
GENERIC CODE WORKING ON mapRectangle AND mipmapLevel
Replace 2nd and 3rd return by continue
Since processing order does not matter
• Is there a way to limit stack size to 16 by using the same entry 4 times? Think cases...
case 0
case 1 case 2 case 3
Wilf LaLonde ©2012Comp 4501
Use 4 cases via a variable called “child”Use 4 cases via a variable called “child”
void WilfIterativeDecrementVisibleArea (float4 mapRectangle, float mipmapLevel) {declareStack; int child;push (mapRectangle, mipmapLevel, 0)while (top >= 0) {
get (mapRectangle, mipmapLevel, child)if (child == 0) {
}#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;}
}}
GENERIC CODE WORKING ON mapRectangle AND mipmapLevel
#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++
unnecessary
Wilf LaLonde ©2012Comp 4501
• 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...
Final RoutinesFinal Routines
Wilf LaLonde ©2012Comp 4501
• 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...
Tricks and ClevernessTricks and Cleverness
Wilf LaLonde ©2012Comp 4501
• 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 texturesSV_Position semantic gives you x/y tex coord.Texture extent can be accessed by shader.Can draw with n vertex ids and decode whatto do with it in vertex shader...
Quick ReviewQuick Review
Wilf LaLonde ©2012Comp 4501
• 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.
ReferencesReferences
Top Related