Kav/Renderer.cs

1024 lines
41 KiB
C#

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<T>(
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<MeshSpriteDrawData> meshSpriteDrawDatas,
AmbientLight ambientLight,
IEnumerable<PointLight> pointLights,
DirectionalLight? directionalLight
) {
GraphicsDevice.SetRenderTarget(renderTarget);
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
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
);
}
}
}
public void RenderMeshSpriteGBuffer(
RenderTargetBinding[] gBuffer,
PerspectiveCamera camera,
IEnumerable<MeshSpriteDrawData> meshSpriteDrawDatas
) {
GraphicsDevice.SetRenderTargets(gBuffer);
GraphicsDevice.RasterizerState = RasterizerState.CullNone;
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
GraphicsDevice.BlendState = BlendState.AlphaBlend;
Deferred_GBufferEffect.HardwareInstancingEnabled = false;
Deferred_GBufferEffect.View = camera.View;
Deferred_GBufferEffect.Projection = camera.Projection;
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;
}
Deferred_GBufferEffect.World = matrix;
Deferred_GBufferEffect.UVOffset = data.UVOffset.Offset;
Deferred_GBufferEffect.SubTextureDimensions = data.UVOffset.Percentage;
Deferred_GBufferEffect.Albedo = Color.White.ToVector3();
Deferred_GBufferEffect.Metallic = 0f;
Deferred_GBufferEffect.Roughness = 1f;
Deferred_GBufferEffect.AlbedoTexture = data.Texture;
Deferred_GBufferEffect.NormalTexture = data.Normal;
Deferred_GBufferEffect.MetallicRoughnessTexture = null;
RenderIndexed(GraphicsDevice, data, Deferred_GBufferEffect);
}
}
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<T, U>(
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<T, U>(
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<T>(
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<T>(
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;
}
/// <summary>
/// GBuffer binding must have 4 render targets.
/// Assumes that vertex buffer has been filled.
/// </summary>
public void RenderGBufferInstanced<T>(
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<T>(
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<T>(
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<T>(
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<T>(
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<T>(
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<T>(
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<T>(
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<T>(
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<T>(
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 IEnumerable<T> FrustumCull<T>(
BoundingFrustum boundingFrustum,
IEnumerable<T> cullableTransformables
) where T : ICullable, ITransformable {
foreach (var cullableTransformable in cullableTransformables)
{
var boundingBox = TransformedBoundingBox(cullableTransformable.BoundingBox, cullableTransformable.TransformMatrix);
var containment = boundingFrustum.Contains(boundingBox);
if (containment != ContainmentType.Disjoint)
{
yield return cullableTransformable;
}
}
}
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)
);
}
}
}