From 466f35b1eea2dbd0e2c4d4db1860e5da2604ae4b Mon Sep 17 00:00:00 2001 From: Evan Hemsley Date: Fri, 31 Jul 2020 00:20:07 -0700 Subject: [PATCH] rewrite PBREffect --- Effects/Macros.fxh | 57 +++++ Effects/PBREffect.cs | 276 ++++++++++++++++------ Effects/pbr.fx | 551 +++++++++++++++++++++++++++++-------------- Effects/pbr.fxo | Bin 7240 -> 8060 bytes 4 files changed, 629 insertions(+), 255 deletions(-) create mode 100644 Effects/Macros.fxh diff --git a/Effects/Macros.fxh b/Effects/Macros.fxh new file mode 100644 index 0000000..91d1702 --- /dev/null +++ b/Effects/Macros.fxh @@ -0,0 +1,57 @@ +//----------------------------------------------------------------------------- +// Macros.fxh +// +// Microsoft XNA Community Game Platform +// Copyright (C) Microsoft Corporation. All rights reserved. +//----------------------------------------------------------------------------- + +#ifdef SM4 + + +// Macros for targetting shader model 4.0 (DX11) + +#define BEGIN_CONSTANTS cbuffer Parameters : register(b0) { +#define MATRIX_CONSTANTS }; cbuffer ProjectionMatrix : register(b1) { +#define END_CONSTANTS }; + +#define _vs(r) +#define _ps(r) +#define _cb(r) : packoffset(r) + +#define DECLARE_TEXTURE(Name, index) \ + Texture2D Name : register(t##index); \ + sampler Name##Sampler : register(s##index) + +#define DECLARE_CUBEMAP(Name, index) \ + TextureCube Name : register(t##index); \ + sampler Name##Sampler : register(s##index) + +#define SAMPLE_TEXTURE(Name, texCoord) Name.Sample(Name##Sampler, texCoord) +#define SAMPLE_CUBEMAP(Name, texCoord) Name.Sample(Name##Sampler, texCoord) +#define SAMPLE_CUBEMAP_LOD(Name, texCoord) Name.SampleLevel(Name##Sampler, texCoord.xyz, texCoord.w) + +#else + + +// Macros for targetting shader model 2.0 (DX9) + +#define BEGIN_CONSTANTS +#define MATRIX_CONSTANTS +#define END_CONSTANTS + +#define _vs(r) : register(vs, r) +#define _ps(r) : register(ps, r) +#define _cb(r) + +#define DECLARE_TEXTURE(Name, index) \ + texture2D Name; \ + sampler Name##Sampler : register(s##index) = sampler_state { Texture = (Name); }; + +#define DECLARE_CUBEMAP(Name, index) \ + textureCUBE Name; \ + sampler Name##Sampler : register(s##index) = sampler_state { Texture = (Name); }; + +#define SAMPLE_TEXTURE(Name, texCoord) tex2D(Name##Sampler, texCoord) +#define SAMPLE_CUBEMAP(Name, texCoord) texCUBE(Name##Sampler, texCoord) +#define SAMPLE_CUBEMAP_LOD(Name, texCoord) texCUBElod(Name##Sampler, texCoord) +#endif diff --git a/Effects/PBREffect.cs b/Effects/PBREffect.cs index be79d33..b50d027 100644 --- a/Effects/PBREffect.cs +++ b/Effects/PBREffect.cs @@ -6,71 +6,48 @@ namespace Smuggler public struct PBRLight { public Vector3 direction; - public Vector3 radiance; + public Vector3 colour; - public PBRLight(Vector3 direction, Vector3 radiance) + public PBRLight(Vector3 direction, Vector3 colour) { 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); - } + this.colour = colour; } } public class PBREffect : Effect { - public static readonly int NUM_LIGHTS = 3; + readonly EffectParameter modelParam; + readonly EffectParameter viewParam; + readonly EffectParameter projectionParam; + readonly EffectParameter lightDirParam; + readonly EffectParameter lightColourParam; + readonly EffectParameter normalScaleParam; + readonly EffectParameter emissiveFactorParam; + readonly EffectParameter occlusionStrengthParam; + readonly EffectParameter metallicRoughnessValuesParam; + readonly EffectParameter baseColorFactorParam; + readonly EffectParameter cameraLookParam; - readonly EffectParameter viewProjectionParam; - readonly EffectParameter sceneRotationParam; - readonly EffectParameter eyePositionParam; - readonly EffectParameter specularTextureLevelsParam; - - readonly EffectParameter albedoTextureParam; + readonly EffectParameter baseColourTextureParam; + readonly EffectParameter normalTextureParam; + readonly EffectParameter emissionTextureParam; + readonly EffectParameter occlusionTextureParam; + readonly EffectParameter metallicRoughnessTextureParam; + readonly EffectParameter envDiffuseTextureParam; + readonly EffectParameter brdfLutTextureParam; + readonly EffectParameter envSpecularTextureParam; Matrix world = Matrix.Identity; Matrix view = Matrix.Identity; Matrix projection = Matrix.Identity; - Vector3 eyePosition = Vector3.Zero; - int specularTextureLevels = 0; + PBRLight light = new PBRLight(); + float normalScale = 1; + Vector3 emissiveFactor; + float occlusionStrength; + Vector2 metallicRoughnessValue; + Vector4 baseColorFactor; + Vector3 cameraLook; public Matrix World { @@ -78,7 +55,7 @@ namespace Smuggler set { world = value; - sceneRotationParam.SetValue(world); + modelParam.SetValue(world); } } @@ -88,8 +65,7 @@ namespace Smuggler set { view = value; - viewProjectionParam.SetValue(projection * view); - eyePositionParam.SetValue(Matrix.Invert(view).Backward); + viewParam.SetValue(view); } } @@ -99,51 +75,201 @@ namespace Smuggler set { projection = value; - viewProjectionParam.SetValue(projection * view); + projectionParam.SetValue(value); } } - public PBRLightCollection Lights { get; } - - public int SpecularTextureLevels + public PBRLight Light { - get { return specularTextureLevels; } + get { return light; } + set + { + light = value; + lightDirParam.SetValue(light.direction); + lightColourParam.SetValue(light.colour); + } + } + + public float NormalScale + { + get { return normalScale; } + set + { + normalScale = value; + normalScaleParam.SetValue(normalScale); + } + } + + public Vector3 EmissiveFactor + { + get { return emissiveFactor; } + set + { + emissiveFactor = value; + emissiveFactorParam.SetValue(emissiveFactor); + } + } + + public float OcclusionStrength + { + get { return occlusionStrength; } + set + { + occlusionStrength = value; + occlusionStrengthParam.SetValue(occlusionStrength); + } + } + + public Vector2 MetallicRoughnessValue + { + get { return metallicRoughnessValue; } set { - specularTextureLevels = value; - specularTextureLevelsParam.SetValue(specularTextureLevels); + metallicRoughnessValue = value; + metallicRoughnessValuesParam.SetValue(metallicRoughnessValue); } } - public Texture2D AlbedoTexture + public Vector4 BaseColorFactor { - get { return albedoTextureParam.GetValueTexture2D(); } - set { albedoTextureParam.SetValue(value); } + get { return baseColorFactor; } + set + { + baseColorFactor = value; + baseColorFactorParam.SetValue(baseColorFactor); + } + } + + public Vector3 CameraLook + { + get { return cameraLook; } + set + { + cameraLook = value; + cameraLookParam.SetValue(cameraLook); + } + } + + public Texture2D BaseColourTexture + { + get { return baseColourTextureParam.GetValueTexture2D(); } + set { baseColourTextureParam.SetValue(value); } + } + + public Texture2D NormalTexture + { + get { return normalTextureParam.GetValueTexture2D(); } + set { normalTextureParam.SetValue(value); } + } + + public Texture2D EmissionTexture + { + get { return emissionTextureParam.GetValueTexture2D(); } + set { emissionTextureParam.SetValue(value); } + } + + public Texture2D OcclusionTexture + { + get { return occlusionTextureParam.GetValueTexture2D(); } + set { occlusionTextureParam.SetValue(value); } + } + + public Texture2D MetallicRoughnessTexture + { + get { return metallicRoughnessTextureParam.GetValueTexture2D(); } + set { metallicRoughnessTextureParam.SetValue(value); } + } + + public TextureCube EnvDiffuseTexture + { + get { return envDiffuseTextureParam.GetValueTextureCube(); } + set { envDiffuseTextureParam.SetValue(value); } + } + + public Texture2D BRDFLutTexture + { + get { return brdfLutTextureParam.GetValueTexture2D(); } + set { brdfLutTextureParam.SetValue(value); } + } + + public TextureCube EnvSpecularTexture + { + get { return envSpecularTextureParam.GetValueTextureCube(); } + set { envSpecularTextureParam.SetValue(value); } } public PBREffect(GraphicsDevice graphicsDevice, byte[] effectCode) : base(graphicsDevice, effectCode) { - viewProjectionParam = Parameters["viewProjectionMatrix"]; - sceneRotationParam = Parameters["sceneRotationMatrix"]; + modelParam = Parameters["model"]; + viewParam = Parameters["view"]; + projectionParam = Parameters["param"]; - eyePositionParam = Parameters["eyePosition"]; - specularTextureLevelsParam = Parameters["specularTextureLevels"]; + lightDirParam = Parameters["lightDir"]; + lightColourParam = Parameters["lightColour"]; - Lights = new PBRLightCollection(Parameters["directions"], Parameters["radiance"]); + normalScaleParam = Parameters["normalScale"]; + emissiveFactorParam = Parameters["emissiveFactor"]; + occlusionStrengthParam = Parameters["occlusionStrength"]; + metallicRoughnessValuesParam = Parameters["metallicRoughnessValues"]; + baseColorFactorParam = Parameters["baseColorFactor"]; + cameraLookParam = Parameters["camera"]; + + baseColourTextureParam = Parameters["baseColourTexture"]; + normalTextureParam = Parameters["normalTexture"]; + emissionTextureParam = Parameters["emissionTexture"]; + occlusionTextureParam = Parameters["occlusionTexture"]; + metallicRoughnessTextureParam = Parameters["metallicRoughnessTexture"]; + envDiffuseTextureParam = Parameters["envDiffuseTexture"]; + brdfLutTextureParam = Parameters["brdfLutTexture"]; + envSpecularTextureParam = Parameters["envSpecularTexture"]; } protected PBREffect(PBREffect cloneSource) : base(cloneSource) { - viewProjectionParam = Parameters["viewProjectionMatrix"]; - sceneRotationParam = Parameters["sceneRotationMatrix"]; + modelParam = Parameters["model"]; + viewParam = Parameters["view"]; + projectionParam = Parameters["param"]; - eyePositionParam = Parameters["eyePosition"]; - specularTextureLevelsParam = Parameters["specularTextureLevels"]; + lightDirParam = Parameters["lightDir"]; + lightColourParam = Parameters["lightColour"]; + + normalScaleParam = Parameters["normalScale"]; + emissiveFactorParam = Parameters["emissiveFactor"]; + occlusionStrengthParam = Parameters["occlusionStrength"]; + metallicRoughnessValuesParam = Parameters["metallicRoughnessValues"]; + baseColorFactorParam = Parameters["baseColorFactor"]; + cameraLookParam = Parameters["camera"]; + + baseColourTextureParam = Parameters["baseColourTexture"]; + normalTextureParam = Parameters["normalTexture"]; + emissionTextureParam = Parameters["emissionTexture"]; + occlusionTextureParam = Parameters["occlusionTexture"]; + metallicRoughnessTextureParam = Parameters["metallicRoughnessTexture"]; + envDiffuseTextureParam = Parameters["envDiffuseTexture"]; + brdfLutTextureParam = Parameters["brdfLutTexture"]; + envSpecularTextureParam = Parameters["envSpecularTexture"]; World = cloneSource.World; View = cloneSource.View; Projection = cloneSource.Projection; - Lights = new PBRLightCollection(cloneSource.Lights); + + Light = cloneSource.Light; + + NormalScale = cloneSource.normalScale; + EmissiveFactor = cloneSource.EmissiveFactor; + OcclusionStrength = cloneSource.OcclusionStrength; + MetallicRoughnessValue = cloneSource.MetallicRoughnessValue; + BaseColorFactor = cloneSource.BaseColorFactor; + CameraLook = cloneSource.CameraLook; + + BaseColourTexture = cloneSource.BaseColourTexture; + NormalTexture = cloneSource.NormalTexture; + EmissionTexture = cloneSource.EmissionTexture; + OcclusionTexture = cloneSource.OcclusionTexture; + MetallicRoughnessTexture = cloneSource.MetallicRoughnessTexture; + EnvDiffuseTexture = cloneSource.EnvDiffuseTexture; + BRDFLutTexture = cloneSource.BRDFLutTexture; + EnvSpecularTexture = cloneSource.EnvSpecularTexture; } public override Effect Clone() diff --git a/Effects/pbr.fx b/Effects/pbr.fx index 71b2b02..ca57701 100644 --- a/Effects/pbr.fx +++ b/Effects/pbr.fx @@ -1,217 +1,408 @@ -// Physically Based Rendering -// Copyright (c) 2017-2018 MichaƂ Siejak -// Physically Based shading model: Lambetrtian diffuse BRDF + Cook-Torrance microfacet specular BRDF + IBL for ambient. +#include "Macros.fxh" -// 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 +#define NORMALS +#define UV -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; +// A constant buffer that stores the three basic column-major matrices for composing geometry. +cbuffer ModelViewProjectionConstantBuffer : register(b0) +{ + matrix model; + matrix view; + matrix projection; +}; +// Per-vertex data used as input to the vertex shader. struct VertexShaderInput { - float3 position : POSITION; - float3 normal : NORMAL; - float3 tangent : TANGENT; - float3 binormal : BINORMAL; - float2 texcoord : TEXCOORD; + float4 position : POSITION; +#ifdef NORMALS + float3 normal : NORMAL; +#endif +#ifdef UV + float2 texcoord : TEXCOORD0; +#endif }; +// Per-pixel color data passed through the pixel shader. struct PixelShaderInput { - float4 pixelPosition : SV_POSITION; - float3 position : POSITION1; - float2 texcoord : TEXCOORD0; - float3 T : TEXCOORD1; - float3 B : TEXCOORD2; - float3 N : TEXCOORD3; + float4 position : SV_POSITION; + float3 poswithoutw : POSITION1; + +#ifdef NORMALS + float3 normal : NORMAL; +#endif + + float2 texcoord : TEXCOORD0; }; -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) +PixelShaderInput main_vs(VertexShaderInput input) { - float alpha = roughness * roughness; - float alphaSq = alpha * alpha; + PixelShaderInput output; - float denom = (cosLh * cosLh) * (alphaSq - 1.0) + 1.0; - return alphaSq / (PI * denom * denom); + // Transform the vertex position into projected space. + float4 pos = mul(input.position, model); + output.poswithoutw = float3(pos.xyz) / pos.w; + +#ifdef NORMALS + // If we have normals... + output.normal = normalize(mul(float4(input.normal.xyz, 0.0), model)); +#endif + +#ifdef UV + output.texcoord = input.texcoord; +#else + output.texcoord = float2(0.0f, 0.0f); +#endif + +#ifdef HAS_NORMALS +#ifdef HAS_TANGENTS + vec3 normalW = normalize(vec3(u_ModelMatrix * vec4(a_Normal.xyz, 0.0))); + vec3 tangentW = normalize(vec3(u_ModelMatrix * vec4(a_Tangent.xyz, 0.0))); + vec3 bitangentW = cross(normalW, tangentW) * a_Tangent.w; + v_TBN = mat3(tangentW, bitangentW, normalW); +#else // HAS_TANGENTS != 1 + v_Normal = normalize(vec3(u_ModelMatrix * vec4(a_Normal.xyz, 0.0))); +#endif +#endif + + // Transform the vertex position into projected space. + pos = mul(pos, view); + pos = mul(pos, projection); + output.position = pos; + + return output; } -// Single term for separable Schlick-GGX below. -float gaSchlickG1(float cosTheta, float k) +// +// This fragment shader defines a reference implementation for Physically Based Shading of +// a microfacet surface material defined by a glTF model. +// +// References: +// [1] Real Shading in Unreal Engine 4 +// http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf +// [2] Physically Based Shading at Disney +// http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf +// [3] README.md - Environment Maps +// https://github.com/KhronosGroup/glTF-WebGL-PBR/#environment-maps +// [4] "An Inexpensive BRDF Model for Physically based Rendering" by Christophe Schlick +// https://www.cs.virginia.edu/~jdl/bib/appearance/analytic%20models/schlick94b.pdf + +#define NORMALS +#define UV +#define HAS_NORMALS +#define USE_IBL +#define USE_TEX_LOD + +DECLARE_TEXTURE(baseColourTexture, 0); +DECLARE_TEXTURE(normalTexture, 1); +DECLARE_TEXTURE(emissionTexture, 2); +DECLARE_TEXTURE(occlusionTexture, 3); +DECLARE_TEXTURE(metallicRoughnessTexture, 4); +DECLARE_CUBEMAP(envDiffuseTexture, 8); +DECLARE_TEXTURE(brdfLutTexture, 9); +DECLARE_CUBEMAP(envSpecularTexture, 10); + +cbuffer cbPerFrame : register(b0) { - return cosTheta / (cosTheta * (1.0 - k) + k); + float3 lightDir; + float3 lightColour; +}; + +cbuffer cbPerObject : register(b1) +{ + float normalScale; + float3 emissiveFactor; + float occlusionStrength; + float2 metallicRoughnessValues; + float4 baseColorFactor; + float3 camera; + + // debugging flags used for shader output of intermediate PBR variables + float4 scaleDiffBaseMR; + float4 scaleFGDSpec; + float4 scaleIBLAmbient; +}; + +#ifdef HAS_NORMALS +#ifdef HAS_TANGENTS +varying mat3 v_TBN; +#else +#endif +#endif + +// Encapsulate the various inputs used by the various functions in the shading equation +// We store values in this struct to simplify the integration of alternative implementations +// of the shading terms, outlined in the Readme.MD Appendix. +struct PBRInfo +{ + float NdotL; // cos angle between normal and light direction + float NdotV; // cos angle between normal and view direction + float NdotH; // cos angle between normal and half vector + float LdotH; // cos angle between light direction and half vector + float VdotH; // cos angle between view direction and half vector + float perceptualRoughness; // roughness value, as authored by the model creator (input to shader) + float metalness; // metallic value at the surface + float3 reflectance0; // full reflectance color (normal incidence angle) + float3 reflectance90; // reflectance color at grazing angle + float alphaRoughness; // roughness mapped to a more linear change in the roughness (proposed by [2]) + float3 diffuseColor; // color contribution from diffuse lighting + float3 specularColor; // color contribution from specular lighting +}; + +static const float M_PI = 3.141592653589793; +static const float c_MinRoughness = 0.04; + +float4 SRGBtoLINEAR(float4 srgbIn) +{ +#ifdef MANUAL_SRGB +#ifdef SRGB_FAST_APPROXIMATION + float3 linOut = pow(srgbIn.xyz,float3(2.2, 2.2, 2.2)); +#else //SRGB_FAST_APPROXIMATION + float3 bLess = step(float3(0.04045, 0.04045, 0.04045), srgbIn.xyz); + float3 linOut = lerp(srgbIn.xyz / float3(12.92, 12.92, 12.92), pow((srgbIn.xyz + float3(0.055, 0.055, 0.055)) / float3(1.055, 1.055, 1.055), float3(2.4, 2.4, 2.4)), bLess); +#endif //SRGB_FAST_APPROXIMATION + return float4(linOut,srgbIn.w);; +#else //MANUAL_SRGB + return srgbIn; +#endif //MANUAL_SRGB } -// Schlick-GGX approximation of geometric attenuation function using Smith's method. -float gaSchlickGGX(float cosLi, float cosLo, float roughness) +// Find the normal for this fragment, pulling either from a predefined normal map +// or from the interpolated mesh normal and tangent attributes. +float3 getNormal(float3 position, float3 normal, float2 uv) { - 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); + // Retrieve the tangent space matrix +#ifndef HAS_TANGENTS + float3 pos_dx = ddx(position); + float3 pos_dy = ddy(position); + float3 tex_dx = ddx(float3(uv, 0.0)); + float3 tex_dy = ddy(float3(uv, 0.0)); + float3 t = (tex_dy.y * pos_dx - tex_dx.y * pos_dy) / (tex_dx.x * tex_dy.y - tex_dy.x * tex_dx.y); + +#ifdef HAS_NORMALS + float3 ng = normalize(normal); +#else + float3 ng = cross(pos_dx, pos_dy); +#endif + + t = normalize(t - ng * dot(ng, t)); + float3 b = normalize(cross(ng, t)); + row_major float3x3 tbn = float3x3(t, b, ng); + +#else // HAS_TANGENTS + mat3 tbn = v_TBN; +#endif + +#ifdef HAS_NORMALMAP + float3 n = SAMPLE_TEXTURE(normalTexture, uv).rgb; + + // Need to check the multiplication is equivalent.. + n = normalize(mul(((2.0 * n - 1.0) * float3(normalScale, normalScale, 1.0)), tbn)); +#else + float3 n = tbn[2].xyz; +#endif + + return n; } -// Shlick's approximation of the Fresnel factor. -float3 fresnelSchlick(float3 F0, float cosTheta) +#ifdef USE_IBL +// Calculation of the lighting contribution from an optional Image Based Light source. +// Precomputed Environment Maps are required uniform inputs and are computed as outlined in [1]. +// See our README.md on Environment Maps [3] for additional discussion. +float3 getIBLContribution(PBRInfo pbrInputs, float3 n, float3 reflection) { - return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0); + float mipCount = 9.0; // resolution of 512x512 + float lod = (pbrInputs.perceptualRoughness * mipCount); + + // retrieve a scale and bias to F0. See [1], Figure 3 + float2 val = float2(pbrInputs.NdotV, 1.0 - pbrInputs.perceptualRoughness); + float3 brdf = SRGBtoLINEAR(SAMPLE_TEXTURE(brdfLutTexture, val)).rgb; + + float3 diffuseLight = SRGBtoLINEAR(SAMPLE_CUBEMAP(envDiffuseTexture, n)).rgb; + +#ifdef USE_TEX_LOD + float4 reflectionWithLOD = float4(reflection, 0); + float3 specularLight = SRGBtoLINEAR(SAMPLE_CUBEMAP_LOD(envSpecularTexture, reflectionWithLOD)).rgb; +#else + float3 specularLight = SRGBtoLINEAR(SAMPLE_CUBEMAP(envSpecularTexture, reflection)).rgb; +#endif + + float3 diffuse = diffuseLight * pbrInputs.diffuseColor; + float3 specular = specularLight * (pbrInputs.specularColor * brdf.x + brdf.y); + + // For presentation, this allows us to disable IBL terms + diffuse *= scaleIBLAmbient.x; + specular *= scaleIBLAmbient.y; + + return diffuse + specular; +} +#endif + +// Basic Lambertian diffuse +// Implementation from Lambert's Photometria https://archive.org/details/lambertsphotome00lambgoog +// See also [1], Equation 1 +float3 diffuse(PBRInfo pbrInputs) +{ + return pbrInputs.diffuseColor / M_PI; } -// Vertex shader -PixelShaderInput main_vs(VertexShaderInput vin) +// The following equation models the Fresnel reflectance term of the spec equation (aka F()) +// Implementation of fresnel from [4], Equation 15 +float3 specularReflection(PBRInfo pbrInputs) { - 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; + return pbrInputs.reflectance0 + (pbrInputs.reflectance90 - pbrInputs.reflectance0) * pow(clamp(1.0 - pbrInputs.VdotH, 0.0, 1.0), 5.0); } -// Pixel shader -float4 main_ps(PixelShaderInput pin) : SV_Target0 +// This calculates the specular geometric attenuation (aka G()), +// where rougher material will reflect less light back to the viewer. +// This implementation is based on [1] Equation 4, and we adopt their modifications to +// alphaRoughness as input as originally proposed in [2]. +float geometricOcclusion(PBRInfo pbrInputs) { - // 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; + float NdotL = pbrInputs.NdotL; + float NdotV = pbrInputs.NdotV; + float r = pbrInputs.alphaRoughness; - // Outgoing light direction (vector from world-space fragment position to the "eye"). - float3 Lo = normalize(eyePosition - pin.position); + float attenuationL = 2.0 * NdotL / (NdotL + sqrt(r * r + (1.0 - r * r) * (NdotL * NdotL))); + float attenuationV = 2.0 * NdotV / (NdotV + sqrt(r * r + (1.0 - r * r) * (NdotV * NdotV))); + return attenuationL * attenuationV; +} - // tranpose to transform tangent space => world - float3x3 TBN = transpose(float3x3(normalize(pin.T), normalize(pin.B), normalize(pin.N))); +// The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D()) +// Implementation from "Average Irregularity Representation of a Roughened Surface for Ray Reflection" by T. S. Trowbridge, and K. P. Reitz +// Follows the distribution function recommended in the SIGGRAPH 2013 course notes from EPIC Games [1], Equation 3. +float microfacetDistribution(PBRInfo pbrInputs) +{ + float roughnessSq = pbrInputs.alphaRoughness * pbrInputs.alphaRoughness; + float f = (pbrInputs.NdotH * roughnessSq - pbrInputs.NdotH) * pbrInputs.NdotH + 1.0; + return roughnessSq / (M_PI * f * f); +} - // 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; +float4 main_ps(PixelShaderInput input) : SV_TARGET +{ + // Metallic and Roughness material properties are packed together + // In glTF, these factors can be specified by fixed scalar values + // or from a metallic-roughness map + float perceptualRoughness = metallicRoughnessValues.y; + float metallic = metallicRoughnessValues.x; - // Fresnel reflectance at normal incidence (for metals use albedo color). - float3 F0 = lerp(Fdielectric, albedo, metalness); +#ifdef HAS_METALROUGHNESSMAP + // Roughness is stored in the 'g' channel, metallic is stored in the 'b' channel. + // This layout intentionally reserves the 'r' channel for (optional) occlusion map data + float4 mrSample = SAMPLE_TEXTURE(metallicRoughnessTexture, input.texcoord); - // Direct lighting calculation for analytical lights. - float3 directLighting = 0.0; - for(uint i=0; i*eZNzSN85JOOwjHt``kUg=J-jKMyG_Ic>Kd>(K zBR*~^-vhAcnywY<`BJI4JXhYhvASNU)g(FS6HF~NjymxNHX@DbL)hOz%cA!<|ALJ; zxb|88@x)qZ9T(PbUMya}zELZ5KnbQzWq$&;;>WwN7mXji$!}bh)sqY z%aiCt=9Oz`$GL;co~6`Jsd0?YMxwnt1Ll|WrGm9*-=?xZDh_k#&VieSOZnw`87OH- z`aq4`I-kPsh3+p%PtI&!6hiV+RyI=%s_hru>_GOFWy0d8SdU*kS4h{e9J&<)pVm*^sHzb~QBnd4kk)=>2VC~u<(ao}mlkN6l z`<9tmxG?=P#0Ttzj(#bFvY$YA4@?3l9=i|FY%M}a0zqKDW=^sLG=XaZC@Jz8ly+0N zCNY!1eAWDb9iWoc4MO*-eZHe^8@dE$MiUvkV|7DTcK|*8{nqM+yL5lDx=fcYepUrQ z5n!j$|50@GeQ<9tW#=74MOe2`eWblAQS)yKz}Ds`+cTOWbRegu9AErEZ`#VXn++HI?I=iGZ< zw_|lK4z!AA`j5Bn{BzQUI5!m!^^>vRyz%!R@BE=#|7gPey# z-uasPOul#~>iN)@oRj2*7&D6o-#MAPTxp|u85fvJ7e?BVB*t-ZwJ*kHev_s%AI>|2 zMxt#z=W)N1bHT-NNbM>}jqeNp!+n zrPuey=&lRNC4F#R;~4Z>YYe^~yD(@x+E&MKIqmEZjh$HK9!~R-_$Z~@`aux=jNB4F*J=}CQ zrZLdrw+F$G+p+zyZ#94uL(}ln3X`VAdQIY~19CgWJ*kN~Ky}i}cy#{wPmdOv6@Ar&~>8#JXDhv|kgCe{00-T6{7G zz}m5&nDKTI0$mtkoE2Mm#f>fx1}%Ud;l`$V%L>pDG%5hY1tgmp7JPa4YdGVCeddSA-*9tSvP%V z7#LdN0P2QwXWg=<;0w*=8t;@CzuUY5`y}jH%Y(koSUa?*j`4jhg&D-7Kh`1Z9_Lk) zHlphXPdx0yJX0KpcW; zD7UFocI_TJ$(gpp_uk0P)w`YB)(Nbe;2i=siK%T2#&&EvxU>zv1vr~@e>jA?5bqh z*jnFCCLZU{$vod$4f=C3&ze?){uqyECuH0Dv5Zc3=LP4&^N;<`)?y8@XZ=`5N)6KI~m9D_ZK0K;k$SAn?dx8 qk@JuBiiozHL;SZ%`o#Ga5$!mKuHQW&(~kR7h<2QteAm&x0sk9jwc3RM literal 7240 zcmchbO>CS;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