From f0ffc1746c5052279d6820cff69a85b701a3aff1 Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Thu, 30 Jul 2020 17:57:57 -0700 Subject: [PATCH] start implementing PBR effect --- Effects/PBREffect.cs | 160 +++++++++++++++++++++++++++++++ Effects/pbr.fx | 217 +++++++++++++++++++++++++++++++++++++++++++ Effects/pbr.fxo | Bin 0 -> 7240 bytes Importer.cs | 4 +- MeshPart.cs | 4 +- 5 files changed, 380 insertions(+), 5 deletions(-) create mode 100644 Effects/PBREffect.cs create mode 100644 Effects/pbr.fx create mode 100644 Effects/pbr.fxo diff --git a/Effects/PBREffect.cs b/Effects/PBREffect.cs new file mode 100644 index 0000000..be79d33 --- /dev/null +++ b/Effects/PBREffect.cs @@ -0,0 +1,160 @@ +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; + +namespace Smuggler +{ + public struct PBRLight + { + public Vector3 direction; + public Vector3 radiance; + + public PBRLight(Vector3 direction, Vector3 radiance) + { + this.direction = direction; + this.radiance = radiance; + } + } + + public class PBRLightCollection + { + readonly EffectParameter directionParam; + readonly EffectParameter radianceParam; + + private readonly Vector3[] directions = new Vector3[PBREffect.NUM_LIGHTS]; + private readonly Vector3[] radiances = new Vector3[PBREffect.NUM_LIGHTS]; + + public PBRLightCollection(EffectParameter directionParam, EffectParameter radianceParam) + { + this.directionParam = directionParam; + this.radianceParam = radianceParam; + } + + public PBRLightCollection(PBRLightCollection other) + { + for (int i = 0; i < PBREffect.NUM_LIGHTS; i++) + { + directions[i] = other.directions[i]; + radiances[i] = other.radiances[i]; + } + } + + public PBRLight this[int index] + { + get + { + return new PBRLight(directions[index], radiances[index]); + } + + set + { + directions[index] = value.direction; + radiances[index] = value.radiance; + directionParam.SetValue(directions); + radianceParam.SetValue(radiances); + } + } + } + + public class PBREffect : Effect + { + public static readonly int NUM_LIGHTS = 3; + + readonly EffectParameter viewProjectionParam; + readonly EffectParameter sceneRotationParam; + readonly EffectParameter eyePositionParam; + readonly EffectParameter specularTextureLevelsParam; + + readonly EffectParameter albedoTextureParam; + + Matrix world = Matrix.Identity; + Matrix view = Matrix.Identity; + Matrix projection = Matrix.Identity; + Vector3 eyePosition = Vector3.Zero; + int specularTextureLevels = 0; + + public Matrix World + { + get { return world; } + set + { + world = value; + sceneRotationParam.SetValue(world); + } + } + + public Matrix View + { + get { return view; } + set + { + view = value; + viewProjectionParam.SetValue(projection * view); + eyePositionParam.SetValue(Matrix.Invert(view).Backward); + } + } + + public Matrix Projection + { + get { return projection; } + set + { + projection = value; + viewProjectionParam.SetValue(projection * view); + } + } + + public PBRLightCollection Lights { get; } + + public int SpecularTextureLevels + { + get { return specularTextureLevels; } + set + { + specularTextureLevels = value; + specularTextureLevelsParam.SetValue(specularTextureLevels); + } + } + + public Texture2D AlbedoTexture + { + get { return albedoTextureParam.GetValueTexture2D(); } + set { albedoTextureParam.SetValue(value); } + } + + public PBREffect(GraphicsDevice graphicsDevice, byte[] effectCode) : base(graphicsDevice, effectCode) + { + viewProjectionParam = Parameters["viewProjectionMatrix"]; + sceneRotationParam = Parameters["sceneRotationMatrix"]; + + eyePositionParam = Parameters["eyePosition"]; + specularTextureLevelsParam = Parameters["specularTextureLevels"]; + + Lights = new PBRLightCollection(Parameters["directions"], Parameters["radiance"]); + } + + protected PBREffect(PBREffect cloneSource) : base(cloneSource) + { + viewProjectionParam = Parameters["viewProjectionMatrix"]; + sceneRotationParam = Parameters["sceneRotationMatrix"]; + + eyePositionParam = Parameters["eyePosition"]; + specularTextureLevelsParam = Parameters["specularTextureLevels"]; + + World = cloneSource.World; + View = cloneSource.View; + Projection = cloneSource.Projection; + Lights = new PBRLightCollection(cloneSource.Lights); + } + + public override Effect Clone() + { + return new PBREffect(this); + } + + // FIXME: do param applications here for performance + protected override void OnApply() + { + base.OnApply(); + } + } +} diff --git a/Effects/pbr.fx b/Effects/pbr.fx new file mode 100644 index 0000000..71b2b02 --- /dev/null +++ b/Effects/pbr.fx @@ -0,0 +1,217 @@ +// Physically Based Rendering +// Copyright (c) 2017-2018 MichaƂ Siejak + +// Physically Based shading model: Lambetrtian diffuse BRDF + Cook-Torrance microfacet specular BRDF + IBL for ambient. + +// This implementation is based on "Real Shading in Unreal Engine 4" SIGGRAPH 2013 course notes by Epic Games. +// See: http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf + +static const float PI = 3.141592; +static const float Epsilon = 0.00001; + +static const uint NumLights = 3; + +// Constant normal incidence Fresnel factor for all dielectrics. +static const float3 Fdielectric = 0.04; + +// UNIFORM CONSTANTS +float4x4 viewProjectionMatrix; +float4x4 sceneRotationMatrix; + +float3 direction[NumLights]; +float3 radiance[NumLights]; + +float3 eyePosition; +int specularTextureLevels; + +struct VertexShaderInput +{ + float3 position : POSITION; + float3 normal : NORMAL; + float3 tangent : TANGENT; + float3 binormal : BINORMAL; + float2 texcoord : TEXCOORD; +}; + +struct PixelShaderInput +{ + float4 pixelPosition : SV_POSITION; + float3 position : POSITION1; + float2 texcoord : TEXCOORD0; + float3 T : TEXCOORD1; + float3 B : TEXCOORD2; + float3 N : TEXCOORD3; +}; + +sampler albedoTexture : register(s0); +sampler normalTexture : register(s1); +sampler metalnessTexture : register(s2); +sampler roughnessTexture : register(s3); +sampler specularTexture : register(s4); +sampler irradianceTexture : register(s5); +sampler specularBRDF_LUT : register(s6); + +// GGX/Towbridge-Reitz normal distribution function. +// Uses Disney's reparametrization of alpha = roughness^2. +float ndfGGX(float cosLh, float roughness) +{ + float alpha = roughness * roughness; + float alphaSq = alpha * alpha; + + float denom = (cosLh * cosLh) * (alphaSq - 1.0) + 1.0; + return alphaSq / (PI * denom * denom); +} + +// Single term for separable Schlick-GGX below. +float gaSchlickG1(float cosTheta, float k) +{ + return cosTheta / (cosTheta * (1.0 - k) + k); +} + +// Schlick-GGX approximation of geometric attenuation function using Smith's method. +float gaSchlickGGX(float cosLi, float cosLo, float roughness) +{ + float r = roughness + 1.0; + float k = (r * r) / 8.0; // Epic suggests using this roughness remapping for analytic lights. + return gaSchlickG1(cosLi, k) * gaSchlickG1(cosLo, k); +} + +// Shlick's approximation of the Fresnel factor. +float3 fresnelSchlick(float3 F0, float cosTheta) +{ + return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); +} + +// Vertex shader +PixelShaderInput main_vs(VertexShaderInput vin) +{ + PixelShaderInput vout; + vout.position = mul(sceneRotationMatrix, float4(vin.position, 1.0)).xyz; + vout.texcoord = float2(vin.texcoord.x, 1.0-vin.texcoord.y); + + // Pass tangent space basis vectors (for normal mapping). + float3 worldTangent = mul(vin.tangent, (float3x3) sceneRotationMatrix); + vout.T = normalize(worldTangent); + + float3 worldBinormal = mul(vin.binormal, (float3x3) sceneRotationMatrix); + vout.B = normalize(worldBinormal); + + float3 worldNormal = mul(vin.normal, (float3x3) sceneRotationMatrix); + vout.N = normalize(worldNormal); + + float4x4 mvpMatrix = mul(viewProjectionMatrix, sceneRotationMatrix); + vout.pixelPosition = mul(mvpMatrix, float4(vin.position, 1.0)); + return vout; +} + +// Pixel shader +float4 main_ps(PixelShaderInput pin) : SV_Target0 +{ + // Sample input textures to get shading model params. + float3 albedo = tex2D(albedoTexture, pin.texcoord).rgb; + float metalness = tex2D(metalnessTexture, pin.texcoord).r; + float roughness = tex2D(roughnessTexture, pin.texcoord).r; + + // Outgoing light direction (vector from world-space fragment position to the "eye"). + float3 Lo = normalize(eyePosition - pin.position); + + // tranpose to transform tangent space => world + float3x3 TBN = transpose(float3x3(normalize(pin.T), normalize(pin.B), normalize(pin.N))); + + // Get current fragment's normal and transform to world space. + float3 tangentNormal = normalize(2.0 * tex2D(normalTexture, pin.texcoord).rgb - 1.0); + + // world normal + float3 N = normalize(mul(TBN, tangentNormal)); + + // Angle between surface normal and outgoing light direction. + float cosLo = max(0.0, dot(N, Lo)); + + // Specular reflection vector. + float3 Lr = 2.0 * cosLo * N - Lo; + + // Fresnel reflectance at normal incidence (for metals use albedo color). + float3 F0 = lerp(Fdielectric, albedo, metalness); + + // Direct lighting calculation for analytical lights. + float3 directLighting = 0.0; + for(uint i=0; iCS;8OLWo>|GlZ(-KN31uQiZMM9C&7SUY5I3XmGD;X!2a;TIp_CgjpUTfD0 zLF(a+#IX_!^nk==E%kux9y#;?DiT8CSP3L@jL>`Nu?G%~RQcuqo0(^KcGvl65;6Ad z`+UvwHP6gD@04bW!?!EWHy&m6H$4`9s(R3$w0%hmZnm1gSnafb-rU%2wYOhy>~>nW z&}(+@Bu1W=-Q&{P-Dqw%*V?;{#4I*T>WloSfg8;e9`Z9Q{${HaLP^GEkEw4)Iy#Nb zR%3gkiD~oY{irXc0b83B(EMd{wcTwIou5B=A0^`e4Sh~lmCMBkHtFB?Gr4%wPyviIyM7CXDrS|CM0S2Zn~G(&Yi!y zvVIvhdB*Pen!&aCBk0qjtv0&d(~-BjyyiYH7(Lj=EV>rdt-U4S_lD^CvHF}%u;3-Z zZzOn6@OugV55akYSLNJ^BzKE~&n5U9f_D=9ZNdEn|Fhr^6Z|8=pCY8J+SNm`{S4vY8Q*~>pzenIE~N=5se9^h&V z)YK;eP*>7wsYeAT^z`DQpA`J6`lhE%OHT3YCsQYv6*nc?bM9RjYJc*yvS_o8+|urN zT1m9&!CL%8w6aEHo1A0gxSmi-?wETC2h?8kH0(a_Rw9i#sHhT3?pe1LX>aLSb~VwQ zdq;34V04`x98}$$`22-7+_Mf2O5SepC$c}&Bi(mQm)RLtG`Y4WeVf<|4kl$r_m)Y% ztzf6I=2I*kH?emdp?_jcKS0KIiSZnpj>wPu>%jrDmCnUZ_v$NG7v1ZvjZVAU{^{<* zOKaa+xUh0*W#Q7b#%8m#aJGGYr?n;E%&C{poI3NJmruQN`t)h{W9O#oZoYqK{^FAI z{5{S5_tlr6&xj8E-rv9ZUAOwTA2AQmXFc65iT44t*?+(Imv8uzFJ;cvdcad+DrXTc zWvZW{RW2SjlVry8LzeuPIJ&V$3tsUzJYd2l;(3 zXMDzJX=zw3n|66$_;2bSuX{Q8HSafg;cej?ba3h1Lznr+*pjP9-||d*lVU3gFBHpu zKlon9rtfL&6~zv}?8*Z^zL#B9_`2`pVki@6$OM~(WcFoeeb4M*m!9JCv9I@%@{gaR zJnSH=8u0NECoyR!%`{hvKgYL4*_jh&L)flJoxoi}B(5BomuLD1(UC-;SNiM{7PdxM-6Fv97lBrKx+Xtv&2gW41J zPRf^=CYSUbTVCt9lC%ODr^gR^(-N1PbIUB)^HISTcueJsk5w{@QfY8V;iWUvmv z<)5b=k*mQvu-t@Pgf*MiF!@C$#2Vl*f5aNc)H>)=gN;_z`qkF4B1o zv|&ChUaOmckLHrvv-d)}=USbv5B8<>U4f2(@B7*fIAg%qecgn$7RKBDS=Ras{^j0o zT%Uuw3Ha##$vm*eSc~kRX>KeRz-xu-qRdtMi8tvv|p8XGtUzb+b zet4-=zHe{7?%%imwX5PS4+F?<4XIC!zdz-`JwbJ=3D`&}F)vfl)BaJ8b zIW(gI*Z5HzmwhIXy{y?nHrQTTFda4?apN1TePKec)|hXMhfgydRd;{Asci6JG`24W zRo>sZ2tSHmi;XAw8PCJ%p4^geE#k)QFL#l76mMKp2W zoP90v;c4dV>xtep50IZEXS!GMjrx7uoROc8)#lZBLvk0y-H{7-MjvYzj8X|6z8CNr7{2M&nPo&bc&Z!xQFN1oaoy50q8mKb4gM&)$*JiM vxt&*@)7<(qVBd}8cAm4MXv5s9CZs3N^bOc2|IDY(Z#|tkmAn7+sjL41pLhSJ literal 0 HcmV?d00001 diff --git a/Importer.cs b/Importer.cs index 1369802..43ae13c 100644 --- a/Importer.cs +++ b/Importer.cs @@ -147,9 +147,7 @@ namespace Smuggler /* TODO: We need a new Effect subclass to support some GLTF features */ - var effect = new BasicEffect(graphicsDevice); - effect.EnableDefaultLighting(); - effect.PreferPerPixelLighting = true; + var effect = new PBREffect(graphicsDevice, File.ReadAllBytes("Effects/pbr.fxo")); return new MeshPart( vertexBuffer, diff --git a/MeshPart.cs b/MeshPart.cs index b68bfab..bc019ea 100644 --- a/MeshPart.cs +++ b/MeshPart.cs @@ -8,10 +8,10 @@ namespace Smuggler public IndexBuffer IndexBuffer { get; } public VertexBuffer VertexBuffer { get; } public Triangle[] Triangles { get; } - public BasicEffect Effect { get; } + public PBREffect Effect { get; } public Vector3[] Positions { get; } - public MeshPart(VertexBuffer vertexBuffer, IndexBuffer indexBuffer, Vector3[] positions, Triangle[] triangles, BasicEffect effect) + public MeshPart(VertexBuffer vertexBuffer, IndexBuffer indexBuffer, Vector3[] positions, Triangle[] triangles, PBREffect effect) { VertexBuffer = vertexBuffer; IndexBuffer = indexBuffer;