cascaded shadow maps

pull/2/head
cosmonaut 2020-09-18 04:00:53 -07:00
parent 10f3d9cd98
commit a55e33f9a9
5 changed files with 243 additions and 102 deletions

View File

@ -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);
}
}
}

View File

@ -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"];
}
}
}

Binary file not shown.

View File

@ -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));

View File

@ -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<PointLight> 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<PointLight> pointLights,
IEnumerable<DirectionalLight> directionalLights