using System; using System.Collections.Generic; using System.IO; using Kav.Data; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Kav { public class Renderer { private GraphicsDevice GraphicsDevice { get; } private VertexBuffer FullscreenTriangle { get; } private DeferredPBREffect DeferredPBREffect { get; } /* FIXME: these next two dont actually have anything to do with PBR */ private DeferredPBR_GBufferEffect Deferred_GBufferEffect { get; } private DeferredPBR_AmbientLightEffect DeferredAmbientLightEffect { get; } private DeferredPBR_PointLightEffect DeferredPointLightEffect { get; } private DeferredPBR_DirectionalLightEffect DeferredDirectionalLightEffect { get; } private Deferred_ToonEffect Deferred_ToonEffect { get; } private SimpleDepthEffect SimpleDepthEffect { get; } private SimpleDepthEffectInstanced SimpleDepthEffectInstanced { get; } private LinearDepthEffect LinearDepthEffect { get; } private LinearDepthEffectInstanced LinearDepthEffectInstanced { get; } private Effect ToneMapEffect { get; } private SkyboxEffect SkyboxEffect { get; } private DiffuseLitSpriteEffect DiffuseLitSpriteEffect { get; } private Kav.Model UnitCube { get; } private Kav.Model UnitSphere { get; } public Renderer( GraphicsDevice graphicsDevice ) { GraphicsDevice = graphicsDevice; SimpleDepthEffect = new SimpleDepthEffect(GraphicsDevice); SimpleDepthEffectInstanced = new SimpleDepthEffectInstanced(GraphicsDevice); LinearDepthEffect = new LinearDepthEffect(GraphicsDevice); LinearDepthEffectInstanced = new LinearDepthEffectInstanced(GraphicsDevice); DeferredPBREffect = new DeferredPBREffect(GraphicsDevice); Deferred_GBufferEffect = new DeferredPBR_GBufferEffect(GraphicsDevice); DeferredAmbientLightEffect = new DeferredPBR_AmbientLightEffect(GraphicsDevice); DeferredPointLightEffect = new DeferredPBR_PointLightEffect(GraphicsDevice); DeferredDirectionalLightEffect = new DeferredPBR_DirectionalLightEffect(GraphicsDevice); ToneMapEffect = new Effect(graphicsDevice, Resources.ToneMapEffect); Deferred_ToonEffect = new Deferred_ToonEffect(GraphicsDevice); SkyboxEffect = new SkyboxEffect(GraphicsDevice); DiffuseLitSpriteEffect = new DiffuseLitSpriteEffect(GraphicsDevice); FullscreenTriangle = new VertexBuffer(GraphicsDevice, typeof(VertexPositionTexture), 3, BufferUsage.WriteOnly); FullscreenTriangle.SetData(new VertexPositionTexture[3] { new VertexPositionTexture(new Vector3(-1, -3, 0), new Vector2(0, 2)), new VertexPositionTexture(new Vector3(-1, 1, 0), new Vector2(0, 0)), new VertexPositionTexture(new Vector3(3, 1, 0), new Vector2(2, 0)) }); UnitCube = Kav.ModelLoader.Load( GraphicsDevice, Smuggler.Importer.ImportGLB(GraphicsDevice, new MemoryStream(Resources.UnitCubeModel)) ); UnitSphere = Kav.ModelLoader.Load( graphicsDevice, Smuggler.Importer.ImportGLB(graphicsDevice, new MemoryStream(Resources.UnitSphereModel)) ); } public static (T[], DynamicVertexBuffer) CreateInstanceVertexBuffer( GraphicsDevice graphicsDevice, int instanceVertexCount ) where T : IVertexType { var positionData = new T[instanceVertexCount]; var vertexBuffer = new DynamicVertexBuffer( graphicsDevice, typeof(T), instanceVertexCount, BufferUsage.WriteOnly ); return (positionData, vertexBuffer); } public static RenderTargetCube CreateShadowCubeMap( GraphicsDevice graphicsDevice, int shadowMapSize ) { return new RenderTargetCube( graphicsDevice, shadowMapSize, false, SurfaceFormat.Single, DepthFormat.Depth24, 0, RenderTargetUsage.PreserveContents ); } public static DirectionalShadowMapData CreateDirectionalShadowMaps( GraphicsDevice graphicsDevice, int shadowMapSize, int numCascades ) { return new DirectionalShadowMapData( graphicsDevice, shadowMapSize, numCascades ); } // TODO: we could make this a lot more efficient probably // draws mesh sprites with a forward rendered diffuse lighting technique public void MeshSpriteRender( RenderTarget2D renderTarget, PerspectiveCamera camera, IEnumerable meshSpriteDrawDatas, AmbientLight ambientLight, IEnumerable pointLights, DirectionalLight? directionalLight ) { GraphicsDevice.SetRenderTarget(renderTarget); GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; GraphicsDevice.RasterizerState = RasterizerState.CullNone; GraphicsDevice.SamplerStates[0] = SamplerState.PointClamp; GraphicsDevice.SamplerStates[1] = SamplerState.PointClamp; GraphicsDevice.BlendState = BlendState.AlphaBlend; DiffuseLitSpriteEffect.View = camera.View; DiffuseLitSpriteEffect.Projection = camera.Projection; DiffuseLitSpriteEffect.AmbientColor = ambientLight.Color.ToVector3(); if (directionalLight.HasValue) { DiffuseLitSpriteEffect.DirectionalLightDirection = directionalLight.Value.Direction; DiffuseLitSpriteEffect.DirectionalLightColor = directionalLight.Value.Color.ToVector3() * directionalLight.Value.Intensity; } else { DiffuseLitSpriteEffect.DirectionalLightColor = Vector3.Zero; } var i = 0; foreach (var pointLight in pointLights) { if (i > DiffuseLitSpriteEffect.MaxPointLights) { break; } DiffuseLitSpriteEffect.PointLights[i] = pointLight; i += 1; } var boundingFrustum = new BoundingFrustum(camera.View * camera.Projection); foreach (var data in meshSpriteDrawDatas) { var matrix = BillboardTransforms(camera, data.TransformMatrix, data.BillboardConstraint); if (FrustumCull(boundingFrustum, data.MeshSprite, matrix)) { continue; } DiffuseLitSpriteEffect.NormalMapEnabled = data.Normal != null; DiffuseLitSpriteEffect.World = matrix; DiffuseLitSpriteEffect.UVOffset = data.UVOffset.Offset; DiffuseLitSpriteEffect.SubTextureDimensions = data.UVOffset.Percentage; GraphicsDevice.Textures[0] = data.Texture; GraphicsDevice.Textures[1] = data.Normal; GraphicsDevice.SetVertexBuffer(data.MeshSprite.VertexBuffer); GraphicsDevice.Indices = data.MeshSprite.IndexBuffer; foreach (var pass in DiffuseLitSpriteEffect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0, 0, data.MeshSprite.VertexBuffer.VertexCount, 0, 2 ); } } } private static Matrix BillboardTransforms( PerspectiveCamera camera, Matrix transform, SpriteBillboardConstraint billboardConstraint ) { if (billboardConstraint == SpriteBillboardConstraint.None) { return transform; } else if (billboardConstraint == SpriteBillboardConstraint.Horizontal) { return Matrix.CreateConstrainedBillboard( transform.Translation, camera.Position, Vector3.Up, camera.Forward, camera.Position - transform.Translation ); } else { return Matrix.CreateConstrainedBillboard( transform.Translation, camera.Position, Vector3.Up, null, null ); } } // Renders a series of drawable-transform pairs using an effect that has a World matrix. // Effect must be pre-configured!! public static void CullAndRenderIndexed( GraphicsDevice graphicsDevice, BoundingFrustum boundingFrustum, IEnumerable<(T, Matrix)> drawableTransformPairs, U effect ) where T : IIndexDrawable, ICullable where U : Effect, IHasWorldMatrix { foreach (var (drawable, transform) in FrustumCull(boundingFrustum, drawableTransformPairs)) { effect.World = transform; RenderIndexed( graphicsDevice, drawable, effect ); } } public static void RenderIndexed( GraphicsDevice graphicsDevice, T drawable, U effect ) where T : IIndexDrawable where U : Effect { graphicsDevice.SetVertexBuffer(drawable.VertexBuffer); graphicsDevice.Indices = drawable.IndexBuffer; foreach (var pass in effect.CurrentTechnique.Passes) { pass.Apply(); graphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0, 0, drawable.VertexBuffer.VertexCount, 0, drawable.IndexBuffer.IndexCount / 3 ); } } public static void RenderInstanced( GraphicsDevice graphicsDevice, T drawable, VertexBuffer instanceVertexBuffer, int numInstances, Effect effect ) where T : IIndexDrawable { graphicsDevice.SetVertexBuffers( drawable.VertexBuffer, new VertexBufferBinding(instanceVertexBuffer, 0, 1) ); graphicsDevice.Indices = drawable.IndexBuffer; foreach (var pass in effect.CurrentTechnique.Passes) { pass.Apply(); graphicsDevice.DrawInstancedPrimitives( PrimitiveType.TriangleList, 0, 0, drawable.VertexBuffer.VertexCount, 0, drawable.IndexBuffer.IndexCount / 3, numInstances ); } } // TODO: can probably make this static somehow public void RenderFullscreenEffect( Effect effect ) { foreach (var pass in effect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.SetVertexBuffer(FullscreenTriangle); GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); } } public void RenderDepthIndexed( RenderTarget2D renderTarget, PerspectiveCamera camera, IEnumerable<(T, Matrix)> drawableTransforms ) where T : ICullable, IIndexDrawable { GraphicsDevice.SetRenderTarget(renderTarget); GraphicsDevice.DepthStencilState = DepthStencilState.Default; SimpleDepthEffect.View = camera.View; SimpleDepthEffect.Projection = camera.Projection; CullAndRenderIndexed( GraphicsDevice, new BoundingFrustum(camera.View * camera.Projection), drawableTransforms, SimpleDepthEffect ); } public void RenderSkybox( RenderTarget2D renderTarget, PerspectiveCamera camera, TextureCube skybox ) { GraphicsDevice.SetRenderTarget(renderTarget); GraphicsDevice.RasterizerState.CullMode = CullMode.CullClockwiseFace; GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; SkyboxEffect.Skybox = skybox; var view = camera.View; view.Translation = Vector3.Zero; SkyboxEffect.View = view; SkyboxEffect.Projection = camera.Projection; RenderIndexed( GraphicsDevice, UnitCube.Meshes[0].MeshParts[0], SkyboxEffect ); GraphicsDevice.RasterizerState.CullMode = CullMode.CullCounterClockwiseFace; } /// /// GBuffer binding must have 4 render targets. /// Assumes that vertex buffer has been filled. /// public void RenderGBufferInstanced( RenderTargetBinding[] gBuffer, RenderTarget2D depthBuffer, PerspectiveCamera camera, T drawable, VertexBuffer instanceVertexBuffer, int numInstances ) where T : IGBufferDrawable, IIndexDrawable { GraphicsDevice.SetRenderTargets(gBuffer); GraphicsDevice.DepthStencilState = DepthStencilState.Default; GraphicsDevice.BlendState = BlendState.Opaque; Deferred_GBufferEffect.HardwareInstancingEnabled = true; Deferred_GBufferEffect.Albedo = drawable.Albedo; Deferred_GBufferEffect.Metallic = drawable.Metallic; Deferred_GBufferEffect.Roughness = drawable.Roughness; Deferred_GBufferEffect.NumTextureRows = drawable.NumTextureRows; Deferred_GBufferEffect.NumTextureColumns = drawable.NumTextureColumns; Deferred_GBufferEffect.AlbedoTexture = drawable.AlbedoTexture; Deferred_GBufferEffect.NormalTexture = drawable.NormalTexture; Deferred_GBufferEffect.MetallicRoughnessTexture = drawable.MetallicRoughnessTexture; Deferred_GBufferEffect.View = camera.View; Deferred_GBufferEffect.Projection = camera.Projection; RenderInstanced( GraphicsDevice, drawable, instanceVertexBuffer, numInstances, Deferred_GBufferEffect ); // re-render to get depth GraphicsDevice.SetRenderTargets(depthBuffer); SimpleDepthEffectInstanced.View = camera.View; SimpleDepthEffectInstanced.Projection = camera.Projection; RenderInstanced( GraphicsDevice, drawable, instanceVertexBuffer, numInstances, SimpleDepthEffectInstanced ); } public void RenderGBufferIndexed( RenderTargetBinding[] gBuffer, PerspectiveCamera camera, IEnumerable<(T, Matrix)> drawableTransforms ) where T : ICullable, IIndexDrawable, IGBufferDrawable { GraphicsDevice.SetRenderTargets(gBuffer); GraphicsDevice.DepthStencilState = DepthStencilState.Default; GraphicsDevice.BlendState = BlendState.Opaque; Deferred_GBufferEffect.HardwareInstancingEnabled = false; Deferred_GBufferEffect.View = camera.View; Deferred_GBufferEffect.Projection = camera.Projection; var boundingFrustum = new BoundingFrustum(camera.View * camera.Projection); foreach (var (drawable, transform) in FrustumCull(boundingFrustum, drawableTransforms)) { Deferred_GBufferEffect.World = transform; Deferred_GBufferEffect.HardwareInstancingEnabled = false; Deferred_GBufferEffect.Albedo = drawable.Albedo; Deferred_GBufferEffect.Metallic = drawable.Metallic; Deferred_GBufferEffect.Roughness = drawable.Roughness; Deferred_GBufferEffect.AlbedoTexture = drawable.AlbedoTexture; Deferred_GBufferEffect.NormalTexture = drawable.NormalTexture; Deferred_GBufferEffect.MetallicRoughnessTexture = drawable.MetallicRoughnessTexture; RenderIndexed(GraphicsDevice, drawable, Deferred_GBufferEffect); } } public void RenderAmbientLight( RenderTarget2D renderTarget, Texture2D gPosition, Texture2D gAlbedo, AmbientLight ambientLight ) { GraphicsDevice.SetRenderTarget(renderTarget); GraphicsDevice.BlendState = BlendState.Opaque; GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; DeferredAmbientLightEffect.GPosition = gPosition; DeferredAmbientLightEffect.GAlbedo = gAlbedo; DeferredAmbientLightEffect.AmbientColor = ambientLight.Color.ToVector3(); RenderFullscreenEffect(DeferredAmbientLightEffect); } public void RenderPointLight( RenderTarget2D renderTarget, Texture2D gPosition, Texture2D gAlbedo, Texture2D gNormal, Texture2D gMetallicRoughness, TextureCube shadowMap, PerspectiveCamera camera, PointLight pointLight ) { GraphicsDevice.SetRenderTarget(renderTarget); GraphicsDevice.RasterizerState = RasterizerState.CullClockwise; GraphicsDevice.DepthStencilState = DepthStencilState.None; GraphicsDevice.BlendState = BlendState.Additive; DeferredPointLightEffect.GPosition = gPosition; DeferredPointLightEffect.GAlbedo = gAlbedo; DeferredPointLightEffect.GNormal = gNormal; DeferredPointLightEffect.GMetallicRoughness = gMetallicRoughness; DeferredPointLightEffect.ShadowMap = shadowMap; DeferredPointLightEffect.EyePosition = camera.Position; DeferredPointLightEffect.PointLightPosition = pointLight.Position; DeferredPointLightEffect.PointLightColor = pointLight.Color.ToVector3() * pointLight.Radius; DeferredPointLightEffect.FarPlane = 25f; // FIXME: magic value DeferredPointLightEffect.World = Matrix.CreateScale(pointLight.Radius) * Matrix.CreateTranslation(pointLight.Position); DeferredPointLightEffect.View = camera.View; DeferredPointLightEffect.Projection = camera.Projection; RenderIndexed( GraphicsDevice, UnitSphere.Meshes[0].MeshParts[0], DeferredPointLightEffect ); } public void RenderDirectionalLight( RenderTarget2D renderTarget, Texture2D gPosition, Texture2D gAlbedo, Texture2D gNormal, Texture2D gMetallicRoughness, DirectionalShadowMapData shadowMapData, PerspectiveCamera camera, DirectionalLight directionalLight ) { GraphicsDevice.SetRenderTarget(renderTarget); GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise; GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; GraphicsDevice.BlendState = BlendState.Additive; DeferredDirectionalLightEffect.GPosition = gPosition; DeferredDirectionalLightEffect.GAlbedo = gAlbedo; DeferredDirectionalLightEffect.GNormal = gNormal; DeferredDirectionalLightEffect.GMetallicRoughness = gMetallicRoughness; DeferredDirectionalLightEffect.ShadowMapSize = shadowMapData.ShadowMapSize; DeferredDirectionalLightEffect.ShadowMapOne = shadowMapData.ShadowMaps[0]; DeferredDirectionalLightEffect.LightSpaceMatrixOne = shadowMapData.LightSpaceViews[0] * shadowMapData.LightSpaceProjections[0]; DeferredDirectionalLightEffect.CascadeFarPlanes[0] = shadowMapData.CascadeFarPlanes[0]; if (shadowMapData.NumShadowCascades > 1) { DeferredDirectionalLightEffect.ShadowMapTwo = shadowMapData.ShadowMaps[1]; DeferredDirectionalLightEffect.LightSpaceMatrixTwo = shadowMapData.LightSpaceViews[1] * shadowMapData.LightSpaceProjections[1]; DeferredDirectionalLightEffect.CascadeFarPlanes[1] = shadowMapData.CascadeFarPlanes[1]; } if (shadowMapData.NumShadowCascades > 2) { DeferredDirectionalLightEffect.ShadowMapThree = shadowMapData.ShadowMaps[2]; DeferredDirectionalLightEffect.LightSpaceMatrixThree = shadowMapData.LightSpaceViews[2] * shadowMapData.LightSpaceProjections[2]; DeferredDirectionalLightEffect.CascadeFarPlanes[2] = shadowMapData.CascadeFarPlanes[2]; } if (shadowMapData.NumShadowCascades > 3) { DeferredDirectionalLightEffect.ShadowMapFour = shadowMapData.ShadowMaps[3]; DeferredDirectionalLightEffect.LightSpaceMatrixFour = shadowMapData.LightSpaceViews[3] * shadowMapData.LightSpaceProjections[3]; DeferredDirectionalLightEffect.CascadeFarPlanes[3] = shadowMapData.CascadeFarPlanes[3]; } DeferredDirectionalLightEffect.DirectionalLightDirection = directionalLight.Direction; DeferredDirectionalLightEffect.DirectionalLightColor = directionalLight.Color.ToVector3() * directionalLight.Intensity; DeferredDirectionalLightEffect.ViewMatrix = camera.View; DeferredDirectionalLightEffect.EyePosition = camera.Position; RenderFullscreenEffect(DeferredDirectionalLightEffect); } public void RenderDirectionalLightToon( RenderTarget2D renderTarget, Texture2D gPosition, Texture2D gAlbedo, Texture2D gNormal, Texture2D gMetallicRoughness, DirectionalShadowMapData shadowMapData, PerspectiveCamera camera, DirectionalLight directionalLight, bool ditheredShadows ) { GraphicsDevice.SetRenderTarget(renderTarget); GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; GraphicsDevice.BlendState = BlendState.Additive; Deferred_ToonEffect.GPosition = gPosition; Deferred_ToonEffect.GAlbedo = gAlbedo; Deferred_ToonEffect.GNormal = gNormal; Deferred_ToonEffect.GMetallicRoughness = gMetallicRoughness; Deferred_ToonEffect.DitheredShadows = ditheredShadows; Deferred_ToonEffect.EyePosition = camera.Position; Deferred_ToonEffect.DirectionalLightDirection = directionalLight.Direction; Deferred_ToonEffect.DirectionalLightColor = directionalLight.Color.ToVector3() * directionalLight.Intensity; Deferred_ToonEffect.ShadowMapOne = shadowMapData.ShadowMaps[0]; Deferred_ToonEffect.LightSpaceMatrixOne = shadowMapData.LightSpaceViews[0] * shadowMapData.LightSpaceProjections[0]; if (shadowMapData.NumShadowCascades > 1) { Deferred_ToonEffect.ShadowMapTwo = shadowMapData.ShadowMaps[1]; Deferred_ToonEffect.LightSpaceMatrixTwo = shadowMapData.LightSpaceViews[1] * shadowMapData.LightSpaceProjections[1]; } if (shadowMapData.NumShadowCascades > 2) { Deferred_ToonEffect.ShadowMapThree = shadowMapData.ShadowMaps[2]; Deferred_ToonEffect.LightSpaceMatrixThree = shadowMapData.LightSpaceViews[1] * shadowMapData.LightSpaceProjections[2]; } if (shadowMapData.NumShadowCascades > 3) { Deferred_ToonEffect.ShadowMapFour = shadowMapData.ShadowMaps[3]; Deferred_ToonEffect.LightSpaceMatrixFour = shadowMapData.LightSpaceViews[2] * shadowMapData.LightSpaceProjections[3]; } Deferred_ToonEffect.ViewMatrix = camera.View; RenderFullscreenEffect(Deferred_ToonEffect); } public void PrepareDirectionalShadowData( DirectionalShadowMapData shadowMapData, PerspectiveCamera camera, DirectionalLight directionalLight ) { var previousFarPlane = camera.NearPlane; for (var i = 0; i < shadowMapData.NumShadowCascades; i++) { var farPlane = camera.FarPlane / (MathHelper.Max((shadowMapData.NumShadowCascades - i - 1) * 2f, 1f)); // divide the view frustum var shadowCamera = new PerspectiveCamera( camera.Position, camera.Forward, camera.Up, camera.FieldOfView, camera.AspectRatio, previousFarPlane, farPlane ); PrepareDirectionalShadowCascade( shadowMapData, i, shadowCamera, directionalLight ); shadowMapData.CascadeFarPlanes[i] = farPlane; previousFarPlane = farPlane; } } private void PrepareDirectionalShadowCascade( DirectionalShadowMapData shadowMapData, int shadowCascadeIndex, PerspectiveCamera shadowCamera, DirectionalLight directionalLight ) { var cameraBoundingFrustum = new BoundingFrustum(shadowCamera.View * shadowCamera.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 + directionalLight.Direction, frustumCenter, Vector3.Backward); for (var i = 0; i < frustumCorners.Length; i++) { frustumCorners[i] = Vector3.Transform(frustumCorners[i], lightView); } BoundingBox lightBox = BoundingBox.CreateFromPoints(frustumCorners); shadowMapData.LightSpaceViews[shadowCascadeIndex] = lightView; shadowMapData.LightSpaceProjections[shadowCascadeIndex] = Matrix.CreateOrthographicOffCenter( lightBox.Min.X, lightBox.Max.X, lightBox.Min.Y, lightBox.Max.Y, -lightBox.Max.Z - 10f, // TODO: near clip plane needs scene AABB info to get rid of this magic value -lightBox.Min.Z ); } public void RenderDirectionalShadowsIndexed( DirectionalShadowMapData shadowMapData, IEnumerable<(T, Matrix)> drawableTransforms ) where T : ICullable, IIndexDrawable { // render the individual shadow cascades for (var i = 0; i < shadowMapData.NumShadowCascades; i++) { RenderDirectionalShadowMapIndexed( shadowMapData, i, drawableTransforms ); } } private void RenderDirectionalShadowMapIndexed( DirectionalShadowMapData shadowMapData, int shadowCascadeIndex, IEnumerable<(T, Matrix)> drawableTransforms ) where T : ICullable, IIndexDrawable { GraphicsDevice.SetRenderTarget(shadowMapData.ShadowMaps[shadowCascadeIndex]); GraphicsDevice.DepthStencilState = DepthStencilState.Default; GraphicsDevice.BlendState = BlendState.Opaque; SimpleDepthEffect.View = shadowMapData.LightSpaceViews[shadowCascadeIndex]; SimpleDepthEffect.Projection = shadowMapData.LightSpaceProjections[shadowCascadeIndex]; CullAndRenderIndexed( GraphicsDevice, new BoundingFrustum(SimpleDepthEffect.View * SimpleDepthEffect.Projection), drawableTransforms, SimpleDepthEffect ); } public void RenderDirectionalShadowsInstanced( DirectionalShadowMapData shadowMapData, T drawable, VertexBuffer instanceVertexBuffer, int numInstances ) where T : IIndexDrawable { // render the individual shadow cascades for (var i = 0; i < shadowMapData.NumShadowCascades; i++) { RenderDirectionalShadowMapInstanced( shadowMapData, i, drawable, instanceVertexBuffer, numInstances ); } } private void RenderDirectionalShadowMapInstanced( DirectionalShadowMapData shadowMapData, int shadowCascadeIndex, T drawable, VertexBuffer instanceVertexBuffer, int numInstances ) where T : IIndexDrawable { GraphicsDevice.SetRenderTarget(shadowMapData.ShadowMaps[shadowCascadeIndex]); GraphicsDevice.DepthStencilState = DepthStencilState.Default; GraphicsDevice.BlendState = BlendState.Opaque; SimpleDepthEffectInstanced.View = shadowMapData.LightSpaceViews[shadowCascadeIndex]; SimpleDepthEffectInstanced.Projection = shadowMapData.LightSpaceProjections[shadowCascadeIndex]; RenderInstanced( GraphicsDevice, drawable, instanceVertexBuffer, numInstances, SimpleDepthEffectInstanced ); } public void RenderPointShadowMapIndexed( RenderTargetCube pointShadowCubeMap, IEnumerable<(T, Matrix)> modelTransforms, PointLight pointLight ) where T : ICullable, IIndexDrawable { GraphicsDevice.DepthStencilState = DepthStencilState.Default; GraphicsDevice.BlendState = BlendState.Opaque; foreach (CubeMapFace face in Enum.GetValues(typeof(CubeMapFace))) { GraphicsDevice.SetRenderTarget(pointShadowCubeMap, face); Vector3 targetDirection; Vector3 targetUpDirection; switch(face) { case CubeMapFace.PositiveX: targetDirection = Vector3.Right; targetUpDirection = Vector3.Up; break; case CubeMapFace.NegativeX: targetDirection = Vector3.Left; targetUpDirection = Vector3.Up; break; case CubeMapFace.PositiveY: targetDirection = Vector3.Up; targetUpDirection = Vector3.Forward; break; case CubeMapFace.NegativeY: targetDirection = Vector3.Down; targetUpDirection = Vector3.Backward; break; case CubeMapFace.PositiveZ: targetDirection = Vector3.Backward; targetUpDirection = Vector3.Up; break; case CubeMapFace.NegativeZ: targetDirection = Vector3.Forward; targetUpDirection = Vector3.Up; break; default: targetDirection = Vector3.Right; targetUpDirection = Vector3.Up; break; } LinearDepthEffect.View = Matrix.CreateLookAt( pointLight.Position, pointLight.Position + targetDirection, targetUpDirection ); LinearDepthEffect.Projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver2, 1, 0.1f, 25f // FIXME: magic value ); LinearDepthEffect.FarPlane = 25f; LinearDepthEffect.LightPosition = pointLight.Position; CullAndRenderIndexed( GraphicsDevice, new BoundingFrustum(LinearDepthEffect.View * LinearDepthEffect.Projection), modelTransforms, LinearDepthEffect ); } } public void RenderPointShadowMapInstanced( RenderTargetCube pointShadowCubeMap, T drawable, VertexBuffer instanceVertexBuffer, int numInstances, PointLight pointLight ) where T : ICullable, IIndexDrawable { GraphicsDevice.DepthStencilState = DepthStencilState.Default; GraphicsDevice.BlendState = BlendState.Opaque; foreach (CubeMapFace face in Enum.GetValues(typeof(CubeMapFace))) { GraphicsDevice.SetRenderTarget(pointShadowCubeMap, face); Vector3 targetDirection; Vector3 targetUpDirection; switch(face) { case CubeMapFace.PositiveX: targetDirection = Vector3.Right; targetUpDirection = Vector3.Up; break; case CubeMapFace.NegativeX: targetDirection = Vector3.Left; targetUpDirection = Vector3.Up; break; case CubeMapFace.PositiveY: targetDirection = Vector3.Up; targetUpDirection = Vector3.Forward; break; case CubeMapFace.NegativeY: targetDirection = Vector3.Down; targetUpDirection = Vector3.Backward; break; case CubeMapFace.PositiveZ: targetDirection = Vector3.Backward; targetUpDirection = Vector3.Up; break; case CubeMapFace.NegativeZ: targetDirection = Vector3.Forward; targetUpDirection = Vector3.Up; break; default: targetDirection = Vector3.Right; targetUpDirection = Vector3.Up; break; } LinearDepthEffectInstanced.View = Matrix.CreateLookAt( pointLight.Position, pointLight.Position + targetDirection, targetUpDirection ); LinearDepthEffectInstanced.Projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver2, 1, 0.1f, 25f // FIXME: magic value ); LinearDepthEffectInstanced.FarPlane = 25f; LinearDepthEffectInstanced.LightPosition = pointLight.Position; RenderInstanced( GraphicsDevice, drawable, instanceVertexBuffer, numInstances, LinearDepthEffectInstanced ); } } private static IEnumerable<(T, Matrix)> FrustumCull( BoundingFrustum boundingFrustum, IEnumerable<(T, Matrix)> cullableTransforms ) where T : ICullable { foreach (var (cullable, transform) in cullableTransforms) { var boundingBox = TransformedBoundingBox(cullable.BoundingBox, transform); var containment = boundingFrustum.Contains(boundingBox); if (containment != ContainmentType.Disjoint) { yield return (cullable, transform); } } } private static bool FrustumCull( BoundingFrustum boundingFrustum, T cullable, Matrix transform ) where T : ICullable { var boundingBox = TransformedBoundingBox(cullable.BoundingBox, transform); var containment = boundingFrustum.Contains(boundingBox); return (containment == ContainmentType.Disjoint); } private static BoundingBox TransformedBoundingBox(BoundingBox boundingBox, Matrix matrix) { var center = (boundingBox.Min + boundingBox.Max) / 2f; var extent = (boundingBox.Max - boundingBox.Min) / 2f; var newCenter = Vector3.Transform(center, matrix); var newExtent = Vector3.TransformNormal(extent, AbsoluteMatrix(matrix)); return new BoundingBox(newCenter - newExtent, newCenter + newExtent); } private static Matrix AbsoluteMatrix(Matrix matrix) { return new Matrix( Math.Abs(matrix.M11), Math.Abs(matrix.M12), Math.Abs(matrix.M13), Math.Abs(matrix.M14), Math.Abs(matrix.M21), Math.Abs(matrix.M22), Math.Abs(matrix.M23), Math.Abs(matrix.M24), Math.Abs(matrix.M31), Math.Abs(matrix.M32), Math.Abs(matrix.M33), Math.Abs(matrix.M34), Math.Abs(matrix.M41), Math.Abs(matrix.M42), Math.Abs(matrix.M43), Math.Abs(matrix.M44) ); } } }