From 964e02c5705db9dd84f11e73dcc763481e50fda2 Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Mon, 3 Aug 2020 17:35:42 -0700 Subject: [PATCH] fist attempt at multiple pixel shader paths --- Effects/PBREffect.cs | 62 ++++++++++- Effects/PBREffect.fx | 250 +++++++++++++++++++----------------------- Effects/PBREffect.fxb | Bin 7940 -> 37732 bytes 3 files changed, 168 insertions(+), 144 deletions(-) diff --git a/Effects/PBREffect.cs b/Effects/PBREffect.cs index 5920c2e..0d477b3 100644 --- a/Effects/PBREffect.cs +++ b/Effects/PBREffect.cs @@ -67,6 +67,8 @@ namespace Smuggler EffectParameter eyePositionParam; + EffectParameter shaderIndexParam; + Matrix world = Matrix.Identity; Matrix view = Matrix.Identity; Matrix projection = Matrix.Identity; @@ -77,6 +79,10 @@ namespace Smuggler float roughness; float ao; + bool albedoTextureEnabled = false; + bool metallicRoughnessMapEnabled = false; + bool normalMapEnabled = false; + // FIXME: lazily set properties for performance public Matrix World @@ -175,13 +181,22 @@ namespace Smuggler public Texture2D AlbedoTexture { get { return albedoTextureParam.GetValueTexture2D(); } - set { albedoTextureParam.SetValue(value); } + set + { + albedoTextureParam.SetValue(value); + albedoTextureEnabled = value != null; + System.Console.WriteLine(albedoTextureEnabled); + } } public Texture2D NormalTexture { get { return normalTextureParam.GetValueTexture2D(); } - set { normalTextureParam.SetValue(value); } + set + { + normalTextureParam.SetValue(value); + normalMapEnabled = value != null; + } } public Texture2D EmissionTexture @@ -199,7 +214,11 @@ namespace Smuggler public Texture2D MetallicRoughnessTexture { get { return metallicRoughnessTextureParam.GetValueTexture2D(); } - set { metallicRoughnessTextureParam.SetValue(value); } + set + { + metallicRoughnessTextureParam.SetValue(value); + metallicRoughnessMapEnabled = value != null; + } } public TextureCube EnvDiffuseTexture @@ -271,7 +290,40 @@ namespace Smuggler // FIXME: do param applications here for performance protected override void OnApply() { - base.OnApply(); + int shaderIndex = 0; + + if (albedoTextureEnabled && metallicRoughnessMapEnabled && normalMapEnabled) + { + System.Console.WriteLine("all enabled"); + 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; + } + + System.Console.WriteLine(shaderIndex); + shaderIndexParam.SetValue(shaderIndex); } void CacheEffectParameters(PBREffect cloneSource) @@ -298,6 +350,8 @@ namespace Smuggler aoParam = Parameters["AO"]; eyePositionParam = Parameters["EyePosition"]; + + shaderIndexParam = Parameters["ShaderIndex"]; } } } diff --git a/Effects/PBREffect.fx b/Effects/PBREffect.fx index 1ea26fa..08fabd7 100644 --- a/Effects/PBREffect.fx +++ b/Effects/PBREffect.fx @@ -121,15 +121,15 @@ float3 GetNormalFromMap(float3 worldPos, float2 texCoords, float3 normal) return normalize(mul(tangentNormal, TBN)); } -// The case where we have no texture maps for any PBR data -float4 None(PixelShaderInput input) : SV_TARGET0 -{ - float3 albedo = AlbedoValue; - float metallic = MetallicValue; - float roughness = RoughnessValue; - - float3 N = normalize(input.NormalWS); - float3 V = normalize(EyePosition - input.PositionWS); +float4 ComputeColor( + float3 worldPosition, + float3 worldNormal, + float3 albedo, + float metallic, + float roughness +) { + float3 V = normalize(EyePosition - worldPosition); + float3 N = worldNormal; float3 F0 = float3(0.04, 0.04, 0.04); F0 = lerp(F0, albedo, metallic); @@ -138,7 +138,7 @@ float4 None(PixelShaderInput input) : SV_TARGET0 for (int i = 0; i < 4; i++) { - float3 lightDir = LightPositions[i] - input.PositionWS; + float3 lightDir = LightPositions[i] - worldPosition; float3 L = normalize(lightDir); float3 H = normalize(V + L); @@ -173,166 +173,136 @@ float4 None(PixelShaderInput input) : SV_TARGET0 return float4(color, 1.0); } +// The case where we have no texture maps for any PBR data +float4 None(PixelShaderInput input) : SV_TARGET0 +{ + return ComputeColor( + input.PositionWS, + input.NormalWS, + AlbedoValue, + MetallicValue, + RoughnessValue + ); +} + float4 AlbedoMapPS(PixelShaderInput input) : SV_TARGET { - float3 albedo = SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord).rgb; - float metallic = MetallicValue; - float roughness = RoughnessValue; + float3 albedo = pow(SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord), 2.2).rgb; - float3 N = normalize(input.NormalWS); - float3 V = normalize(EyePosition - input.PositionWS); + return ComputeColor( + input.PositionWS, + input.NormalWS, + albedo, + MetallicValue, + RoughnessValue + ); +} - float3 F0 = float3(0.04, 0.04, 0.04); - F0 = lerp(F0, albedo, metallic); +float4 MetallicRoughnessPS(PixelShaderInput input) : SV_TARGET +{ + float2 metallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord).rg; - float3 Lo = float3(0.0, 0.0, 0.0); + return ComputeColor( + input.PositionWS, + input.NormalWS, + AlbedoValue, + metallicRoughness.r, + metallicRoughness.g + ); +} - for (int i = 0; i < 4; i++) - { - float3 lightDir = LightPositions[i] - input.PositionWS; - float3 L = normalize(lightDir); - float3 H = normalize(V + L); +float4 NormalPS(PixelShaderInput input) : SV_TARGET +{ + float3 normal = GetNormalFromMap(input.PositionWS, input.TexCoord, input.NormalWS); - float distance = length(lightDir); - float attenuation = 1.0 / (distance * distance); - float3 radiance = LightColors[i] * attenuation; - - float NDF = DistributionGGX(N, H, roughness); - float G = GeometrySmith(N, V, L, roughness); - float3 F = FresnelSchlick(max(dot(H, V), 0.0), F0); - - float3 numerator = NDF * G * F; - float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0); - float3 specular = numerator / max(denominator, 0.001); - - float3 kS = F; - float3 kD = float3(1.0, 1.0, 1.0) - kS; - - kD *= 1.0 - metallic; - - float NdotL = max(dot(N, L), 0.0); - Lo += (kD * albedo / PI + specular) * radiance * NdotL; - } - - float3 ambient = float3(0.03, 0.03, 0.03) * albedo * AO; - float3 color = ambient + Lo; - - color = color / (color + float3(1.0, 1.0, 1.0)); - float exposureConstant = 1.0 / 2.2; - color = pow(color, float3(exposureConstant, exposureConstant, exposureConstant)); - - return float4(color, 1.0); + return ComputeColor( + input.PositionWS, + normal, + AlbedoValue, + MetallicValue, + RoughnessValue + ); } float4 AlbedoMetallicRoughnessMapPS(PixelShaderInput input) : SV_TARGET { float3 albedo = pow(SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord), 2.2).rgb; float2 metallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord).rg; - float metallic = metallicRoughness.r; - float roughness = metallicRoughness.g; - float3 N = normalize(input.NormalWS); - float3 V = normalize(EyePosition - input.PositionWS); + return ComputeColor( + input.PositionWS, + input.NormalWS, + albedo, + metallicRoughness.r, + metallicRoughness.g + ); +} - float3 F0 = float3(0.04, 0.04, 0.04); - F0 = lerp(F0, albedo, metallic); +float4 AlbedoNormalPS(PixelShaderInput input) : SV_TARGET +{ + float3 albedo = pow(SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord), 2.2).rgb; + float3 normal = GetNormalFromMap(input.PositionWS, input.TexCoord, input.NormalWS); - float3 Lo = float3(0.0, 0.0, 0.0); + return ComputeColor( + input.PositionWS, + normal, + albedo, + MetallicValue, + RoughnessValue + ); +} - for (int i = 0; i < 4; i++) - { - float3 lightDir = LightPositions[i] - input.PositionWS; - float3 L = normalize(lightDir); - float3 H = normalize(V + L); +float4 MetallicRoughnessNormalPS(PixelShaderInput input) : SV_TARGET +{ + float2 metallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord).rg; + float3 normal = GetNormalFromMap(input.PositionWS, input.TexCoord, input.NormalWS); - float distance = length(lightDir); - float attenuation = 1.0 / (distance * distance); - float3 radiance = LightColors[i] * attenuation; - - float NDF = DistributionGGX(N, H, roughness); - float G = GeometrySmith(N, V, L, roughness); - float3 F = FresnelSchlick(max(dot(H, V), 0.0), F0); - - float3 numerator = NDF * G * F; - float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0); - float3 specular = numerator / max(denominator, 0.001); - - float3 kS = F; - float3 kD = float3(1.0, 1.0, 1.0) - kS; - - kD *= 1.0 - metallic; - - float NdotL = max(dot(N, L), 0.0); - Lo += (kD * albedo / PI + specular) * radiance * NdotL; - } - - float3 ambient = float3(0.03, 0.03, 0.03) * albedo * AO; - float3 color = ambient + Lo; - - color = color / (color + float3(1.0, 1.0, 1.0)); - float exposureConstant = 1.0 / 2.2; - color = pow(color, float3(exposureConstant, exposureConstant, exposureConstant)); - - return float4(color, 1.0); + return ComputeColor( + input.PositionWS, + normal, + AlbedoValue, + metallicRoughness.r, + metallicRoughness.g + ); } float4 AlbedoMetallicRoughnessNormalMapPS(PixelShaderInput input) : SV_TARGET { float3 albedo = pow(SAMPLE_TEXTURE(AlbedoTexture, input.TexCoord), 2.2).rgb; float2 metallicRoughness = SAMPLE_TEXTURE(MetallicRoughnessTexture, input.TexCoord).rg; - float metallic = metallicRoughness.r; - float roughness = metallicRoughness.g; + float3 normal = GetNormalFromMap(input.PositionWS, input.TexCoord, input.NormalWS); - float3 N = GetNormalFromMap(input.PositionWS, input.TexCoord, input.NormalWS); - float3 V = normalize(EyePosition - input.PositionWS); - - float3 F0 = float3(0.04, 0.04, 0.04); - F0 = lerp(F0, albedo, metallic); - - float3 Lo = float3(0.0, 0.0, 0.0); - - for (int i = 0; i < 4; i++) - { - float3 lightDir = LightPositions[i] - input.PositionWS; - float3 L = normalize(lightDir); - float3 H = normalize(V + L); - - float distance = length(lightDir); - float attenuation = 1.0 / (distance * distance); - float3 radiance = LightColors[i] * attenuation; - - float NDF = DistributionGGX(N, H, roughness); - float G = GeometrySmith(N, V, L, roughness); - float3 F = FresnelSchlick(max(dot(H, V), 0.0), F0); - - float3 numerator = NDF * G * F; - float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0); - float3 specular = numerator / max(denominator, 0.001); - - float3 kS = F; - float3 kD = float3(1.0, 1.0, 1.0) - kS; - - kD *= 1.0 - metallic; - - float NdotL = max(dot(N, L), 0.0); - Lo += (kD * albedo / PI + specular) * radiance * NdotL; - } - - float3 ambient = float3(0.03, 0.03, 0.03) * albedo * AO; - float3 color = ambient + Lo; - - color = color / (color + float3(1.0, 1.0, 1.0)); - float exposureConstant = 1.0 / 2.2; - color = pow(color, float3(exposureConstant, exposureConstant, exposureConstant)); - - return float4(color, 1.0); + return ComputeColor( + input.PositionWS, + normal, + albedo, + metallicRoughness.r, + metallicRoughness.g + ); } +PixelShader PSArray[8] = +{ + compile ps_3_0 None(), + + compile ps_3_0 AlbedoMapPS(), + compile ps_3_0 MetallicRoughnessPS(), + compile ps_3_0 NormalPS(), + + compile ps_3_0 AlbedoMetallicRoughnessMapPS(), + compile ps_3_0 AlbedoNormalPS(), + compile ps_3_0 MetallicRoughnessNormalPS(), + + compile ps_3_0 AlbedoMetallicRoughnessNormalMapPS() +}; + +int ShaderIndex = 0; + Technique PBR { - Pass Pass1 + Pass { VertexShader = compile vs_3_0 main_vs(); - PixelShader = compile ps_3_0 AlbedoMetallicRoughnessNormalMapPS(); + PixelShader = (PSArray[ShaderIndex]); } } diff --git a/Effects/PBREffect.fxb b/Effects/PBREffect.fxb index f901f3efcd0aefef3522815494b3ebe31d5492d9..a4ca2d7cf6fe43feeab8564aee8983a09a57cc8c 100644 GIT binary patch literal 37732 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 ZuW95?mCC@;7N3B-cj>DOCzjiR{~rKB*}DJ$ delta 167 zcmaE|jH$(rjgj;JzXR+W+3MI?1sNC^1SSRwvkCzj{6K~x8;}u@SX^ufq**|0ryvF? zAk7HG+(0b6*^y&0quma6AR7ok!fT*xCLntfNDv4bfMPX3JQ>1em;q!@0b&jypNnDg jtiB*dhRM3MT1*O0CKqx^Pgal$*nFh+hQQ_<{XNV8On@G_