using System; using System.Collections.Generic; using System.IO; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Kav { public class Renderer { private const int MAX_SHADOW_CASCADES = 4; private int ShadowMapSize { get; } private GraphicsDevice GraphicsDevice { get; } private int RenderDimensionsX { get; } private int RenderDimensionsY { get; } private VertexBuffer FullscreenTriangle { get; } private int NumShadowCascades { get; } private RenderTarget2D ColorRenderTarget { get; } private RenderTarget2D DirectionalRenderTarget { get; } private RenderTarget2D[] ShadowRenderTargets { 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 LinearDepthEffect LinearDepthEffect { get; } private Effect ToneMapEffect { get; } private SkyboxEffect SkyboxEffect { get; } private BasicEffect BasicEffect { get; } private RenderTarget2D gPosition { get; } private RenderTarget2D gNormal { get; } private RenderTarget2D gAlbedo { get; } private RenderTarget2D gMetallicRoughness { get; } private RenderTargetCube PointShadowCubeMap { get; } private RenderTargetBinding[] GBuffer { get; } private Kav.Model UnitCube { get; } private SpriteBatch SpriteBatch { get; } public Renderer( GraphicsDevice graphicsDevice, int renderDimensionsX, int renderDimensionsY, int numShadowCascades, int shadowMapSize ) { GraphicsDevice = graphicsDevice; RenderDimensionsX = renderDimensionsX; RenderDimensionsY = renderDimensionsY; ShadowMapSize = shadowMapSize; 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, ShadowMapSize, ShadowMapSize, false, SurfaceFormat.Single, DepthFormat.Depth24 ); } ColorRenderTarget = new RenderTarget2D( graphicsDevice, renderDimensionsX, renderDimensionsY, false, SurfaceFormat.Color, DepthFormat.Depth24, 0, RenderTargetUsage.PreserveContents ); DirectionalRenderTarget = new RenderTarget2D( graphicsDevice, renderDimensionsX, renderDimensionsY, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents ); 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) }; PointShadowCubeMap = new RenderTargetCube( GraphicsDevice, shadowMapSize, false, SurfaceFormat.Single, DepthFormat.Depth24 ); SimpleDepthEffect = new SimpleDepthEffect(GraphicsDevice); LinearDepthEffect = new LinearDepthEffect(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); DeferredDirectionalLightEffect.ShadowMapSize = ShadowMapSize; ToneMapEffect = new Effect(graphicsDevice, Resources.ToneMapEffect); Deferred_ToonEffect = new Deferred_ToonEffect(GraphicsDevice); SkyboxEffect = new SkyboxEffect(GraphicsDevice); BasicEffect = new BasicEffect(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)) ); SpriteBatch = new SpriteBatch(graphicsDevice); } public void DeferredRender( RenderTarget2D renderTarget, PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, AmbientLight ambientLight, IEnumerable pointLights, DirectionalLight directionalLight ) { GBufferRender(camera, modelTransforms); GraphicsDevice.SetRenderTarget(ColorRenderTarget); GraphicsDevice.Clear(Color.Black); AmbientLightRender(ambientLight); DeferredPointLightEffect.EyePosition = camera.Position; foreach (var pointLight in pointLights) { PointLightRender(camera, modelTransforms, pointLight); } DirectionalLightRender(camera, modelTransforms, directionalLight); GraphicsDevice.SetRenderTarget(renderTarget); SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, null, null, null, ToneMapEffect); SpriteBatch.Draw(ColorRenderTarget, Vector2.Zero, Color.White); SpriteBatch.End(); } public void DeferredToonRender( RenderTarget2D renderTarget, PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, AmbientLight ambientLight, IEnumerable pointLights, DirectionalLight directionalLight, TextureCube skybox ) { GBufferRender(camera, modelTransforms); GraphicsDevice.SetRenderTarget(ColorRenderTarget); GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0); GraphicsDevice.DepthStencilState = DepthStencilState.Default; DepthRender(camera, modelTransforms); GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; AmbientLightRender(ambientLight); foreach (var pointLight in pointLights) { PointLightRender(camera, modelTransforms, pointLight); } DirectionalLightToonRender(camera, modelTransforms, directionalLight); SkyboxRender(camera, skybox); GraphicsDevice.SetRenderTarget(renderTarget); SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, null, null, null, null); SpriteBatch.Draw(ColorRenderTarget, Vector2.Zero, Color.White); SpriteBatch.End(); } // billboards sprites into the scene // FIXME: we can frustum cull the sprites probably public void BillboardSpriteRender( RenderTarget2D renderTarget, PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, IEnumerable sprites ) { GraphicsDevice.SetRenderTarget(ColorRenderTarget); GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0); GraphicsDevice.DepthStencilState = DepthStencilState.Default; DepthRender(camera, modelTransforms); GraphicsDevice.Clear(ClearOptions.Target, new Color(0, 0, 0, 0), 1f, 0); BasicEffect.View = camera.View; BasicEffect.Projection = camera.Projection; BasicEffect.TextureEnabled = true; BasicEffect.VertexColorEnabled = true; foreach (var sprite in sprites) { if (sprite.BillboardConstraint == SpriteBillboardConstraint.None) { BasicEffect.World = sprite.TransformMatrix; } else if (sprite.BillboardConstraint == SpriteBillboardConstraint.Horizontal) { BasicEffect.World = Matrix.CreateConstrainedBillboard( sprite.Position, camera.Position, Vector3.Up, camera.Forward, camera.Position - sprite.Position ); } else { BasicEffect.World = Matrix.CreateConstrainedBillboard( sprite.Position, camera.Position, Vector3.Up, null, null ); } SpriteBatch.Begin(0, null, null, DepthStencilState.DepthRead, RasterizerState.CullNone, BasicEffect); SpriteBatch.Draw( sprite.Texture, Vector2.Zero, null, Color.White, sprite.Rotation, sprite.Origin, sprite.Scale / new Vector2(sprite.Texture.Width, -sprite.Texture.Height), 0, 0 ); SpriteBatch.End(); } GraphicsDevice.SetRenderTarget(renderTarget); GraphicsDevice.Clear(new Color(0, 0, 0, 0)); SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, null, null); SpriteBatch.Draw(ColorRenderTarget, Vector2.Zero, Color.White); SpriteBatch.End(); } private void DepthRender( PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms ) { var boundingFrustum = new BoundingFrustum(camera.View * camera.Projection); foreach (var (model, transform) in FrustumCull(boundingFrustum, modelTransforms)) { foreach (var modelMesh in model.Meshes) { foreach (var meshPart in modelMesh.MeshParts) { SimpleDepthEffect.Model = transform; SimpleDepthEffect.View = camera.View; SimpleDepthEffect.Projection = camera.Projection; GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer); GraphicsDevice.Indices = meshPart.IndexBuffer; foreach (var pass in SimpleDepthEffect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0, 0, meshPart.VertexBuffer.VertexCount, 0, meshPart.Triangles.Length ); } } } } } private void SkyboxRender( PerspectiveCamera camera, TextureCube skybox ) { GraphicsDevice.RasterizerState.CullMode = CullMode.CullClockwiseFace; SkyboxEffect.Skybox = skybox; var view = camera.View; view.Translation = Vector3.Zero; SkyboxEffect.View = view; SkyboxEffect.Projection = camera.Projection; GraphicsDevice.SetVertexBuffer(UnitCube.Meshes[0].MeshParts[0].VertexBuffer); GraphicsDevice.Indices = UnitCube.Meshes[0].MeshParts[0].IndexBuffer; foreach (var pass in SkyboxEffect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0, 0, UnitCube.Meshes[0].MeshParts[0].VertexBuffer.VertexCount, 0, UnitCube.Meshes[0].MeshParts[0].Triangles.Length ); } GraphicsDevice.RasterizerState.CullMode = CullMode.CullCounterClockwiseFace; } private void GBufferRender( PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms ) { GraphicsDevice.SetRenderTargets(GBuffer); GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0); GraphicsDevice.DepthStencilState = DepthStencilState.Default; GraphicsDevice.BlendState = BlendState.Opaque; var boundingFrustum = new BoundingFrustum(camera.View * camera.Projection); foreach (var (model, transform) in FrustumCull(boundingFrustum, modelTransforms)) { foreach (var modelMesh in model.Meshes) { foreach (var meshPart in modelMesh.MeshParts) { Deferred_GBufferEffect.World = transform; Deferred_GBufferEffect.View = camera.View; Deferred_GBufferEffect.Projection = camera.Projection; Deferred_GBufferEffect.Albedo = meshPart.Albedo; Deferred_GBufferEffect.Metallic = meshPart.Metallic; Deferred_GBufferEffect.Roughness = meshPart.Roughness; Deferred_GBufferEffect.AlbedoTexture = meshPart.AlbedoTexture; Deferred_GBufferEffect.NormalTexture = meshPart.NormalTexture; Deferred_GBufferEffect.MetallicRoughnessTexture = meshPart.MetallicRoughnessTexture; GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer); GraphicsDevice.Indices = meshPart.IndexBuffer; foreach (var pass in Deferred_GBufferEffect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0, 0, meshPart.VertexBuffer.VertexCount, 0, meshPart.Triangles.Length ); } } } } } private void AmbientLightRender(AmbientLight ambientLight) { GraphicsDevice.SetRenderTarget(ColorRenderTarget); GraphicsDevice.BlendState = BlendState.Opaque; DeferredAmbientLightEffect.GPosition = gPosition; DeferredAmbientLightEffect.GAlbedo = gAlbedo; DeferredAmbientLightEffect.AmbientColor = ambientLight.Color.ToVector3(); foreach (var pass in DeferredAmbientLightEffect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.SetVertexBuffer(FullscreenTriangle); GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); } } private void PointLightRender( PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, PointLight pointLight ) { RenderPointShadows(camera, modelTransforms, pointLight); GraphicsDevice.SetRenderTarget(ColorRenderTarget); GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; GraphicsDevice.BlendState = BlendState.Additive; DeferredPointLightEffect.GPosition = gPosition; DeferredPointLightEffect.GAlbedo = gAlbedo; DeferredPointLightEffect.GNormal = gNormal; DeferredPointLightEffect.GMetallicRoughness = gMetallicRoughness; DeferredPointLightEffect.ShadowMap = PointShadowCubeMap; DeferredPointLightEffect.PointLightPosition = pointLight.Position; DeferredPointLightEffect.PointLightColor = pointLight.Color.ToVector3() * pointLight.Intensity; DeferredPointLightEffect.FarPlane = 25f; // FIXME: magic value foreach (var pass in DeferredPointLightEffect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.SetVertexBuffer(FullscreenTriangle); GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); } } private void DirectionalLightRender( PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, DirectionalLight directionalLight ) { RenderDirectionalShadows(camera, modelTransforms, directionalLight, DeferredDirectionalLightEffect); DeferredDirectionalLightEffect.GPosition = gPosition; DeferredDirectionalLightEffect.GAlbedo = gAlbedo; DeferredDirectionalLightEffect.GNormal = gNormal; DeferredDirectionalLightEffect.GMetallicRoughness = gMetallicRoughness; DeferredDirectionalLightEffect.ShadowMapOne = ShadowRenderTargets[0]; if (NumShadowCascades > 1) { DeferredDirectionalLightEffect.ShadowMapTwo = ShadowRenderTargets[1]; } if (NumShadowCascades > 2) { DeferredDirectionalLightEffect.ShadowMapThree = ShadowRenderTargets[2]; } if (NumShadowCascades > 3) { DeferredDirectionalLightEffect.ShadowMapFour = ShadowRenderTargets[3]; } DeferredDirectionalLightEffect.DirectionalLightDirection = directionalLight.Direction; DeferredDirectionalLightEffect.DirectionalLightColor = directionalLight.Color.ToVector3() * directionalLight.Intensity; DeferredDirectionalLightEffect.ViewMatrix = camera.View; DeferredDirectionalLightEffect.EyePosition = camera.Position; GraphicsDevice.SetRenderTarget(ColorRenderTarget); GraphicsDevice.BlendState = BlendState.Additive; foreach (EffectPass pass in DeferredDirectionalLightEffect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.SetVertexBuffer(FullscreenTriangle); GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); } } private void DirectionalLightToonRender( PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, DirectionalLight directionalLight ) { RenderDirectionalShadows(camera, modelTransforms, directionalLight, Deferred_ToonEffect); GraphicsDevice.SetRenderTarget(ColorRenderTarget); 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 = false; Deferred_ToonEffect.EyePosition = camera.Position; Deferred_ToonEffect.DirectionalLightDirection = directionalLight.Direction; Deferred_ToonEffect.DirectionalLightColor = directionalLight.Color.ToVector3() * directionalLight.Intensity; Deferred_ToonEffect.ShadowMapOne = ShadowRenderTargets[0]; if (NumShadowCascades > 1) { Deferred_ToonEffect.ShadowMapTwo = ShadowRenderTargets[1]; } if (NumShadowCascades > 2) { Deferred_ToonEffect.ShadowMapThree = ShadowRenderTargets[2]; } if (NumShadowCascades > 3) { Deferred_ToonEffect.ShadowMapFour = ShadowRenderTargets[3]; } Deferred_ToonEffect.ViewMatrix = camera.View; foreach (EffectPass pass in Deferred_ToonEffect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.SetVertexBuffer(FullscreenTriangle); GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); } } private void RenderDirectionalShadows( PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, DirectionalLight directionalLight, ShadowCascadeEffect effect ) { // render the individual shadow cascades var previousFarPlane = camera.NearPlane; for (var i = 0; i < NumShadowCascades; i++) { var farPlane = camera.FarPlane / (MathHelper.Max((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 ); // TODO: This is tightly coupled to the effect and it sucks RenderDirectionalShadowMap(shadowCamera, modelTransforms, directionalLight, effect, i); effect.CascadeFarPlanes[i] = farPlane; previousFarPlane = farPlane; } } private void RenderDirectionalShadowMap( PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, DirectionalLight directionalLight, ShadowCascadeEffect effect, int shadowCascadeIndex ) { GraphicsDevice.SetRenderTarget(ShadowRenderTargets[shadowCascadeIndex]); GraphicsDevice.Clear(Color.White); GraphicsDevice.DepthStencilState = DepthStencilState.Default; GraphicsDevice.BlendState = BlendState.Opaque; 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 + 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); SimpleDepthEffect.View = lightView; SimpleDepthEffect.Projection = 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 ); var lightSpaceMatrix = SimpleDepthEffect.View * SimpleDepthEffect.Projection; if (shadowCascadeIndex == 0) { effect.LightSpaceMatrixOne = lightSpaceMatrix; } else if (shadowCascadeIndex == 1) { effect.LightSpaceMatrixTwo = lightSpaceMatrix; } else if (shadowCascadeIndex == 2) { effect.LightSpaceMatrixThree = lightSpaceMatrix; } else if (shadowCascadeIndex == 3) { effect.LightSpaceMatrixFour = lightSpaceMatrix; } var boundingFrustum = new BoundingFrustum(lightSpaceMatrix); foreach (var (model, transform) in FrustumCull(boundingFrustum, 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 ); } } } } } private void RenderPointShadows( PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, PointLight pointLight ) { GraphicsDevice.DepthStencilState = DepthStencilState.Default; GraphicsDevice.BlendState = BlendState.Opaque; LinearDepthEffect.Projection = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver2, 1, 0.1f, 25f // FIXME: magic value ); LinearDepthEffect.FarPlane = 25f; LinearDepthEffect.LightPosition = pointLight.Position; 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 ); var boundingFrustum = new BoundingFrustum(LinearDepthEffect.View * LinearDepthEffect.Projection); foreach (var (model, transform) in FrustumCull(boundingFrustum, modelTransforms)) { foreach (var modelMesh in model.Meshes) { foreach (var meshPart in modelMesh.MeshParts) { GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer); GraphicsDevice.Indices = meshPart.IndexBuffer; LinearDepthEffect.Model = transform; foreach (var pass in LinearDepthEffect.CurrentTechnique.Passes) { pass.Apply(); GraphicsDevice.DrawIndexedPrimitives( PrimitiveType.TriangleList, 0, 0, meshPart.VertexBuffer.VertexCount, 0, meshPart.Triangles.Length ); } } } } } } private static IEnumerable<(Model, Matrix)> FrustumCull( BoundingFrustum boundingFrustum, IEnumerable<(Model, Matrix)> modelTransforms ) { foreach (var modelTransform in modelTransforms) { var boundingBox = TransformedBoundingBox(modelTransform.Item1.BoundingBox, modelTransform.Item2); var containment = boundingFrustum.Contains(boundingBox); if (containment != ContainmentType.Disjoint) { yield return modelTransform; } } } 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) ); } } }