Skip to content

Instantly share code, notes, and snippets.

@JSandusky
Created September 23, 2018 19:49
Show Gist options
  • Save JSandusky/fdf97852fc9c84e35dd776f6a39998ab to your computer and use it in GitHub Desktop.
Save JSandusky/fdf97852fc9c84e35dd776f6a39998ab to your computer and use it in GitHub Desktop.
Monogame PBR Effect w/ POM
// Performs parallax
float HeightFieldRaycast(float2 dp, float2 ds)
{
const int linearSteps = 20;
const int binarySteps = 10;
float size = 1.0 / float(linearSteps);
float depth = 0.0;
for (int i = 0; i < linearSteps; i++)
{
float t = tex2D(HeightMapSampler, dp + ds*depth).r;
if (depth < (1.0 - t))
depth += size;
}
for (int ii = 0; ii < binarySteps; ii++)
{
size *= 0.5;
float t = tex2D(HeightMapSampler, dp + ds * depth).r;
if (depth<(1.0 - t))
depth += (2.0 * size);
depth -= size;
}
return depth;
}
float2 updateUV(
float3 pointToCameraDirWS,
float3 n,
float3 t,
float3 b,
float Depth,
float2 uv)
{
if (Depth > 0.0)
{
float a = dot(n, -pointToCameraDirWS);
float3 s = float3(-dot(pointToCameraDirWS, t), dot(pointToCameraDirWS, b), a);
s *= -Depth / a * 0.1;
float2 ds = s.xy;
float d = HeightFieldRaycast(uv, ds);
return uv + (ds * d);
}
else
return uv;
return uv;
}
//128 -> 64 -> 32 -> 16 -> 4 -> 2 -> 1
float GetMipFromRoughness(float roughness)
{
return ((roughness) * 7.0);// - pow(roughness, 7.0) * 1.5;
}
float3 EnvBRDFApprox (float3 specColor, float roughness, float ndv)
{
const float4 c0 = float4(-1, -0.0275, -0.572, 0.022 );
const float4 c1 = float4(1, 0.0425, 1.0, -0.04 );
float4 r = roughness * c0 + c1;
float a004 = min( r.x * r.x, exp2( -9.28 * ndv ) ) * r.x + r.y;
float2 AB = float2( -1.04, 1.04 ) * a004 + r.zw;
return specColor * AB.x + AB.y;
}
float3 FixCubeLookup(float3 v)
{
float M = max(max(abs(v.x), abs(v.y)), abs(v.z));
float scale = (128 - 1) / 128;
if (abs(v.x) != M) v.x += scale;
if (abs(v.y) != M) v.y += scale;
if (abs(v.z) != M) v.z += scale;
return v;
}
float2 GetIBLBrdf(in float ndv, in float roughness)
{
return (tex2D(IBLLUTSampler, float2(ndv, 1.0 - roughness))).xy;
}
/// Calculate IBL contributation
/// reflectVec: reflection vector for cube sampling
/// wsNormal: surface normal in word space
/// toCamera: normalized direction from surface point to camera
/// roughness: surface roughness
/// ambientOcclusion: ambient occlusion
float3 ImageBasedLighting(in float3 reflectVec, in float3 wsNormal, in float3 toCamera, in float3 diffColor, in float3 specColor, in float roughness, inout float3 reflectionCubeColor)
{
const float ndv = abs(dot(-toCamera, wsNormal)) + 0.001;
const float mipSelect = GetMipFromRoughness(roughness);
float3 cube = (texCUBElod(IBLSampler, float4(FixCubeLookup(reflectVec), mipSelect)).rgb);
float3 cubeD = (texCUBElod(IBLSampler, float4(FixCubeLookup(wsNormal), 7.0)).rgb);
const float3 Hn = normalize(toCamera + reflectVec);
const float vdh = clamp((dot(toCamera, Hn)), M_EPSILON, 1.0);
const float3 fresnelTerm = max(Fresnel(cube, vdh, vdh) * specColor, 0);
const float2 brdf = GetIBLBrdf(ndv, roughness);
const float3 environmentSpecular = (specColor * brdf.x + brdf.y) * cube;
const float3 environmentDiffuse = cubeD * diffColor;
return environmentDiffuse + environmentSpecular + fresnelTerm;
}
#define SPECULAR
#define M_PI 3.141596
#define M_EPSILON 0.0001
#define ROUGHNESS_FLOOR 0.004
#define METALNESS_FLOOR 0.03
#define GAMMA 2.2
//---------------------------------------------------------------------------------------
// Utility Functions
//---------------------------------------------------------------------------------------
float4 FromGamma(float4 val)
{
return pow(val, 2.2);
}
float3 FromGamma(float3 val)
{
return pow(val, 2.2);
}
float4 ToGamma(float4 val)
{
return pow(val, 1.0/2.2);
}
float3 ToGamma(float3 val)
{
return pow(val, 1.0/2.2);
}
float FromGamma(float val)
{
return pow(val, 2.2);
}
float3 GetMetalnessSpecular(float4 diffColor, float metalness)
{
return lerp(0.04, diffColor.rgb, clamp(metalness, 0, 1.0));
}
float4 GetMetalnessDiffuse(float4 diffColor, float metalness, float roughness)
{
return float4(max(diffColor.rgb - diffColor.rgb * metalness, 0.04), diffColor.a);
}
//---------------------------------------------------------------------------------------
// Surface Functions
//---------------------------------------------------------------------------------------
//Schlick Fresnel
//specular = the rgb specular color value of the pixel
//VdotH = the dot product of the camera view direction and the half vector
float3 SchlickFresnel(float3 specular, float VdotH)
{
return specular + (float3(1.0, 1.0, 1.0) - specular) * pow(1.0 - VdotH, 5.0);
}
float3 SchlickFresnelCustom(float3 specular, float LdotH)
{
float ior = 0.25;
float airIor = 1.000277;
float f0 = (ior - airIor) / (ior + airIor);
const float max_ior = 2.5;
f0 = clamp(f0 * f0, 0.0, (max_ior - airIor) / (max_ior + airIor));
return specular * (f0 + (1 - f0) * pow(2, (-5.55473 * LdotH - 6.98316) * LdotH));
}
//Get Fresnel
//specular = the rgb specular color value of the pixel
//VdotH = the dot product of the camera view direction and the half vector
float3 Fresnel(float3 specular, float VdotH, float LdotH)
{
//return SchlickFresnelCustom(specular, LdotH);
return SchlickFresnel(specular, VdotH);
}
// Smith GGX corrected Visibility
// NdotL = the dot product of the normal and direction to the light
// NdotV = the dot product of the normal and the camera view direction
// roughness = the roughness of the pixel
float SmithGGXSchlickVisibility(float NdotL, float NdotV, float roughness)
{
float rough2 = roughness * roughness;
float lambdaV = NdotL * sqrt((-NdotV * rough2 + NdotV) * NdotV + rough2);
float lambdaL = NdotV * sqrt((-NdotL * rough2 + NdotL) * NdotL + rough2);
return 0.5 / (lambdaV + lambdaL);
}
float NeumannVisibility(float NdotV, float NdotL)
{
return NdotL * NdotV / max(1e-7, max(NdotL, NdotV));
}
// Get Visibility
// NdotL = the dot product of the normal and direction to the light
// NdotV = the dot product of the normal and the camera view direction
// roughness = the roughness of the pixel
float Visibility(float NdotL, float NdotV, float roughness)
{
return NeumannVisibility(NdotV, NdotL);
//return SmithGGXSchlickVisibility(NdotL, NdotV, roughness);
}
// GGX Distribution
// NdotH = the dot product of the normal and the half vector
// roughness = the roughness of the pixel
float GGXDistribution(float NdotH, float roughness)
{
float rough2 = roughness * roughness;
float tmp = (NdotH * rough2 - NdotH) * NdotH + 1;
return rough2 / (tmp * tmp);
}
// Get Distribution
// NdotH = the dot product of the normal and the half vector
// roughness = the roughness of the pixel
float Distribution(float NdotH, float roughness)
{
return GGXDistribution(NdotH, roughness);
}
// Custom Lambertian Diffuse
// diffuseColor = the rgb color value of the pixel
// roughness = the roughness of the pixel
// NdotV = the normal dot with the camera view direction
// NdotL = the normal dot with the light direction
// VdotH = the camera view direction dot with the half vector
float3 CustomLambertianDiffuse(float3 diffuseColor, float NdotV, float roughness)
{
return diffuseColor * (1.0 / M_PI) * pow(NdotV, 0.5 + 0.3 * roughness);
}
// Burley Diffuse
// diffuseColor = the rgb color value of the pixel
// roughness = the roughness of the pixel
// NdotV = the normal dot with the camera view direction
// NdotL = the normal dot with the light direction
// VdotH = the camera view direction dot with the half vector
float3 BurleyDiffuse(float3 diffuseColor, float roughness, float NdotV, float NdotL, float VdotH)
{
const float energyBias = lerp(0, 0.5, roughness);
const float energyFactor = lerp(1.0, 1.0 / 1.51, roughness);
const float fd90 = energyBias + 2.0 * VdotH * VdotH * roughness;
const float f0 = 1.0;
const float lightScatter = f0 + (fd90 - f0) * pow(1.0f - NdotL, 5.0f);
const float viewScatter = f0 + (fd90 - f0) * pow(1.0f - NdotV, 5.0f);
return diffuseColor * lightScatter * viewScatter * energyFactor;
}
//Get Diffuse
// diffuseColor = the rgb color value of the pixel
// roughness = the roughness of the pixel
// NdotV = the normal dot with the camera view direction
// NdotL = the normal dot with the light direction
// VdotH = the camera view direction dot with the half vector
float3 Diffuse(float3 diffuseColor, float roughness, float NdotV, float NdotL, float VdotH)
{
//return LambertianDiffuse(diffuseColor);
//return CustomLambertianDiffuse(diffuseColor, NdotV, roughness);
return BurleyDiffuse(diffuseColor, roughness, NdotV, NdotL, VdotH);
}
//---------------------------------------------------------------------------------------
// Light Functions
//---------------------------------------------------------------------------------------
float3 GetSpecularDominantDir(float3 normal, float3 reflection, float roughness)
{
const float smoothness = 1.0 - roughness;
const float lerpFactor = smoothness * (sqrt(smoothness) + roughness);
return lerp(normal, reflection, lerpFactor);
}
//Return the PBR BRDF value
// lightDir = the vector to the light
// lightVec = normalised lightDir
// toCamera = vector to the camera
// normal = surface normal of the pixel
// roughness = roughness of the pixel
// diffColor = the rgb color of the pixel
// specColor = the rgb specular color of the pixel
float3 GetBRDF(float3 worldPos, float3 lightDir, float3 lightVec, float3 toCamera, float3 normal, float roughness, float3 diffColor, float3 specColor)
{
const float3 Hn = normalize(toCamera + lightDir);
const float vdh = clamp((dot(toCamera, Hn)), M_EPSILON, 1.0);
const float ndh = clamp((dot(normal, Hn)), M_EPSILON, 1.0);
float ndl = clamp((dot(normal, lightVec)), M_EPSILON, 1.0);
const float ndv = clamp((dot(normal, toCamera)), M_EPSILON, 1.0);
const float ldh = clamp((dot(lightVec, Hn)), M_EPSILON, 1.0);
const float3 diffuseFactor = Diffuse(diffColor, roughness, ndv, ndl, vdh) * ndl;
float3 specularFactor = 0;
const float3 fresnelTerm = Fresnel(specColor, vdh, ldh);
const float distTerm = Distribution(ndh, roughness);
const float visTerm = Visibility(ndl, ndv, roughness);
specularFactor = distTerm * visTerm * fresnelTerm * ndl/ M_PI;
return diffuseFactor + specularFactor;
}
#if OPENGL
#define SV_POSITION POSITION
#define VS_SHADERMODEL vs_3_0
#define PS_SHADERMODEL ps_3_0
#else
#define VS_SHADERMODEL vs_4_0
#define PS_SHADERMODEL ps_4_0
#endif
float2 UVTiling = float2(1,1);
float3 CameraPosition;
float3 LightDir = float3(0, -1, 0);
matrix WorldViewProjection;
matrix Transform;
float AmbientBrightness;
int FlipCulling = -1;
#include "PBR.inc"
#include "NoTanNormals.inc"
Texture2D DiffuseTex;
Texture2D NormalMapTex;
Texture2D RoughnessTex;
Texture2D MetalnessTex;
Texture2D HeightMapTex;
Texture2D AOTex;
Texture2D SubsurfaceColorTex;
Texture2D SubsurfaceDepthTex;
Texture2D EmissiveMaskTex;
Texture2D IBLLUTTex;
TextureCube IBLTex;
sampler2D DiffuseSampler = sampler_state
{
Texture = <DiffuseTex>;
AddressU = Wrap;
AddressV = Wrap;
};
sampler2D NormalMapSampler = sampler_state
{
Texture = <NormalMapTex>;
AddressU = Wrap;
AddressV = Wrap;
};
sampler2D RoughnessSampler = sampler_state
{
Texture = <RoughnessTex>;
AddressU = Wrap;
AddressV = Wrap;
};
sampler2D MetalnessSampler = sampler_state
{
Texture = <MetalnessTex>;
AddressU = Wrap;
AddressV = Wrap;
};
sampler2D HeightMapSampler = sampler_state
{
Texture = <HeightMapTex>;
AddressU = Wrap;
AddressV = Wrap;
};
sampler2D AOSampler = sampler_state
{
Texture = <AOTex>;
AddressU = Wrap;
AddressV = Wrap;
};
sampler2D EmissiveMaskSampler = sampler_state
{
Texture = <EmissiveMaskTex>;
AddressU = Wrap;
AddressV = Wrap;
};
sampler2D SSColorSampler = sampler_state
{
Texture = <SubsurfaceColorTex>;
AddressU = Wrap;
AddressV = Wrap;
};
sampler2D SSDepthSampler = sampler_state
{
Texture = <SubsurfaceDepthTex>;
AddressU = Wrap;
AddressV = Wrap;
};
sampler2D IBLLUTSampler = sampler_state
{
Texture = <IBLLUTTex>;
AddressU = Clamp;
AddressV = Clamp;
};
samplerCUBE IBLSampler = sampler_state
{
Texture = <IBLTex>;
};
#include "Disp.inc"
struct VertexShaderInput
{
float4 Position : POSITION0;
float3 Normal : NORMAL0;
float4 Tangent : TANGENT0;
float2 UVCoord : TEXCOORD0;
};
struct VertexShaderOutput
{
float4 Position : SV_POSITION;
float3 Normal : NORMAL0;
float2 SampleUV : TEXCOORD0;
float3 WorldPos : TEXCOORD1;
float3 Tangent : TANGENT0;
float3 Bitangent : BITANGENT0;
};
#include "IBL.inc"
VertexShaderOutput MainVS(in VertexShaderInput input)
{
VertexShaderOutput output = (VertexShaderOutput)0;
output.Position = mul(mul(input.Position, Transform), WorldViewProjection);
output.WorldPos = input.Position; //mul(input.Position, Transform).xyz;
output.Normal = normalize(mul(input.Normal, (float3x3)Transform));
float3 tangent = normalize(mul(input.Tangent.xyz, (float3x3)Transform));
float3 bitangent = cross(tangent.xyz, output.Normal.xyz).xyz * input.Tangent.w;
output.Tangent = normalize(tangent);
output.Bitangent = normalize(bitangent);
output.SampleUV = input.UVCoord * UVTiling;
return output;
}
float GetAtten(float3 normal, float3 worldPos)
{
return saturate(dot(normal, -LightDir));
}
float4 NormalToColor(float3 val)
{
return float4(val * 0.5 + 0.5, 1.0);
}
float4 PBRCommon(VertexShaderOutput input, float4 diffuse, float3 specColor, float roughness)
{
float3 normVal = tex2D(NormalMapSampler, input.SampleUV).xyz * 2.0 - 1.0;
float3 eyeToPixel = normalize(input.WorldPos - CameraPosition);
const float3x3 tbn = transpose(float3x3(input.Tangent, input.Bitangent * FlipCulling, input.Normal));
float3 norm = normalize(mul(tbn, normVal).xyz);
//float3 norm = perturb_normal(input.Normal, eyeToPixel, NormalMapSampler, input.SampleUV);
float atten = GetAtten(norm, input.WorldPos);
float4 oColor = float4(GetBRDF(input.WorldPos, -LightDir, normalize(-LightDir), -eyeToPixel, norm, roughness, diffuse.rgb, specColor), diffuse.a);
float3 reflectVec = normalize(reflect(eyeToPixel, norm));
float3 reflectionCubeColor = float3(0.25, 0.25, 0.25);
float3 ibl = ImageBasedLighting(reflectVec, norm, -eyeToPixel, diffuse.rgb, specColor, roughness, reflectionCubeColor);
float AOValue = tex2D(AOSampler, input.SampleUV).x;
AOValue = AOValue < 0.001 ? 1.0 : AOValue;
float emissive = max(tex2D(EmissiveMaskSampler, input.SampleUV).x - 0.2, 0.0);
float ssDepth =tex2D(SSDepthSampler, input.SampleUV).x;
if (ssDepth > 0)
{
float4 ssColor = tex2D(SSColorSampler, input.SampleUV);
float ssWrap = (1.0 - ssDepth);
float scatterWidth = 0.5;
float3 H = normalize(-LightDir + norm * scatterWidth);
float VdotH = pow(saturate(dot(eyeToPixel, H)), 1.3);
float3 I = VdotH * ssDepth;
float3 ssContrib = ssColor * I * ssColor.a;
//float vdh = dot(norm, -LightDir) * 2 - 1;
//float ndh = dot(eyeToPixel, -LightDir) * 2 - 1;
//float ndlWrap = (vdh + ssWrap) / (1 + ssWrap);
//float scatter = smoothstep(0.0, scatterWidth, ndlWrap) * smoothstep(scatterWidth * 2, scatterWidth, ndlWrap);
//float3 ssContrib = ssColor * scatter * ssColor.a;
oColor.rgb += ssContrib;
// SS is emissive in nature, thus it fights off IBL
ibl.rgb = max(ibl.rgb - ssContrib, 0);
//ibl -= ssContrib*0.5;
}
/// ambient term + direct-light + ibl
return
diffuse * emissive * AOValue +
//float4(
//(AmbientBrightness + emissive) * AOValue,
//(AmbientBrightness + emissive) * AOValue,
//(AmbientBrightness + emissive) * AOValue,
//1) +
(oColor + float4(ibl * AOValue /** (1.0f - AmbientBrightness * 0.5 - emissive)*/, diffuse.a));
}
float4 RoughMetalPS(VertexShaderOutput input) : COLOR
{
float4 diffuse = tex2D(DiffuseSampler, input.SampleUV);
float roughness = tex2D(RoughnessSampler, input.SampleUV).r;
roughness *= roughness;
roughness = max(roughness, 0.01);
float metalness = tex2D(MetalnessSampler, input.SampleUV).r;
float3 specColor = GetMetalnessSpecular(diffuse, metalness);
diffuse = GetMetalnessDiffuse(diffuse, metalness, roughness);
return PBRCommon(input, diffuse, specColor, roughness);
}
float4 RoughSpecularPS(VertexShaderOutput input) : COLOR
{
float4 diffuse = tex2D(DiffuseSampler, input.SampleUV);
float roughness = tex2D(RoughnessSampler, input.SampleUV).r;
roughness *= roughness;
roughness = max(roughness, 0.01);
float3 specColor = tex2D(MetalnessSampler, input.SampleUV).rgb;
return PBRCommon(input, diffuse, specColor, roughness);
}
float4 GlossyMetalPS(VertexShaderOutput input) : COLOR
{
float4 diffuse = tex2D(DiffuseSampler, input.SampleUV);
float roughness = tex2D(RoughnessSampler, input.SampleUV).r;
roughness = 1.0 - roughness;
roughness *= roughness;
roughness = max(roughness, 0.01);
float3 metalness = tex2D(MetalnessSampler, input.SampleUV).r;
float3 specColor = GetMetalnessSpecular(diffuse, metalness.x);
diffuse = GetMetalnessDiffuse(diffuse, metalness.x, roughness);
return PBRCommon(input, diffuse, specColor, roughness);
}
float4 GlossySpecularPS(VertexShaderOutput input) : COLOR
{
float height = tex2D(HeightMapSampler, input.SampleUV).r;
float4 diffuse = tex2D(DiffuseSampler, input.SampleUV);
float roughness = tex2D(RoughnessSampler, input.SampleUV).r;
roughness = 1.0 - roughness;
roughness *= roughness;
roughness = max(roughness, 0.01);
float3 specColor = tex2D(MetalnessSampler, input.SampleUV).rgb;
return PBRCommon(input, diffuse, specColor, roughness);
}
float2 DoHeightMap(VertexShaderOutput input)
{
//float3 normVal = tex2D(NormalMapSampler, input.SampleUV).xyz * 2.0 - 1.0;
//const float3x3 tbn = transpose(float3x3(normalize(input.Tangent), normalize(-input.Bitangent), normalize(input.Normal)));
//float3 norm = normalize(mul(tbn, normVal).xyz);
float3 pixelToEye = normalize(CameraPosition - input.WorldPos);
return updateUV(pixelToEye, input.Normal, normalize(input.Tangent), normalize(input.Bitangent), 0.4, input.SampleUV);
}
float4 RoughMetalHeightPS(VertexShaderOutput input) : COLOR
{
float height = tex2D(HeightMapSampler, input.SampleUV).r;
input.SampleUV = DoHeightMap(input);
return RoughMetalPS(input);
}
float4 RoughSpecularHeightPS(VertexShaderOutput input) : COLOR
{
float height = tex2D(HeightMapSampler, input.SampleUV).r;
input.SampleUV = DoHeightMap(input);
return RoughSpecularPS(input);
}
float4 GlossyMetalHeightPS(VertexShaderOutput input) : COLOR
{
float height = tex2D(HeightMapSampler, input.SampleUV).r;
input.SampleUV = DoHeightMap(input);
return GlossyMetalPS(input);
}
float4 GlossySpecularHeightPS(VertexShaderOutput input) : COLOR
{
float height = tex2D(HeightMapSampler, input.SampleUV).r;
input.SampleUV = DoHeightMap(input);
return GlossySpecularPS(input);
}
technique PBRRoughMetal
{
pass P0
{
VertexShader = compile VS_SHADERMODEL MainVS();
PixelShader = compile PS_SHADERMODEL RoughMetalPS();
}
};
technique PBRRoughSpecular
{
pass P0
{
VertexShader = compile VS_SHADERMODEL MainVS();
PixelShader = compile PS_SHADERMODEL RoughSpecularPS();
}
};
technique PBRGlossyMetal
{
pass P0
{
VertexShader = compile VS_SHADERMODEL MainVS();
PixelShader = compile PS_SHADERMODEL GlossyMetalPS();
}
};
technique PBRGlossySpecular
{
pass P0
{
VertexShader = compile VS_SHADERMODEL MainVS();
PixelShader = compile PS_SHADERMODEL GlossySpecularPS();
}
};
// Displacement mapping versions
technique PBRRoughMetalHeight
{
pass P0
{
VertexShader = compile VS_SHADERMODEL MainVS();
PixelShader = compile PS_SHADERMODEL RoughMetalHeightPS();
}
};
technique PBRRoughSpecularHeight
{
pass P0
{
VertexShader = compile VS_SHADERMODEL MainVS();
PixelShader = compile PS_SHADERMODEL RoughSpecularHeightPS();
}
};
technique PBRGlossyMetalHeight
{
pass P0
{
VertexShader = compile VS_SHADERMODEL MainVS();
PixelShader = compile PS_SHADERMODEL GlossyMetalHeightPS();
}
};
technique PBRGlossySpecularHeight
{
pass P0
{
VertexShader = compile VS_SHADERMODEL MainVS();
PixelShader = compile PS_SHADERMODEL GlossySpecularHeightPS();
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment