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