rewrite PBREffect

PBR
Evan Hemsley 2020-07-31 00:20:07 -07:00
parent f0ffc1746c
commit 466f35b1ee
4 changed files with 629 additions and 255 deletions

57
Effects/Macros.fxh Normal file
View File

@ -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<float4> Name : register(t##index); \
sampler Name##Sampler : register(s##index)
#define DECLARE_CUBEMAP(Name, index) \
TextureCube<float4> 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

View File

@ -6,71 +6,48 @@ namespace Smuggler
public struct PBRLight public struct PBRLight
{ {
public Vector3 direction; 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.direction = direction;
this.radiance = radiance; this.colour = colour;
}
}
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 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 baseColourTextureParam;
readonly EffectParameter sceneRotationParam; readonly EffectParameter normalTextureParam;
readonly EffectParameter eyePositionParam; readonly EffectParameter emissionTextureParam;
readonly EffectParameter specularTextureLevelsParam; readonly EffectParameter occlusionTextureParam;
readonly EffectParameter metallicRoughnessTextureParam;
readonly EffectParameter albedoTextureParam; readonly EffectParameter envDiffuseTextureParam;
readonly EffectParameter brdfLutTextureParam;
readonly EffectParameter envSpecularTextureParam;
Matrix world = Matrix.Identity; Matrix world = Matrix.Identity;
Matrix view = Matrix.Identity; Matrix view = Matrix.Identity;
Matrix projection = Matrix.Identity; Matrix projection = Matrix.Identity;
Vector3 eyePosition = Vector3.Zero; PBRLight light = new PBRLight();
int specularTextureLevels = 0; float normalScale = 1;
Vector3 emissiveFactor;
float occlusionStrength;
Vector2 metallicRoughnessValue;
Vector4 baseColorFactor;
Vector3 cameraLook;
public Matrix World public Matrix World
{ {
@ -78,7 +55,7 @@ namespace Smuggler
set set
{ {
world = value; world = value;
sceneRotationParam.SetValue(world); modelParam.SetValue(world);
} }
} }
@ -88,8 +65,7 @@ namespace Smuggler
set set
{ {
view = value; view = value;
viewProjectionParam.SetValue(projection * view); viewParam.SetValue(view);
eyePositionParam.SetValue(Matrix.Invert(view).Backward);
} }
} }
@ -99,51 +75,201 @@ namespace Smuggler
set set
{ {
projection = value; projection = value;
viewProjectionParam.SetValue(projection * view); projectionParam.SetValue(value);
} }
} }
public PBRLightCollection Lights { get; } public PBRLight Light
public int SpecularTextureLevels
{ {
get { return specularTextureLevels; } get { return light; }
set set
{ {
specularTextureLevels = value; light = value;
specularTextureLevelsParam.SetValue(specularTextureLevels); lightDirParam.SetValue(light.direction);
lightColourParam.SetValue(light.colour);
} }
} }
public Texture2D AlbedoTexture public float NormalScale
{ {
get { return albedoTextureParam.GetValueTexture2D(); } get { return normalScale; }
set { albedoTextureParam.SetValue(value); } 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
{
metallicRoughnessValue = value;
metallicRoughnessValuesParam.SetValue(metallicRoughnessValue);
}
}
public Vector4 BaseColorFactor
{
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) public PBREffect(GraphicsDevice graphicsDevice, byte[] effectCode) : base(graphicsDevice, effectCode)
{ {
viewProjectionParam = Parameters["viewProjectionMatrix"]; modelParam = Parameters["model"];
sceneRotationParam = Parameters["sceneRotationMatrix"]; viewParam = Parameters["view"];
projectionParam = Parameters["param"];
eyePositionParam = Parameters["eyePosition"]; lightDirParam = Parameters["lightDir"];
specularTextureLevelsParam = Parameters["specularTextureLevels"]; 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) protected PBREffect(PBREffect cloneSource) : base(cloneSource)
{ {
viewProjectionParam = Parameters["viewProjectionMatrix"]; modelParam = Parameters["model"];
sceneRotationParam = Parameters["sceneRotationMatrix"]; viewParam = Parameters["view"];
projectionParam = Parameters["param"];
eyePositionParam = Parameters["eyePosition"]; lightDirParam = Parameters["lightDir"];
specularTextureLevelsParam = Parameters["specularTextureLevels"]; 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; World = cloneSource.World;
View = cloneSource.View; View = cloneSource.View;
Projection = cloneSource.Projection; 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() public override Effect Clone()

View File

@ -1,215 +1,406 @@
// 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. #define NORMALS
// See: http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf #define UV
static const float PI = 3.141592; // A constant buffer that stores the three basic column-major matrices for composing geometry.
static const float Epsilon = 0.00001; cbuffer ModelViewProjectionConstantBuffer : register(b0)
{
static const uint NumLights = 3; matrix model;
matrix view;
// Constant normal incidence Fresnel factor for all dielectrics. matrix projection;
static const float3 Fdielectric = 0.04; };
// UNIFORM CONSTANTS
float4x4 viewProjectionMatrix;
float4x4 sceneRotationMatrix;
float3 direction[NumLights];
float3 radiance[NumLights];
float3 eyePosition;
int specularTextureLevels;
// Per-vertex data used as input to the vertex shader.
struct VertexShaderInput struct VertexShaderInput
{ {
float3 position : POSITION; float4 position : POSITION;
#ifdef NORMALS
float3 normal : NORMAL; float3 normal : NORMAL;
float3 tangent : TANGENT; #endif
float3 binormal : BINORMAL; #ifdef UV
float2 texcoord : TEXCOORD; float2 texcoord : TEXCOORD0;
#endif
}; };
// Per-pixel color data passed through the pixel shader.
struct PixelShaderInput struct PixelShaderInput
{ {
float4 pixelPosition : SV_POSITION; float4 position : SV_POSITION;
float3 position : POSITION1; float3 poswithoutw : POSITION1;
#ifdef NORMALS
float3 normal : NORMAL;
#endif
float2 texcoord : TEXCOORD0; float2 texcoord : TEXCOORD0;
float3 T : TEXCOORD1;
float3 B : TEXCOORD2;
float3 N : TEXCOORD3;
}; };
sampler albedoTexture : register(s0); PixelShaderInput main_vs(VertexShaderInput input)
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; PixelShaderInput output;
float alphaSq = alpha * alpha;
float denom = (cosLh * cosLh) * (alphaSq - 1.0) + 1.0; // Transform the vertex position into projected space.
return alphaSq / (PI * denom * denom); 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. // Find the normal for this fragment, pulling either from a predefined normal map
float gaSchlickGGX(float cosLi, float cosLo, float roughness) // or from the interpolated mesh normal and tangent attributes.
float3 getNormal(float3 position, float3 normal, float2 uv)
{ {
float r = roughness + 1.0; // Retrieve the tangent space matrix
float k = (r * r) / 8.0; // Epic suggests using this roughness remapping for analytic lights. #ifndef HAS_TANGENTS
return gaSchlickG1(cosLi, k) * gaSchlickG1(cosLo, k); 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. #ifdef USE_IBL
float3 fresnelSchlick(float3 F0, float cosTheta) // 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 // The following equation models the Fresnel reflectance term of the spec equation (aka F())
PixelShaderInput main_vs(VertexShaderInput vin) // Implementation of fresnel from [4], Equation 15
float3 specularReflection(PBRInfo pbrInputs)
{ {
PixelShaderInput vout; return pbrInputs.reflectance0 + (pbrInputs.reflectance90 - pbrInputs.reflectance0) * pow(clamp(1.0 - pbrInputs.VdotH, 0.0, 1.0), 5.0);
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 // This calculates the specular geometric attenuation (aka G()),
float4 main_ps(PixelShaderInput pin) : SV_Target0 // 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. float NdotL = pbrInputs.NdotL;
float3 albedo = tex2D(albedoTexture, pin.texcoord).rgb; float NdotV = pbrInputs.NdotV;
float metalness = tex2D(metalnessTexture, pin.texcoord).r; float r = pbrInputs.alphaRoughness;
float roughness = tex2D(roughnessTexture, pin.texcoord).r;
// Outgoing light direction (vector from world-space fragment position to the "eye"). float attenuationL = 2.0 * NdotL / (NdotL + sqrt(r * r + (1.0 - r * r) * (NdotL * NdotL)));
float3 Lo = normalize(eyePosition - pin.position); 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)));
// 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; i<NumLights; ++i)
{
float3 Li = -direction[i];
float3 Lradiance = radiance[i];
// Half-vector between Li and Lo.
float3 Lh = normalize(Li + Lo);
// Calculate angles between surface normal and various light vectors.
float cosLi = max(0.0, dot(N, Li));
float cosLh = max(0.0, dot(N, Lh));
// Calculate Fresnel term for direct lighting.
float3 F = fresnelSchlick(F0, max(0.0, dot(Lh, Lo)));
// Calculate normal distribution for specular BRDF.
float D = ndfGGX(cosLh, roughness);
// Calculate geometric attenuation for specular BRDF.
float G = gaSchlickGGX(cosLi, cosLo, roughness);
// Diffuse scattering happens due to light being refracted multiple times by a dielectric medium.
// Metals on the other hand either reflect or absorb energy, so diffuse contribution is always zero.
// To be energy conserving we must scale diffuse BRDF contribution based on Fresnel factor & metalness.
float3 kd = lerp(float3(1, 1, 1) - F, float3(0, 0, 0), metalness);
// Lambert diffuse BRDF.
// We don't scale by 1/PI for lighting & material units to be more convenient.
// See: https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
float3 diffuseBRDF = kd * albedo;
// Cook-Torrance specular microfacet BRDF.
float3 specularBRDF = (F * D * G) / max(Epsilon, 4.0 * cosLi * cosLo);
// Total contribution for this light.
directLighting += (diffuseBRDF + specularBRDF) * Lradiance * cosLi;
} }
// Ambient lighting (IBL). // The following equation(s) model the distribution of microfacet normals across the area being drawn (aka D())
float3 ambientLighting; // 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)
{ {
// Sample diffuse irradiance at normal direction. float roughnessSq = pbrInputs.alphaRoughness * pbrInputs.alphaRoughness;
float3 irradiance = tex2D(irradianceTexture, N.xy).rgb; float f = (pbrInputs.NdotH * roughnessSq - pbrInputs.NdotH) * pbrInputs.NdotH + 1.0;
return roughnessSq / (M_PI * f * f);
// Calculate Fresnel term for ambient lighting.
// Since we use pre-filtered cubemap(s) and irradiance is coming from many directions
// use cosLo instead of angle with light's half-vector (cosLh above).
// See: https://seblagarde.wordpress.com/2011/08/17/hello-world/
float3 F = fresnelSchlick(F0, cosLo);
// Get diffuse contribution factor (as with direct lighting).
float3 kd = lerp(1.0 - F, 0.0, metalness);
// Irradiance map contains exitant radiance assuming Lambertian BRDF, no need to scale by 1/PI here either.
float3 diffuseIBL = kd * albedo * irradiance;
// Sample pre-filtered specular reflection environment at correct mipmap level.
float4 levelTexCoord = float4(Lr, roughness * specularTextureLevels);
float3 specularIrradiance = tex2Dlod(specularTexture, levelTexCoord).rgb;
// Split-sum approximation factors for Cook-Torrance specular BRDF.
float2 specularBRDF = tex2D(specularBRDF_LUT, float2(cosLo, roughness)).rg;
// Total specular IBL contribution.
float3 specularIBL = (F0 * specularBRDF.x + specularBRDF.y) * specularIrradiance;
// Total ambient lighting contribution.
ambientLighting = diffuseIBL + specularIBL;
} }
// Final fragment color. float4 main_ps(PixelShaderInput input) : SV_TARGET
return float4(directLighting + ambientLighting, 1.0); {
// 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;
#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);
// Had to reverse the order of the channels here - TODO: investigate..
perceptualRoughness = mrSample.g * perceptualRoughness;
metallic = mrSample.b * metallic;
#endif
perceptualRoughness = clamp(perceptualRoughness, c_MinRoughness, 1.0);
metallic = clamp(metallic, 0.0, 1.0);
// Roughness is authored as perceptual roughness; as is convention,
// convert to material roughness by squaring the perceptual roughness [2].
float alphaRoughness = perceptualRoughness * perceptualRoughness;
// The albedo may be defined from a base texture or a flat color
#ifdef HAS_BASECOLORMAP
float4 baseColor = SRGBtoLINEAR(SAMPLE_TEXTURE(baseColourTexture, input.texcoord)) * baseColorFactor;
#else
float4 baseColor = baseColorFactor;
#endif
float3 f0 = float3(0.04, 0.04, 0.04);
float3 diffuseColor = baseColor.rgb * (float3(1.0, 1.0, 1.0) - f0);
diffuseColor *= 1.0 - metallic;
float3 specularColor = lerp(f0, baseColor.rgb, metallic);
// Compute reflectance.
float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b);
// For typical incident reflectance range (between 4% to 100%) set the grazing reflectance to 100% for typical fresnel effect.
// For very low reflectance range on highly diffuse objects (below 4%), incrementally reduce grazing reflecance to 0%.
float reflectance90 = clamp(reflectance * 25.0, 0.0, 1.0);
float3 specularEnvironmentR0 = specularColor.rgb;
float3 specularEnvironmentR90 = float3(1.0, 1.0, 1.0) * reflectance90;
float3 n = getNormal(input.poswithoutw, input.normal, input.texcoord); // normal at surface point
float3 v = normalize(camera - input.poswithoutw); // Vector from surface point to camera
float3 l = normalize(lightDir); // Vector from surface point to light
float3 h = normalize(l + v); // Half vector between both l and v
float3 reflection = -normalize(reflect(v, n));
float NdotL = clamp(dot(n, l), 0.001, 1.0);
float NdotV = abs(dot(n, v)) + 0.001;
float NdotH = clamp(dot(n, h), 0.0, 1.0);
float LdotH = clamp(dot(l, h), 0.0, 1.0);
float VdotH = clamp(dot(v, h), 0.0, 1.0);
PBRInfo pbrInputs;
pbrInputs.NdotL = NdotL;
pbrInputs.NdotV = NdotV;
pbrInputs.NdotH = NdotH;
pbrInputs.LdotH = LdotH;
pbrInputs.VdotH = VdotH;
pbrInputs.perceptualRoughness = perceptualRoughness;
pbrInputs.metalness = metallic;
pbrInputs.reflectance0 = specularEnvironmentR0;
pbrInputs.reflectance90 = specularEnvironmentR90;
pbrInputs.alphaRoughness = alphaRoughness;
pbrInputs.diffuseColor = diffuseColor;
pbrInputs.specularColor = specularColor;
// Calculate the shading terms for the microfacet specular shading model
float3 F = specularReflection(pbrInputs);
float G = geometricOcclusion(pbrInputs);
float D = microfacetDistribution(pbrInputs);
// Calculation of analytical lighting contribution
float3 diffuseContrib = (1.0 - F) * diffuse(pbrInputs);
float3 specContrib = F * G * D / (4.0 * NdotL * NdotV);
float3 color = NdotL * lightColour * (diffuseContrib + specContrib);
// Calculate lighting contribution from image based lighting source (IBL)
#ifdef USE_IBL
color += getIBLContribution(pbrInputs, n, reflection);
#endif
// Apply optional PBR terms for additional (optional) shading
#ifdef HAS_OCCLUSIONMAP
float ao = SAMPLE_TEXTURE(occlusionTexture, input.texcoord).r;
color = lerp(color, color * ao, occlusionStrength);
#endif
#ifdef HAS_EMISSIVEMAP
float3 emissive = SRGBtoLINEAR(SAMPLE_TEXTURE(emissionTexture, input.texcoord)).rgb * emissiveFactor;
color += emissive;
#endif
// This section uses lerp to override final color for reference app visualization
// of various parameters in the lighting equation.
color = lerp(color, F, scaleFGDSpec.x);
color = lerp(color, float3(G, G, G), scaleFGDSpec.y);
color = lerp(color, float3(D, D, D), scaleFGDSpec.z);
color = lerp(color, specContrib, scaleFGDSpec.w);
color = lerp(color, diffuseContrib, scaleDiffBaseMR.x);
color = lerp(color, baseColor.rgb, scaleDiffBaseMR.y);
color = lerp(color, float3(metallic, metallic, metallic), scaleDiffBaseMR.z);
color = lerp(color, float3(perceptualRoughness, perceptualRoughness, perceptualRoughness), scaleDiffBaseMR.w);
return float4(color, 1.0);
} }
technique PBR Technique PBR
{ {
pass Pass1 Pass pass1
{ {
VertexShader = compile vs_3_0 main_vs(); VertexShader = compile vs_3_0 main_vs();
PixelShader = compile ps_3_0 main_ps(); PixelShader = compile ps_3_0 main_ps();

Binary file not shown.