diff --git a/Effects/FXB/GBufferEffect.fxb b/Effects/FXB/GBufferEffect.fxb new file mode 100644 index 0000000..9b2d684 Binary files /dev/null and b/Effects/FXB/GBufferEffect.fxb differ diff --git a/Effects/GBufferEffect.cs b/Effects/GBufferEffect.cs new file mode 100644 index 0000000..ff8769d --- /dev/null +++ b/Effects/GBufferEffect.cs @@ -0,0 +1,241 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Kav +{ + public class GBufferEffect : Effect, TransformEffect + { + EffectParameter worldParam; + EffectParameter worldViewProjectionParam; + EffectParameter worldInverseTransposeParam; + + EffectParameter albedoTextureParam; + EffectParameter normalTextureParam; + EffectParameter metallicRoughnessTextureParam; + + EffectParameter albedoParam; + EffectParameter metallicParam; + EffectParameter roughnessParam; + + EffectParameter shaderIndexParam; + + Matrix world = Matrix.Identity; + Matrix view = Matrix.Identity; + Matrix projection = Matrix.Identity; + + Vector3 albedo; + float metallic; + float roughness; + + bool albedoTextureEnabled = false; + bool metallicRoughnessMapEnabled = false; + bool normalMapEnabled = false; + + EffectDirtyFlags dirtyFlags = EffectDirtyFlags.All; + + public Matrix World + { + get { return world; } + set + { + world = value; + dirtyFlags |= EffectDirtyFlags.World | EffectDirtyFlags.WorldViewProj; + } + } + + public Matrix View + { + get { return view; } + set + { + view = value; + dirtyFlags |= EffectDirtyFlags.WorldViewProj | EffectDirtyFlags.EyePosition; + } + } + + public Matrix Projection + { + get { return projection; } + set + { + projection = value; + dirtyFlags |= EffectDirtyFlags.WorldViewProj; + } + } + + public Vector3 Albedo + { + get { return albedo; } + set + { + albedo = value; + albedoParam.SetValue(albedo); + } + } + + public float Metallic + { + get { return metallic; } + set + { + metallic = value; + metallicParam.SetValue(metallic); + } + } + + public float Roughness + { + get { return roughness; } + set + { + roughness = value; + roughnessParam.SetValue(roughness); + } + } + + public Texture2D AlbedoTexture + { + get { return albedoTextureParam.GetValueTexture2D(); } + set + { + albedoTextureParam.SetValue(value); + albedoTextureEnabled = value != null; + dirtyFlags |= EffectDirtyFlags.ShaderIndex; + } + } + + public Texture2D NormalTexture + { + get { return normalTextureParam.GetValueTexture2D(); } + set + { + normalTextureParam.SetValue(value); + normalMapEnabled = value != null; + dirtyFlags |= EffectDirtyFlags.ShaderIndex; + } + } + + public Texture2D MetallicRoughnessTexture + { + get { return metallicRoughnessTextureParam.GetValueTexture2D(); } + set + { + metallicRoughnessTextureParam.SetValue(value); + metallicRoughnessMapEnabled = value != null; + dirtyFlags |= EffectDirtyFlags.ShaderIndex; + } + } + + public GBufferEffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.GBufferEffect) + { + CacheEffectParameters(); + } + + protected GBufferEffect(GBufferEffect cloneSource) : base(cloneSource) + { + CacheEffectParameters(); + + World = cloneSource.World; + View = cloneSource.View; + Projection = cloneSource.Projection; + + AlbedoTexture = cloneSource.AlbedoTexture; + NormalTexture = cloneSource.NormalTexture; + MetallicRoughnessTexture = cloneSource.MetallicRoughnessTexture; + + Albedo = cloneSource.Albedo; + Metallic = cloneSource.Metallic; + Roughness = cloneSource.Roughness; + } + + public override Effect Clone() + { + return new GBufferEffect(this); + } + + protected override void OnApply() + { + if ((dirtyFlags & EffectDirtyFlags.World) != 0) + { + worldParam.SetValue(world); + + Matrix.Invert(ref world, out Matrix worldInverse); + Matrix.Transpose(ref worldInverse, out Matrix worldInverseTranspose); + worldInverseTransposeParam.SetValue(worldInverseTranspose); + + dirtyFlags &= ~EffectDirtyFlags.World; + } + + if ((dirtyFlags & EffectDirtyFlags.WorldViewProj) != 0) + { + Matrix.Multiply(ref world, ref view, out Matrix worldView); + Matrix.Multiply(ref worldView, ref projection, out Matrix worldViewProj); + worldViewProjectionParam.SetValue(worldViewProj); + + dirtyFlags &= ~EffectDirtyFlags.WorldViewProj; + } + + if ((dirtyFlags & EffectDirtyFlags.EyePosition) != 0) + { + Matrix.Invert(ref view, out Matrix inverseView); + + dirtyFlags &= ~EffectDirtyFlags.EyePosition; + } + + if ((dirtyFlags & EffectDirtyFlags.ShaderIndex) != 0) + { + int shaderIndex = 0; + + if (albedoTextureEnabled && metallicRoughnessMapEnabled && normalMapEnabled) + { + shaderIndex = 7; + } + else if (metallicRoughnessMapEnabled && normalMapEnabled) + { + shaderIndex = 6; + } + else if (albedoTextureEnabled && normalMapEnabled) + { + shaderIndex = 5; + } + else if (albedoTextureEnabled && metallicRoughnessMapEnabled) + { + shaderIndex = 4; + } + else if (normalMapEnabled) + { + shaderIndex = 3; + } + else if (metallicRoughnessMapEnabled) + { + shaderIndex = 2; + } + else if (albedoTextureEnabled) + { + shaderIndex = 1; + } + + shaderIndexParam.SetValue(shaderIndex); + + dirtyFlags &= ~EffectDirtyFlags.ShaderIndex; + } + } + + void CacheEffectParameters() + { + worldParam = Parameters["World"]; + worldViewProjectionParam = Parameters["WorldViewProjection"]; + worldInverseTransposeParam = Parameters["WorldInverseTranspose"]; + + albedoTextureParam = Parameters["AlbedoTexture"]; + normalTextureParam = Parameters["NormalTexture"]; + metallicRoughnessTextureParam = Parameters["MetallicRoughnessTexture"]; + + albedoParam = Parameters["AlbedoValue"]; + metallicParam = Parameters["MetallicValue"]; + roughnessParam = Parameters["RoughnessValue"]; + + shaderIndexParam = Parameters["ShaderIndex"]; + } + } +} diff --git a/Effects/HLSL/GBufferEffect.fx b/Effects/HLSL/GBufferEffect.fx new file mode 100644 index 0000000..63cb2ce --- /dev/null +++ b/Effects/HLSL/GBufferEffect.fx @@ -0,0 +1,203 @@ +#include "Macros.fxh" + +DECLARE_TEXTURE(AlbedoTexture, 0); +DECLARE_TEXTURE(NormalTexture, 1); +DECLARE_TEXTURE(MetallicRoughnessTexture, 2); + +BEGIN_CONSTANTS + + float3 AlbedoValue _ps(c0) _cb(c0); + float MetallicValue _ps(c1) _cb(c1); + float RoughnessValue _ps(c2) _cb(c2); + +MATRIX_CONSTANTS + + float4x4 World _vs(c0) _cb(c7); + float4x4 WorldInverseTranspose _vs(c4) _cb(c11); + float4x4 WorldViewProjection _vs(c8) _cb(c15); + +END_CONSTANTS + +struct VertexInput +{ + float4 Position : POSITION; + float3 Normal : NORMAL; + float2 TexCoord : TEXCOORD0; +}; + +struct PixelInput +{ + float4 Position : SV_POSITION; + float3 PositionWorld : TEXCOORD0; + float3 NormalWorld : TEXCOORD1; + float2 TexCoord : TEXCOORD2; +}; + +struct PixelOutput +{ + float4 gPosition : COLOR0; + float4 gNormal : COLOR1; + float4 gAlbedo : COLOR2; + float4 gMetallicRoughness : COLOR3; +}; + +// Vertex Shader + +PixelInput main_vs(VertexInput input) +{ + PixelInput output; + + output.PositionWorld = mul(input.Position, World).xyz; + output.NormalWorld = mul(input.Normal, (float3x3)WorldInverseTranspose).xyz; + output.TexCoord = input.TexCoord; + output.Position = mul(input.Position, WorldViewProjection); + + return output; +} + +// Pixel Shaders + +// Easy trick to get tangent-normals to world-space to keep PBR code simplified. +float3 GetNormalFromMap(float3 worldPos, float2 texCoords, float3 normal) +{ + float3 tangentNormal = SAMPLE_TEXTURE(NormalTexture, texCoords).xyz * 2.0 - 1.0; + + float3 Q1 = ddx(worldPos); + float3 Q2 = ddy(worldPos); + float2 st1 = ddx(texCoords); + float2 st2 = ddy(texCoords); + + float3 N = normalize(normal); + float3 T = normalize(Q1*st2.y - Q2*st1.y); + float3 B = -normalize(cross(N, T)); + float3x3 TBN = float3x3(T, B, N); + + return normalize(mul(tangentNormal, TBN)); +} + +PixelOutput NonePS(PixelInput input) +{ + PixelOutput output; + + output.gPosition = float4(input.PositionWorld, 0.0); + output.gNormal = float4(normalize(input.NormalWorld), 0.0); + output.gAlbedo = float4(AlbedoValue, 1.0); + output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 0.0); + + return output; +} + +PixelOutput AlbedoPS(PixelInput input) +{ + PixelOutput output; + + output.gPosition = float4(input.PositionWorld, 0.0); + output.gNormal = float4(normalize(input.NormalWorld), 0.0); + output.gAlbedo = SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord); + output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 0.0); + + return output; +} + +PixelOutput MetallicRoughnessPS(PixelInput input) +{ + PixelOutput output; + + output.gPosition = float4(input.PositionWorld, 0.0); + output.gNormal = float4(normalize(input.NormalWorld), 0.0); + output.gAlbedo = float4(AlbedoValue, 1.0); + output.gMetallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord); + + return output; +} + +PixelOutput NormalPS(PixelInput input) +{ + PixelOutput output; + + output.gPosition = float4(input.PositionWorld, 0.0); + output.gNormal = float4(GetNormalFromMap(input.PositionWorld, input.TexCoord, input.NormalWorld), 0.0); + output.gAlbedo = float4(AlbedoValue, 1.0); + output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 0.0); + + return output; +} + +PixelOutput AlbedoMetallicRoughnessPS(PixelInput input) +{ + PixelOutput output; + + output.gPosition = float4(input.PositionWorld, 0.0); + output.gNormal = float4(normalize(input.NormalWorld), 0.0); + output.gAlbedo = SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord); + output.gMetallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord); + + return output; +} + +PixelOutput AlbedoNormalPS(PixelInput input) +{ + PixelOutput output; + + output.gPosition = float4(input.PositionWorld, 0.0); + output.gNormal = float4(GetNormalFromMap(input.PositionWorld, input.TexCoord, input.NormalWorld), 0.0); + output.gAlbedo = SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord); + output.gMetallicRoughness = float4(MetallicValue, RoughnessValue, 0.0, 0.0); + + return output; +} + +PixelOutput MetallicRoughnessNormalPS(PixelInput input) +{ + PixelOutput output; + + output.gPosition = float4(input.PositionWorld, 0.0); + output.gNormal = float4(GetNormalFromMap(input.PositionWorld, input.TexCoord, input.NormalWorld), 0.0); + output.gAlbedo = float4(AlbedoValue, 1.0); + output.gMetallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord); + + return output; +} + +PixelOutput AlbedoMetallicRoughnessNormalMapPS(PixelInput input) +{ + PixelOutput output; + + output.gPosition = float4(input.PositionWorld, 0.0); + output.gNormal = float4(GetNormalFromMap(input.PositionWorld, input.TexCoord, input.NormalWorld), 0.0); + output.gAlbedo = SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord); + output.gMetallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord); + + return output; +} + +PixelShader PSArray[8] = +{ + compile ps_3_0 NonePS(), + + compile ps_3_0 AlbedoPS(), + compile ps_3_0 MetallicRoughnessPS(), + compile ps_3_0 NormalPS(), + + compile ps_3_0 AlbedoMetallicRoughnessPS(), + compile ps_3_0 AlbedoNormalPS(), + compile ps_3_0 MetallicRoughnessNormalPS(), + + compile ps_3_0 AlbedoMetallicRoughnessNormalMapPS() +}; + +int PSIndices[8] = +{ + 0, 1, 2, 3, 4, 5, 6, 7 +}; + +int ShaderIndex = 0; + +Technique GBuffer +{ + Pass + { + VertexShader = compile vs_3_0 main_vs(); + PixelShader = (PSArray[PSIndices[ShaderIndex]]); + } +} diff --git a/Kav.Core.csproj b/Kav.Core.csproj index 812b812..e143f89 100644 --- a/Kav.Core.csproj +++ b/Kav.Core.csproj @@ -15,6 +15,9 @@ + + Kav.Resources.GBufferEffect.fxb + Kav.Resources.PBREffect.fxb diff --git a/Kav.Framework.csproj b/Kav.Framework.csproj index 592a8ff..3d70199 100644 --- a/Kav.Framework.csproj +++ b/Kav.Framework.csproj @@ -15,6 +15,9 @@ + + Kav.Resources.GBufferEffect.fxb + Kav.Resources.PBREffect.fxb diff --git a/Loaders/ModelLoader.cs b/Loaders/ModelLoader.cs index fc5b206..2655368 100644 --- a/Loaders/ModelLoader.cs +++ b/Loaders/ModelLoader.cs @@ -15,7 +15,7 @@ namespace Kav foreach (var meshPartData in meshData.MeshParts) { - var effect = new Kav.PBREffect( + var effect = new Kav.GBufferEffect( graphicsDevice ) { diff --git a/Renderer.cs b/Renderer.cs index 77037b3..f31419e 100644 --- a/Renderer.cs +++ b/Renderer.cs @@ -13,6 +13,17 @@ namespace Kav private RenderTarget2D DepthRenderTarget { get; } private SimpleDepthEffect SimpleDepthEffect { get; } + private RenderTarget2D gPosition { get; } + private RenderTarget2D gNormal { get; } + private RenderTarget2D gAlbedo { get; } + private RenderTarget2D gMetallicRoughness { get; } + + private RenderTargetBinding[] GBuffer { get; } + + private GBufferEffect GBufferEffect { get; } + + private SpriteBatch SpriteBatch { get; } + public Renderer(GraphicsDevice graphicsDevice, int renderDimensionsX, int renderDimensionsY) { GraphicsDevice = graphicsDevice; @@ -28,7 +39,104 @@ namespace Kav DepthFormat.Depth24 ); + gPosition = new RenderTarget2D( + GraphicsDevice, + renderDimensionsX, + renderDimensionsY, + false, + SurfaceFormat.Color, + DepthFormat.None + ); + + gNormal = new RenderTarget2D( + GraphicsDevice, + renderDimensionsX, + renderDimensionsY, + false, + SurfaceFormat.Color, + DepthFormat.None + ); + + gAlbedo = new RenderTarget2D( + GraphicsDevice, + renderDimensionsX, + renderDimensionsY, + false, + SurfaceFormat.Color, + DepthFormat.None + ); + + gMetallicRoughness = new RenderTarget2D( + GraphicsDevice, + renderDimensionsX, + renderDimensionsY, + false, + SurfaceFormat.Color, + DepthFormat.None + ); + + GBuffer = new RenderTargetBinding[4] { + new RenderTargetBinding(gPosition), + new RenderTargetBinding(gNormal), + new RenderTargetBinding(gAlbedo), + new RenderTargetBinding(gMetallicRoughness) + }; + SimpleDepthEffect = new SimpleDepthEffect(GraphicsDevice); + + SpriteBatch = new SpriteBatch(GraphicsDevice); + } + + public void DeferredRender( + Camera camera, + IEnumerable<(Model, Matrix)> modelTransforms, + IEnumerable pointLights, + IEnumerable directionalLights + ) { + GraphicsDevice.SetRenderTargets(GBuffer); + GraphicsDevice.Clear(Color.Black); + + 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.Black); + SpriteBatch.Begin(SpriteSortMode.Deferred, null, null, null, null); + SpriteBatch.Draw(gPosition, new Rectangle(0, 0, 640, 360), Color.White); + SpriteBatch.Draw(gAlbedo, new Rectangle(640, 0, 640, 360), Color.White); + SpriteBatch.Draw(gNormal, new Rectangle(0, 360, 640, 360), Color.White); + SpriteBatch.Draw(gMetallicRoughness, new Rectangle(640, 360, 640, 360), Color.White); + SpriteBatch.End(); } public void Render( diff --git a/Resources.cs b/Resources.cs index 6ff3806..293307d 100644 --- a/Resources.cs +++ b/Resources.cs @@ -4,6 +4,17 @@ namespace Kav { internal class Resources { + public static byte[] GBufferEffect + { + get + { + if (gBufferEffect == null) + { + gBufferEffect = GetResource("GBufferEffect"); + } + return gBufferEffect; + } + } public static byte[] PBREffect { get @@ -28,6 +39,7 @@ namespace Kav } } + private static byte[] gBufferEffect; private static byte[] pbrEffect; private static byte[] simpleDepthEffect;