Kav/Renderer.cs

413 lines
16 KiB
C#
Raw Normal View History

2020-08-04 09:32:02 +00:00
using System.Collections.Generic;
2020-08-07 00:58:50 +00:00
using Microsoft.Xna.Framework;
2020-08-04 09:32:02 +00:00
using Microsoft.Xna.Framework.Graphics;
namespace Kav
{
2020-08-07 00:58:50 +00:00
public class Renderer
2020-08-04 09:32:02 +00:00
{
2020-09-18 11:00:53 +00:00
private const int MAX_SHADOW_CASCADES = 4;
2020-08-07 00:58:50 +00:00
private GraphicsDevice GraphicsDevice { get; }
private int RenderDimensionsX { get; }
private int RenderDimensionsY { get; }
2020-09-17 22:47:23 +00:00
private VertexBuffer FullscreenTriangle { get; }
2020-09-18 11:00:53 +00:00
private int NumShadowCascades { get; }
private RenderTarget2D[] ShadowRenderTargets { get; }
2020-09-17 22:47:23 +00:00
private DeferredPBREffect DeferredPBREffect { get; }
2020-08-07 00:58:50 +00:00
private SimpleDepthEffect SimpleDepthEffect { get; }
private RenderTarget2D gPosition { get; }
private RenderTarget2D gNormal { get; }
private RenderTarget2D gAlbedo { get; }
private RenderTarget2D gMetallicRoughness { get; }
private RenderTarget2D deferredRenderTarget { get; }
private RenderTargetBinding[] GBuffer { get; }
2020-09-18 11:00:53 +00:00
public Renderer(GraphicsDevice graphicsDevice, int renderDimensionsX, int renderDimensionsY, int numShadowCascades)
2020-08-07 00:58:50 +00:00
{
GraphicsDevice = graphicsDevice;
RenderDimensionsX = renderDimensionsX;
RenderDimensionsY = renderDimensionsY;
2020-09-18 11:00:53 +00:00
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
);
}
2020-08-07 00:58:50 +00:00
gPosition = new RenderTarget2D(
GraphicsDevice,
renderDimensionsX,
renderDimensionsY,
false,
SurfaceFormat.Vector4,
DepthFormat.Depth24
);
gNormal = new RenderTarget2D(
GraphicsDevice,
renderDimensionsX,
renderDimensionsY,
false,
SurfaceFormat.Vector4,
DepthFormat.None
);
gAlbedo = new RenderTarget2D(
GraphicsDevice,
renderDimensionsX,
renderDimensionsY,
false,
SurfaceFormat.Color,
DepthFormat.None
);
gMetallicRoughness = new RenderTarget2D(
GraphicsDevice,
renderDimensionsX,
renderDimensionsY,
false,
SurfaceFormat.HalfVector2,
DepthFormat.None
);
GBuffer = new RenderTargetBinding[4] {
new RenderTargetBinding(gPosition),
new RenderTargetBinding(gNormal),
new RenderTargetBinding(gAlbedo),
new RenderTargetBinding(gMetallicRoughness)
};
deferredRenderTarget = new RenderTarget2D(
GraphicsDevice,
renderDimensionsX,
renderDimensionsY
);
2020-08-07 00:58:50 +00:00
SimpleDepthEffect = new SimpleDepthEffect(GraphicsDevice);
DeferredPBREffect = new DeferredPBREffect(GraphicsDevice);
2020-09-17 22:47:23 +00:00
FullscreenTriangle = new VertexBuffer(GraphicsDevice, typeof(VertexPositionTexture), 3, BufferUsage.WriteOnly);
FullscreenTriangle.SetData(new VertexPositionTexture[3] {
new VertexPositionTexture(new Vector3(-1, -1, 0), new Vector2(0, 0)),
new VertexPositionTexture(new Vector3(-1, 3, 0), new Vector2(0, -2)),
new VertexPositionTexture(new Vector3(3, -1, 0), new Vector2(2, 0))
});
GraphicsDevice.SetRenderTarget(deferredRenderTarget);
graphicsDevice.Clear(Color.White);
GraphicsDevice.SetRenderTarget(null);
}
public void DeferredRender(
2020-09-18 11:00:53 +00:00
PerspectiveCamera camera,
IEnumerable<(Model, Matrix)> modelTransforms,
IEnumerable<PointLight> pointLights,
2020-09-18 11:00:53 +00:00
DirectionalLight directionalLight
) {
2020-09-18 11:00:53 +00:00
ShadowMapRender(camera, modelTransforms, directionalLight);
2020-08-27 21:46:20 +00:00
GraphicsDevice.SetRenderTargets(GBuffer);
GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0);
GraphicsDevice.DepthStencilState = DepthStencilState.Default;
GraphicsDevice.BlendState = BlendState.Opaque;
foreach (var (model, transform) in modelTransforms)
{
foreach (var modelMesh in model.Meshes)
{
foreach (var meshPart in modelMesh.MeshParts)
{
GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer);
GraphicsDevice.Indices = meshPart.IndexBuffer;
if (meshPart.Effect is TransformEffect transformEffect)
{
transformEffect.World = transform;
transformEffect.View = camera.View;
transformEffect.Projection = camera.Projection;
}
foreach (var pass in meshPart.Effect.CurrentTechnique.Passes)
{
pass.Apply();
GraphicsDevice.DrawIndexedPrimitives(
PrimitiveType.TriangleList,
0,
0,
meshPart.VertexBuffer.VertexCount,
0,
meshPart.Triangles.Length
);
}
}
}
}
GraphicsDevice.SetRenderTarget(null);
GraphicsDevice.Clear(Color.CornflowerBlue);
DeferredPBREffect.GPosition = gPosition;
DeferredPBREffect.GAlbedo = gAlbedo;
DeferredPBREffect.GNormal = gNormal;
DeferredPBREffect.GMetallicRoughness = gMetallicRoughness;
2020-09-18 11:00:53 +00:00
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;
foreach (var pointLight in pointLights)
{
if (i > DeferredPBREffect.MaxPointLights) { break; }
DeferredPBREffect.PointLights[i] = pointLight;
i++;
}
2020-09-16 19:58:23 +00:00
DeferredPBREffect.DirectionalLightColor = directionalLight.Color.ToVector3() * directionalLight.Intensity;
DeferredPBREffect.DirectionalLightDirection = directionalLight.Direction;
2020-09-17 22:47:23 +00:00
foreach (EffectPass pass in DeferredPBREffect.CurrentTechnique.Passes)
{
pass.Apply();
GraphicsDevice.SetVertexBuffer(FullscreenTriangle);
GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1);
}
2020-08-07 00:58:50 +00:00
}
2020-09-17 22:47:23 +00:00
public void ShadowMapRender(
2020-09-18 11:00:53 +00:00
PerspectiveCamera camera,
2020-09-17 22:47:23 +00:00
IEnumerable<(Model, Matrix)> modelTransforms,
2020-09-18 11:00:53 +00:00
DirectionalLight directionalLight
2020-09-17 22:47:23 +00:00
) {
2020-09-18 11:00:53 +00:00
// set up global light matrix
2020-09-16 19:58:23 +00:00
var right = Vector3.Cross(Vector3.Up, directionalLight.Direction);
var up = Vector3.Cross(directionalLight.Direction, right);
var cameraBoundingFrustum = new BoundingFrustum(camera.View * camera.Projection);
Vector3[] frustumCorners = cameraBoundingFrustum.GetCorners();
2020-09-16 19:58:23 +00:00
Vector3 frustumCenter = Vector3.Zero;
for (var i = 0; i < frustumCorners.Length; i++)
{
frustumCenter += frustumCorners[i];
}
frustumCenter /= 8f;
2020-09-16 19:58:23 +00:00
var lightView = Matrix.CreateLookAt(frustumCenter, frustumCenter - directionalLight.Direction, camera.View.Right);
2020-09-16 19:58:23 +00:00
for (var i = 0; i < frustumCorners.Length; i++)
{
frustumCorners[i] = Vector3.Transform(frustumCorners[i], lightView);
2020-09-16 19:58:23 +00:00
}
BoundingBox lightBox = BoundingBox.CreateFromPoints(frustumCorners);
Vector3 lightPosition = frustumCenter + directionalLight.Direction * -lightBox.Min.Z;
2020-09-18 11:00:53 +00:00
// render the individual shadow maps
2020-09-17 22:47:23 +00:00
2020-09-18 11:00:53 +00:00
var frustumDistance = camera.FarPlane - camera.NearPlane;
var sectionDistance = frustumDistance / NumShadowCascades;
2020-09-17 22:47:23 +00:00
2020-09-18 11:00:53 +00:00
for (var i = 0; i < NumShadowCascades; i++)
{
// 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);
2020-09-17 22:47:23 +00:00
}
}
2020-09-18 11:00:53 +00:00
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;
2020-09-17 22:47:23 +00:00
var right = Vector3.Cross(Vector3.Up, directionalLight.Direction);
var up = Vector3.Cross(directionalLight.Direction, right);
var cameraBoundingFrustum = new BoundingFrustum(camera.View * camera.Projection);
Vector3[] frustumCorners = cameraBoundingFrustum.GetCorners();
Vector3 frustumCenter = Vector3.Zero;
for (var i = 0; i < frustumCorners.Length; i++)
{
frustumCenter += frustumCorners[i];
}
frustumCenter /= 8f;
var lightView = Matrix.CreateLookAt(frustumCenter, frustumCenter - directionalLight.Direction, camera.View.Right);
for (var i = 0; i < frustumCorners.Length; i++)
{
frustumCorners[i] = Vector3.Transform(frustumCorners[i], lightView);
}
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
);
2020-09-18 11:00:53 +00:00
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;
2020-08-07 00:58:50 +00:00
foreach (var (model, transform) in modelTransforms)
{
foreach (var modelMesh in model.Meshes)
{
foreach (var meshPart in modelMesh.MeshParts)
{
GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer);
GraphicsDevice.Indices = meshPart.IndexBuffer;
SimpleDepthEffect.Model = transform;
2020-08-04 09:32:02 +00:00
2020-08-07 00:58:50 +00:00
foreach (var pass in SimpleDepthEffect.CurrentTechnique.Passes)
{
pass.Apply();
GraphicsDevice.DrawIndexedPrimitives(
PrimitiveType.TriangleList,
0,
0,
meshPart.VertexBuffer.VertexCount,
0,
meshPart.Triangles.Length
);
}
}
}
}
}
2020-08-27 21:46:20 +00:00
public void Render(
2020-09-18 11:00:53 +00:00
PerspectiveCamera camera,
2020-08-27 21:46:20 +00:00
IEnumerable<(Model, Matrix)> modelTransforms,
IEnumerable<PointLight> pointLights,
IEnumerable<DirectionalLight> directionalLights
) {
Render(camera.View, camera.Projection, modelTransforms, pointLights, directionalLights);
}
2020-08-07 08:12:46 +00:00
private void Render(
Matrix view,
Matrix projection,
IEnumerable<(Model, Matrix)> modelTransforms,
IEnumerable<PointLight> pointLights,
IEnumerable<DirectionalLight> directionalLights
) {
foreach (var (model, transform) in modelTransforms)
2020-08-04 09:32:02 +00:00
{
foreach (var modelMesh in model.Meshes)
{
foreach (var meshPart in modelMesh.MeshParts)
{
2020-08-07 00:58:50 +00:00
GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer);
GraphicsDevice.Indices = meshPart.IndexBuffer;
2020-08-04 09:32:02 +00:00
if (meshPart.Effect is TransformEffect transformEffect)
{
2020-08-07 08:12:46 +00:00
transformEffect.World = transform;
2020-08-04 09:32:02 +00:00
transformEffect.View = view;
transformEffect.Projection = projection;
}
if (meshPart.Effect is PointLightEffect pointLightEffect)
{
2020-08-05 03:50:44 +00:00
int i = 0;
foreach (var pointLight in pointLights)
2020-08-04 09:32:02 +00:00
{
if (i > pointLightEffect.MaxPointLights) { break; }
2020-08-05 03:50:44 +00:00
pointLightEffect.PointLights[i] = pointLight;
i++;
2020-08-04 09:32:02 +00:00
}
}
foreach (var pass in meshPart.Effect.CurrentTechnique.Passes)
{
pass.Apply();
2020-08-07 00:58:50 +00:00
GraphicsDevice.DrawIndexedPrimitives(
2020-08-04 09:32:02 +00:00
PrimitiveType.TriangleList,
0,
0,
meshPart.VertexBuffer.VertexCount,
0,
meshPart.Triangles.Length
);
}
}
}
}
}
}
}