diff --git a/EffectInterfaces/ShadowCascadeEffect.cs b/EffectInterfaces/ShadowCascadeEffect.cs new file mode 100644 index 0000000..e55ba70 --- /dev/null +++ b/EffectInterfaces/ShadowCascadeEffect.cs @@ -0,0 +1,14 @@ +using Microsoft.Xna.Framework; + +namespace Kav +{ + public interface ShadowCascadeEffect + { + Matrix LightSpaceMatrixOne { get; set; } + Matrix LightSpaceMatrixTwo { get; set; } + Matrix LightSpaceMatrixThree { get; set; } + Matrix LightSpaceMatrixFour { get; set; } + + float[] CascadeFarPlanes { get; } + } +} diff --git a/Effects/DeferredPBR_AmbientLightEffect.cs b/Effects/DeferredPBR_AmbientLightEffect.cs index f1d5e31..8595426 100644 --- a/Effects/DeferredPBR_AmbientLightEffect.cs +++ b/Effects/DeferredPBR_AmbientLightEffect.cs @@ -1,3 +1,4 @@ +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; namespace Kav @@ -8,9 +9,13 @@ namespace Kav EffectParameter gPositionParam; EffectParameter gAlbedoParam; + EffectParameter ambientColorParam; + public Texture2D GPosition { get; set; } public Texture2D GAlbedo { get; set; } + public Vector3 AmbientColor { get; set; } + public DeferredPBR_AmbientLightEffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.DeferredPBR_AmbientLightEffect) { CacheEffectParameters(); @@ -20,12 +25,14 @@ namespace Kav { gPositionParam.SetValue(GPosition); gAlbedoParam.SetValue(GAlbedo); + ambientColorParam.SetValue(AmbientColor); } void CacheEffectParameters() { - gPositionParam = Parameters["gPosition"]; - gAlbedoParam = Parameters["gAlbedo"]; + gPositionParam = Parameters["gPosition"]; + gAlbedoParam = Parameters["gAlbedo"]; + ambientColorParam = Parameters["AmbientLightColor"]; } } } diff --git a/Effects/DeferredPBR_DirectionalLightEffect.cs b/Effects/DeferredPBR_DirectionalLightEffect.cs index 5e1f919..a30c1fe 100644 --- a/Effects/DeferredPBR_DirectionalLightEffect.cs +++ b/Effects/DeferredPBR_DirectionalLightEffect.cs @@ -3,7 +3,7 @@ using Microsoft.Xna.Framework.Graphics; namespace Kav { - public class DeferredPBR_DirectionalLightEffect : Effect + public class DeferredPBR_DirectionalLightEffect : Effect, ShadowCascadeEffect { EffectParameter gPositionParam; EffectParameter gAlbedoParam; @@ -46,7 +46,7 @@ namespace Kav public Vector3 DirectionalLightDirection { get; set; } public Vector3 DirectionalLightColor { get; set; } - public readonly float[] CascadeFarPlanes; + public float[] CascadeFarPlanes { get; } public int ShadowMapSize { get; set; } @@ -147,7 +147,6 @@ namespace Kav directionalLightColorParam = Parameters["DirectionalLightColor"]; cascadeFarPlanesParam = Parameters["CascadeFarPlanes"]; - shadowMapSizeParam = Parameters["ShadowMapSize"]; lightSpaceMatrixOneParam = Parameters["LightSpaceMatrixOne"]; diff --git a/Effects/DeferredPBR_PointLightEffect.cs b/Effects/DeferredPBR_PointLightEffect.cs index 6f959ea..82a73ea 100644 --- a/Effects/DeferredPBR_PointLightEffect.cs +++ b/Effects/DeferredPBR_PointLightEffect.cs @@ -9,22 +9,28 @@ namespace Kav EffectParameter gAlbedoParam; EffectParameter gNormalParam; EffectParameter gMetallicRoughnessParam; + EffectParameter shadowMapParam; EffectParameter eyePositionParam; EffectParameter pointLightColorParam; EffectParameter pointLightPositionParam; + EffectParameter farPlaneParam; + public Texture2D GPosition { get; set; } public Texture2D GAlbedo { get; set; } public Texture2D GNormal { get; set; } public Texture2D GMetallicRoughness { get; set; } + public TextureCube ShadowMap { get; set; } public Vector3 EyePosition { get; set; } public Vector3 PointLightPosition { get; set; } public Vector3 PointLightColor { get; set; } + public float FarPlane { get; set; } + public DeferredPBR_PointLightEffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.DeferredPBR_PointLightEffect) { CacheEffectParameters(); @@ -32,15 +38,20 @@ namespace Kav public DeferredPBR_PointLightEffect(DeferredPBR_PointLightEffect cloneSource) : base(cloneSource) { + CacheEffectParameters(); + GPosition = cloneSource.GPosition; GAlbedo = cloneSource.GAlbedo; GNormal = cloneSource.GNormal; GMetallicRoughness = cloneSource.GMetallicRoughness; + ShadowMap = cloneSource.ShadowMap; EyePosition = cloneSource.EyePosition; PointLightPosition = cloneSource.PointLightPosition; PointLightColor = cloneSource.PointLightColor; + + FarPlane = cloneSource.FarPlane; } public override Effect Clone() @@ -54,11 +65,14 @@ namespace Kav gAlbedoParam.SetValue(GAlbedo); gNormalParam.SetValue(GNormal); gMetallicRoughnessParam.SetValue(GMetallicRoughness); + shadowMapParam.SetValue(ShadowMap); eyePositionParam.SetValue(EyePosition); pointLightPositionParam.SetValue(PointLightPosition); pointLightColorParam.SetValue(PointLightColor); + + farPlaneParam.SetValue(FarPlane); } void CacheEffectParameters() @@ -67,11 +81,14 @@ namespace Kav gAlbedoParam = Parameters["gAlbedo"]; gNormalParam = Parameters["gNormal"]; gMetallicRoughnessParam = Parameters["gMetallicRoughness"]; + shadowMapParam = Parameters["shadowMap"]; eyePositionParam = Parameters["EyePosition"]; pointLightPositionParam = Parameters["PointLightPosition"]; pointLightColorParam = Parameters["PointLightColor"]; + + farPlaneParam = Parameters["FarPlane"]; } } } diff --git a/Effects/Deferred_ToonEffect.cs b/Effects/Deferred_ToonEffect.cs new file mode 100644 index 0000000..031f8e7 --- /dev/null +++ b/Effects/Deferred_ToonEffect.cs @@ -0,0 +1,147 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Kav +{ + public class Deferred_ToonEffect : Effect, ShadowCascadeEffect + { + EffectParameter gPositionParam; + EffectParameter gAlbedoParam; + EffectParameter gNormalParam; + EffectParameter gMetallicRoughnessParam; + + EffectParameter shadowMapOneParam; + EffectParameter shadowMapTwoParam; + EffectParameter shadowMapThreeParam; + EffectParameter shadowMapFourParam; + + EffectParameter eyePositionParam; + EffectParameter directionalLightDirectionParam; + EffectParameter directionalLightColorParam; + + EffectParameter cascadeFarPlanesParam; + EffectParameter shadowMapSizeParam; + + EffectParameter lightSpaceMatrixOneParam; + EffectParameter lightSpaceMatrixTwoParam; + EffectParameter lightSpaceMatrixThreeParam; + EffectParameter lightSpaceMatrixFourParam; + + EffectParameter viewMatrixParam; + + EffectParameter shaderIndexParam; + + public Texture2D GPosition { get; set; } + public Texture2D GAlbedo { get; set; } + public Texture2D GNormal { get; set; } + public Texture2D GMetallicRoughness { get; set; } + + public Texture2D ShadowMapOne { get; set; } + public Texture2D ShadowMapTwo { get; set; } + public Texture2D ShadowMapThree { get; set; } + public Texture2D ShadowMapFour { get; set; } + + public Vector3 EyePosition { get; set; } + public Vector3 DirectionalLightDirection { get; set; } + public Vector3 DirectionalLightColor { get; set; } + + public float[] CascadeFarPlanes { get; } + public float ShadowMapSize { 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; } + + private bool ditheredShadowValue = false; + public bool DitheredShadows + { + get { return ditheredShadowValue; } + set + { + ditheredShadowValue = value; + CalculateShaderIndex(); + } + } + + private int shaderIndex = 0; + + public Deferred_ToonEffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.Deferred_ToonEffect) + { + CascadeFarPlanes = new float[4]; + CacheEffectParameters(); + } + + protected override void OnApply() + { + gPositionParam.SetValue(GPosition); + gAlbedoParam.SetValue(GAlbedo); + gNormalParam.SetValue(GNormal); + gMetallicRoughnessParam.SetValue(GMetallicRoughness); + + shadowMapOneParam.SetValue(ShadowMapOne); + shadowMapTwoParam.SetValue(ShadowMapTwo); + shadowMapThreeParam.SetValue(ShadowMapThree); + shadowMapFourParam.SetValue(ShadowMapFour); + + eyePositionParam.SetValue(EyePosition); + directionalLightDirectionParam.SetValue(DirectionalLightDirection); + directionalLightColorParam.SetValue(DirectionalLightColor); + + cascadeFarPlanesParam.SetValue(CascadeFarPlanes); + shadowMapSizeParam.SetValue(ShadowMapSize); + + lightSpaceMatrixOneParam.SetValue(LightSpaceMatrixOne); + lightSpaceMatrixTwoParam.SetValue(LightSpaceMatrixTwo); + lightSpaceMatrixThreeParam.SetValue(LightSpaceMatrixThree); + lightSpaceMatrixFourParam.SetValue(LightSpaceMatrixFour); + + viewMatrixParam.SetValue(ViewMatrix); + + shaderIndexParam.SetValue(shaderIndex); + } + + void CacheEffectParameters() + { + gPositionParam = Parameters["gPosition"]; + gAlbedoParam = Parameters["gAlbedo"]; + gNormalParam = Parameters["gNormal"]; + gMetallicRoughnessParam = Parameters["gMetallicRoughness"]; + + shadowMapOneParam = Parameters["shadowMapOne"]; + shadowMapTwoParam = Parameters["shadowMapTwo"]; + shadowMapThreeParam = Parameters["shadowMapThree"]; + shadowMapFourParam = Parameters["shadowMapFour"]; + + eyePositionParam = Parameters["EyePosition"]; + directionalLightDirectionParam = Parameters["DirectionalLightDirection"]; + directionalLightColorParam = Parameters["DirectionalLightColor"]; + + cascadeFarPlanesParam = Parameters["CascadeFarPlanes"]; + shadowMapSizeParam = Parameters["ShadowMapSize"]; + + lightSpaceMatrixOneParam = Parameters["LightSpaceMatrixOne"]; + lightSpaceMatrixTwoParam = Parameters["LightSpaceMatrixTwo"]; + lightSpaceMatrixThreeParam = Parameters["LightSpaceMatrixThree"]; + lightSpaceMatrixFourParam = Parameters["LightSpaceMatrixFour"]; + + viewMatrixParam = Parameters["ViewMatrix"]; + + shaderIndexParam = Parameters["ShaderIndex"]; + } + + private void CalculateShaderIndex() + { + if (ditheredShadowValue) + { + shaderIndex = 1; + } + else + { + shaderIndex = 0; + } + } + } +} diff --git a/Effects/FXB/DeferredPBR_AmbientLightEffect.fxb b/Effects/FXB/DeferredPBR_AmbientLightEffect.fxb index 62de278..a439c6a 100644 --- a/Effects/FXB/DeferredPBR_AmbientLightEffect.fxb +++ b/Effects/FXB/DeferredPBR_AmbientLightEffect.fxb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0ea0cb071a1e53fec5058fae9919d376e408d33cd3de9485d42e1ebcbee85546 -size 1016 +oid sha256:244ded9c443bc10e538958075cabd14d2b066b9fd0743186ce620823380cb488 +size 1168 diff --git a/Effects/FXB/DeferredPBR_DirectionalLightEffect.fxb b/Effects/FXB/DeferredPBR_DirectionalLightEffect.fxb index 9f6cf27..1ec8b6e 100644 --- a/Effects/FXB/DeferredPBR_DirectionalLightEffect.fxb +++ b/Effects/FXB/DeferredPBR_DirectionalLightEffect.fxb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be44f16057328acf2bf9d2e4ff1e2d448df720711a2900daa39f5fc8c8732711 -size 21692 +oid sha256:3eb5c7dfc3e166c9baf57be77405a644fdf390aa8deb4bb7d39e044b7b3d338b +size 21776 diff --git a/Effects/FXB/DeferredPBR_PointLightEffect.fxb b/Effects/FXB/DeferredPBR_PointLightEffect.fxb index 398f66b..9511784 100644 --- a/Effects/FXB/DeferredPBR_PointLightEffect.fxb +++ b/Effects/FXB/DeferredPBR_PointLightEffect.fxb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e51395e0f0dd1e1b86272dde1e4fb5aea1fdf9b86399416015ad33cfb7260691 -size 3108 +oid sha256:033598dc2f22c2766a8b0d46215e5a9764b5445a520d67bc68f1566dbdd15035 +size 3740 diff --git a/Effects/FXB/Deferred_ToonEffect.fxb b/Effects/FXB/Deferred_ToonEffect.fxb new file mode 100644 index 0000000..529d086 --- /dev/null +++ b/Effects/FXB/Deferred_ToonEffect.fxb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:78552dce830d09b83694863355e51d4b2eaa31fafb11a16a207f8b42a6d004e5 +size 10964 diff --git a/Effects/FXB/LinearDepthEffect.fxb b/Effects/FXB/LinearDepthEffect.fxb new file mode 100644 index 0000000..c92681f --- /dev/null +++ b/Effects/FXB/LinearDepthEffect.fxb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2e91546d1db9b0bb2eb93ab2dcdeaaa260c5eb08d9acf6460c091f8fdf4c88ee +size 1344 diff --git a/Effects/FXB/SimpleDepthEffect.fxb b/Effects/FXB/SimpleDepthEffect.fxb index c60bf15..8281157 100644 --- a/Effects/FXB/SimpleDepthEffect.fxb +++ b/Effects/FXB/SimpleDepthEffect.fxb @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b7e52830f9c3e89d230f12b98bc42cbc3af7a488c584c9ff48cd571115d57d85 -size 832 +oid sha256:e0984ae92245afe3e2bda5fab0135293b5121c3ee7b5f146e7d4ddd1684ee74b +size 848 diff --git a/Effects/FXB/SkyboxEffect.fxb b/Effects/FXB/SkyboxEffect.fxb new file mode 100644 index 0000000..10d5b22 --- /dev/null +++ b/Effects/FXB/SkyboxEffect.fxb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:847ffdb1472a2c6ca32b7bbf0f41e2b972d2435d8fe4d72e82d97bc8c88b4b59 +size 1072 diff --git a/Effects/HLSL/Conversion.fxh b/Effects/HLSL/Conversion.fxh new file mode 100644 index 0000000..3d828bc --- /dev/null +++ b/Effects/HLSL/Conversion.fxh @@ -0,0 +1,34 @@ +float3 HUEtoRGB(in float H) +{ + float R = abs(H * 6 - 3) - 1; + float G = 2 - abs(H * 6 - 2); + float B = 2 - abs(H * 6 - 4); + return saturate(float3(R,G,B)); +} + +float Epsilon = 1e-10; + +float3 RGBtoHCV(in float3 RGB) +{ + // Based on work by Sam Hocevar and Emil Persson + float4 P = (RGB.g < RGB.b) ? float4(RGB.bg, -1.0, 2.0/3.0) : float4(RGB.gb, 0.0, -1.0/3.0); + float4 Q = (RGB.r < P.x) ? float4(P.xyw, RGB.r) : float4(RGB.r, P.yzx); + float C = Q.x - min(Q.w, Q.y); + float H = abs((Q.w - Q.y) / (6 * C + Epsilon) + Q.z); + return float3(H, C, Q.x); +} + +float3 RGBtoHSL(float3 RGB) +{ + float3 HCV = RGBtoHCV(RGB); + float L = HCV.z - HCV.y * 0.5; + float S = HCV.y / (1 - abs(L * 2 - 1) + Epsilon); + return float3(HCV.x, S, L); +} + +float3 HSLtoRGB(float3 HSL) +{ + float3 RGB = HUEtoRGB(HSL.x); + float C = (1 - abs(2 * HSL.z - 1)) * HSL.y; + return (RGB - 0.5) * C + HSL.z; +} diff --git a/Effects/HLSL/DeferredPBR_AmbientLightEffect.fx b/Effects/HLSL/DeferredPBR_AmbientLightEffect.fx index 4144c8d..6c6e173 100644 --- a/Effects/HLSL/DeferredPBR_AmbientLightEffect.fx +++ b/Effects/HLSL/DeferredPBR_AmbientLightEffect.fx @@ -3,6 +3,12 @@ DECLARE_TEXTURE(gPosition, 0); DECLARE_TEXTURE(gAlbedo, 1); +BEGIN_CONSTANTS + +float3 AmbientLightColor _ps(c0) _cb(c0); + +END_CONSTANTS + struct VertexInput { float4 Position : POSITION; @@ -29,7 +35,7 @@ float4 ComputeColor( float3 worldPosition, float3 albedo ) { - float3 color = float3(0.03, 0.03, 0.03) * albedo; + float3 color = AmbientLightColor * albedo; return float4(color, 1.0); } diff --git a/Effects/HLSL/DeferredPBR_DirectionalLightEffect.fx b/Effects/HLSL/DeferredPBR_DirectionalLightEffect.fx index 0982521..de683cd 100644 --- a/Effects/HLSL/DeferredPBR_DirectionalLightEffect.fx +++ b/Effects/HLSL/DeferredPBR_DirectionalLightEffect.fx @@ -1,5 +1,6 @@ #include "Macros.fxh" //from FNA #include "Lighting.fxh" +#include "Shadow.fxh" static const int NUM_SHADOW_CASCADES = 4; @@ -35,26 +36,6 @@ MATRIX_CONSTANTS END_CONSTANTS -static float2 poissonDisk[16] = -{ - float2( -0.94201624, -0.39906216 ), - float2( 0.94558609, -0.76890725 ), - float2( -0.094184101, -0.92938870 ), - float2( 0.34495938, 0.29387760 ), - float2( -0.91588581, 0.45771432 ), - float2( -0.81544232, -0.87912464 ), - float2( -0.38277543, 0.27676845 ), - float2( 0.97484398, 0.75648379 ), - float2( 0.44323325, -0.97511554 ), - float2( 0.53742981, -0.47373420 ), - float2( -0.26496911, -0.41893023 ), - float2( 0.79197514, 0.19090188 ), - float2( -0.24188840, 0.99706507 ), - float2( -0.81409955, 0.91437590 ), - float2( 0.19984126, 0.78641367 ), - float2( 0.14383161, -0.14100790 ) -}; - struct VertexInput { float4 Position : POSITION; @@ -79,35 +60,8 @@ PixelInput main_vs(VertexInput input) // Pixel Shader -// Returns a random number based on a vec3 and an int. -float random(float3 seed, int i){ - float4 seed4 = float4(seed, i); - float dot_product = dot(seed4, float4(12.9898,78.233,45.164,94.673)); - return frac(sin(dot_product) * 43758.5453); -} - -float PoissonCoord(sampler shadowMap, float3 worldPosition, float2 texCoord, float fragmentDepth, float bias) -{ - float visibility = 1.0; - - for (int i = 0; i < 16; i++) - { - int index = int(16.0 * random(floor(worldPosition * 1000.0), i)) % 16; - - if (tex2D(shadowMap, texCoord + poissonDisk[index] / 1024.0).r < fragmentDepth - bias) - { - visibility -= 0.05; - } - } - - return visibility; -} - float ComputeShadow(float3 positionWorldSpace, float3 N, float3 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 @@ -139,69 +93,52 @@ float ComputeShadow(float3 positionWorldSpace, float3 N, float3 L) lightSpaceMatrix = LightSpaceMatrixFour; } - float4 positionLightSpace = mul(float4(positionWorldSpace, 1.0), lightSpaceMatrix); - - // maps to [-1, 1] - float3 projectionCoords = positionLightSpace.xyz / positionLightSpace.w; - - // maps to [0, 1] - projectionCoords.x = (projectionCoords.x * 0.5) + 0.5; - projectionCoords.y = (projectionCoords.y * 0.5) + 0.5; - projectionCoords.y *= -1; - // in XNA clip z is 0 to 1 already - - if (projectionCoords.z > 1.0) - { - return 1.0; - } - - float inc = 1.0 / ShadowMapSize; // TODO: shadow map size uniform - // PCF + Poisson soft shadows - float visibility = 0.0; - // for (int row = -1; row <= 1; row++) - // { - // for (int col = -1; col <= 1; col++) - // { - // if (shadowCascadeIndex == 0) - // { - // visibility += PoissonCoord(SAMPLER(shadowMapOne), positionWorldSpace, projectionCoords.xy + float2(row, col) * inc, projectionCoords.z, bias); - // } - // else if (shadowCascadeIndex == 1) - // { - // visibility += PoissonCoord(SAMPLER(shadowMapTwo), positionWorldSpace, projectionCoords.xy + float2(row, col) * inc, projectionCoords.z, bias); - // } - // else if (shadowCascadeIndex == 2) - // { - // visibility += PoissonCoord(SAMPLER(shadowMapThree), positionWorldSpace, projectionCoords.xy + float2(row, col) * inc, projectionCoords.z, bias); - // } - // else - // { - // visibility += PoissonCoord(SAMPLER(shadowMapFour), positionWorldSpace, projectionCoords.xy + float2(row, col) * inc, projectionCoords.z, bias); - // } - // } - // } - - // visibility /= 9.0; if (shadowCascadeIndex == 0) { - visibility = PoissonCoord(SAMPLER(shadowMapOne), positionWorldSpace, projectionCoords.xy, projectionCoords.z, bias); + return PoissonShadow( + positionWorldSpace, + N, + L, + lightSpaceMatrix, + SAMPLER(shadowMapOne), + ShadowMapSize + ); } else if (shadowCascadeIndex == 1) { - visibility = PoissonCoord(SAMPLER(shadowMapTwo), positionWorldSpace, projectionCoords.xy, projectionCoords.z, bias); + return PoissonShadow( + positionWorldSpace, + N, + L, + lightSpaceMatrix, + SAMPLER(shadowMapTwo), + ShadowMapSize + ); } else if (shadowCascadeIndex == 2) { - visibility = PoissonCoord(SAMPLER(shadowMapThree), positionWorldSpace, projectionCoords.xy, projectionCoords.z, bias); + return PoissonShadow( + positionWorldSpace, + N, + L, + lightSpaceMatrix, + SAMPLER(shadowMapThree), + ShadowMapSize + ); } else { - visibility = PoissonCoord(SAMPLER(shadowMapFour), positionWorldSpace, projectionCoords.xy, projectionCoords.z, bias); + return PoissonShadow( + positionWorldSpace, + N, + L, + lightSpaceMatrix, + SAMPLER(shadowMapFour), + ShadowMapSize + ); } - - return visibility; } float4 ComputeColor( diff --git a/Effects/HLSL/DeferredPBR_PointLightEffect.fx b/Effects/HLSL/DeferredPBR_PointLightEffect.fx index fba3c6e..d6e1933 100644 --- a/Effects/HLSL/DeferredPBR_PointLightEffect.fx +++ b/Effects/HLSL/DeferredPBR_PointLightEffect.fx @@ -1,10 +1,12 @@ #include "Macros.fxh" //from FNA #include "Lighting.fxh" +#include "Shadow.fxh" DECLARE_TEXTURE(gPosition, 0); DECLARE_TEXTURE(gAlbedo, 1); DECLARE_TEXTURE(gNormal, 2); DECLARE_TEXTURE(gMetallicRoughness, 3); +DECLARE_CUBEMAP(shadowMap, 4); BEGIN_CONSTANTS @@ -13,6 +15,8 @@ BEGIN_CONSTANTS float3 PointLightPosition _ps(c1) _cb(c1); float3 PointLightColor _ps(c2) _cb(c2); + float FarPlane _ps(c3) _cb(c3); + MATRIX_CONSTANTS END_CONSTANTS @@ -60,9 +64,11 @@ float4 ComputeColor( float attenuation = 1.0 / (distance * distance); float3 radiance = PointLightColor * attenuation; - float3 color = ComputeLight(L, radiance, F0, V, N, albedo, metallic, roughness, 1.0); + float shadow = HardPointShadow(worldPosition, N, L, PointLightPosition, SAMPLER(shadowMap), FarPlane); + float3 color = ComputeLight(L, radiance, F0, V, N, albedo, metallic, roughness, shadow); return float4(color, 1.0); + //return float4(shadow, shadow, shadow, 1.0); } float4 main_ps(PixelInput input) : SV_TARGET0 diff --git a/Effects/HLSL/Deferred_ToonEffect.fx b/Effects/HLSL/Deferred_ToonEffect.fx new file mode 100644 index 0000000..62fbcad --- /dev/null +++ b/Effects/HLSL/Deferred_ToonEffect.fx @@ -0,0 +1,285 @@ +#include "Macros.fxh" +#include "Shadow.fxh" +#include "Dither.fxh" + +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(shadowMapOne, 4); +DECLARE_TEXTURE(shadowMapTwo, 5); +DECLARE_TEXTURE(shadowMapThree, 6); +DECLARE_TEXTURE(shadowMapFour, 7); + +BEGIN_CONSTANTS + +float3 EyePosition _ps(c0) _cb(c0); + +float3 DirectionalLightDirection _ps(c1) _cb(c1); +float3 DirectionalLightColor _ps(c2) _cb(c2); + +float CascadeFarPlanes[NUM_SHADOW_CASCADES] _ps(c4) _cb(c4); + +float ShadowMapSize _ps(c8) _cb(c8); + +MATRIX_CONSTANTS + +float4x4 LightSpaceMatrixOne _ps(c9) _cb(c9); +float4x4 LightSpaceMatrixTwo _ps(c13) _cb(c13); +float4x4 LightSpaceMatrixThree _ps(c17) _cb(c17); +float4x4 LightSpaceMatrixFour _ps(c21) _cb(c21); + +float4x4 ViewMatrix _ps(c25) _cb(c25); + +END_CONSTANTS + +struct VertexInput +{ + float4 Position : POSITION; + float2 TexCoord : TEXCOORD; +}; + +struct PixelInput +{ + float4 Position : SV_POSITION; + float2 TexCoord : TEXCOORD0; +}; + +PixelInput main_vs(VertexInput input) +{ + PixelInput output; + + output.Position = input.Position; + output.TexCoord = input.TexCoord; + + return output; +} + +float ComputeShadow(float3 positionWorldSpace, float3 N, float3 L) +{ + float4 positionCameraSpace = mul(float4(positionWorldSpace, 1.0), ViewMatrix); + + int shadowCascadeIndex = 0; // 0 is closest + for (int i = 0; i < NUM_SHADOW_CASCADES; i++) + { + if (abs(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; + } + + if (shadowCascadeIndex == 0) + { + return HardShadow( + positionWorldSpace, + N, + L, + lightSpaceMatrix, + SAMPLER(shadowMapOne), + ShadowMapSize + ); + } + else if (shadowCascadeIndex == 1) + { + return HardShadow( + positionWorldSpace, + N, + L, + lightSpaceMatrix, + SAMPLER(shadowMapTwo), + ShadowMapSize + ); + } + else if (shadowCascadeIndex == 2) + { + return HardShadow( + positionWorldSpace, + N, + L, + lightSpaceMatrix, + SAMPLER(shadowMapThree), + ShadowMapSize + ); + } + else + { + return HardShadow( + positionWorldSpace, + N, + L, + lightSpaceMatrix, + SAMPLER(shadowMapFour), + ShadowMapSize + ); + } +} + +float IntensityBanding(float NdotL) +{ + // if (NdotL > 0.5) + // { + // return 1.0; + // } + // else if (NdotL > 0.25) + // { + // return 0.5; + // } + // else if (NdotL > 0.0) + // { + // return 0.25; + // } + // else + // { + // return 0.0; + // } + if (NdotL > 0) + { + return 1.0; + } + else + { + return 0.25; + } +} + +float4 FlatShadow(PixelInput input) : SV_TARGET0 +{ + float2 screenPosition = input.Position.xy; + float3 worldPosition = SAMPLE_TEXTURE(gPosition, input.TexCoord).rgb; + float3 normal = SAMPLE_TEXTURE(gNormal, input.TexCoord).xyz; + float3 albedo = SAMPLE_TEXTURE(gAlbedo, input.TexCoord).rgb; + float2 metallicRoughness = SAMPLE_TEXTURE(gMetallicRoughness, input.TexCoord).rg; + + // the lower the glossiness, the sharper the specular highlight + float glossiness = lerp(64, 16, 1.0 - metallicRoughness.r); + + float3 V = normalize(EyePosition - worldPosition); + float3 L = normalize(DirectionalLightDirection); + float3 N = normalize(normal); + float3 H = normalize(V + L); + + float NdotL = dot(N, L); + float NdotH = max(dot(N, H), 0.0); + + float lightIntensity = IntensityBanding(NdotL); + float3 light = lightIntensity * DirectionalLightColor; + + float specularIntensity = pow(NdotH * lightIntensity, glossiness * glossiness); + float specularSmooth = smoothstep(0.005, 0.01, specularIntensity); + + float3 specular = specularSmooth * float3(1.0, 1.0, 1.0); + + if (metallicRoughness.r == 0.0) { specular = float3(0.0, 0.0, 0.0); } + + float3 rimColor = float3(1.0, 1.0, 1.0); + float rimThreshold = 0.1; + float rimAmount = 1 - metallicRoughness.g; + float rimDot = 1 - dot(V, N); + float rimIntensity = rimDot * pow(max(NdotL, 0.0), rimThreshold); + rimIntensity = smoothstep(rimAmount - 0.01, rimAmount + 0.01, rimIntensity); + float3 rim = rimIntensity * rimColor; + + float shadow = ComputeShadow(worldPosition, N, L); + float3 color = albedo * (light + specular + rim) * shadow; + + return float4(color, 1.0); +} + +// FIXME: organize this +float4 DitheredShadow(PixelInput input) : SV_TARGET0 +{ + float2 screenPosition = input.Position.xy; + float3 worldPosition = SAMPLE_TEXTURE(gPosition, input.TexCoord).rgb; + float3 normal = SAMPLE_TEXTURE(gNormal, input.TexCoord).xyz; + float3 albedo = SAMPLE_TEXTURE(gAlbedo, input.TexCoord).rgb; + float2 metallicRoughness = SAMPLE_TEXTURE(gMetallicRoughness, input.TexCoord).rg; + + // the lower the glossiness, the sharper the specular highlight + float glossiness = lerp(64, 16, 1.0 - metallicRoughness.r); + + float3 V = normalize(EyePosition - worldPosition); + float3 L = normalize(DirectionalLightDirection); + float3 N = normalize(normal); + float3 H = normalize(V + L); + + float NdotL = dot(N, L); + float NdotH = max(dot(N, H), 0.0); + + float lightIntensity = IntensityBanding(NdotL); + //float3 light = lightIntensity * DirectionalLightColor; + float3 light = DirectionalLightColor; + + if (lightIntensity < 1) + { + light *= dither(lightIntensity, screenPosition); + } + + float specularIntensity = pow(NdotH * lightIntensity, glossiness * glossiness); + float specularSmooth = smoothstep(0.005, 0.01, specularIntensity); + + float3 specular = specularSmooth * float3(1.0, 1.0, 1.0); + + if (metallicRoughness.r == 0.0) { specular = float3(0.0, 0.0, 0.0); } + + float3 rimColor = float3(1.0, 1.0, 1.0); + float rimThreshold = 0.1; + float rimAmount = 1 - metallicRoughness.g; + float rimDot = 1 - dot(V, N); + float rimIntensity = rimDot * pow(max(NdotL, 0.0), rimThreshold); + rimIntensity = smoothstep(rimAmount - 0.01, rimAmount + 0.01, rimIntensity); + float3 rim = rimIntensity * rimColor; + + float shadow = ComputeShadow(worldPosition, N, L); + float3 color = albedo * (light + specular + rim); // * shadow; + + if (shadow < 1) + { + color *= dither(shadow, screenPosition); + } + + return float4(color, 1.0); +} + +PixelShader PSArray[2] = +{ + compile ps_3_0 FlatShadow(), + compile ps_3_0 DitheredShadow() +}; + +int PSIndices[2] = +{ + 0, 1 +}; + +int ShaderIndex = 0; + +Technique Deferred_Toon +{ + Pass + { + VertexShader = compile vs_3_0 main_vs(); + PixelShader = (PSArray[PSIndices[ShaderIndex]]); + } +} diff --git a/Effects/HLSL/Dither.fxh b/Effects/HLSL/Dither.fxh new file mode 100644 index 0000000..e0cf119 --- /dev/null +++ b/Effects/HLSL/Dither.fxh @@ -0,0 +1,89 @@ +#include "Conversion.fxh" + +// technique from http://alex-charlton.com/posts/Dithering_on_the_GPU/ + +uniform float3 palette[8]; +static const int paletteSize = 8; + +static const int indexMatrix4x4[16] = +{ + 0, 8, 2, 10, + 12, 4, 14, 6, + 3, 11, 1, 9, + 15, 7, 13, 5 +}; + +float indexValue(float2 screenCoords) +{ + int x = int(screenCoords.x % 4); + int y = int(screenCoords.y % 4); + + return indexMatrix4x4[(x + y * 4)] / 16.0; +} + +float hueDistance(float h1, float h2) +{ + float diff = abs(h1 - h2); + return min(abs(1.0 - diff), diff); +} + +void closestColors(float hue, out float3 ret[2]) +{ + float3 closest = float3(-2, 0, 0); + float3 secondClosest = float3(-2, 0, 0); + float3 temp; + + for (int i = 0; i < paletteSize; i++) + { + temp = palette[i]; + float tempDistance = hueDistance(temp.x, hue); + if (tempDistance < hueDistance(closest.x, hue)) + { + secondClosest = closest; + closest = temp; + } + else + { + if (tempDistance < hueDistance(secondClosest.x, hue)) + { + secondClosest = temp; + } + } + } + + ret[0] = closest; + ret[1] = secondClosest; +} + +float3 dither(float3 color, float2 screenCoords) +{ + float3 colors[2]; + float3 hsl = RGBtoHSL(color); + closestColors(hsl.x, colors); + float3 closestColor = colors[0]; + float3 secondClosestColor = colors[1]; + float d = indexValue(screenCoords); + float hueDiff = hueDistance(hsl.x, closestColor.x) / hueDistance(secondClosestColor.x, secondClosestColor.x); + return HSLtoRGB(hueDiff < d ? closestColor : secondClosestColor); +} + +// brightColor refers to undithered max color +// float3 dither(float3 color, float3 brightColor, float2 screenCoords) +// { +// float brightHue = RGBtoHSL(brightColor.x); +// float colorHue = RGBtoHSL(color.x); +// float halfDistance = hueDistance(0.0, brightHue) / 2.0; +// float3 closestColor = (colorHue < halfDistance) ? float3(0.0, 0.0, 0.0) : brightColor; +// float3 secondClosestColor = brightColor - closestColor; +// float d = indexValue(screenCoords); +// float distance = abs(closestColor - color); +// return (distance < d) ? closestColor : secondClosestColor; +// } + +float3 dither(float color, float2 screenCoords) { + float closestColor = (color < 0.5) ? 0 : 1; + float secondClosestColor = 1 - closestColor; + float d = indexValue(screenCoords); + float distance = abs(closestColor - color); + return (distance < d) ? closestColor : secondClosestColor; +} diff --git a/Effects/HLSL/LinearDepthEffect.fx b/Effects/HLSL/LinearDepthEffect.fx new file mode 100644 index 0000000..67db0e2 --- /dev/null +++ b/Effects/HLSL/LinearDepthEffect.fx @@ -0,0 +1,47 @@ +#include "Macros.fxh" + +BEGIN_CONSTANTS + + float4x4 Model _vs(c0) _cb(c0); + float4x4 ModelViewProjection _vs(c4) _cb(c4); + + float3 LightPosition _ps(c0) _cb(c8); + float FarPlane _ps(c1) _cb(c9); + +END_CONSTANTS + +struct VertexShaderInput +{ + float4 Position : POSITION; +}; + +struct VertexShaderOutput +{ + float4 Position : SV_Position; + float3 PositionWorld : TEXCOORD0; +}; + +VertexShaderOutput main_vs(VertexShaderInput input) +{ + VertexShaderOutput output; + output.Position = mul(input.Position, ModelViewProjection); + output.Position.x *= -1; // otherwise cube map render will be horizontally flipped + output.PositionWorld = mul(input.Position, Model); + return output; +} + +float4 main_ps(VertexShaderOutput input) : SV_TARGET0 +{ + float lightDistance = length(input.PositionWorld - LightPosition); + lightDistance /= FarPlane; + return float4(lightDistance, 0.0, 0.0, 0.0); +} + +Technique SimpleDepth +{ + Pass + { + VertexShader = compile vs_3_0 main_vs(); + PixelShader = compile ps_3_0 main_ps(); + } +} diff --git a/Effects/HLSL/Shadow.fxh b/Effects/HLSL/Shadow.fxh new file mode 100644 index 0000000..5f8351f --- /dev/null +++ b/Effects/HLSL/Shadow.fxh @@ -0,0 +1,141 @@ +static float2 poissonDisk[16] = +{ + float2( -0.94201624, -0.39906216 ), + float2( 0.94558609, -0.76890725 ), + float2( -0.094184101, -0.92938870 ), + float2( 0.34495938, 0.29387760 ), + float2( -0.91588581, 0.45771432 ), + float2( -0.81544232, -0.87912464 ), + float2( -0.38277543, 0.27676845 ), + float2( 0.97484398, 0.75648379 ), + float2( 0.44323325, -0.97511554 ), + float2( 0.53742981, -0.47373420 ), + float2( -0.26496911, -0.41893023 ), + float2( 0.79197514, 0.19090188 ), + float2( -0.24188840, 0.99706507 ), + float2( -0.81409955, 0.91437590 ), + float2( 0.19984126, 0.78641367 ), + float2( 0.14383161, -0.14100790 ) +}; + +// TODO: this should probably sample a noise texture instead +// Returns a random number based on a vec3 and an int. +float random(float3 seed, int i){ + float4 seed4 = float4(seed, i); + float dot_product = dot(seed4, float4(12.9898,78.233,45.164,94.673)); + return frac(sin(dot_product) * 43758.5453); +} + +float PoissonCoord(sampler shadowMap, float3 worldPosition, float2 texCoord, float fragmentDepth, float bias) +{ + float visibility = 1.0; + + for (int i = 0; i < 16; i++) + { + int index = int(16.0 * random(floor(worldPosition * 1000.0), i)) % 16; + + if (tex2D(shadowMap, texCoord + poissonDisk[index] / 1024.0).r < fragmentDepth - bias) + { + visibility -= 0.05; + } + } + + return visibility; +} + +float PoissonShadow( + float3 positionWorldSpace, + float3 N, + float3 L, + float4x4 lightSpaceMatrix, + sampler shadowMap, + int shadowMapSize +) { + float bias = 0.005 * tan(acos(dot(N, L))); + bias = clamp(bias, 0, 0.01); + + float4 positionLightSpace = mul(float4(positionWorldSpace, 1.0), lightSpaceMatrix); + + // maps to [-1, 1] + float3 projectionCoords = positionLightSpace.xyz / positionLightSpace.w; + + // maps to [0, 1] + // NOTE: In XNA, clip space z is [0, 1] range + projectionCoords.x = (projectionCoords.x * 0.5) + 0.5; + projectionCoords.y = (projectionCoords.y * 0.5) + 0.5; + projectionCoords.y *= -1; + + if (projectionCoords.z > 1.0) + { + return 1.0; + } + + float inc = 1.0 / shadowMapSize; + + // Poisson soft shadows + float visibility = 0.0; + + visibility = PoissonCoord( + shadowMap, + positionWorldSpace, + projectionCoords.xy, + projectionCoords.z, + bias + ); + + return visibility; +} + +float HardShadow( + float3 positionWorldSpace, + float3 N, + float3 L, + float4x4 lightSpaceMatrix, + sampler shadowMap, + int shadowMapSize +) { + // float bias = 0.005 * tan(acos(dot(N, L))); + // bias = clamp(bias, 0, 0.01); + + float bias = max(0.05 * (1.0 - dot(N, L)), 0.005); + + float4 positionLightSpace = mul(float4(positionWorldSpace, 1.0), lightSpaceMatrix); + + // maps to [-1, 1] + float3 projectionCoords = positionLightSpace.xyz / positionLightSpace.w; + + // maps to [0, 1] + // NOTE: In XNA, clip space z is [0, 1] range + projectionCoords.x = (projectionCoords.x * 0.5) + 0.5; + projectionCoords.y = (projectionCoords.y * 0.5) + 0.5; + projectionCoords.y *= -1; + + if (projectionCoords.z > 1.0) + { + return 1.0; + } + + float closestDepth = tex2D(shadowMap, projectionCoords.xy); + float currentDepth = projectionCoords.z; + + return (currentDepth - bias > closestDepth ? 0.25 : 1.0); +} + +float HardPointShadow( + float3 positionWorldSpace, + float3 N, + float3 L, + float3 lightPosition, + sampler shadowMap, + float farPlane +) { + float3 lightToFrag = positionWorldSpace - lightPosition; + float closestDepth = texCUBE(shadowMap, lightToFrag).r; + closestDepth *= farPlane; + + float currentDepth = length(lightToFrag); + + float bias = max(0.05 * (1.0 - dot(N, L)), 0.005); + + return (currentDepth - bias > closestDepth ? 0 : 1.0); +} diff --git a/Effects/HLSL/SimpleDepthEffect.fx b/Effects/HLSL/SimpleDepthEffect.fx index 3598eb5..f813bd7 100644 --- a/Effects/HLSL/SimpleDepthEffect.fx +++ b/Effects/HLSL/SimpleDepthEffect.fx @@ -24,7 +24,7 @@ VertexShaderOutput main_vs(VertexShaderInput input) VertexShaderOutput output; output.Position = mul(input.Position, ModelViewProjection); - output.Depth = output.Position.z; + output.Depth = output.Position.z / output.Position.w; return output; } diff --git a/Effects/HLSL/SkyboxEffect.fx b/Effects/HLSL/SkyboxEffect.fx new file mode 100644 index 0000000..88ac7f3 --- /dev/null +++ b/Effects/HLSL/SkyboxEffect.fx @@ -0,0 +1,45 @@ +#include "Macros.fxh" + +DECLARE_CUBEMAP(skybox, 0); + +BEGIN_CONSTANTS + + float4x4 ViewProjection _vs(c0) _cb(c0); + +END_CONSTANTS + +struct VertexShaderInput +{ + float3 Position : POSITION; +}; + +struct VertexShaderOutput +{ + float4 Position : SV_POSITION; + float3 TexCoord : TEXCOORD; +}; + +VertexShaderOutput main_vs(VertexShaderInput input) +{ + VertexShaderOutput output; + + output.Position = mul(float4(input.Position, 1.0), ViewProjection); + output.Position = output.Position.xyww; + output.TexCoord = input.Position; + + return output; +} + +float4 main_ps(VertexShaderOutput input) : SV_TARGET0 +{ + return SAMPLE_CUBEMAP(skybox, input.TexCoord); +} + +Technique Skybox +{ + Pass + { + VertexShader = compile vs_3_0 main_vs(); + PixelShader = compile ps_3_0 main_ps(); + } +} diff --git a/Effects/LinearDepthEffect.cs b/Effects/LinearDepthEffect.cs new file mode 100644 index 0000000..2a6a76a --- /dev/null +++ b/Effects/LinearDepthEffect.cs @@ -0,0 +1,90 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Kav +{ + public class LinearDepthEffect : Effect + { + EffectParameter modelParam; + EffectParameter modelViewProjectionParam; + EffectParameter lightPositionParam; + EffectParameter farPlaneParam; + + EffectDirtyFlags dirtyFlags = EffectDirtyFlags.All; + + Matrix model; + Matrix view; + Matrix projection; + + public Vector3 LightPosition { get; set; } + public float FarPlane { get; set; } + + public Matrix Model + { + get { return model; } + set + { + model = value; + dirtyFlags |= EffectDirtyFlags.World; + dirtyFlags |= EffectDirtyFlags.WorldViewProj; + } + } + + public Matrix View + { + get { return view; } + set + { + view = value; + dirtyFlags |= EffectDirtyFlags.WorldViewProj; + } + } + + public Matrix Projection + { + get { return projection; } + set + { + projection = value; + dirtyFlags |= EffectDirtyFlags.WorldViewProj; + } + } + + public LinearDepthEffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.LinearDepthEffect) + { + CacheEffectParameters(); + } + + protected override void OnApply() + { + if ((dirtyFlags & EffectDirtyFlags.WorldViewProj) != 0) + { + Matrix.Multiply(ref view, ref projection, out Matrix viewProjection); + Matrix.Multiply(ref model, ref viewProjection, out Matrix worldViewProj); + + modelViewProjectionParam.SetValue(worldViewProj); + + dirtyFlags &= ~EffectDirtyFlags.WorldViewProj; + } + + if ((dirtyFlags & EffectDirtyFlags.World) != 0) + { + modelParam.SetValue(model); + + dirtyFlags &= ~EffectDirtyFlags.World; + } + + lightPositionParam.SetValue(LightPosition); + farPlaneParam.SetValue(FarPlane); + } + + private void CacheEffectParameters() + { + modelParam = Parameters["Model"]; + modelViewProjectionParam = Parameters["ModelViewProjection"]; + + lightPositionParam = Parameters["LightPosition"]; + farPlaneParam = Parameters["FarPlane"]; + } + } +} diff --git a/Effects/SkyboxEffect.cs b/Effects/SkyboxEffect.cs new file mode 100644 index 0000000..b4f4427 --- /dev/null +++ b/Effects/SkyboxEffect.cs @@ -0,0 +1,77 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Kav +{ + public class SkyboxEffect : Effect + { + EffectParameter viewProjectionParam; + EffectParameter skyboxParam; + + Matrix view; + Matrix projection; + TextureCube skybox; + + EffectDirtyFlags dirtyFlags = EffectDirtyFlags.All; + + public Matrix View + { + get { return view; } + set + { + view = value; + dirtyFlags |= EffectDirtyFlags.WorldViewProj; + } + } + + public Matrix Projection + { + get { return projection; } + set + { + projection = value; + dirtyFlags |= EffectDirtyFlags.WorldViewProj; + } + } + + public TextureCube Skybox + { + get { return skybox; } + set + { + skybox = value; + dirtyFlags |= EffectDirtyFlags.World; // hack flag but whatever + } + } + + public SkyboxEffect(GraphicsDevice graphicsDevice) : base(graphicsDevice, Resources.SkyboxEffect) + { + CacheEffectParameters(); + } + + protected override void OnApply() + { + if ((dirtyFlags & EffectDirtyFlags.WorldViewProj) != 0) + { + Matrix.Multiply(ref view, ref projection, out Matrix viewProjection); + + viewProjectionParam.SetValue(viewProjection); + + dirtyFlags &= ~EffectDirtyFlags.WorldViewProj; + } + + if ((dirtyFlags & EffectDirtyFlags.World) != 0) + { + skyboxParam.SetValue(skybox); + + dirtyFlags &= ~EffectDirtyFlags.World; + } + } + + private void CacheEffectParameters() + { + viewProjectionParam = Parameters["ViewProjection"]; + skyboxParam = Parameters["skybox"]; + } + } +} diff --git a/Geometry/MeshPart.cs b/Geometry/MeshPart.cs index e97b49c..248fb1a 100644 --- a/Geometry/MeshPart.cs +++ b/Geometry/MeshPart.cs @@ -9,15 +9,47 @@ namespace Kav public VertexBuffer VertexBuffer { get; } public Triangle[] Triangles { get; } public Vector3[] Positions { get; } - public Effect Effect { get; } + + private Texture2D albedoTexture = null; + private Texture2D normalTexture = null; + private Texture2D metallicRoughnessTexture = null; - public MeshPart(VertexBuffer vertexBuffer, IndexBuffer indexBuffer, Vector3[] positions, Triangle[] triangles, Effect effect) - { + public Texture2D AlbedoTexture + { + get { return DisableAlbedoMap ? null : albedoTexture; } + set { albedoTexture = value; } + } + + public Texture2D NormalTexture + { + get { return DisableNormalMap ? null : normalTexture; } + set { normalTexture = value; } + } + + public Texture2D MetallicRoughnessTexture + { + get { return DisableMetallicRoughnessMap ? null : metallicRoughnessTexture; } + set { metallicRoughnessTexture = value; } + } + + public Vector3 Albedo { get; set; } = Vector3.One; + public float Metallic { get; set; } = 0.5f; + public float Roughness { get; set; } = 0.5f; + + public bool DisableAlbedoMap { get; set; } = false; + public bool DisableNormalMap { get; set; } = false; + public bool DisableMetallicRoughnessMap { get; set; } = false; + + public MeshPart( + VertexBuffer vertexBuffer, + IndexBuffer indexBuffer, + Vector3[] positions, + Triangle[] triangles + ) { VertexBuffer = vertexBuffer; IndexBuffer = indexBuffer; Positions = positions; Triangles = triangles; - Effect = effect; } } } diff --git a/Geometry/Model.cs b/Geometry/Model.cs index 1d83410..9ab1dc0 100644 --- a/Geometry/Model.cs +++ b/Geometry/Model.cs @@ -6,9 +6,84 @@ namespace Kav { public Mesh[] Meshes { get; } + public Color Albedo + { + set + { + foreach (var mesh in Meshes) + { + foreach (var meshPart in mesh.MeshParts) + { + meshPart.Albedo = value.ToVector3(); + } + } + } + } + + public float Metallic + { + set + { + foreach (var mesh in Meshes) + { + foreach (var meshPart in mesh.MeshParts) + { + meshPart.Metallic = value; + } + } + } + } + + public float Roughness + { + set + { + foreach (var mesh in Meshes) + { + foreach (var meshPart in mesh.MeshParts) + { + meshPart.Roughness = value; + } + } + } + } + public Model(Mesh[] meshes) { Meshes = meshes; } + + public void DisableAlbedoMaps() + { + foreach (var mesh in Meshes) + { + foreach (var meshPart in mesh.MeshParts) + { + meshPart.DisableAlbedoMap = true; + } + } + } + + public void DisableNormalMaps() + { + foreach (var mesh in Meshes) + { + foreach (var meshPart in mesh.MeshParts) + { + meshPart.DisableNormalMap = true; + } + } + } + + public void DisableMetallicRoughnessMaps() + { + foreach (var mesh in Meshes) + { + foreach (var meshPart in mesh.MeshParts) + { + meshPart.DisableMetallicRoughnessMap = true; + } + } + } } } diff --git a/Kav.Core.csproj b/Kav.Core.csproj index 327d7bb..1a7954e 100644 --- a/Kav.Core.csproj +++ b/Kav.Core.csproj @@ -30,6 +30,9 @@ Kav.Resources.ToneMapEffect.fxb + + Kav.Resources.Deferred_ToonEffect.fxb + Kav.Resources.DeferredPBREffect.fxb @@ -39,6 +42,15 @@ Kav.Resources.SimpleDepthEffect.fxb + + Kav.Resources.LinearDepthEffect.fxb + + + Kav.Resources.SkyboxEffect.fxb + + + Kav.Resources.UnitCube.glb + diff --git a/Kav.Framework.csproj b/Kav.Framework.csproj index 2b2dd4f..c60f949 100644 --- a/Kav.Framework.csproj +++ b/Kav.Framework.csproj @@ -30,6 +30,9 @@ Kav.Resources.ToneMapEffect.fxb + + Kav.Resources.Deferred_ToonEffect.fxb + Kav.Resources.DeferredPBREffect.fxb @@ -39,6 +42,15 @@ Kav.Resources.SimpleDepthEffect.fxb + + Kav.Resources.LinearDepthEffect.fxb + + + Kav.Resources.SkyboxEffect.fxb + + + Kav.Resources.UnitCube.glb + diff --git a/Lights/AmbientLight.cs b/Lights/AmbientLight.cs new file mode 100644 index 0000000..94bb42e --- /dev/null +++ b/Lights/AmbientLight.cs @@ -0,0 +1,14 @@ +using Microsoft.Xna.Framework; + +namespace Kav +{ + public struct AmbientLight + { + public Color Color { get; set; } + + public AmbientLight(Color color) + { + Color = color; + } + } +} diff --git a/Lights/DirectionalLight.cs b/Lights/DirectionalLight.cs index a22ab33..985745f 100644 --- a/Lights/DirectionalLight.cs +++ b/Lights/DirectionalLight.cs @@ -4,9 +4,9 @@ namespace Kav { public struct DirectionalLight { - public Vector3 Direction { get; set; } - public Color Color { get; set; } - public float Intensity { get; set; } + public Vector3 Direction { get; } + public Color Color { get; } + public float Intensity { get; } public Matrix View { diff --git a/Loaders/ModelLoader.cs b/Loaders/ModelLoader.cs index 4e908a0..634ae94 100644 --- a/Loaders/ModelLoader.cs +++ b/Loaders/ModelLoader.cs @@ -15,19 +15,6 @@ namespace Kav foreach (var meshPartData in meshData.MeshParts) { - var effect = new Kav.DeferredPBR_GBufferEffect( - graphicsDevice - ) - { - Albedo = meshPartData.Albedo, - Metallic = meshPartData.Metallic, - Roughness = meshPartData.Roughness, - - AlbedoTexture = meshPartData.AlbedoTexture, - NormalTexture = meshPartData.NormalTexture, - MetallicRoughnessTexture = meshPartData.MetallicRoughnessTexture - }; - var triangles = new Kav.Triangle[meshPartData.Triangles.Length]; for (int i = 0; i < meshPartData.Triangles.Length; i++) { @@ -40,13 +27,22 @@ namespace Kav ); } - meshParts.Add(new Kav.MeshPart( + var meshPart = new Kav.MeshPart( meshPartData.VertexBuffer, meshPartData.IndexBuffer, meshPartData.Positions, - triangles, - effect - )); + triangles + ); + + meshPart.Albedo = meshPartData.Albedo; + meshPart.Metallic = meshPartData.Metallic; + meshPart.Roughness = meshPartData.Roughness; + + meshPart.AlbedoTexture = meshPartData.AlbedoTexture; + meshPart.NormalTexture = meshPartData.NormalTexture; + meshPart.MetallicRoughnessTexture = meshPartData.MetallicRoughnessTexture; + + meshParts.Add(meshPart); } meshes.Add(new Kav.Mesh(meshParts.ToArray())); diff --git a/Models/UnitCube.glb b/Models/UnitCube.glb new file mode 100644 index 0000000..aa53f03 Binary files /dev/null and b/Models/UnitCube.glb differ diff --git a/README.md b/README.md index 8f3190b..29c7615 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,9 @@ Essential - [x] Tone map shader - [x] Poisson soft shadowing - [ ] Frustum culling -- [ ] Shadow-casting point lights +- [x] Shadow-casting point lights - [ ] Parabolic lights -- [ ] Skyboxes +- [x] Skyboxes - [ ] Screen-space reflection Nice-To-Haves diff --git a/Renderer.cs b/Renderer.cs index 63b34e5..3eb2440 100644 --- a/Renderer.cs +++ b/Renderer.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.IO; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; @@ -21,19 +23,27 @@ namespace Kav private RenderTarget2D[] ShadowRenderTargets { 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 LinearDepthEffect LinearDepthEffect { get; } private Effect ToneMapEffect { get; } + private SkyboxEffect SkyboxEffect { get; } private RenderTarget2D gPosition { get; } private RenderTarget2D gNormal { get; } private RenderTarget2D gAlbedo { get; } private RenderTarget2D gMetallicRoughness { get; } + private RenderTargetCube PointShadowCubeMap { get; } private RenderTargetBinding[] GBuffer { get; } + private Kav.Model UnitCube { get; } + private SpriteBatch SpriteBatch { get; } public Renderer( @@ -70,7 +80,7 @@ namespace Kav renderDimensionsY, false, SurfaceFormat.Color, - DepthFormat.None, + DepthFormat.Depth24, 0, RenderTargetUsage.PreserveContents ); @@ -129,13 +139,26 @@ namespace Kav new RenderTargetBinding(gMetallicRoughness) }; + PointShadowCubeMap = new RenderTargetCube( + GraphicsDevice, + shadowMapSize, + false, + SurfaceFormat.Single, + DepthFormat.Depth24 + ); + SimpleDepthEffect = new SimpleDepthEffect(GraphicsDevice); + LinearDepthEffect = new LinearDepthEffect(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); DeferredDirectionalLightEffect.ShadowMapSize = ShadowMapSize; ToneMapEffect = new Effect(graphicsDevice, Resources.ToneMapEffect); + Deferred_ToonEffect = new Deferred_ToonEffect(GraphicsDevice); + SkyboxEffect = new SkyboxEffect(GraphicsDevice); FullscreenTriangle = new VertexBuffer(GraphicsDevice, typeof(VertexPositionTexture), 3, BufferUsage.WriteOnly); FullscreenTriangle.SetData(new VertexPositionTexture[3] { @@ -144,39 +167,93 @@ namespace Kav new VertexPositionTexture(new Vector3(3, 1, 0), new Vector2(2, 0)) }); + UnitCube = Kav.ModelLoader.Load( + GraphicsDevice, + Smuggler.Importer.ImportGLB(GraphicsDevice, new MemoryStream(Resources.UnitCubeModel)) + ); + SpriteBatch = new SpriteBatch(graphicsDevice); } public void DeferredRender( PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, + AmbientLight ambientLight, IEnumerable pointLights, DirectionalLight directionalLight ) { - // g-buffer pass + GBufferRender(camera, modelTransforms); - GraphicsDevice.SetRenderTargets(GBuffer); + GraphicsDevice.SetRenderTarget(ColorRenderTarget); + GraphicsDevice.Clear(Color.Black); + + AmbientLightRender(ambientLight); + + DeferredPointLightEffect.EyePosition = camera.Position; + + foreach (var pointLight in pointLights) + { + PointLightRender(camera, modelTransforms, pointLight); + } + + DirectionalLightRender(camera, modelTransforms, directionalLight); + + GraphicsDevice.SetRenderTarget(null); + GraphicsDevice.Clear(Color.Black); + SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, null, null, null, ToneMapEffect); + SpriteBatch.Draw(ColorRenderTarget, Vector2.Zero, Color.White); + SpriteBatch.End(); + } + + public void DeferredToonRender( + PerspectiveCamera camera, + IEnumerable<(Model, Matrix)> modelTransforms, + AmbientLight ambientLight, + IEnumerable pointLights, + DirectionalLight directionalLight, + TextureCube skybox + ) { + GBufferRender(camera, modelTransforms); + + GraphicsDevice.SetRenderTarget(ColorRenderTarget); GraphicsDevice.Clear(ClearOptions.Target | ClearOptions.DepthBuffer, Color.Black, 1f, 0); GraphicsDevice.DepthStencilState = DepthStencilState.Default; - GraphicsDevice.BlendState = BlendState.Opaque; + DepthRender(camera, modelTransforms); + GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; + + AmbientLightRender(ambientLight); + foreach (var pointLight in pointLights) + { + PointLightRender(camera, modelTransforms, pointLight); + } + DirectionalLightToonRender(camera, modelTransforms, directionalLight); + SkyboxRender(camera, skybox); + + GraphicsDevice.SetRenderTarget(null); + SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, null, null, null, null); + SpriteBatch.Draw(ColorRenderTarget, Vector2.Zero, Color.White); + SpriteBatch.End(); + } + + private void DepthRender( + PerspectiveCamera camera, + IEnumerable<(Model, Matrix)> modelTransforms + ) { foreach (var (model, transform) in modelTransforms) { foreach (var modelMesh in model.Meshes) { foreach (var meshPart in modelMesh.MeshParts) { + SimpleDepthEffect.Model = transform; + SimpleDepthEffect.View = camera.View; + SimpleDepthEffect.Projection = camera.Projection; + 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) + foreach (var pass in SimpleDepthEffect.CurrentTechnique.Passes) { pass.Apply(); @@ -192,14 +269,96 @@ namespace Kav } } } + } + private void SkyboxRender( + PerspectiveCamera camera, + TextureCube skybox + ) { + GraphicsDevice.RasterizerState.CullMode = CullMode.CullClockwiseFace; + SkyboxEffect.Skybox = skybox; + + var view = camera.View; + view.Translation = Vector3.Zero; + SkyboxEffect.View = view; + + SkyboxEffect.Projection = camera.Projection; + + GraphicsDevice.SetVertexBuffer(UnitCube.Meshes[0].MeshParts[0].VertexBuffer); + GraphicsDevice.Indices = UnitCube.Meshes[0].MeshParts[0].IndexBuffer; + + foreach (var pass in SkyboxEffect.CurrentTechnique.Passes) + { + pass.Apply(); + + GraphicsDevice.DrawIndexedPrimitives( + PrimitiveType.TriangleList, + 0, + 0, + UnitCube.Meshes[0].MeshParts[0].VertexBuffer.VertexCount, + 0, + UnitCube.Meshes[0].MeshParts[0].Triangles.Length + ); + } + GraphicsDevice.RasterizerState.CullMode = CullMode.CullCounterClockwiseFace; + } + + private void GBufferRender( + PerspectiveCamera camera, + IEnumerable<(Model, Matrix)> modelTransforms + ) { + 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) + { + Deferred_GBufferEffect.World = transform; + Deferred_GBufferEffect.View = camera.View; + Deferred_GBufferEffect.Projection = camera.Projection; + + Deferred_GBufferEffect.Albedo = meshPart.Albedo; + Deferred_GBufferEffect.Metallic = meshPart.Metallic; + Deferred_GBufferEffect.Roughness = meshPart.Roughness; + + Deferred_GBufferEffect.AlbedoTexture = meshPart.AlbedoTexture; + Deferred_GBufferEffect.NormalTexture = meshPart.NormalTexture; + Deferred_GBufferEffect.MetallicRoughnessTexture = meshPart.MetallicRoughnessTexture; + + GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer); + GraphicsDevice.Indices = meshPart.IndexBuffer; + + foreach (var pass in Deferred_GBufferEffect.CurrentTechnique.Passes) + { + pass.Apply(); + + GraphicsDevice.DrawIndexedPrimitives( + PrimitiveType.TriangleList, + 0, + 0, + meshPart.VertexBuffer.VertexCount, + 0, + meshPart.Triangles.Length + ); + } + } + } + } + } + + private void AmbientLightRender(AmbientLight ambientLight) + { GraphicsDevice.SetRenderTarget(ColorRenderTarget); - GraphicsDevice.Clear(Color.Black); - GraphicsDevice.BlendState = BlendState.Additive; - GraphicsDevice.DepthStencilState = DepthStencilState.None; + GraphicsDevice.BlendState = BlendState.Opaque; DeferredAmbientLightEffect.GPosition = gPosition; DeferredAmbientLightEffect.GAlbedo = gAlbedo; + DeferredAmbientLightEffect.AmbientColor = ambientLight.Color.ToVector3(); foreach (var pass in DeferredAmbientLightEffect.CurrentTechnique.Passes) { @@ -207,39 +366,31 @@ namespace Kav GraphicsDevice.SetVertexBuffer(FullscreenTriangle); GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); } - - DeferredPointLightEffect.EyePosition = camera.Position; - - foreach (var pointLight in pointLights) - { - PointLightRender(pointLight); - } - - DirectionalLightRender(camera, modelTransforms, directionalLight); - // return; - // GraphicsDevice.SetRenderTarget(null); - // SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied); - // SpriteBatch.Draw(DirectionalRenderTarget, Vector2.Zero, Color.White); - // SpriteBatch.End(); - - GraphicsDevice.SetRenderTarget(null); - GraphicsDevice.Clear(Color.Black); - SpriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Opaque, null, null, null, ToneMapEffect); - SpriteBatch.Draw(ColorRenderTarget, Vector2.Zero, Color.White); - SpriteBatch.End(); } - private void PointLightRender(PointLight pointLight) - { + private void PointLightRender( + PerspectiveCamera camera, + IEnumerable<(Model, Matrix)> modelTransforms, + PointLight pointLight + ) { + RenderPointShadows(camera, modelTransforms, pointLight); + + GraphicsDevice.SetRenderTarget(ColorRenderTarget); + GraphicsDevice.DepthStencilState = DepthStencilState.DepthRead; + GraphicsDevice.BlendState = BlendState.Additive; + DeferredPointLightEffect.GPosition = gPosition; DeferredPointLightEffect.GAlbedo = gAlbedo; DeferredPointLightEffect.GNormal = gNormal; DeferredPointLightEffect.GMetallicRoughness = gMetallicRoughness; + DeferredPointLightEffect.ShadowMap = PointShadowCubeMap; DeferredPointLightEffect.PointLightPosition = pointLight.Position; DeferredPointLightEffect.PointLightColor = pointLight.Color.ToVector3() * pointLight.Intensity; + DeferredPointLightEffect.FarPlane = 25f; // FIXME: magic value + foreach (var pass in DeferredPointLightEffect.CurrentTechnique.Passes) { pass.Apply(); @@ -253,28 +404,7 @@ namespace Kav IEnumerable<(Model, Matrix)> modelTransforms, DirectionalLight directionalLight ) { - // render the individual shadow cascades - var previousFarPlane = camera.NearPlane; - for (var i = 0; i < NumShadowCascades; i++) - { - var farPlane = camera.FarPlane / (MathHelper.Max((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 - ); - - // TODO: This is tightly coupled to the effect and it sucks - RenderShadowMap(shadowCamera, modelTransforms, directionalLight, i); - - previousFarPlane = farPlane; - } + RenderDirectionalShadows(camera, modelTransforms, directionalLight, DeferredDirectionalLightEffect); DeferredDirectionalLightEffect.GPosition = gPosition; DeferredDirectionalLightEffect.GAlbedo = gAlbedo; @@ -300,7 +430,7 @@ namespace Kav directionalLight.Color.ToVector3() * directionalLight.Intensity; DeferredDirectionalLightEffect.ViewMatrix = camera.View; - DeferredDirectionalLightEffect.EyePosition = Matrix.Invert(camera.View).Translation; + DeferredDirectionalLightEffect.EyePosition = camera.Position; GraphicsDevice.SetRenderTarget(ColorRenderTarget); GraphicsDevice.BlendState = BlendState.Additive; @@ -313,10 +443,89 @@ namespace Kav } } - private void RenderShadowMap( + private void DirectionalLightToonRender( + PerspectiveCamera camera, + IEnumerable<(Model, Matrix)> modelTransforms, + DirectionalLight directionalLight + ) { + RenderDirectionalShadows(camera, modelTransforms, directionalLight, Deferred_ToonEffect); + + GraphicsDevice.SetRenderTarget(ColorRenderTarget); + 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 = false; + + Deferred_ToonEffect.EyePosition = camera.Position; + Deferred_ToonEffect.DirectionalLightDirection = directionalLight.Direction; + Deferred_ToonEffect.DirectionalLightColor = + directionalLight.Color.ToVector3() * directionalLight.Intensity; + + Deferred_ToonEffect.ShadowMapOne = ShadowRenderTargets[0]; + if (NumShadowCascades > 1) + { + Deferred_ToonEffect.ShadowMapTwo = ShadowRenderTargets[1]; + } + if (NumShadowCascades > 2) + { + Deferred_ToonEffect.ShadowMapThree = ShadowRenderTargets[2]; + } + if (NumShadowCascades > 3) + { + Deferred_ToonEffect.ShadowMapFour = ShadowRenderTargets[3]; + } + + Deferred_ToonEffect.ViewMatrix = camera.View; + + foreach (EffectPass pass in Deferred_ToonEffect.CurrentTechnique.Passes) + { + pass.Apply(); + GraphicsDevice.SetVertexBuffer(FullscreenTriangle); + GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, 1); + } + } + + private void RenderDirectionalShadows( + PerspectiveCamera camera, + IEnumerable<(Model, Matrix)> modelTransforms, + DirectionalLight directionalLight, + ShadowCascadeEffect effect + ) { + // render the individual shadow cascades + var previousFarPlane = camera.NearPlane; + for (var i = 0; i < NumShadowCascades; i++) + { + var farPlane = camera.FarPlane / (MathHelper.Max((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 + ); + + // TODO: This is tightly coupled to the effect and it sucks + RenderDirectionalShadowMap(shadowCamera, modelTransforms, directionalLight, effect, i); + + effect.CascadeFarPlanes[i] = farPlane; + previousFarPlane = farPlane; + } + } + + private void RenderDirectionalShadowMap( PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, DirectionalLight directionalLight, + ShadowCascadeEffect effect, int shadowCascadeIndex ) { GraphicsDevice.SetRenderTarget(ShadowRenderTargets[shadowCascadeIndex]); @@ -357,23 +566,21 @@ namespace Kav if (shadowCascadeIndex == 0) { - DeferredDirectionalLightEffect.LightSpaceMatrixOne = lightSpaceMatrix; + effect.LightSpaceMatrixOne = lightSpaceMatrix; } else if (shadowCascadeIndex == 1) { - DeferredDirectionalLightEffect.LightSpaceMatrixTwo = lightSpaceMatrix; + effect.LightSpaceMatrixTwo = lightSpaceMatrix; } else if (shadowCascadeIndex == 2) { - DeferredDirectionalLightEffect.LightSpaceMatrixThree = lightSpaceMatrix; + effect.LightSpaceMatrixThree = lightSpaceMatrix; } else if (shadowCascadeIndex == 3) { - DeferredDirectionalLightEffect.LightSpaceMatrixFour = lightSpaceMatrix; + effect.LightSpaceMatrixFour = lightSpaceMatrix; } - DeferredDirectionalLightEffect.CascadeFarPlanes[shadowCascadeIndex] = camera.FarPlane; - foreach (var (model, transform) in modelTransforms) { foreach (var modelMesh in model.Meshes) @@ -403,62 +610,98 @@ namespace Kav } } - public void Render( - PerspectiveCamera camera, - IEnumerable<(Model, Matrix)> modelTransforms, - IEnumerable pointLights, - IEnumerable directionalLights - ) { - Render(camera.View, camera.Projection, modelTransforms, pointLights, directionalLights); - } - - private void Render( - Matrix view, - Matrix projection, + private void RenderPointShadows( + PerspectiveCamera camera, IEnumerable<(Model, Matrix)> modelTransforms, - IEnumerable pointLights, - IEnumerable directionalLights + PointLight pointLight ) { - foreach (var (model, transform) in modelTransforms) + GraphicsDevice.DepthStencilState = DepthStencilState.Default; + GraphicsDevice.BlendState = BlendState.Opaque; + + LinearDepthEffect.Projection = Matrix.CreatePerspectiveFieldOfView( + MathHelper.PiOver2, + 1, + 0.1f, + 25f // FIXME: magic value + ); + LinearDepthEffect.FarPlane = 25f; + LinearDepthEffect.LightPosition = pointLight.Position; + + foreach (CubeMapFace face in Enum.GetValues(typeof(CubeMapFace))) { - foreach (var modelMesh in model.Meshes) + GraphicsDevice.SetRenderTarget(PointShadowCubeMap, face); + + Vector3 targetDirection; + Vector3 targetUpDirection; + switch(face) { - foreach (var meshPart in modelMesh.MeshParts) + 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 + ); + + foreach (var (model, transform) in modelTransforms) + { + foreach (var modelMesh in model.Meshes) { - GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer); - GraphicsDevice.Indices = meshPart.IndexBuffer; - - if (meshPart.Effect is TransformEffect transformEffect) + foreach (var meshPart in modelMesh.MeshParts) { - transformEffect.World = transform; - transformEffect.View = view; - transformEffect.Projection = projection; - } + GraphicsDevice.SetVertexBuffer(meshPart.VertexBuffer); + GraphicsDevice.Indices = meshPart.IndexBuffer; - if (meshPart.Effect is PointLightEffect pointLightEffect) - { - int i = 0; - foreach (var pointLight in pointLights) + LinearDepthEffect.Model = transform; + + foreach (var pass in LinearDepthEffect.CurrentTechnique.Passes) { - if (i > pointLightEffect.MaxPointLights) { break; } - pointLightEffect.PointLights[i] = pointLight; - i++; + pass.Apply(); + + GraphicsDevice.DrawIndexedPrimitives( + PrimitiveType.TriangleList, + 0, + 0, + meshPart.VertexBuffer.VertexCount, + 0, + meshPart.Triangles.Length + ); } } - - foreach (var pass in meshPart.Effect.CurrentTechnique.Passes) - { - pass.Apply(); - - GraphicsDevice.DrawIndexedPrimitives( - PrimitiveType.TriangleList, - 0, - 0, - meshPart.VertexBuffer.VertexCount, - 0, - meshPart.Triangles.Length - ); - } } } } diff --git a/Resources.cs b/Resources.cs index 7791eaa..fd5daa5 100644 --- a/Resources.cs +++ b/Resources.cs @@ -10,7 +10,7 @@ namespace Kav { if (ambientLightEffect == null) { - ambientLightEffect = GetResource("DeferredPBR_AmbientLightEffect"); + ambientLightEffect = GetResource("DeferredPBR_AmbientLightEffect.fxb"); } return ambientLightEffect; } @@ -21,7 +21,7 @@ namespace Kav { if (pointLightEffect == null) { - pointLightEffect = GetResource("DeferredPBR_PointLightEffect"); + pointLightEffect = GetResource("DeferredPBR_PointLightEffect.fxb"); } return pointLightEffect; } @@ -33,7 +33,7 @@ namespace Kav { if (directionalLightEffect == null) { - directionalLightEffect = GetResource("DeferredPBR_DirectionalLightEffect"); + directionalLightEffect = GetResource("DeferredPBR_DirectionalLightEffect.fxb"); } return directionalLightEffect; } @@ -45,7 +45,7 @@ namespace Kav { if (gBufferEffect == null) { - gBufferEffect = GetResource("DeferredPBR_GBufferEffect"); + gBufferEffect = GetResource("DeferredPBR_GBufferEffect.fxb"); } return gBufferEffect; } @@ -57,19 +57,31 @@ namespace Kav { if (toneMapEffect == null) { - toneMapEffect = GetResource("ToneMapEffect"); + toneMapEffect = GetResource("ToneMapEffect.fxb"); } return toneMapEffect; } } + public static byte[] Deferred_ToonEffect + { + get + { + if (deferredToonEffect == null) + { + deferredToonEffect = GetResource("Deferred_ToonEffect.fxb"); + } + return deferredToonEffect; + } + } + public static byte[] DeferredPBREffect { get { if (deferredPBREffect == null) { - deferredPBREffect = GetResource("DeferredPBREffect"); + deferredPBREffect = GetResource("DeferredPBREffect.fxb"); } return deferredPBREffect; } @@ -81,7 +93,7 @@ namespace Kav { if (pbrEffect == null) { - pbrEffect = GetResource("PBREffect"); + pbrEffect = GetResource("PBREffect.fxb"); } return pbrEffect; } @@ -93,25 +105,66 @@ namespace Kav { if (simpleDepthEffect == null) { - simpleDepthEffect = GetResource("SimpleDepthEffect"); + simpleDepthEffect = GetResource("SimpleDepthEffect.fxb"); } return simpleDepthEffect; } } + public static byte[] LinearDepthEffect + { + get + { + if (linearDepthEffect == null) + { + linearDepthEffect = GetResource("LinearDepthEffect.fxb"); + } + return linearDepthEffect; + } + } + + public static byte[] SkyboxEffect + { + get + { + if (skyboxEffect == null) + { + skyboxEffect = GetResource("SkyboxEffect.fxb"); + } + return skyboxEffect; + } + } + + public static byte[] UnitCubeModel + { + get + { + if (unitCubeModel == null) + { + unitCubeModel = GetResource("UnitCube.glb"); + } + return unitCubeModel; + } + } + private static byte[] ambientLightEffect; private static byte[] pointLightEffect; private static byte[] directionalLightEffect; private static byte[] gBufferEffect; private static byte[] toneMapEffect; + private static byte[] deferredToonEffect; private static byte[] deferredPBREffect; private static byte[] pbrEffect; private static byte[] simpleDepthEffect; + private static byte[] linearDepthEffect; + private static byte[] skyboxEffect; + + private static byte[] unitCubeModel; private static byte[] GetResource(string name) { Stream stream = typeof(Resources).Assembly.GetManifestResourceStream( - "Kav.Resources." + name + ".fxb" + "Kav.Resources." + name ); using (MemoryStream ms = new MemoryStream()) {