967 lines
39 KiB
C#
967 lines
39 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.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.MeshSprite.Normal != null;
|
|
|
|
DiffuseLitSpriteEffect.World = matrix;
|
|
DiffuseLitSpriteEffect.UVOffset = data.UVOffset.Offset;
|
|
DiffuseLitSpriteEffect.SubTextureDimensions = data.UVOffset.Percentage;
|
|
|
|
GraphicsDevice.Textures[0] = data.MeshSprite.Texture;
|
|
GraphicsDevice.Textures[1] = data.MeshSprite.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<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 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)
|
|
);
|
|
}
|
|
}
|
|
}
|