diff --git a/Cameras/Camera.cs b/Cameras/Camera.cs index c0729ee..5462985 100644 --- a/Cameras/Camera.cs +++ b/Cameras/Camera.cs @@ -2,22 +2,32 @@ using Microsoft.Xna.Framework; namespace Kav { - public struct Camera + public struct PerspectiveCamera { - public Matrix Transform { get; } - public Matrix View - { - get - { - return Matrix.CreateLookAt(Transform.Translation, Transform.Translation + Transform.Forward, Transform.Up); - } - } + public Matrix View { get; } public Matrix Projection { get; } - public Camera(Matrix transform, Matrix projection) + public Vector3 Position { get; } + public Vector3 Forward { get; } + public Vector3 Up { get; } + + public float FieldOfView { get; } + public float AspectRatio { get; } + public float NearPlane { get; } + public float FarPlane { get; } + + public PerspectiveCamera(Vector3 position, Vector3 forward, Vector3 up, float fieldOfView, float aspectRatio, float nearPlane, float farPlane) { - Transform = transform; - Projection = projection; + Position = position; + Forward = forward; + Up = up; + View = Matrix.CreateLookAt(Position, Position + Forward, Up); + + FieldOfView = fieldOfView; + AspectRatio = aspectRatio; + NearPlane = nearPlane; + FarPlane = farPlane; + Projection = Matrix.CreatePerspectiveFieldOfView(FieldOfView, AspectRatio, NearPlane, FarPlane); } } } diff --git a/Effects/DeferredPBREffect.cs b/Effects/DeferredPBREffect.cs index 390f5f3..1ad21d4 100644 --- a/Effects/DeferredPBREffect.cs +++ b/Effects/DeferredPBREffect.cs @@ -9,13 +9,24 @@ namespace Kav EffectParameter gAlbedoParam; EffectParameter gNormalParam; EffectParameter gMetallicRoughnessParam; - EffectParameter shadowMapParam; + + EffectParameter shadowMapOneParam; + EffectParameter shadowMapTwoParam; + EffectParameter shadowMapThreeParam; + EffectParameter shadowMapFourParam; + + EffectParameter lightSpaceMatrixOneParam; + EffectParameter lightSpaceMatrixTwoParam; + EffectParameter lightSpaceMatrixThreeParam; + EffectParameter lightSpaceMatrixFourParam; + + EffectParameter viewMatrixParam; + EffectParameter cascadeFarPlanesParam; EffectParameter directionalLightColorParam; EffectParameter directionalLightDirectionParam; EffectParameter eyePositionParam; - EffectParameter lightSpaceMatrixParam; PointLightCollection pointLightCollection; @@ -23,11 +34,22 @@ namespace Kav public Texture2D GAlbedo { get; set; } public Texture2D GNormal { get; set; } public Texture2D GMetallicRoughness { get; set; } - public Texture2D ShadowMap { get; set; } + + public Texture2D ShadowMapOne { get; set; } + public Texture2D ShadowMapTwo { get; set; } + public Texture2D ShadowMapThree { get; set; } + public Texture2D ShadowMapFour { get; set; } + + public Matrix LightSpaceMatrixOne { get; set; } + public Matrix LightSpaceMatrixTwo { get; set; } + public Matrix LightSpaceMatrixThree { get; set; } + public Matrix LightSpaceMatrixFour { get; set; } + + public Matrix ViewMatrix { get; set; } + public float[] CascadeFarPlanes; public Vector3 DirectionalLightColor { get; set; } public Vector3 DirectionalLightDirection { get; set; } - public Matrix LightSpaceMatrix { get; set; } public Vector3 EyePosition { get; set; } @@ -41,6 +63,8 @@ namespace Kav public DeferredPBREffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.DeferredPBREffect) { + CascadeFarPlanes = new float[4]; + CacheEffectParameters(); pointLightCollection = new PointLightCollection( @@ -82,28 +106,50 @@ namespace Kav gAlbedoParam.SetValue(GAlbedo); gNormalParam.SetValue(GNormal); gMetallicRoughnessParam.SetValue(GMetallicRoughness); - shadowMapParam.SetValue(ShadowMap); + + shadowMapOneParam.SetValue(ShadowMapOne); + shadowMapTwoParam.SetValue(ShadowMapTwo); + shadowMapThreeParam.SetValue(ShadowMapThree); + shadowMapFourParam.SetValue(ShadowMapFour); + + lightSpaceMatrixOneParam.SetValue(LightSpaceMatrixOne); + lightSpaceMatrixTwoParam.SetValue(LightSpaceMatrixTwo); + lightSpaceMatrixThreeParam.SetValue(LightSpaceMatrixThree); + lightSpaceMatrixFourParam.SetValue(LightSpaceMatrixFour); + + viewMatrixParam.SetValue(ViewMatrix); + cascadeFarPlanesParam.SetValue(CascadeFarPlanes); directionalLightColorParam.SetValue(DirectionalLightColor); directionalLightDirectionParam.SetValue(DirectionalLightDirection); eyePositionParam.SetValue(EyePosition); - lightSpaceMatrixParam.SetValue(LightSpaceMatrix); } void CacheEffectParameters() { - gPositionParam = Parameters["gPosition"]; - gAlbedoParam = Parameters["gAlbedo"]; - gNormalParam = Parameters["gNormal"]; - gMetallicRoughnessParam = Parameters["gMetallicRoughness"]; - shadowMapParam = Parameters["shadowMap"]; + gPositionParam = Parameters["gPosition"]; + gAlbedoParam = Parameters["gAlbedo"]; + gNormalParam = Parameters["gNormal"]; + gMetallicRoughnessParam = Parameters["gMetallicRoughness"]; + + shadowMapOneParam = Parameters["shadowMapOne"]; + shadowMapTwoParam = Parameters["shadowMapTwo"]; + shadowMapThreeParam = Parameters["shadowMapThree"]; + shadowMapFourParam = Parameters["shadowMapFour"]; + + lightSpaceMatrixOneParam = Parameters["LightSpaceMatrixOne"]; + lightSpaceMatrixTwoParam = Parameters["LightSpaceMatrixTwo"]; + lightSpaceMatrixThreeParam = Parameters["LightSpaceMatrixThree"]; + lightSpaceMatrixFourParam = Parameters["LightSpaceMatrixFour"]; + + viewMatrixParam = Parameters["ViewMatrix"]; + cascadeFarPlanesParam = Parameters["CascadeFarPlanes"]; directionalLightDirectionParam = Parameters["DirectionalLightDirection"]; - directionalLightColorParam = Parameters["DirectionalLightColor"]; - lightSpaceMatrixParam = Parameters["LightSpaceMatrix"]; + directionalLightColorParam = Parameters["DirectionalLightColor"]; - eyePositionParam = Parameters["EyePosition"]; + eyePositionParam = Parameters["EyePosition"]; } } } diff --git a/Effects/FXB/DeferredPBREffect.fxb b/Effects/FXB/DeferredPBREffect.fxb index f06f5aa..cc460f8 100644 Binary files a/Effects/FXB/DeferredPBREffect.fxb and b/Effects/FXB/DeferredPBREffect.fxb differ diff --git a/Effects/HLSL/DeferredPBREffect.fx b/Effects/HLSL/DeferredPBREffect.fx index 2f9f3a1..0ce4d56 100644 --- a/Effects/HLSL/DeferredPBREffect.fx +++ b/Effects/HLSL/DeferredPBREffect.fx @@ -2,12 +2,16 @@ static const float PI = 3.141592653589793; static const int MAX_POINT_LIGHTS = 64; +static const int NUM_SHADOW_CASCADES = 4; DECLARE_TEXTURE(gPosition, 0); DECLARE_TEXTURE(gAlbedo, 1); DECLARE_TEXTURE(gNormal, 2); DECLARE_TEXTURE(gMetallicRoughness, 3); -DECLARE_TEXTURE(shadowMap, 4); +DECLARE_TEXTURE(shadowMapOne, 4); +DECLARE_TEXTURE(shadowMapTwo, 5); +DECLARE_TEXTURE(shadowMapThree, 6); +DECLARE_TEXTURE(shadowMapFour, 7); BEGIN_CONSTANTS @@ -19,9 +23,17 @@ BEGIN_CONSTANTS float3 DirectionalLightDirection _ps(c129) _cb(c129); float3 DirectionalLightColor _ps(c130) _cb(c130); + float CascadeFarPlanes[NUM_SHADOW_CASCADES] _ps(c131) _cb(c131); + MATRIX_CONSTANTS - float4x4 LightSpaceMatrix _ps(c131) _cb(c131); + float4x4 LightSpaceMatrixOne _ps(c135) _cb(c135); + float4x4 LightSpaceMatrixTwo _ps(c139) _cb(c139); + float4x4 LightSpaceMatrixThree _ps(c143) _cb(c143); + float4x4 LightSpaceMatrixFour _ps(c147) _cb(c147); + + // used to select shadow cascade + float4x4 ViewMatrix _ps(c151) _cb(c151); END_CONSTANTS @@ -89,11 +101,44 @@ float GeometrySmith(float3 N, float3 V, float3 L, float roughness) return ggx1 * ggx2; } -float ComputeShadow(float4 positionLightSpace, float3 N, float L) +float ComputeShadow(float3 positionWorldSpace, float3 N, float L) { float bias = 0.005 * tan(acos(dot(N, L))); bias = clamp(bias, 0, 0.01); + float4 positionCameraSpace = mul(float4(positionWorldSpace, 1.0), ViewMatrix); + + int shadowCascadeIndex = 0; // 0 is closest + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) + { + if (positionCameraSpace.z < CascadeFarPlanes[i]) + { + shadowCascadeIndex = i; + break; + } + } + + float4x4 lightSpaceMatrix; + + if (shadowCascadeIndex == 0) + { + lightSpaceMatrix = LightSpaceMatrixOne; + } + else if (shadowCascadeIndex == 1) + { + lightSpaceMatrix = LightSpaceMatrixTwo; + } + else if (shadowCascadeIndex == 2) + { + lightSpaceMatrix = LightSpaceMatrixThree; + } + else + { + lightSpaceMatrix = LightSpaceMatrixFour; + } + + float4 positionLightSpace = mul(float4(positionWorldSpace, 1.0), lightSpaceMatrix); + // maps to [-1, 1] float3 projectionCoords = positionLightSpace.xyz / positionLightSpace.w; @@ -102,7 +147,24 @@ float ComputeShadow(float4 positionLightSpace, float3 N, float L) projectionCoords.y = (projectionCoords.y * 0.5) + 0.5; projectionCoords.y *= -1; - float closestDepth = SAMPLE_TEXTURE(shadowMap, projectionCoords.xy).r; + float closestDepth; + if (shadowCascadeIndex == 0) + { + closestDepth = SAMPLE_TEXTURE(shadowMapOne, projectionCoords.xy).r; + } + else if (shadowCascadeIndex == 1) + { + closestDepth = SAMPLE_TEXTURE(shadowMapTwo, projectionCoords.xy).r; + } + else if (shadowCascadeIndex == 2) + { + closestDepth = SAMPLE_TEXTURE(shadowMapThree, projectionCoords.xy).r; + } + else + { + closestDepth = SAMPLE_TEXTURE(shadowMapFour, projectionCoords.xy).r; + } + float currentDepth = projectionCoords.z; if (currentDepth - bias > closestDepth) @@ -176,8 +238,7 @@ float4 ComputeColor( float3 L = normalize(DirectionalLightDirection); float3 radiance = DirectionalLightColor; - float4 positionLightSpace = mul(float4(worldPosition, 1.0), LightSpaceMatrix); - float shadow = ComputeShadow(positionLightSpace, N, L); + float shadow = ComputeShadow(worldPosition, N, L); Lo += ComputeLight(L, radiance, F0, V, N, albedo, metallic, roughness, (1.0 - shadow)); diff --git a/Renderer.cs b/Renderer.cs index 210aed8..8d05f96 100644 --- a/Renderer.cs +++ b/Renderer.cs @@ -6,12 +6,15 @@ 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 RenderTarget2D ShadowRenderTarget { get; } + private int NumShadowCascades { get; } + private RenderTarget2D[] ShadowRenderTargets { get; } private DeferredPBREffect DeferredPBREffect { get; } private SimpleDepthEffect SimpleDepthEffect { get; } @@ -24,22 +27,26 @@ namespace Kav private RenderTargetBinding[] GBuffer { get; } - private SpriteBatch SpriteBatch { get; } - - public Renderer(GraphicsDevice graphicsDevice, int renderDimensionsX, int renderDimensionsY) + public Renderer(GraphicsDevice graphicsDevice, int renderDimensionsX, int renderDimensionsY, int numShadowCascades) { GraphicsDevice = graphicsDevice; RenderDimensionsX = renderDimensionsX; RenderDimensionsY = renderDimensionsY; - ShadowRenderTarget = new RenderTarget2D( - GraphicsDevice, - 1024, - 1024, - false, - SurfaceFormat.Single, - DepthFormat.Depth24 - ); + 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, @@ -100,21 +107,18 @@ namespace Kav new VertexPositionTexture(new Vector3(3, -1, 0), new Vector2(2, 0)) }); - SpriteBatch = new SpriteBatch(GraphicsDevice); - GraphicsDevice.SetRenderTarget(deferredRenderTarget); graphicsDevice.Clear(Color.White); GraphicsDevice.SetRenderTarget(null); } public void DeferredRender( - Camera camera, + PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, IEnumerable pointLights, - DirectionalLight directionalLight, - int numCascades + DirectionalLight directionalLight ) { - ShadowMapRender(camera, modelTransforms, directionalLight, numCascades); + ShadowMapRender(camera, modelTransforms, directionalLight); GraphicsDevice.SetRenderTargets(GBuffer); GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0); @@ -161,7 +165,23 @@ namespace Kav DeferredPBREffect.GAlbedo = gAlbedo; DeferredPBREffect.GNormal = gNormal; DeferredPBREffect.GMetallicRoughness = gMetallicRoughness; - DeferredPBREffect.ShadowMap = ShadowRenderTarget; + + 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; @@ -182,23 +202,15 @@ namespace Kav GraphicsDevice.SetVertexBuffer(FullscreenTriangle); GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); } - - // SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, null, null, null, DeferredPBREffect); - // SpriteBatch.Draw(deferredRenderTarget, Vector2.Zero, Color.White); - // SpriteBatch.End(); } public void ShadowMapRender( - Camera camera, + PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, - DirectionalLight directionalLight, - int numCascades + DirectionalLight directionalLight ) { - GraphicsDevice.SetRenderTarget(ShadowRenderTarget); - GraphicsDevice.Clear(Color.White); - GraphicsDevice.DepthStencilState = DepthStencilState.Default; - GraphicsDevice.BlendState = BlendState.Opaque; - + // 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); @@ -221,48 +233,39 @@ namespace Kav 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 - ); - DeferredPBREffect.LightSpaceMatrix = SimpleDepthEffect.View * SimpleDepthEffect.Projection; - - foreach (var (model, transform) in modelTransforms) + // render the individual shadow maps + + var frustumDistance = camera.FarPlane - camera.NearPlane; + var sectionDistance = frustumDistance / NumShadowCascades; + + for (var i = 0; i < NumShadowCascades; i++) { - 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 - ); - } - } - } + // 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(Camera camera, IEnumerable<(Model, Matrix)> modelTransforms, DirectionalLight directionalLight) - { + 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); @@ -295,6 +298,27 @@ namespace Kav 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) @@ -325,7 +349,7 @@ namespace Kav } public void Render( - Camera camera, + PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, IEnumerable pointLights, IEnumerable directionalLights