using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Kav { public class Renderer { private const int MAX_SHADOW_CASCADES = 4; private GraphicsDevice GraphicsDevice { get; } private int RenderDimensionsX { get; } private int RenderDimensionsY { get; } private VertexBuffer FullscreenTriangle { get; } private int NumShadowCascades { get; } private RenderTarget2D[] ShadowRenderTargets { get; } private DeferredPBREffect DeferredPBREffect { get; } private SimpleDepthEffect SimpleDepthEffect { get; } private RenderTarget2D gPosition { get; } private RenderTarget2D gNormal { get; } private RenderTarget2D gAlbedo { get; } private RenderTarget2D gMetallicRoughness { get; } private RenderTarget2D deferredRenderTarget { get; } private RenderTargetBinding[] GBuffer { get; } public Renderer(GraphicsDevice graphicsDevice, int renderDimensionsX, int renderDimensionsY, int numShadowCascades) { GraphicsDevice = graphicsDevice; RenderDimensionsX = renderDimensionsX; RenderDimensionsY = renderDimensionsY; NumShadowCascades = (int)MathHelper.Clamp(numShadowCascades, 1, MAX_SHADOW_CASCADES); ShadowRenderTargets = new RenderTarget2D[numShadowCascades]; for (var i = 0; i < numShadowCascades; i++) { ShadowRenderTargets[i] = new RenderTarget2D( GraphicsDevice, 1024, 1024, false, SurfaceFormat.Single, DepthFormat.Depth24 ); } gPosition = new RenderTarget2D( GraphicsDevice, renderDimensionsX, renderDimensionsY, false, SurfaceFormat.Vector4, DepthFormat.Depth24 ); gNormal = new RenderTarget2D( GraphicsDevice, renderDimensionsX, renderDimensionsY, false, SurfaceFormat.Vector4, DepthFormat.None ); gAlbedo = new RenderTarget2D( GraphicsDevice, renderDimensionsX, renderDimensionsY, false, SurfaceFormat.Color, DepthFormat.None ); gMetallicRoughness = new RenderTarget2D( GraphicsDevice, renderDimensionsX, renderDimensionsY, false, SurfaceFormat.HalfVector2, DepthFormat.None ); GBuffer = new RenderTargetBinding[4] { new RenderTargetBinding(gPosition), new RenderTargetBinding(gNormal), new RenderTargetBinding(gAlbedo), new RenderTargetBinding(gMetallicRoughness) }; deferredRenderTarget = new RenderTarget2D( GraphicsDevice, renderDimensionsX, renderDimensionsY ); SimpleDepthEffect = new SimpleDepthEffect(GraphicsDevice); DeferredPBREffect = new DeferredPBREffect(GraphicsDevice); FullscreenTriangle = new VertexBuffer(GraphicsDevice, typeof(VertexPositionTexture), 3, BufferUsage.WriteOnly); FullscreenTriangle.SetData(new VertexPositionTexture[3] { new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 0)), new VertexPositionTexture(new Vector3(-1, 3, 0), new Vector2(0, -2)), new VertexPositionTexture(new Vector3(3, -1, 0), new Vector2(2, 0)) }); GraphicsDevice.SetRenderTarget(deferredRenderTarget); graphicsDevice.Clear(Color.White); GraphicsDevice.SetRenderTarget(null); } public void DeferredRender( PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, IEnumerable pointLights, DirectionalLight directionalLight ) { ShadowMapRender(camera, modelTransforms, directionalLight); GraphicsDevice.SetRenderTargets(GBuffer); GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0); GraphicsDevice.DepthStencilState = DepthStencilState.Default; GraphicsDevice.BlendState = BlendState.Opaque; foreach (var (model, transform) in modelTransforms) { foreach (var modelMesh in model.Meshes) { foreach (var meshPart in modelMesh.MeshParts) { GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer); GraphicsDevice.Indices = meshPart.IndexBuffer; if (meshPart.Effect is TransformEffect transformEffect) { transformEffect.World = transform; transformEffect.View = camera.View; transformEffect.Projection = camera.Projection; } foreach (var pass in meshPart.Effect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0, 0, meshPart.VertexBuffer.VertexCount, 0, meshPart.Triangles.Length ); } } } } GraphicsDevice.SetRenderTarget(null); GraphicsDevice.Clear(Color.CornflowerBlue); DeferredPBREffect.GPosition = gPosition; DeferredPBREffect.GAlbedo = gAlbedo; DeferredPBREffect.GNormal = gNormal; DeferredPBREffect.GMetallicRoughness = gMetallicRoughness; DeferredPBREffect.ShadowMapOne = ShadowRenderTargets[0]; if (NumShadowCascades > 1) { DeferredPBREffect.ShadowMapTwo = ShadowRenderTargets[1]; } if (NumShadowCascades > 2) { DeferredPBREffect.ShadowMapThree = ShadowRenderTargets[2]; } if (NumShadowCascades > 3) { DeferredPBREffect.ShadowMapFour = ShadowRenderTargets[3]; } DeferredPBREffect.ViewMatrix = camera.View; DeferredPBREffect.EyePosition = Matrix.Invert(camera.View).Translation; int i = 0; foreach (var pointLight in pointLights) { if (i > DeferredPBREffect.MaxPointLights) { break; } DeferredPBREffect.PointLights[i] = pointLight; i++; } DeferredPBREffect.DirectionalLightColor = directionalLight.Color.ToVector3() * directionalLight.Intensity; DeferredPBREffect.DirectionalLightDirection = directionalLight.Direction; foreach (EffectPass pass in DeferredPBREffect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.SetVertexBuffer(FullscreenTriangle); GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); } } public void ShadowMapRender( PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, DirectionalLight directionalLight ) { // set up global light matrix var right = Vector3.Cross(Vector3.Up, directionalLight.Direction); var up = Vector3.Cross(directionalLight.Direction, right); var cameraBoundingFrustum = new BoundingFrustum(camera.View * camera.Projection); Vector3[] frustumCorners = cameraBoundingFrustum.GetCorners(); Vector3 frustumCenter = Vector3.Zero; for (var i = 0; i < frustumCorners.Length; i++) { frustumCenter += frustumCorners[i]; } frustumCenter /= 8f; var lightView = Matrix.CreateLookAt(frustumCenter, frustumCenter - directionalLight.Direction, camera.View.Right); for (var i = 0; i < frustumCorners.Length; i++) { frustumCorners[i] = Vector3.Transform(frustumCorners[i], lightView); } BoundingBox lightBox = BoundingBox.CreateFromPoints(frustumCorners); Vector3 lightPosition = frustumCenter + directionalLight.Direction * -lightBox.Min.Z; // render the individual shadow maps var frustumDistance = camera.FarPlane - camera.NearPlane; var sectionDistance = frustumDistance / NumShadowCascades; for (var i = 0; i < NumShadowCascades; i++) { // divide the view frustum var shadowCamera = new PerspectiveCamera( camera.Position, camera.Forward, camera.Up, camera.FieldOfView, camera.AspectRatio, camera.NearPlane + (i * sectionDistance), camera.NearPlane + ((i + 1) * sectionDistance) ); RenderShadowMap(shadowCamera, modelTransforms, directionalLight, i); } } private void RenderShadowMap( PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, DirectionalLight directionalLight, int shadowCascadeIndex ) { GraphicsDevice.SetRenderTarget(ShadowRenderTargets[shadowCascadeIndex]); GraphicsDevice.Clear(Color.White); GraphicsDevice.DepthStencilState = DepthStencilState.Default; GraphicsDevice.BlendState = BlendState.Opaque; var right = Vector3.Cross(Vector3.Up, directionalLight.Direction); var up = Vector3.Cross(directionalLight.Direction, right); var cameraBoundingFrustum = new BoundingFrustum(camera.View * camera.Projection); Vector3[] frustumCorners = cameraBoundingFrustum.GetCorners(); Vector3 frustumCenter = Vector3.Zero; for (var i = 0; i < frustumCorners.Length; i++) { frustumCenter += frustumCorners[i]; } frustumCenter /= 8f; var lightView = Matrix.CreateLookAt(frustumCenter, frustumCenter - directionalLight.Direction, camera.View.Right); for (var i = 0; i < frustumCorners.Length; i++) { frustumCorners[i] = Vector3.Transform(frustumCorners[i], lightView); } BoundingBox lightBox = BoundingBox.CreateFromPoints(frustumCorners); Vector3 lightPosition = frustumCenter + directionalLight.Direction * -lightBox.Min.Z; SimpleDepthEffect.View = Matrix.CreateLookAt(lightPosition, frustumCenter, camera.View.Right); SimpleDepthEffect.Projection = Matrix.CreateOrthographicOffCenter( lightBox.Min.X, lightBox.Max.X, lightBox.Min.Y, lightBox.Max.Y, 0, lightBox.Max.Z - lightBox.Min.Z ); var lightSpaceMatrix = SimpleDepthEffect.View * SimpleDepthEffect.Projection; if (shadowCascadeIndex == 0) { DeferredPBREffect.LightSpaceMatrixOne = lightSpaceMatrix; } else if (shadowCascadeIndex == 1) { DeferredPBREffect.LightSpaceMatrixTwo = lightSpaceMatrix; } else if (shadowCascadeIndex == 2) { DeferredPBREffect.LightSpaceMatrixThree = lightSpaceMatrix; } else if (shadowCascadeIndex == 3) { DeferredPBREffect.LightSpaceMatrixFour = lightSpaceMatrix; } DeferredPBREffect.CascadeFarPlanes[shadowCascadeIndex] = camera.FarPlane; foreach (var (model, transform) in modelTransforms) { foreach (var modelMesh in model.Meshes) { foreach (var meshPart in modelMesh.MeshParts) { GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer); GraphicsDevice.Indices = meshPart.IndexBuffer; SimpleDepthEffect.Model = transform; foreach (var pass in SimpleDepthEffect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0, 0, meshPart.VertexBuffer.VertexCount, 0, meshPart.Triangles.Length ); } } } } } public void Render( PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, IEnumerable pointLights, IEnumerable directionalLights ) { Render(camera.View, camera.Projection, modelTransforms, pointLights, directionalLights); } private void Render( Matrix view, Matrix projection, IEnumerable<(Model, Matrix)> modelTransforms, IEnumerable pointLights, IEnumerable directionalLights ) { foreach (var (model, transform) in modelTransforms) { foreach (var modelMesh in model.Meshes) { foreach (var meshPart in modelMesh.MeshParts) { GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer); GraphicsDevice.Indices = meshPart.IndexBuffer; if (meshPart.Effect is TransformEffect transformEffect) { transformEffect.World = transform; transformEffect.View = view; transformEffect.Projection = projection; } if (meshPart.Effect is PointLightEffect pointLightEffect) { int i = 0; foreach (var pointLight in pointLights) { if (i > pointLightEffect.MaxPointLights) { break; } pointLightEffect.PointLights[i] = pointLight; i++; } } foreach (var pass in meshPart.Effect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0, 0, meshPart.VertexBuffer.VertexCount, 0, meshPart.Triangles.Length ); } } } } } } }