public Shadowmap(Device device) { TextureSize = 1024; //todo: make this a setting... CascadeCount = Math.Min(Settings.Default.ShadowCascades, 8); // 6; //use setting PCFSize = 3; PCFOffset = 0.000125f; //0.002f BlurBetweenCascades = 0.05f; ShadowVars = new GpuVarsBuffer <ShadowmapVars>(device); DepthTexture = DXUtility.CreateTexture2D(device, TextureSize * CascadeCount, TextureSize, 1, 1, Format.R32_Typeless, 1, 0, ResourceUsage.Default, BindFlags.DepthStencil | BindFlags.ShaderResource, 0, 0); DepthTextureSS = DXUtility.CreateSamplerState(device, TextureAddressMode.Border, new Color4(0.0f), Comparison.Less, Filter.ComparisonMinMagLinearMipPoint, 0, 0.0f, 0.0f, 0.0f); DepthTextureSRV = DXUtility.CreateShaderResourceView(device, DepthTexture, Format.R32_Float, ShaderResourceViewDimension.Texture2D, 1, 0, 0, 0); DepthTextureDSV = DXUtility.CreateDepthStencilView(device, DepthTexture, Format.D32_Float, DepthStencilViewDimension.Texture2D); Cascades = new List <ShadowmapCascade>(CascadeCount); for (int i = 0; i < CascadeCount; i++) { ShadowmapCascade c = new ShadowmapCascade(); c.Owner = this; c.Index = i; c.ZNear = 0.0f; c.ZFar = 1.0f; c.IntervalNear = 0.0f; c.IntervalFar = 1.0f; c.DepthRenderVP = new ViewportF() { Height = (float)TextureSize, Width = (float)TextureSize, MaxDepth = 1.0f, MinDepth = 0.0f, X = (float)(TextureSize * i), Y = 0, }; Cascades.Add(c); } DepthRenderRS = DXUtility.CreateRasterizerState(device, FillMode.Solid, CullMode.None, true, false, true, 0, 0.0f, 1.0f); DepthRenderDS = DXUtility.CreateDepthStencilState(device, true, DepthWriteMask.All); DepthRenderVP = new ViewportF(); DepthRenderVP.Height = (float)TextureSize; DepthRenderVP.Width = (float)TextureSize; DepthRenderVP.MaxDepth = 1.0f; DepthRenderVP.MinDepth = 0.0f; DepthRenderVP.X = 0; DepthRenderVP.Y = 0; graphicsMemoryUsage = (long)(TextureSize * TextureSize * CascadeCount * 4); }
public void SetFinalRenderResources(DeviceContext context) { ShadowVars.Vars.CamScenePos = new Vector4(SceneCamPos, 0.0f); //in shadow scene coords ShadowVars.Vars.CamSceneView = Matrix.Transpose(SceneCamView); ShadowVars.Vars.LightView = Matrix.Transpose(LightView); ShadowVars.Vars.LightDir = new Vector4(LightDirection, 0.0f); Matrix dxmatTextureScale = Matrix.Scaling(0.5f, -0.5f, 1.0f); Matrix dxmatTextureTranslation = Matrix.Translation(0.5f, 0.5f, 0.0f); Matrix dxmatTextureST = Matrix.Multiply(dxmatTextureScale, dxmatTextureTranslation); for (int i = 0; i < CascadeCount; ++i) { ShadowmapCascade cascade = Cascades[i]; Matrix mShadowTexture = Matrix.Multiply(cascade.Ortho, dxmatTextureST); ShadowVars.Vars.CascadeScales.Set(i, new Vector4(mShadowTexture.M11, mShadowTexture.M22, mShadowTexture.M33, 1.0f)); ShadowVars.Vars.CascadeOffsets.Set(i, new Vector4(mShadowTexture.M41, mShadowTexture.M42, mShadowTexture.M43, 0.0f)); ShadowVars.Vars.CascadeDepths.Set(i, new Vector4(cascade.IntervalFar, 0.0f, 0.0f, 0.0f)); } ShadowVars.Vars.CascadeCount = CascadeCount; ShadowVars.Vars.CascadeVisual = 0; ShadowVars.Vars.PCFLoopStart = (PCFSize) / -2; ShadowVars.Vars.PCFLoopEnd = (PCFSize) / 2 + 1; // The border padding values keep the pixel shader from reading the borders during PCF filtering. float txs = (float)TextureSize; ShadowVars.Vars.BorderPaddingMax = (txs - 1.0f) / txs; ShadowVars.Vars.BorderPaddingMin = 1.0f / txs; ShadowVars.Vars.Bias = PCFOffset; ShadowVars.Vars.BlurBetweenCascades = BlurBetweenCascades; ShadowVars.Vars.CascadeCountInv = 1.0f / (float)CascadeCount; ShadowVars.Vars.TexelSize = 1.0f / txs; ShadowVars.Vars.TexelSizeX = ShadowVars.Vars.TexelSize / (float)CascadeCount; ShadowVars.Vars.ShadowMaxDistance = maxShadowDistance; ShadowVars.Update(context); SetBuffers(context); }
public void BeginDepthRender(DeviceContext context, int cascadeIndex) { ShadowmapCascade cascade = Cascades[cascadeIndex]; context.Rasterizer.SetViewport(cascade.DepthRenderVP); }
public void BeginUpdate(DeviceContext context, Camera cam, Vector3 lightDir, List <RenderableGeometryInst> items) { //items should be potential shadow casters. RTS.Set(context); var ppos = cam.Position; var view = cam.ViewMatrix; var proj = cam.ProjMatrix; var viewproj = cam.ViewProjMatrix; //need to compute a local scene space for the shadows. use a snapped version of the camera coords... Vector3 pp = ppos; float snapsize = 20.0f; //20m snap... //ideally should snap to texel size SceneOrigin.X = pp.X - (pp.X % snapsize); SceneOrigin.Y = pp.Y - (pp.Y % snapsize); SceneOrigin.Z = pp.Z - (pp.Z % snapsize); SceneCamPos = (pp - SceneOrigin); //the items passed in here are visible items. need to compute the scene bounds from these. Vector4 vFLTMAX = new Vector4(float.MaxValue); Vector4 vFLTMIN = new Vector4(float.MinValue); Vector3 vHMAX = new Vector3(float.MaxValue); Vector3 vHMIN = new Vector3(float.MinValue); WorldMin = vHMAX; WorldMax = vHMIN; for (int i = 0; i < items.Count; i++) { var inst = items[i].Inst; Vector3 imin = inst.BBMin - 100.0f; //extra bias to make sure scene isn't too small in model view... Vector3 imax = inst.BBMax + 100.0f; WorldMin = Vector3.Min(WorldMin, imin); WorldMax = Vector3.Max(WorldMax, imax); } SceneMin = (WorldMin - SceneOrigin); SceneMax = (WorldMax - SceneOrigin); SceneCenter = (SceneMax + SceneMin) * 0.5f; SceneExtent = (SceneMax - SceneMin) * 0.5f; Matrix sceneCamTrans = Matrix.Translation(-SceneCamPos); SceneCamView = Matrix.Multiply(sceneCamTrans, view); Matrix camViewInv = Matrix.Invert(SceneCamView); Vector3 lightUp = new Vector3(0.0f, 1.0f, 0.0f); //BUG: should select this depending on light dir!? LightView = Matrix.LookAtLH(lightDir, Vector3.Zero, lightUp); //BUG?: pos/lightdir wrong way around?? LightDirection = lightDir; Vector4[] vSceneAABBPointsLightSpace = new Vector4[8]; // This function simply converts the center and extents of an AABB into 8 points CreateAABBPoints(ref vSceneAABBPointsLightSpace, SceneCenter, SceneExtent); // Transform the scene AABB to Light space. for (int index = 0; index < 8; ++index) { vSceneAABBPointsLightSpace[index] = LightView.Multiply(vSceneAABBPointsLightSpace[index]); } float fFrustumIntervalBegin, fFrustumIntervalEnd; Vector4 vLightCameraOrthographicMin; // light space frustrum aabb Vector4 vLightCameraOrthographicMax; //float[] fCascadeIntervals = { 7.5f, 20.0f, 60.0f, 150.0f, 500.0f, 1000.0f, 1500.0f, 2500.0f }; //float[] fCascadeIntervals = { 7.0f, 20.0f, 65.0f, 160.0f, 650.0f, 2000.0f, 5000.0f, 10000.0f }; Vector4 vWorldUnitsPerTexel = Vector4.Zero; float fInvTexelCount = 1.0f / (float)TextureSize; // We loop over the cascades to calculate the orthographic projection for each cascade. for (int iCascadeIndex = 0; iCascadeIndex < CascadeCount; ++iCascadeIndex) { ShadowmapCascade cascade = Cascades[iCascadeIndex]; fFrustumIntervalBegin = 0.0f; // Scale the intervals between 0 and 1. They are now percentages that we can scale with. fFrustumIntervalEnd = fCascadeIntervals[iCascadeIndex]; //fFrustumIntervalBegin = fFrustumIntervalBegin * fCameraNearFarRange; //fFrustumIntervalEnd = fFrustumIntervalEnd * fCameraNearFarRange; Vector4[] vFrustumPoints = new Vector4[8]; // This function takes the began and end intervals along with the projection matrix and returns the 8 // points that repreresent the cascade Interval CreateFrustumPointsFromCascadeInterval(fFrustumIntervalBegin, fFrustumIntervalEnd, proj, ref vFrustumPoints); vLightCameraOrthographicMin = vFLTMAX; vLightCameraOrthographicMax = vFLTMIN; Vector4 vTempTranslatedCornerPoint; // This next section of code calculates the min and max values for the orthographic projection. for (int icpIndex = 0; icpIndex < 8; ++icpIndex) { // Transform the frustum from camera view space to world space. vFrustumPoints[icpIndex] = camViewInv.Multiply(vFrustumPoints[icpIndex]); // Transform the point from world space to Light Camera Space. vTempTranslatedCornerPoint = LightView.Multiply(vFrustumPoints[icpIndex]); // Find the closest point. vLightCameraOrthographicMin = Vector4.Min(vTempTranslatedCornerPoint, vLightCameraOrthographicMin); vLightCameraOrthographicMax = Vector4.Max(vTempTranslatedCornerPoint, vLightCameraOrthographicMax); } // This code removes the shimmering effect along the edges of shadows due to // the light changing to fit the camera. // Fit the ortho projection to the cascades far plane and a near plane of zero. // Pad the projection to be the size of the diagonal of the Frustum partition. // // To do this, we pad the ortho transform so that it is always big enough to cover // the entire camera view frustum. Vector4 vDiagonal = (vFrustumPoints[0] - vFrustumPoints[6]); // The bound is the length of the diagonal of the frustum interval. float fCascadeBound = vDiagonal.XYZ().Length(); vDiagonal = new Vector4(fCascadeBound); // The offset calculated will pad the ortho projection so that it is always the same size // and big enough to cover the entire cascade interval. Vector4 vBorderOffset = (vDiagonal - (vLightCameraOrthographicMax - vLightCameraOrthographicMin)) * 0.5f; // Set the Z and W components to zero. //vBoarderOffset *= g_vMultiplySetzwToZero; vBorderOffset.Z = 0.0f; vBorderOffset.W = 0.0f; // Add the offsets to the projection. vLightCameraOrthographicMax += vBorderOffset; vLightCameraOrthographicMin -= vBorderOffset; // The world units per texel are used to snap the shadow the orthographic projection // to texel sized increments. This keeps the edges of the shadows from shimmering. float fWorldUnitsPerTexel = fCascadeBound / (float)TextureSize; vWorldUnitsPerTexel = new Vector4(fWorldUnitsPerTexel, fWorldUnitsPerTexel, 1.0f, 1.0f); //1.0 instead of 0.0 to remove divide by 0 // We snap the camera to 1 pixel increments so that moving the camera does not cause the shadows to jitter. // This is a matter of integer dividing by the world space size of a texel vLightCameraOrthographicMin = vLightCameraOrthographicMin / vWorldUnitsPerTexel; vLightCameraOrthographicMin = vLightCameraOrthographicMin.Floor(); vLightCameraOrthographicMin = vLightCameraOrthographicMin * vWorldUnitsPerTexel; vLightCameraOrthographicMax = vLightCameraOrthographicMax / vWorldUnitsPerTexel; vLightCameraOrthographicMax = vLightCameraOrthographicMax.Floor(); vLightCameraOrthographicMax = vLightCameraOrthographicMax * vWorldUnitsPerTexel; //These are the unconfigured near and far plane values. They are purposly awful to show // how important calculating accurate near and far planes is. float fNearPlane; float fFarPlane; // By intersecting the light frustum with the scene AABB we can get a tighter bound on the near and far plane. ComputeNearAndFar(out fNearPlane, out fFarPlane, vLightCameraOrthographicMin, vLightCameraOrthographicMax, vSceneAABBPointsLightSpace); // Create the orthographic projection for this cascade. cascade.Ortho = Matrix.OrthoOffCenterLH(vLightCameraOrthographicMin.X, vLightCameraOrthographicMax.X, vLightCameraOrthographicMin.Y, vLightCameraOrthographicMax.Y, fNearPlane, fFarPlane); cascade.ZNear = fNearPlane; cascade.ZFar = fFarPlane; cascade.IntervalNear = fFrustumIntervalBegin; cascade.IntervalFar = fFrustumIntervalEnd; cascade.Matrix = Matrix.Multiply(LightView, cascade.Ortho); cascade.MatrixInv = Matrix.Invert(cascade.Matrix); cascade.WorldUnitsPerTexel = fWorldUnitsPerTexel; cascade.WorldUnitsToCascadeUnits = 2.0f / fCascadeBound; } context.ClearDepthStencilView(DepthTextureDSV, DepthStencilClearFlags.Depth, 1.0f, 0); // Set a null render target so as not to render color. context.OutputMerger.SetRenderTargets(DepthTextureDSV, (RenderTargetView)null); context.OutputMerger.SetDepthStencilState(DepthRenderDS); context.Rasterizer.State = DepthRenderRS; }