public override void Render(RenderContext context, ShadowMapRenderer shadowMapRenderer, LightShadowMapTexture lightShadowMap) { var shadow = (LightDirectionalShadowMap)lightShadowMap.Shadow; // TODO: Min and Max distance can be auto-computed from readback from Z buffer var camera = shadowMapRenderer.Camera; var shadowCamera = shadowMapRenderer.ShadowCamera; var viewToWorld = camera.ViewMatrix; viewToWorld.Invert(); // Update the frustum infos UpdateFrustum(shadowMapRenderer.Camera); // Computes the cascade splits var minMaxDistance = ComputeCascadeSplits(context, shadowMapRenderer, ref lightShadowMap); var direction = lightShadowMap.LightComponent.Direction; // Fake value // It will be setup by next loop Vector3 side = Vector3.UnitX; Vector3 upDirection = Vector3.UnitX; // Select best Up vector // TODO: User preference? foreach (var vectorUp in VectorUps) { if (Math.Abs(Vector3.Dot(direction, vectorUp)) < (1.0 - 0.0001)) { side = Vector3.Normalize(Vector3.Cross(vectorUp, direction)); upDirection = Vector3.Normalize(Vector3.Cross(direction, side)); break; } } int cascadeCount = lightShadowMap.CascadeCount; // Get new shader data from pool LightDirectionalShadowMapShaderData shaderData; if (cascadeCount == 1) { shaderData = shaderDataPoolCascade1.Add(); } else if (cascadeCount == 2) { shaderData = shaderDataPoolCascade2.Add(); } else { shaderData = shaderDataPoolCascade4.Add(); } lightShadowMap.ShaderData = shaderData; shaderData.Texture = lightShadowMap.Atlas.Texture; shaderData.DepthBias = shadow.BiasParameters.DepthBias; shaderData.OffsetScale = shadow.BiasParameters.NormalOffsetScale; // Push a new graphics state var graphicsDevice = context.GraphicsDevice; graphicsDevice.PushState(); float splitMaxRatio = (minMaxDistance.X - camera.NearClipPlane) / (camera.FarClipPlane - camera.NearClipPlane); for (int cascadeLevel = 0; cascadeLevel < cascadeCount; ++cascadeLevel) { // Calculate frustum corners for this cascade var splitMinRatio = splitMaxRatio; splitMaxRatio = cascadeSplitRatios[cascadeLevel]; for (int j = 0; j < 4; j++) { // Calculate frustum in WS and VS var frustumRange = frustumCornersWS[j + 4] - frustumCornersWS[j]; cascadeFrustumCornersWS[j] = frustumCornersWS[j] + frustumRange * splitMinRatio; cascadeFrustumCornersWS[j + 4] = frustumCornersWS[j] + frustumRange * splitMaxRatio; frustumRange = frustumCornersVS[j + 4] - frustumCornersVS[j]; cascadeFrustumCornersVS[j] = frustumCornersVS[j] + frustumRange * splitMinRatio; cascadeFrustumCornersVS[j + 4] = frustumCornersVS[j] + frustumRange * splitMaxRatio; } Vector3 cascadeMinBoundLS; Vector3 cascadeMaxBoundLS; Vector3 target; if (!shadow.DepthRange.IsAutomatic && (shadow.StabilizationMode == LightShadowMapStabilizationMode.ViewSnapping || shadow.StabilizationMode == LightShadowMapStabilizationMode.ProjectionSnapping)) { // Make sure we are using the same direction when stabilizing var boundingVS = BoundingSphere.FromPoints(cascadeFrustumCornersVS); // Compute bounding box center & radius target = Vector3.TransformCoordinate(boundingVS.Center, viewToWorld); var radius = boundingVS.Radius; //if (shadow.AutoComputeMinMax) //{ // var snapRadius = (float)Math.Ceiling(radius / snapRadiusValue) * snapRadiusValue; // Debug.WriteLine("Radius: {0} SnapRadius: {1} (snap: {2})", radius, snapRadius, snapRadiusValue); // radius = snapRadius; //} cascadeMaxBoundLS = new Vector3(radius, radius, radius); cascadeMinBoundLS = -cascadeMaxBoundLS; if (shadow.StabilizationMode == LightShadowMapStabilizationMode.ViewSnapping) { // Snap camera to texel units (so that shadow doesn't jitter when light doesn't change direction but camera is moving) // Technique from ShaderX7 - Practical Cascaded Shadows Maps - p310-311 var shadowMapHalfSize = lightShadowMap.Size * 0.5f; float x = (float)Math.Ceiling(Vector3.Dot(target, upDirection) * shadowMapHalfSize / radius) * radius / shadowMapHalfSize; float y = (float)Math.Ceiling(Vector3.Dot(target, side) * shadowMapHalfSize / radius) * radius / shadowMapHalfSize; float z = Vector3.Dot(target, direction); //target = up * x + side * y + direction * R32G32B32_Float.Dot(target, direction); target = upDirection * x + side * y + direction * z; } } else { var cascadeBoundWS = BoundingBox.FromPoints(cascadeFrustumCornersWS); target = cascadeBoundWS.Center; // Computes the bouding box of the frustum cascade in light space var lightViewMatrix = Matrix.LookAtLH(cascadeBoundWS.Center, cascadeBoundWS.Center + direction, upDirection); cascadeMinBoundLS = new Vector3(float.MaxValue); cascadeMaxBoundLS = new Vector3(-float.MaxValue); for (int i = 0; i < cascadeFrustumCornersWS.Length; i++) { Vector3 cornerViewSpace; Vector3.TransformCoordinate(ref cascadeFrustumCornersWS[i], ref lightViewMatrix, out cornerViewSpace); cascadeMinBoundLS = Vector3.Min(cascadeMinBoundLS, cornerViewSpace); cascadeMaxBoundLS = Vector3.Max(cascadeMaxBoundLS, cornerViewSpace); } // TODO: Adjust orthoSize by taking into account filtering size } // Update the shadow camera shadowCamera.ViewMatrix = Matrix.LookAtLH(target + direction * cascadeMinBoundLS.Z, target, upDirection); // View;; shadowCamera.ProjectionMatrix = Matrix.OrthoOffCenterLH(cascadeMinBoundLS.X, cascadeMaxBoundLS.X, cascadeMinBoundLS.Y, cascadeMaxBoundLS.Y, 0.0f, cascadeMaxBoundLS.Z - cascadeMinBoundLS.Z); // Projection shadowCamera.Update(); // Stabilize the Shadow matrix on the projection if (shadow.StabilizationMode == LightShadowMapStabilizationMode.ProjectionSnapping) { var shadowPixelPosition = shadowCamera.ViewProjectionMatrix.TranslationVector * lightShadowMap.Size * 0.5f; shadowPixelPosition.Z = 0; var shadowPixelPositionRounded = new Vector3((float)Math.Round(shadowPixelPosition.X), (float)Math.Round(shadowPixelPosition.Y), 0.0f); var shadowPixelOffset = new Vector4(shadowPixelPositionRounded - shadowPixelPosition, 0.0f); shadowPixelOffset *= 2.0f / lightShadowMap.Size; shadowCamera.ProjectionMatrix.Row4 += shadowPixelOffset; shadowCamera.Update(); } // Cascade splits in light space using depth: Store depth on first CascaderCasterMatrix in last column of each row shaderData.CascadeSplits[cascadeLevel] = MathUtil.Lerp(camera.NearClipPlane, camera.FarClipPlane, cascadeSplitRatios[cascadeLevel]); var shadowMapRectangle = lightShadowMap.GetRectangle(cascadeLevel); var cascadeTextureCoords = new Vector4((float)shadowMapRectangle.Left / lightShadowMap.Atlas.Width, (float)shadowMapRectangle.Top / lightShadowMap.Atlas.Height, (float)shadowMapRectangle.Right / lightShadowMap.Atlas.Width, (float)shadowMapRectangle.Bottom / lightShadowMap.Atlas.Height); //// Add border (avoid using edges due to bilinear filtering and blur) //var borderSizeU = VsmBlurSize / lightShadowMap.Atlas.Width; //var borderSizeV = VsmBlurSize / lightShadowMap.Atlas.Height; //cascadeTextureCoords.X += borderSizeU; //cascadeTextureCoords.Y += borderSizeV; //cascadeTextureCoords.Z -= borderSizeU; //cascadeTextureCoords.W -= borderSizeV; float leftX = (float)lightShadowMap.Size / lightShadowMap.Atlas.Width * 0.5f; float leftY = (float)lightShadowMap.Size / lightShadowMap.Atlas.Height * 0.5f; float centerX = 0.5f * (cascadeTextureCoords.X + cascadeTextureCoords.Z); float centerY = 0.5f * (cascadeTextureCoords.Y + cascadeTextureCoords.W); // Compute receiver view proj matrix Matrix adjustmentMatrix = Matrix.Scaling(leftX, -leftY, 1.0f) * Matrix.Translation(centerX, centerY, 0.0f); // Calculate View Proj matrix from World space to Cascade space Matrix.Multiply(ref shadowCamera.ViewProjectionMatrix, ref adjustmentMatrix, out shaderData.WorldToShadowCascadeUV[cascadeLevel]); // Render to the atlas lightShadowMap.Atlas.RenderFrame.Activate(context); graphicsDevice.SetViewport(new Viewport(shadowMapRectangle.X, shadowMapRectangle.Y, shadowMapRectangle.Width, shadowMapRectangle.Height)); // Render the scene for this cascade shadowMapRenderer.RenderCasters(context, lightShadowMap.LightComponent.CullingMask); //// Copy texture coords with border //cascades[cascadeLevel].CascadeLevels.CascadeTextureCoordsBorder = cascadeTextureCoords; } graphicsDevice.PopState(); }
public override void Collect(RenderContext context, ShadowMapRenderer shadowMapRenderer, LightShadowMapTexture lightShadowMap) { // TODO: Min and Max distance can be auto-computed from readback from Z buffer var shadow = (LightStandardShadowMap)lightShadowMap.Shadow; // Computes the cascade splits var lightComponent = lightShadowMap.LightComponent; var spotLight = (LightSpot)lightComponent.Type; var position = lightComponent.Position; var direction = lightComponent.Direction; var target = position + spotLight.Range * direction; var orthoSize = spotLight.LightRadiusAtTarget; // Fake value // It will be setup by next loop Vector3 side = Vector3.UnitX; Vector3 upDirection = Vector3.UnitX; // Select best Up vector // TODO: User preference? foreach (var vectorUp in VectorUps) { if (Vector3.Dot(direction, vectorUp) < (1.0 - 0.0001)) { side = Vector3.Normalize(Vector3.Cross(vectorUp, direction)); upDirection = Vector3.Normalize(Vector3.Cross(direction, side)); break; } } // Get new shader data from pool var shaderData = shaderDataPool.Add(); lightShadowMap.ShaderData = shaderData; shaderData.Texture = lightShadowMap.Atlas.Texture; shaderData.DepthBias = shadow.BiasParameters.DepthBias; shaderData.OffsetScale = shadow.BiasParameters.NormalOffsetScale; // Update the shadow camera var viewMatrix = Matrix.LookAtLH(position, target, upDirection); // View;; // TODO: Calculation of near and far is hardcoded/approximated. We should find a better way to calculate it. var projectionMatrix = Matrix.PerspectiveFovLH(spotLight.AngleOuterInRadians, 1.0f, 0.01f, spotLight.Range * 2.0f); // Perspective Projection for spotlights Matrix viewProjectionMatrix; Matrix.Multiply(ref viewMatrix, ref projectionMatrix, out viewProjectionMatrix); var shadowMapRectangle = lightShadowMap.GetRectangle(0); var cascadeTextureCoords = new Vector4((float)shadowMapRectangle.Left / lightShadowMap.Atlas.Width, (float)shadowMapRectangle.Top / lightShadowMap.Atlas.Height, (float)shadowMapRectangle.Right / lightShadowMap.Atlas.Width, (float)shadowMapRectangle.Bottom / lightShadowMap.Atlas.Height); //// Add border (avoid using edges due to bilinear filtering and blur) //var borderSizeU = VsmBlurSize / lightShadowMap.Atlas.Width; //var borderSizeV = VsmBlurSize / lightShadowMap.Atlas.Height; //cascadeTextureCoords.X += borderSizeU; //cascadeTextureCoords.Y += borderSizeV; //cascadeTextureCoords.Z -= borderSizeU; //cascadeTextureCoords.W -= borderSizeV; float leftX = (float)lightShadowMap.Size / lightShadowMap.Atlas.Width * 0.5f; float leftY = (float)lightShadowMap.Size / lightShadowMap.Atlas.Height * 0.5f; float centerX = 0.5f * (cascadeTextureCoords.X + cascadeTextureCoords.Z); float centerY = 0.5f * (cascadeTextureCoords.Y + cascadeTextureCoords.W); // Compute receiver view proj matrix Matrix adjustmentMatrix = Matrix.Scaling(leftX, -leftY, 1.0f) * Matrix.Translation(centerX, centerY, 0.0f); // Calculate View Proj matrix from World space to Cascade space Matrix.Multiply(ref viewProjectionMatrix, ref adjustmentMatrix, out shaderData.WorldToShadowCascadeUV); shaderData.ViewMatrix = viewMatrix; shaderData.ProjectionMatrix = projectionMatrix; }
private Vector2 ComputeCascadeSplits(RenderContext context, ShadowMapRenderer shadowContext, ref LightShadowMapTexture lightShadowMap) { var shadow = (LightDirectionalShadowMap)lightShadowMap.Shadow; var cameraNear = shadowContext.Camera.NearClipPlane; var cameraFar = shadowContext.Camera.FarClipPlane; var cameraRange = cameraFar - cameraNear; var minDistance = cameraNear + LightDirectionalShadowMap.DepthRangeParameters.DefaultMinDistance; var maxDistance = cameraNear + LightDirectionalShadowMap.DepthRangeParameters.DefaultMaxDistance; if (shadow.DepthRange.IsAutomatic) { var depthReadBack = DepthReadback.GetDepthReadback(context); if (depthReadBack.IsResultAvailable) { var depthMinMax = depthReadBack.DepthMinMax; minDistance = ToLinearDepth(depthMinMax.X, ref shadowContext.Camera.ProjectionMatrix); // Reserve 1/3 of the guard distance for the min distance minDistance = Math.Max(cameraNear, minDistance - shadow.DepthRange.GuardDistance / 3); // Reserve 2/3 of the guard distance for the max distance var guardMaxDistance = minDistance + shadow.DepthRange.GuardDistance * 2 / 3; maxDistance = ToLinearDepth(depthMinMax.Y, ref shadowContext.Camera.ProjectionMatrix); maxDistance = Math.Max(maxDistance, guardMaxDistance); } } else { minDistance = cameraNear + shadow.DepthRange.ManualMinDistance; maxDistance = cameraNear + shadow.DepthRange.ManualMaxDistance; } var manualPartitionMode = shadow.PartitionMode as LightDirectionalShadowMap.PartitionManual; var logarithmicPartitionMode = shadow.PartitionMode as LightDirectionalShadowMap.PartitionLogarithmic; if (logarithmicPartitionMode != null) { var minZ = minDistance; var maxZ = maxDistance; var range = maxZ - minZ; var ratio = maxZ / minZ; var logRatio = MathUtil.Clamp(1.0f - logarithmicPartitionMode.PSSMFactor, 0.0f, 1.0f); for (int cascadeLevel = 0; cascadeLevel < lightShadowMap.CascadeCount; ++cascadeLevel) { // Compute cascade split (between znear and zfar) float distrib = (float)(cascadeLevel + 1) / lightShadowMap.CascadeCount; float logZ = (float)(minZ * Math.Pow(ratio, distrib)); float uniformZ = minZ + range * distrib; float distance = MathUtil.Lerp(uniformZ, logZ, logRatio); cascadeSplitRatios[cascadeLevel] = distance; } } else if (manualPartitionMode != null) { if (lightShadowMap.CascadeCount == 1) { cascadeSplitRatios[0] = minDistance + manualPartitionMode.SplitDistance1 * maxDistance; } else if (lightShadowMap.CascadeCount == 2) { cascadeSplitRatios[0] = minDistance + manualPartitionMode.SplitDistance1 * maxDistance; cascadeSplitRatios[1] = minDistance + manualPartitionMode.SplitDistance3 * maxDistance; } else if (lightShadowMap.CascadeCount == 4) { cascadeSplitRatios[0] = minDistance + manualPartitionMode.SplitDistance0 * maxDistance; cascadeSplitRatios[1] = minDistance + manualPartitionMode.SplitDistance1 * maxDistance; cascadeSplitRatios[2] = minDistance + manualPartitionMode.SplitDistance2 * maxDistance; cascadeSplitRatios[3] = minDistance + manualPartitionMode.SplitDistance3 * maxDistance; } } // Convert distance splits to ratios cascade in the range [0, 1] for (int i = 0; i < cascadeSplitRatios.Length; i++) { cascadeSplitRatios[i] = (cascadeSplitRatios[i] - cameraNear) / cameraRange; } return new Vector2(minDistance, maxDistance); }
public LightEntry(int currentLightGroupIndex, int currentLightGroupIndexNoShadows, LightComponent light, LightShadowMapTexture shadow) { GroupIndex = currentLightGroupIndex; GroupIndexNoShadows = currentLightGroupIndexNoShadows; Light = light; Shadow = shadow; }
public override void GetCascadeViewParameters(LightShadowMapTexture shadowMapTexture, int cascadeIndex, out Matrix view, out Matrix projection) { if (cascadeIndex > 0) throw new ArgumentException("Spot lights do not use multiple shadow cascades", nameof(cascadeIndex)); var shaderData = (LightSpotShadowMapShaderData)shadowMapTexture.ShaderData; view = shaderData.ViewMatrix; projection = shaderData.ProjectionMatrix; }
public override void Collect(RenderContext context, ShadowMapRenderer shadowMapRenderer, LightShadowMapTexture lightShadowMap) { var shadow = (LightDirectionalShadowMap)lightShadowMap.Shadow; // TODO: Min and Max distance can be auto-computed from readback from Z buffer var shadowRenderView = shadowMapRenderer.CurrentView; var viewToWorld = shadowRenderView.View; viewToWorld.Invert(); // Update the frustum infos UpdateFrustum(shadowRenderView); // Computes the cascade splits var minMaxDistance = ComputeCascadeSplits(context, shadowMapRenderer, ref lightShadowMap); var direction = lightShadowMap.LightComponent.Direction; // Fake value // It will be setup by next loop Vector3 side = Vector3.UnitX; Vector3 upDirection = Vector3.UnitX; // Select best Up vector // TODO: User preference? foreach (var vectorUp in VectorUps) { if (Math.Abs(Vector3.Dot(direction, vectorUp)) < (1.0 - 0.0001)) { side = Vector3.Normalize(Vector3.Cross(vectorUp, direction)); upDirection = Vector3.Normalize(Vector3.Cross(direction, side)); break; } } int cascadeCount = lightShadowMap.CascadeCount; // Get new shader data from pool LightDirectionalShadowMapShaderData shaderData; if (cascadeCount == 1) { shaderData = shaderDataPoolCascade1.Add(); } else if (cascadeCount == 2) { shaderData = shaderDataPoolCascade2.Add(); } else { shaderData = shaderDataPoolCascade4.Add(); } lightShadowMap.ShaderData = shaderData; shaderData.Texture = lightShadowMap.Atlas.Texture; shaderData.DepthBias = shadow.BiasParameters.DepthBias; shaderData.OffsetScale = shadow.BiasParameters.NormalOffsetScale; float splitMaxRatio = (minMaxDistance.X - shadowRenderView.NearClipPlane) / (shadowRenderView.FarClipPlane - shadowRenderView.NearClipPlane); for (int cascadeLevel = 0; cascadeLevel < cascadeCount; ++cascadeLevel) { // Calculate frustum corners for this cascade var splitMinRatio = splitMaxRatio; splitMaxRatio = cascadeSplitRatios[cascadeLevel]; for (int j = 0; j < 4; j++) { // Calculate frustum in WS and VS var frustumRange = frustumCornersWS[j + 4] - frustumCornersWS[j]; cascadeFrustumCornersWS[j] = frustumCornersWS[j] + frustumRange * splitMinRatio; cascadeFrustumCornersWS[j + 4] = frustumCornersWS[j] + frustumRange * splitMaxRatio; frustumRange = frustumCornersVS[j + 4] - frustumCornersVS[j]; cascadeFrustumCornersVS[j] = frustumCornersVS[j] + frustumRange * splitMinRatio; cascadeFrustumCornersVS[j + 4] = frustumCornersVS[j] + frustumRange * splitMaxRatio; } Vector3 cascadeMinBoundLS; Vector3 cascadeMaxBoundLS; Vector3 target; if (shadow.StabilizationMode == LightShadowMapStabilizationMode.ViewSnapping || shadow.StabilizationMode == LightShadowMapStabilizationMode.ProjectionSnapping) { // Make sure we are using the same direction when stabilizing var boundingVS = BoundingSphere.FromPoints(cascadeFrustumCornersVS); // Compute bounding box center & radius target = Vector3.TransformCoordinate(boundingVS.Center, viewToWorld); var radius = boundingVS.Radius; //if (shadow.AutoComputeMinMax) //{ // var snapRadius = (float)Math.Ceiling(radius / snapRadiusValue) * snapRadiusValue; // Debug.WriteLine("Radius: {0} SnapRadius: {1} (snap: {2})", radius, snapRadius, snapRadiusValue); // radius = snapRadius; //} cascadeMaxBoundLS = new Vector3(radius, radius, radius); cascadeMinBoundLS = -cascadeMaxBoundLS; if (shadow.StabilizationMode == LightShadowMapStabilizationMode.ViewSnapping) { // Snap camera to texel units (so that shadow doesn't jitter when light doesn't change direction but camera is moving) // Technique from ShaderX7 - Practical Cascaded Shadows Maps - p310-311 var shadowMapHalfSize = lightShadowMap.Size * 0.5f; float x = (float)Math.Ceiling(Vector3.Dot(target, upDirection) * shadowMapHalfSize / radius) * radius / shadowMapHalfSize; float y = (float)Math.Ceiling(Vector3.Dot(target, side) * shadowMapHalfSize / radius) * radius / shadowMapHalfSize; float z = Vector3.Dot(target, direction); //target = up * x + side * y + direction * R32G32B32_Float.Dot(target, direction); target = upDirection * x + side * y + direction * z; } } else { var cascadeBoundWS = BoundingBox.FromPoints(cascadeFrustumCornersWS); target = cascadeBoundWS.Center; // Computes the bouding box of the frustum cascade in light space var lightViewMatrix = Matrix.LookAtLH(cascadeBoundWS.Center, cascadeBoundWS.Center + direction, upDirection); cascadeMinBoundLS = new Vector3(float.MaxValue); cascadeMaxBoundLS = new Vector3(-float.MaxValue); for (int i = 0; i < cascadeFrustumCornersWS.Length; i++) { Vector3 cornerViewSpace; Vector3.TransformCoordinate(ref cascadeFrustumCornersWS[i], ref lightViewMatrix, out cornerViewSpace); cascadeMinBoundLS = Vector3.Min(cascadeMinBoundLS, cornerViewSpace); cascadeMaxBoundLS = Vector3.Max(cascadeMaxBoundLS, cornerViewSpace); } // TODO: Adjust orthoSize by taking into account filtering size } // Update the shadow camera var viewMatrix = Matrix.LookAtLH(target + direction * cascadeMinBoundLS.Z, target, upDirection); // View;; var projectionMatrix = Matrix.OrthoOffCenterLH(cascadeMinBoundLS.X, cascadeMaxBoundLS.X, cascadeMinBoundLS.Y, cascadeMaxBoundLS.Y, 0.0f, cascadeMaxBoundLS.Z - cascadeMinBoundLS.Z); // Projection Matrix viewProjectionMatrix; Matrix.Multiply(ref viewMatrix, ref projectionMatrix, out viewProjectionMatrix); // Stabilize the Shadow matrix on the projection if (shadow.StabilizationMode == LightShadowMapStabilizationMode.ProjectionSnapping) { var shadowPixelPosition = viewProjectionMatrix.TranslationVector * lightShadowMap.Size * 0.5f; // shouln't it be scale and not translation ? shadowPixelPosition.Z = 0; var shadowPixelPositionRounded = new Vector3((float)Math.Round(shadowPixelPosition.X), (float)Math.Round(shadowPixelPosition.Y), 0.0f); var shadowPixelOffset = new Vector4(shadowPixelPositionRounded - shadowPixelPosition, 0.0f); shadowPixelOffset *= 2.0f / lightShadowMap.Size; projectionMatrix.Row4 += shadowPixelOffset; Matrix.Multiply(ref viewMatrix, ref projectionMatrix, out viewProjectionMatrix); } shaderData.ViewMatrix[cascadeLevel] = viewMatrix; shaderData.ProjectionMatrix[cascadeLevel] = projectionMatrix; // Cascade splits in light space using depth: Store depth on first CascaderCasterMatrix in last column of each row shaderData.CascadeSplits[cascadeLevel] = MathUtil.Lerp(shadowRenderView.NearClipPlane, shadowRenderView.FarClipPlane, cascadeSplitRatios[cascadeLevel]); var shadowMapRectangle = lightShadowMap.GetRectangle(cascadeLevel); var cascadeTextureCoords = new Vector4((float)shadowMapRectangle.Left / lightShadowMap.Atlas.Width, (float)shadowMapRectangle.Top / lightShadowMap.Atlas.Height, (float)shadowMapRectangle.Right / lightShadowMap.Atlas.Width, (float)shadowMapRectangle.Bottom / lightShadowMap.Atlas.Height); //// Add border (avoid using edges due to bilinear filtering and blur) //var borderSizeU = VsmBlurSize / lightShadowMap.Atlas.Width; //var borderSizeV = VsmBlurSize / lightShadowMap.Atlas.Height; //cascadeTextureCoords.X += borderSizeU; //cascadeTextureCoords.Y += borderSizeV; //cascadeTextureCoords.Z -= borderSizeU; //cascadeTextureCoords.W -= borderSizeV; float leftX = (float)lightShadowMap.Size / lightShadowMap.Atlas.Width * 0.5f; float leftY = (float)lightShadowMap.Size / lightShadowMap.Atlas.Height * 0.5f; float centerX = 0.5f * (cascadeTextureCoords.X + cascadeTextureCoords.Z); float centerY = 0.5f * (cascadeTextureCoords.Y + cascadeTextureCoords.W); // Compute receiver view proj matrix Matrix adjustmentMatrix = Matrix.Scaling(leftX, -leftY, 1.0f) * Matrix.Translation(centerX, centerY, 0.0f); // Calculate View Proj matrix from World space to Cascade space Matrix.Multiply(ref viewProjectionMatrix, ref adjustmentMatrix, out shaderData.WorldToShadowCascadeUV[cascadeLevel]); } }
/// <summary> /// Try to add light to this group (returns false if not possible). /// </summary> /// <param name="light"></param> /// <param name="shadowMapTexture"></param> /// <returns></returns> public bool AddLight(LightComponent light, LightShadowMapTexture shadowMapTexture) { Lights.Add(new LightDynamicEntry(light, shadowMapTexture)); return true; }
public override void Collect(RenderContext context, RenderView sourceView, LightShadowMapTexture lightShadowMap) { var shaderData = shaderDataPool.Add(); lightShadowMap.ShaderData = shaderData; shaderData.Texture = lightShadowMap.Atlas.Texture; shaderData.DepthBias = lightShadowMap.Light.Shadow.BiasParameters.DepthBias; shaderData.OffsetScale = lightShadowMap.Light.Shadow.BiasParameters.NormalOffsetScale; shaderData.DepthParameters = GetShadowMapDepthParameters(lightShadowMap); var clippingPlanes = GetLightClippingPlanes((LightPoint)lightShadowMap.Light); var textureMapSize = lightShadowMap.GetRectangle(0).Size; // Calculate angle of the projection with border pixels taken into account to allow filtering float halfMapSize = (float)textureMapSize.Width / 2; float halfFov = (float)Math.Atan((halfMapSize + BorderPixels) / halfMapSize); shaderData.Projection = Matrix.PerspectiveFovRH(halfFov * 2, 1.0f, clippingPlanes.X, clippingPlanes.Y); Vector2 atlasSize = new Vector2(lightShadowMap.Atlas.Width, lightShadowMap.Atlas.Height); for (int i = 0; i < 6; i++) { Rectangle faceRectangle = lightShadowMap.GetRectangle(i); shaderData.FaceOffsets[i] = new Vector2(faceRectangle.Left + BorderPixels, faceRectangle.Top + BorderPixels) / atlasSize; // Compute view parameters GetViewParameters(lightShadowMap, i, out shaderData.View[i]); // Allocate shadow render view var shadowRenderView = CreateRenderView(); shadowRenderView.RenderView = sourceView; shadowRenderView.ShadowMapTexture = lightShadowMap; shadowRenderView.Rectangle = lightShadowMap.GetRectangle(i); shadowRenderView.ViewSize = new Vector2(lightShadowMap.GetRectangle(i).Width, lightShadowMap.GetRectangle(i).Height); shadowRenderView.NearClipPlane = clippingPlanes.X; shadowRenderView.FarClipPlane = clippingPlanes.Y; shadowRenderView.View = shaderData.View[i]; shadowRenderView.Projection = shaderData.Projection; shadowRenderView.ViewProjection = shadowRenderView.View * shadowRenderView.Projection; // Create projection matrix with adjustment var textureCoords = new Vector4((float)shadowRenderView.Rectangle.Left / lightShadowMap.Atlas.Width, (float)shadowRenderView.Rectangle.Top / lightShadowMap.Atlas.Height, (float)shadowRenderView.Rectangle.Right / lightShadowMap.Atlas.Width, (float)shadowRenderView.Rectangle.Bottom / lightShadowMap.Atlas.Height); float leftX = (float)lightShadowMap.Size / lightShadowMap.Atlas.Width * 0.5f; float leftY = (float)lightShadowMap.Size / lightShadowMap.Atlas.Height * 0.5f; float centerX = 0.5f * (textureCoords.X + textureCoords.Z); float centerY = 0.5f * (textureCoords.Y + textureCoords.W); var viewProjection = shadowRenderView.ViewProjection; var projectionToShadow = Matrix.Scaling(leftX, -leftY, 1.0f) * Matrix.Translation(centerX, centerY, 0.0f); shaderData.WorldToShadow[i] = viewProjection * projectionToShadow; shadowRenderView.VisiblityIgnoreDepthPlanes = false; // Add the render view for the current frame context.RenderSystem.Views.Add(shadowRenderView); } }
private Vector2 ComputeCascadeSplits(RenderContext context, ShadowMapRenderer shadowContext, ref LightShadowMapTexture lightShadowMap) { var shadow = (LightDirectionalShadowMap)lightShadowMap.Shadow; var shadowRenderView = shadowContext.CurrentView; var cameraNear = shadowRenderView.NearClipPlane; var cameraFar = shadowRenderView.FarClipPlane; var cameraRange = cameraFar - cameraNear; var minDistance = cameraNear + LightDirectionalShadowMap.DepthRangeParameters.DefaultMinDistance; var maxDistance = cameraNear + LightDirectionalShadowMap.DepthRangeParameters.DefaultMaxDistance; if (shadow.DepthRange.IsAutomatic) { //var depthReadBack = DepthReadback.GetDepthReadback(context); //if (depthReadBack.IsResultAvailable) //{ // var depthMinMax = depthReadBack.DepthMinMax; // minDistance = ToLinearDepth(depthMinMax.X, ref camera.ProjectionMatrix); // // Reserve 1/3 of the guard distance for the min distance // minDistance = Math.Max(cameraNear, minDistance - shadow.DepthRange.GuardDistance / 3); // // Reserve 2/3 of the guard distance for the max distance // var guardMaxDistance = minDistance + shadow.DepthRange.GuardDistance * 2 / 3; // maxDistance = ToLinearDepth(depthMinMax.Y, ref camera.ProjectionMatrix); // maxDistance = Math.Max(maxDistance, guardMaxDistance); //} // Reserve 1/3 of the guard distance for the min distance minDistance = Math.Max(cameraNear, shadowContext.CurrentView.MinimumDistance - shadow.DepthRange.GuardDistance / 3); minDistance = LogFloor(minDistance); // Reserve 2/3 of the guard distance for the max distance var guardMaxDistance = minDistance + shadow.DepthRange.GuardDistance * 2 / 3; maxDistance = Math.Max(shadowContext.CurrentView.MaximumDistance, guardMaxDistance); // snap to a 'closest floor' of sorts, to improve stability: maxDistance = LogCeiling(maxDistance); } else { minDistance = cameraNear + shadow.DepthRange.ManualMinDistance; maxDistance = cameraNear + shadow.DepthRange.ManualMaxDistance; } var manualPartitionMode = shadow.PartitionMode as LightDirectionalShadowMap.PartitionManual; var logarithmicPartitionMode = shadow.PartitionMode as LightDirectionalShadowMap.PartitionLogarithmic; if (logarithmicPartitionMode != null) { var minZ = minDistance; var maxZ = maxDistance; var range = maxZ - minZ; var ratio = maxZ / minZ; var logRatio = MathUtil.Clamp(1.0f - logarithmicPartitionMode.PSSMFactor, 0.0f, 1.0f); for (int cascadeLevel = 0; cascadeLevel < lightShadowMap.CascadeCount; ++cascadeLevel) { // Compute cascade split (between znear and zfar) float distrib = (float)(cascadeLevel + 1) / lightShadowMap.CascadeCount; float logZ = (float)(minZ * Math.Pow(ratio, distrib)); float uniformZ = minZ + range * distrib; float distance = MathUtil.Lerp(uniformZ, logZ, logRatio); cascadeSplitRatios[cascadeLevel] = distance; } } else if (manualPartitionMode != null) { if (lightShadowMap.CascadeCount == 1) { cascadeSplitRatios[0] = minDistance + manualPartitionMode.SplitDistance1 * maxDistance; } else if (lightShadowMap.CascadeCount == 2) { cascadeSplitRatios[0] = minDistance + manualPartitionMode.SplitDistance1 * maxDistance; cascadeSplitRatios[1] = minDistance + manualPartitionMode.SplitDistance3 * maxDistance; } else if (lightShadowMap.CascadeCount == 4) { cascadeSplitRatios[0] = minDistance + manualPartitionMode.SplitDistance0 * maxDistance; cascadeSplitRatios[1] = minDistance + manualPartitionMode.SplitDistance1 * maxDistance; cascadeSplitRatios[2] = minDistance + manualPartitionMode.SplitDistance2 * maxDistance; cascadeSplitRatios[3] = minDistance + manualPartitionMode.SplitDistance3 * maxDistance; } } // Convert distance splits to ratios cascade in the range [0, 1] for (int i = 0; i < cascadeSplitRatios.Length; i++) { cascadeSplitRatios[i] = (cascadeSplitRatios[i] - cameraNear) / cameraRange; } return(new Vector2(minDistance, maxDistance)); }
public abstract void Render(RenderContext context, ShadowMapRenderer shadowMapRenderer, LightShadowMapTexture lightShadowMap);
public override void GetCascadeViewParameters(LightShadowMapTexture shadowMapTexture, int cascadeIndex, out Matrix view, out Matrix projection) { var shaderData = (LightDirectionalShadowMapShaderData)shadowMapTexture.ShaderData; view = shaderData.ViewMatrix[cascadeIndex]; projection = shaderData.ProjectionMatrix[cascadeIndex]; }
public LightDynamicEntry(LightComponent light, LightShadowMapTexture shadowMapTexture) { Light = light; ShadowMapTexture = shadowMapTexture; }
public abstract void Render(RenderContext context, ShadowMapRenderer shadowMapRenderer, LightShadowMapTexture lightShadowMap);
public override void Collect(RenderContext context, ShadowMapRenderer shadowMapRenderer, LightShadowMapTexture lightShadowMap) { // TODO: Min and Max distance can be auto-computed from readback from Z buffer var shadow = (LightStandardShadowMap)lightShadowMap.Shadow; // Computes the cascade splits var lightComponent = lightShadowMap.LightComponent; var spotLight = (LightSpot)lightComponent.Type; var position = lightComponent.Position; var direction = lightComponent.Direction; var target = position + spotLight.Range * direction; var orthoSize = spotLight.LightRadiusAtTarget; // Fake value // It will be setup by next loop Vector3 side = Vector3.UnitX; Vector3 upDirection = Vector3.UnitX; // Select best Up vector // TODO: User preference? foreach (var vectorUp in VectorUps) { if (Vector3.Dot(direction, vectorUp) < (1.0 - 0.0001)) { side = Vector3.Normalize(Vector3.Cross(vectorUp, direction)); upDirection = Vector3.Normalize(Vector3.Cross(direction, side)); break; } } // Get new shader data from pool var shaderData = shaderDataPool.Add(); lightShadowMap.ShaderData = shaderData; shaderData.Texture = lightShadowMap.Atlas.Texture; shaderData.DepthBias = shadow.BiasParameters.DepthBias; shaderData.OffsetScale = shadow.BiasParameters.NormalOffsetScale; // Update the shadow camera var viewMatrix = Matrix.LookAtLH(position, target, upDirection); // View;; // TODO: Calculation of near and far is hardcoded/approximated. We should find a better way to calculate it. var projectionMatrix = Matrix.PerspectiveFovLH(spotLight.AngleOuterInRadians, 1.0f, 0.01f, spotLight.Range * 2.0f); // Perspective Projection for spotlights Matrix viewProjectionMatrix; Matrix.Multiply(ref viewMatrix, ref projectionMatrix, out viewProjectionMatrix); var shadowMapRectangle = lightShadowMap.GetRectangle(0); var cascadeTextureCoords = new Vector4((float)shadowMapRectangle.Left / lightShadowMap.Atlas.Width, (float)shadowMapRectangle.Top / lightShadowMap.Atlas.Height, (float)shadowMapRectangle.Right / lightShadowMap.Atlas.Width, (float)shadowMapRectangle.Bottom / lightShadowMap.Atlas.Height); //// Add border (avoid using edges due to bilinear filtering and blur) //var borderSizeU = VsmBlurSize / lightShadowMap.Atlas.Width; //var borderSizeV = VsmBlurSize / lightShadowMap.Atlas.Height; //cascadeTextureCoords.X += borderSizeU; //cascadeTextureCoords.Y += borderSizeV; //cascadeTextureCoords.Z -= borderSizeU; //cascadeTextureCoords.W -= borderSizeV; float leftX = (float)lightShadowMap.Size / lightShadowMap.Atlas.Width * 0.5f; float leftY = (float)lightShadowMap.Size / lightShadowMap.Atlas.Height * 0.5f; float centerX = 0.5f * (cascadeTextureCoords.X + cascadeTextureCoords.Z); float centerY = 0.5f * (cascadeTextureCoords.Y + cascadeTextureCoords.W); // Compute receiver view proj matrix Matrix adjustmentMatrix = Matrix.Scaling(leftX, -leftY, 1.0f) * Matrix.Translation(centerX, centerY, 0.0f); // Calculate View Proj matrix from World space to Cascade space Matrix.Multiply(ref viewProjectionMatrix, ref adjustmentMatrix, out shaderData.WorldToShadowCascadeUV); shaderData.ViewMatrix = viewMatrix; shaderData.ProjectionMatrix = projectionMatrix; }
public abstract void GetCascadeViewParameters(LightShadowMapTexture shadowMapTexture, int cascadeIndex, out Matrix view, out Matrix projection);
public abstract void GetCascadeViewParameters(LightShadowMapTexture shadowMapTexture, int cascadeIndex, out Matrix view, out Matrix projection);