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 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(); }
private static void PrepareLightGroups(RenderDrawContext context, FastList<RenderView> renderViews, RenderView renderView, RenderViewLightData renderViewData, ShadowMapRenderer shadowMapRenderer, EntityGroup group) { foreach (var activeRenderer in renderViewData.ActiveRenderers) { // Find lights var lightRenderer = activeRenderer.LightRenderer; var lightCollection = activeRenderer.LightGroup.FindLightCollectionByGroup(group); var processLightsParameters = new LightGroupRendererBase.ProcessLightsParameters { Context = context, ViewIndex = renderViewData.ViewIndex, View = renderView, Views = renderViews, LightCollection = lightCollection, LightType = activeRenderer.LightGroup.LightType, LightStart = 0, LightEnd = lightCollection.Count, ShadowMapRenderer = shadowMapRenderer, ShadowMapTexturesPerLight = renderViewData.LightComponentsWithShadows, }; lightRenderer.ProcessLights(processLightsParameters); } }
protected override void DrawCore(RenderContext context) { modelProcessor = SceneInstance.GetCurrent(context).GetProcessor<ModelProcessor>(); lightProcessor = SceneInstance.GetCurrent(context).GetProcessor<LightProcessor>(); // No light processors means no light in the scene, so we can early exit if (lightProcessor == null || modelProcessor == null) { return; } // Not in the context of a SceneCameraRenderer? just exit sceneCameraRenderer = context.Tags.Get(SceneCameraRenderer.Current); sceneCamera = context.Tags.Get(CameraComponentRenderer.Current); if (sceneCameraRenderer == null || sceneCamera == null) { return; } sceneCullingMask = sceneCameraRenderer.CullingMask; // Setup the callback on the ModelRenderer and shadow map LightGroupRenderer if (!isModelComponentRendererSetup) { // TODO: Check if we could discover declared renderers in a better way than just hacking the tags of a component var modelRenderer = ModelComponentRenderer.GetAttached(sceneCameraRenderer); if (modelRenderer == null) { return; } modelRenderer.Callbacks.PreRenderModel += PrepareRenderModelForRendering; modelRenderer.Callbacks.PreRenderMesh += PreRenderMesh; // TODO: Make this pluggable // TODO: Shadows should work on mobile platforms if (context.GraphicsDevice.Features.Profile >= GraphicsProfile.Level_10_0 && (Platform.Type == PlatformType.Windows || Platform.Type == PlatformType.WindowsStore || Platform.Type == PlatformType.Windows10)) { shadowMapRenderer = new ShadowMapRenderer(modelRenderer.EffectName); shadowMapRenderer.Renderers.Add(typeof(LightDirectional), new LightDirectionalShadowMapRenderer()); shadowMapRenderer.Renderers.Add(typeof(LightSpot), new LightSpotShadowMapRenderer()); } isModelComponentRendererSetup = true; } // Collect all visible lights CollectVisibleLights(); // Draw shadow maps if (shadowMapRenderer != null) shadowMapRenderer.Draw(context, visibleLightsWithShadows); // Prepare active renderers in an ordered list (by type and shadow on/off) CollectActiveLightRenderers(context); currentModelLightShadersPermutationEntry = null; currentModelShadersParameters = null; currentShadowReceiver = true; // Clear the cache of parameter entries lightParameterEntries.Clear(); parameterCollectionEntryPool.Clear(); // Clear association between model and lights modelToLights.Clear(); // Clear all data generated by shader entries foreach (var shaderEntry in shaderEntries) { shaderEntry.Value.ResetGroupDatas(); } }
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 override void Collect() { // Initialize shadow map renderer if (!isShadowMapRendererSetUp && ShadowMapRenderStage != null) { // TODO: Shadow mapping is currently disabled in new render system // TODO: Make this pluggable // TODO: Shadows should work on mobile platforms if (RenderSystem.RenderContextOld.GraphicsDevice.Features.RequestedProfile >= GraphicsProfile.Level_10_0) { ShadowMapRenderer = new ShadowMapRenderer(RenderSystem, ShadowMapRenderStage); ShadowMapRenderer.Renderers.Add(typeof(LightDirectional), new LightDirectionalShadowMapRenderer()); ShadowMapRenderer.Renderers.Add(typeof(LightSpot), new LightSpotShadowMapRenderer()); } isShadowMapRendererSetUp = true; } // Collect all visible lights CollectVisibleLights(); // Prepare active renderers in an ordered list (by type and shadow on/off) CollectActiveLightRenderers(RenderSystem.RenderContextOld); // Collect shadow maps ShadowMapRenderer?.Collect(RenderSystem.RenderContextOld, renderViewDatas); }
public abstract void Render(RenderContext context, ShadowMapRenderer shadowMapRenderer, LightShadowMapTexture lightShadowMap);
public override void Render(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; var shadowCamera = shadowMapRenderer.ShadowCamera; // 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; var graphicsDevice = context.GraphicsDevice; graphicsDevice.PushState(); // Update the shadow camera shadowCamera.ViewMatrix = Matrix.LookAtLH(position, target, upDirection); // View;; shadowCamera.ProjectionMatrix = Matrix.OrthoOffCenterLH(-orthoSize, orthoSize, -orthoSize, orthoSize, 0.0f, spotLight.Range); // Projection shadowCamera.Update(); 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 shadowCamera.ViewProjectionMatrix, ref adjustmentMatrix, out shaderData.WorldToShadowCascadeUV); // 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(); }
protected override void DrawCore(RenderContext context) { modelProcessor = SceneInstance.GetCurrent(context).GetProcessor <ModelProcessor>(); lightProcessor = SceneInstance.GetCurrent(context).GetProcessor <LightProcessor>(); // No light processors means no light in the scene, so we can early exit if (lightProcessor == null || modelProcessor == null) { return; } // Not in the context of a SceneCameraRenderer? just exit sceneCameraRenderer = context.Tags.Get(SceneCameraRenderer.Current); sceneCamera = context.Tags.Get(CameraComponentRenderer.Current); if (sceneCameraRenderer == null || sceneCamera == null) { return; } sceneCullingMask = sceneCameraRenderer.CullingMask; // Setup the callback on the ModelRenderer and shadow map LightGroupRenderer if (!isModelComponentRendererSetup) { // TODO: Check if we could discover declared renderers in a better way than just hacking the tags of a component var modelRenderer = ModelComponentRenderer.GetAttached(sceneCameraRenderer); if (modelRenderer == null) { return; } modelRenderer.Callbacks.PreRenderModel += PrepareRenderModelForRendering; modelRenderer.Callbacks.PreRenderMesh += PreRenderMesh; // TODO: Make this pluggable if (context.GraphicsDevice.Features.Profile >= GraphicsProfile.Level_10_0) { shadowMapRenderer = new ShadowMapRenderer(modelRenderer.EffectName); shadowMapRenderer.Renderers.Add(typeof(LightDirectional), new LightDirectionalShadowMapRenderer()); shadowMapRenderer.Renderers.Add(typeof(LightSpot), new LightSpotShadowMapRenderer()); } isModelComponentRendererSetup = true; } // Collect all visible lights CollectVisibleLights(); // Draw shadow maps if (shadowMapRenderer != null) { shadowMapRenderer.Draw(context, visibleLightsWithShadows); } // Prepare active renderers in an ordered list (by type and shadow on/off) CollectActiveLightRenderers(context); currentModelLightShadersPermutationEntry = null; currentModelShadersParameters = null; currentShadowReceiver = true; // Clear the cache of parameter entries lightParameterEntries.Clear(); parameterCollectionEntryPool.Clear(); // Clear association between model and lights modelToLights.Clear(); // Clear all data generated by shader entries foreach (var shaderEntry in shaderEntries) { shaderEntry.Value.ResetGroupDatas(); } }
protected internal void RenderScene(CustomSceneQuery sceneQuery, RenderContext context, bool doPostProcessing, bool renderLensFlares, bool renderDebugOutput, bool renderReticle) { var renderTargetPool = GraphicsService.RenderTargetPool; var graphicsDevice = GraphicsService.GraphicsDevice; var originalRenderTarget = context.RenderTarget; var originalViewport = context.Viewport; var originalSourceTexture = context.SourceTexture; // All intermediate render targets have the size of the target viewport. int width = context.Viewport.Width; int height = context.Viewport.Height; context.Viewport = new Viewport(0, 0, width, height); // The render context can be used to share any data, for example: // Store a shared RebuildZBufferRenderer in the context. context.Data[RenderContextKeys.RebuildZBufferRenderer] = _rebuildZBufferRenderer; // ----- G-Buffer Pass // The GBufferRenderer creates context.GBuffer0 and context.GBuffer1. _gBufferRenderer.Render(sceneQuery.RenderableNodes, sceneQuery.DecalNodes, context); // ----- Shadow Pass // The ShadowMapRenderer renders the shadow maps which are stored in the light nodes. context.RenderPass = "******"; ShadowMapRenderer.Render(sceneQuery.Lights, context); context.RenderPass = null; // The ShadowMaskRenderer renders the shadows and stores them in one or more render // targets ("shadows masks"). ShadowMaskRenderer.Render(sceneQuery.Lights, context); RecycleShadowMaps(sceneQuery.Lights); // ----- Light Buffer Pass // The LightBufferRenderer creates context.LightBuffer0 (diffuse light) and // context.LightBuffer1 (specular light). LightBufferRenderer.Render(sceneQuery.Lights, context); // Normally, we do not need the shadow masks anymore - except if we want to // display them for debugging. if (!VisualizeIntermediateRenderTargets) { ShadowMaskRenderer.RecycleShadowMasks(); } // ----- Material Pass if (DebugMode == DeferredGraphicsDebugMode.None) { // In the material pass we render all meshes and decals into a single full-screen // render target. The shaders combine the material properties (diffuse texture, etc.) // with the light buffer info. context.RenderTarget = renderTargetPool.Obtain2D(new RenderTargetFormat(width, height, false, SurfaceFormat.HdrBlendable, DepthFormat.Depth24Stencil8)); graphicsDevice.SetRenderTarget(context.RenderTarget); context.Viewport = graphicsDevice.Viewport; graphicsDevice.Clear(new Color(3, 3, 3, 255)); graphicsDevice.DepthStencilState = DepthStencilState.Default; graphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise; graphicsDevice.BlendState = BlendState.Opaque; context.RenderPass = "******"; _opaqueMeshSceneRenderer.Render(sceneQuery.RenderableNodes, context); _decalRenderer.Render(sceneQuery.DecalNodes, context); context.RenderPass = null; } else { // For debugging: // Ignore the material pass. Keep rendering into one of the light buffers // to visualize only the lighting results. if (DebugMode == DeferredGraphicsDebugMode.VisualizeDiffuseLightBuffer) { context.RenderTarget = context.LightBuffer0; } else { context.RenderTarget = context.LightBuffer1; } } // The meshes rendered in the last step might use additional floating-point // textures (e.g. the light buffers) in the different graphics texture stages. // We reset the texture stages (setting all GraphicsDevice.Textures to null), // otherwise XNA might throw exceptions. graphicsDevice.ResetTextures(); // ----- Occlusion Queries if (renderLensFlares) { _lensFlareRenderer.UpdateOcclusion(sceneQuery.LensFlareNodes, context); } // ----- Sky _skyRenderer.Render(sceneQuery.SkyNodes, context); // ----- Fog _fogRenderer.Render(sceneQuery.FogNodes, context); // ----- Forward Rendering of Alpha-Blended Meshes and Particles graphicsDevice.DepthStencilState = DepthStencilState.DepthRead; graphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise; graphicsDevice.BlendState = BlendState.AlphaBlend; context.RenderPass = "******"; AlphaBlendSceneRenderer.Render(sceneQuery.RenderableNodes, context, RenderOrder.BackToFront); context.RenderPass = null; graphicsDevice.ResetTextures(); renderTargetPool.Recycle(context.SourceTexture); context.SourceTexture = null; _underwaterPostProcessor.Enabled = IsCameraUnderwater(sceneQuery, context.CameraNode); // ----- Post Processors context.SourceTexture = context.RenderTarget; context.RenderTarget = originalRenderTarget; context.Viewport = originalViewport; if (doPostProcessing) { // The post-processors modify the scene image and the result is written into // the final render target - which is usually the back buffer (but this could // also be another off-screen render target used in another graphics screen). PostProcessors.Process(context); } else { // Only copy the current render target to the final render target without post-processing. graphicsDevice.SetRenderTarget(originalRenderTarget); graphicsDevice.Viewport = originalViewport; SpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, SamplerState.PointClamp, DepthStencilState.None, RasterizerState.CullNone); SpriteBatch.Draw(context.SourceTexture, new Rectangle(0, 0, originalViewport.Width, originalViewport.Height), Color.White); SpriteBatch.End(); } renderTargetPool.Recycle((RenderTarget2D)context.SourceTexture); context.SourceTexture = null; // ----- Lens Flares if (renderLensFlares) { _lensFlareRenderer.Render(sceneQuery.LensFlareNodes, context); } // ----- Debug Output if (renderDebugOutput) { // ----- Optional: Restore the Z-Buffer // Currently, the hardware depth buffer is not initialized with useful data because // every time we change the render target, XNA deletes the depth buffer. If we want // the debug rendering to use correct depth buffer, we can restore the depth buffer // using the RebuildZBufferRenderer. If we remove this step, then the DebugRenderer // graphics will overlay the whole 3D scene. _rebuildZBufferRenderer.Render(context, true); // Render debug info added by game objects. DebugRenderer.Render(context); // Render intermediate render targets for debugging. // We do not use the public DebugRenderer here because the public DebugRenderer // might not be cleared every frame (the game logic can choose how it wants to // use the public renderer). if (VisualizeIntermediateRenderTargets) { _internalDebugRenderer.DrawTexture(context.GBuffer0, new Rectangle(0, 0, 200, 200)); _internalDebugRenderer.DrawTexture(context.GBuffer1, new Rectangle(200, 0, 200, 200)); _internalDebugRenderer.DrawTexture(context.LightBuffer0, new Rectangle(400, 0, 200, 200)); _internalDebugRenderer.DrawTexture(context.LightBuffer1, new Rectangle(600, 0, 200, 200)); for (int i = 0; i < ShadowMaskRenderer.ShadowMasks.Count; i++) { var shadowMask = ShadowMaskRenderer.ShadowMasks[i]; if (shadowMask != null) { _internalDebugRenderer.DrawTexture(shadowMask, new Rectangle((i) * 200, 200, 200, 200)); } } _internalDebugRenderer.Render(context); _internalDebugRenderer.Clear(); } } // ----- Draw Reticle if (renderReticle && _sampleFramework.IsGuiVisible) { SpriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend); SpriteBatch.Draw( _reticle, new Vector2(originalViewport.Width / 2 - _reticle.Width / 2, originalViewport.Height / 2 - _reticle.Height / 2), Color.Black); SpriteBatch.End(); } // ----- Clean-up // It is very important to give every intermediate render target back to the // render target pool! renderTargetPool.Recycle(context.GBuffer0); context.GBuffer0 = null; renderTargetPool.Recycle(context.GBuffer1); context.GBuffer1 = null; renderTargetPool.Recycle((RenderTarget2D)context.Data[RenderContextKeys.DepthBufferHalf]); context.Data.Remove(RenderContextKeys.DepthBufferHalf); if (DebugMode != DeferredGraphicsDebugMode.VisualizeDiffuseLightBuffer) { renderTargetPool.Recycle(context.LightBuffer0); } context.LightBuffer0 = null; if (DebugMode != DeferredGraphicsDebugMode.VisualizeSpecularLightBuffer) { renderTargetPool.Recycle(context.LightBuffer1); } context.LightBuffer1 = null; ShadowMaskRenderer.RecycleShadowMasks(); context.Data.Remove(RenderContextKeys.RebuildZBufferRenderer); context.SourceTexture = originalSourceTexture; }
public DeferredGraphicsScreen(IServiceLocator services) : base(services.GetInstance <IGraphicsService>()) { _sampleFramework = services.GetInstance <SampleFramework>(); var contentManager = services.GetInstance <ContentManager>(); SpriteBatch = GraphicsService.GetSpriteBatch(); // Let's create the necessary scene node renderers: TerrainRenderer = new TerrainRenderer(GraphicsService); MeshRenderer = new MeshRenderer(); // The _opaqueMeshSceneRenderer combines all renderers for opaque // (= not alpha blended) meshes. _opaqueMeshSceneRenderer = new SceneRenderer(); _opaqueMeshSceneRenderer.Renderers.Add(TerrainRenderer); _opaqueMeshSceneRenderer.Renderers.Add(MeshRenderer); _decalRenderer = new DecalRenderer(GraphicsService); _billboardRenderer = new BillboardRenderer(GraphicsService, 2048) { EnableSoftParticles = true, // If you have an extreme amount of particles that cover the entire screen, // you can turn on offscreen rendering to improve performance. //EnableOffscreenRendering = true, }; // The AlphaBlendSceneRenderer combines all renderers for transparent // (= alpha blended) objects. AlphaBlendSceneRenderer = new SceneRenderer(); AlphaBlendSceneRenderer.Renderers.Add(MeshRenderer); AlphaBlendSceneRenderer.Renderers.Add(_billboardRenderer); AlphaBlendSceneRenderer.Renderers.Add(new WaterRenderer(GraphicsService)); AlphaBlendSceneRenderer.Renderers.Add(new FogSphereRenderer(GraphicsService)); AlphaBlendSceneRenderer.Renderers.Add(new VolumetricLightRenderer(GraphicsService)); // Update terrain clipmaps. (Only necessary if TerrainNodes are used.) _terrainClipmapRenderer = new TerrainClipmapRenderer(GraphicsService); // Renderer for cloud maps. (Only necessary if LayeredCloudMaps are used.) _cloudMapRenderer = new CloudMapRenderer(GraphicsService); // Renderer for SceneCaptureNodes. See also SceneCapture2DSample. // In the constructor we specify a method which is called in SceneCaptureRenderer.Render() // when the scene must be rendered for the SceneCaptureNodes. SceneCaptureRenderer = new SceneCaptureRenderer(context => { // Get scene nodes which are visible by the current camera. CustomSceneQuery sceneQuery = Scene.Query <CustomSceneQuery>(context.CameraNode, context); // Render scene (with post-processing, with lens flares, no debug rendering, no reticle). RenderScene(sceneQuery, context, true, true, false, false); }); // Renderer for PlanarReflectionNodes. See also PlanarReflectionSample. // In the constructor we specify a method which is called in PlanarReflectionRenderer.Render() // to create the reflection images. _planarReflectionRenderer = new PlanarReflectionRenderer(context => { // Get scene nodes which are visible by the current camera. CustomSceneQuery sceneQuery = Scene.Query <CustomSceneQuery>(context.CameraNode, context); var planarReflectionNode = (PlanarReflectionNode)context.ReferenceNode; // Planar reflections are often for WaterNodes. These nodes should not be rendered // into their own reflection map because when the water surface is displaced by waves, // some waves could be visible in the reflection. // --> Remove the water node from the renderable nodes. (In our samples, the water // node is the parent of the reflection node.) if (planarReflectionNode.Parent is WaterNode) { var index = sceneQuery.RenderableNodes.IndexOf(planarReflectionNode.Parent); if (index >= 0) { sceneQuery.RenderableNodes[index] = null; } } // Render scene (no post-processing, no lens flares, no debug rendering, no reticle). RenderScene(sceneQuery, context, false, false, false, false); }); _waterWavesRenderer = new WaterWavesRenderer(GraphicsService); // The shadow map renderer renders a depth image from the viewpoint of the light and // stores it in LightNode.Shadow.ShadowMap. ShadowMapRenderer = new ShadowMapRenderer(context => { var query = context.Scene.Query <ShadowCasterQuery>(context.CameraNode, context); if (query.ShadowCasters.Count == 0) { return(false); } _opaqueMeshSceneRenderer.Render(query.ShadowCasters, context); return(true); }); // The shadow mask renderer evaluates the shadow maps, does shadow filtering // and stores the resulting shadow factor in a screen space image //(see LightNode.Shadow.ShadowMask/ShadowMaskChannel). ShadowMaskRenderer = new ShadowMaskRenderer(GraphicsService, 2); // Optionally, we can blur the shadow mask to make the shadows smoother. var blur = new Blur(GraphicsService) { IsAnisotropic = false, IsBilateral = true, EdgeSoftness = 0.05f, Scale = 1f, Enabled = false, // Disable blur by default. }; blur.InitializeGaussianBlur(11, 3, true); ShadowMaskRenderer.Filter = blur; // Renderers which create the intermediate render targets: // Those 2 renderers are implemented in this sample. Those functions could // be implemented directly in this class but we have created separate classes // to make the code more readable. _gBufferRenderer = new GBufferRenderer(GraphicsService, _opaqueMeshSceneRenderer, _decalRenderer); LightBufferRenderer = new LightBufferRenderer(GraphicsService); // Other specialized renderers: _lensFlareRenderer = new LensFlareRenderer(GraphicsService); _skyRenderer = new SkyRenderer(GraphicsService); _fogRenderer = new FogRenderer(GraphicsService); _internalDebugRenderer = new DebugRenderer(GraphicsService, null); _rebuildZBufferRenderer = new RebuildZBufferRenderer(GraphicsService); Scene = new Scene(); // This screen needs a HDR filter to map high dynamic range values back to // low dynamic range (LDR). PostProcessors = new PostProcessorChain(GraphicsService); PostProcessors.Add(new HdrFilter(GraphicsService) { EnableBlueShift = true, BlueShiftCenter = 0.0004f, BlueShiftRange = 0.5f, //BlueShiftColor = new Vector3(1.05f / 4f, 0.97f / 4f, 1.27f / 4f), // Default physically-based blue-shift BlueShiftColor = new Vector3(0.25f, 0.25f, 0.7f), // More dramatic blue-shift MinExposure = 0, MaxExposure = 10, BloomIntensity = 1, BloomThreshold = 0.6f, }); _underwaterPostProcessor = new UnderwaterPostProcessor(GraphicsService, contentManager); PostProcessors.Add(_underwaterPostProcessor); // Use 2D texture for reticle. _reticle = contentManager.Load <Texture2D>("Reticle"); // Use the sprite font of the GUI. var uiContentManager = services.GetInstance <ContentManager>("UIContent"); var spriteFont = uiContentManager.Load <SpriteFont>("UI Themes/BlendBlue/Default"); DebugRenderer = new DebugRenderer(GraphicsService, spriteFont) { DefaultColor = new Color(0, 0, 0), DefaultTextPosition = new Vector2F(10), }; EnableLod = true; }