635 lines
26 KiB
SourcePawn
635 lines
26 KiB
SourcePawn
// Based on: https://github.com/KhronosGroup/glTF-WebGL-PBR/blob/master/shaders/pbr-frag.glsl
|
|
|
|
// 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 {
|
|
// geometry properties
|
|
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
|
|
|
|
// Normal
|
|
vec3 n; // Sahding normal
|
|
vec3 ng; // Geometry normal
|
|
vec3 t; // Geometry tangent
|
|
vec3 b; // Geometry bitangent
|
|
|
|
vec3 v; // vector from surface point to camera
|
|
|
|
// material properties
|
|
float perceptualRoughness; // roughness value, as authored by the model creator (input to shader)
|
|
vec3 reflectance0; // full reflectance color (normal incidence angle)
|
|
vec3 reflectance90; // reflectance color at grazing angle
|
|
float alphaRoughness; // roughness mapped to a more linear change in the roughness (proposed by [2])
|
|
vec3 diffuseColor; // color contribution from diffuse lighting
|
|
vec3 specularColor; // color contribution from specular lighting
|
|
|
|
vec4 baseColor;
|
|
float metallic;
|
|
|
|
float sheenRoughnessFactor;
|
|
vec3 sheenColorFactor;
|
|
|
|
vec3 clearcoatF0;
|
|
vec3 clearcoatF90;
|
|
float clearcoatFactor;
|
|
vec3 clearcoatNormal;
|
|
float clearcoatRoughness;
|
|
|
|
// KHR_materials_specular
|
|
float specularWeight; // product of specularFactor and specularTexture.a
|
|
|
|
float transmissionFactor;
|
|
|
|
float thickness;
|
|
vec4 attenuation; // rgb - color, w - distance
|
|
|
|
// KHR_materials_iridescence
|
|
float iridescenceFactor;
|
|
float iridescenceIor;
|
|
float iridescenceThickness;
|
|
|
|
// KHR_materials_anisotropy
|
|
vec3 anisotropicT;
|
|
vec3 anisotropicB;
|
|
float anisotropyStrength;
|
|
|
|
float ior;
|
|
};
|
|
|
|
const float M_PI = 3.141592653589793;
|
|
|
|
vec4 SRGBtoLINEAR(vec4 srgbIn) {
|
|
vec3 linOut = pow(srgbIn.xyz,vec3(2.2));
|
|
|
|
return vec4(linOut, srgbIn.a);
|
|
}
|
|
|
|
float clampedDot(vec3 x, vec3 y) {
|
|
return clamp(dot(x, y), 0.0, 1.0);
|
|
}
|
|
|
|
//
|
|
// Fresnel
|
|
//
|
|
// http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html
|
|
// https://github.com/wdas/brdf/tree/master/src/brdfs
|
|
// https://google.github.io/filament/Filament.md.html
|
|
//
|
|
|
|
// The following equation models the Fresnel reflectance term of the spec equation (aka F())
|
|
// Implementation of fresnel from [4], Equation 15
|
|
vec3 F_Schlick(vec3 f0, vec3 f90, float VdotH) {
|
|
return f0 + (f90 - f0) * pow(clamp(1.0 - VdotH, 0.0, 1.0), 5.0);
|
|
}
|
|
|
|
float F_Schlick(float f0, float f90, float VdotH) {
|
|
float x = clamp(1.0 - VdotH, 0.0, 1.0);
|
|
float x2 = x * x;
|
|
float x5 = x * x2 * x2;
|
|
return f0 + (f90 - f0) * x5;
|
|
}
|
|
|
|
float F_Schlick(float f0, float VdotH) {
|
|
float f90 = 1.0; //clamp(50.0 * f0, 0.0, 1.0);
|
|
return F_Schlick(f0, f90, VdotH);
|
|
}
|
|
|
|
vec3 F_Schlick(vec3 f0, float f90, float VdotH) {
|
|
float x = clamp(1.0 - VdotH, 0.0, 1.0);
|
|
float x2 = x * x;
|
|
float x5 = x * x2 * x2;
|
|
return f0 + (f90 - f0) * x5;
|
|
}
|
|
|
|
vec3 F_Schlick(vec3 f0, float VdotH) {
|
|
float f90 = 1.0; //clamp(dot(f0, vec3(50.0 * 0.33)), 0.0, 1.0);
|
|
return F_Schlick(f0, f90, VdotH);
|
|
}
|
|
|
|
vec3 Schlick_to_F0(vec3 f, vec3 f90, float VdotH) {
|
|
float x = clamp(1.0 - VdotH, 0.0, 1.0);
|
|
float x2 = x * x;
|
|
float x5 = clamp(x * x2 * x2, 0.0, 0.9999);
|
|
|
|
return (f - f90 * x5) / (1.0 - x5);
|
|
}
|
|
|
|
float Schlick_to_F0(float f, float f90, float VdotH) {
|
|
float x = clamp(1.0 - VdotH, 0.0, 1.0);
|
|
float x2 = x * x;
|
|
float x5 = clamp(x * x2 * x2, 0.0, 0.9999);
|
|
|
|
return (f - f90 * x5) / (1.0 - x5);
|
|
}
|
|
|
|
vec3 Schlick_to_F0(vec3 f, float VdotH) {
|
|
return Schlick_to_F0(f, vec3(1.0), VdotH);
|
|
}
|
|
|
|
float Schlick_to_F0(float f, float VdotH) {
|
|
return Schlick_to_F0(f, 1.0, VdotH);
|
|
}
|
|
|
|
// Smith Joint GGX
|
|
// Note: Vis = G / (4 * NdotL * NdotV)
|
|
// see Eric Heitz. 2014. Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs. Journal of Computer Graphics Techniques, 3
|
|
// see Real-Time Rendering. Page 331 to 336.
|
|
// see https://google.github.io/filament/Filament.md.html#materialsystem/specularbrdf/geometricshadowing(specularg)
|
|
float V_GGX(float NdotL, float NdotV, float alphaRoughness) {
|
|
float alphaRoughnessSq = alphaRoughness * alphaRoughness;
|
|
|
|
float GGXV = NdotL * sqrt(NdotV * NdotV * (1.0 - alphaRoughnessSq) + alphaRoughnessSq);
|
|
float GGXL = NdotV * sqrt(NdotL * NdotL * (1.0 - alphaRoughnessSq) + alphaRoughnessSq);
|
|
|
|
float GGX = GGXV + GGXL;
|
|
|
|
return GGX > 0.0 ? 0.5 / GGX : 0.0;
|
|
}
|
|
|
|
|
|
// 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 D_GGX(float NdotH, float alphaRoughness) {
|
|
float alphaRoughnessSq = alphaRoughness * alphaRoughness;
|
|
float f = (NdotH * NdotH) * (alphaRoughnessSq - 1.0) + 1.0;
|
|
return alphaRoughnessSq / (M_PI * f * f);
|
|
}
|
|
|
|
// specularWeight is introduced with KHR_materials_specular
|
|
vec3 getIBLRadianceLambertian(float NdotV, vec3 n, float roughness, vec3 diffuseColor, vec3 F0, float specularWeight, EnvironmentMapDataGPU envMap) {
|
|
vec2 brdfSamplePoint = clamp(vec2(NdotV, roughness), vec2(0.0, 0.0), vec2(1.0, 1.0));
|
|
|
|
vec2 f_ab = sampleBRDF_LUT(brdfSamplePoint, envMap).rg;
|
|
|
|
vec3 irradiance = sampleEnvMapIrradiance(n.xyz, envMap).rgb;
|
|
|
|
// see https://bruop.github.io/ibl/#single_scattering_results at Single Scattering Results
|
|
// Roughness dependent fresnel, from Fdez-Aguera
|
|
|
|
vec3 Fr = max(vec3(1.0 - roughness), F0) - F0;
|
|
vec3 k_S = F0 + Fr * pow(1.0 - NdotV, 5.0);
|
|
vec3 FssEss = specularWeight * k_S * f_ab.x + f_ab.y; // <--- GGX / specular light contribution (scale it down if the specularWeight is low)
|
|
|
|
// Multiple scattering, from Fdez-Aguera
|
|
float Ems = (1.0 - (f_ab.x + f_ab.y));
|
|
vec3 F_avg = specularWeight * (F0 + (1.0 - F0) / 21.0);
|
|
vec3 FmsEms = Ems * FssEss * F_avg / (1.0 - F_avg * Ems);
|
|
vec3 k_D = diffuseColor * (1.0 - FssEss + FmsEms); // we use +FmsEms as indicated by the formula in the blog post (might be a typo in the implementation)
|
|
|
|
return (FmsEms + k_D) * irradiance;
|
|
}
|
|
|
|
//https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#acknowledgments AppendixB
|
|
vec3 getBRDFLambertian(vec3 f0, vec3 f90, vec3 diffuseColor, float specularWeight, float VdotH) {
|
|
// see https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/
|
|
return (1.0 - specularWeight * F_Schlick(f0, f90, VdotH)) * (diffuseColor / M_PI);
|
|
}
|
|
|
|
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#acknowledgments AppendixB
|
|
vec3 getBRDFSpecularGGX(vec3 f0, vec3 f90, float alphaRoughness, float specularWeight, float VdotH, float NdotL, float NdotV, float NdotH) {
|
|
vec3 F = F_Schlick(f0, f90, VdotH);
|
|
float Vis = V_GGX(NdotL, NdotV, alphaRoughness);
|
|
float D = D_GGX(NdotH, alphaRoughness);
|
|
|
|
return specularWeight * F * Vis * D;
|
|
}
|
|
|
|
vec3 getIBLRadianceGGX(vec3 n, vec3 v, float roughness, vec3 F0, float specularWeight, EnvironmentMapDataGPU envMap ) {
|
|
float NdotV = clampedDot(n, v);
|
|
|
|
float mipCount = float(sampleEnvMapQueryLevels(envMap));
|
|
float lod = roughness * float(mipCount - 1);
|
|
vec3 reflection = normalize(reflect(-v, n));
|
|
|
|
vec2 brdfSamplePoint = clamp(vec2(NdotV, roughness), vec2(0.0, 0.0), vec2(1.0, 1.0));
|
|
vec3 f_ab = sampleBRDF_LUT(brdfSamplePoint, envMap).rgb;
|
|
vec3 specularLight = sampleEnvMapLod(reflection.xyz, lod, envMap).rgb;
|
|
|
|
// see https://bruop.github.io/ibl/#single_scattering_results at Single Scattering Results
|
|
// Roughness dependent fresnel, from Fdez-Aguera
|
|
vec3 Fr = max(vec3(1.0 - roughness), F0) - F0;
|
|
vec3 k_S = F0 + Fr * pow(1.0 - NdotV, 5.0);
|
|
vec3 FssEss = k_S * f_ab.x + f_ab.y;
|
|
|
|
return specularWeight * specularLight * FssEss;
|
|
}
|
|
|
|
// 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.
|
|
vec3 getIBLRadianceContributionGGX(PBRInfo pbrInputs, float specularWeight, EnvironmentMapDataGPU envMap) {
|
|
vec3 n = pbrInputs.n;
|
|
vec3 v = pbrInputs.v;
|
|
vec3 reflection = normalize(reflect(-v, n));
|
|
float mipCount = float(sampleEnvMapQueryLevels(envMap));
|
|
float lod = pbrInputs.perceptualRoughness * (mipCount - 1);
|
|
|
|
// retrieve a scale and bias to F0. See [1], Figure 3
|
|
vec2 brdfSamplePoint = clamp(vec2(pbrInputs.NdotV, pbrInputs.perceptualRoughness), vec2(0.0, 0.0), vec2(1.0, 1.0));
|
|
vec3 brdf = sampleBRDF_LUT(brdfSamplePoint, envMap).rgb;
|
|
// HDR envmaps are already linear
|
|
vec3 specularLight = sampleEnvMapLod(reflection.xyz, lod, envMap).rgb;
|
|
|
|
// see https://bruop.github.io/ibl/#single_scattering_results at Single Scattering Results
|
|
// Roughness dependent fresnel, from Fdez-Aguera
|
|
vec3 Fr = max(vec3(1.0 - pbrInputs.perceptualRoughness), pbrInputs.reflectance0) - pbrInputs.reflectance0;
|
|
vec3 k_S = pbrInputs.reflectance0 + Fr * pow(1.0 - pbrInputs.NdotV, 5.0);
|
|
vec3 FssEss = k_S * brdf.x + brdf.y;
|
|
|
|
return specularWeight * specularLight * FssEss;
|
|
}
|
|
|
|
|
|
// Disney Implementation of diffuse from Physically-Based Shading at Disney by Brent Burley. See Section 5.3.
|
|
// http://blog.selfshadow.com/publications/s2012-shading-course/burley/s2012_pbs_disney_brdf_notes_v3.pdf
|
|
vec3 diffuseBurley(PBRInfo pbrInputs) {
|
|
float f90 = 2.0 * pbrInputs.LdotH * pbrInputs.LdotH * pbrInputs.alphaRoughness - 0.5;
|
|
|
|
return (pbrInputs.diffuseColor / M_PI) * (1.0 + f90 * pow((1.0 - pbrInputs.NdotL), 5.0)) * (1.0 + f90 * pow((1.0 - pbrInputs.NdotV), 5.0));
|
|
}
|
|
|
|
// The following equation models the Fresnel reflectance term of the spec equation (aka F())
|
|
// Implementation of fresnel from [4], Equation 15
|
|
vec3 specularReflection(PBRInfo pbrInputs) {
|
|
return pbrInputs.reflectance0 + (pbrInputs.reflectance90 - pbrInputs.reflectance0) * pow(clamp(1.0 - pbrInputs.VdotH, 0.0, 1.0), 5.0);
|
|
}
|
|
|
|
// 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) {
|
|
float NdotL = pbrInputs.NdotL;
|
|
float NdotV = pbrInputs.NdotV;
|
|
float rSqr = pbrInputs.alphaRoughness * pbrInputs.alphaRoughness;
|
|
|
|
float attenuationL = 2.0 * NdotL / (NdotL + sqrt(rSqr + (1.0 - rSqr) * (NdotL * NdotL)));
|
|
float attenuationV = 2.0 * NdotV / (NdotV + sqrt(rSqr + (1.0 - rSqr) * (NdotV * NdotV)));
|
|
return attenuationL * attenuationV;
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
vec3 getIBLRadianceCharlie(PBRInfo pbrInputs, EnvironmentMapDataGPU envMap) {
|
|
float sheenRoughness = pbrInputs.sheenRoughnessFactor;
|
|
vec3 sheenColor = pbrInputs.sheenColorFactor;
|
|
float mipCount = float(sampleEnvMapQueryLevels(envMap));
|
|
float lod = sheenRoughness * float(mipCount - 1);
|
|
vec3 reflection = normalize(reflect(-pbrInputs.v, pbrInputs.n));
|
|
|
|
vec2 brdfSamplePoint = clamp(vec2(pbrInputs.NdotV, sheenRoughness), vec2(0.0, 0.0), vec2(1.0, 1.0));
|
|
float brdf = sampleBRDF_LUT(brdfSamplePoint, envMap).b;
|
|
vec3 sheenSample = sampleCharlieEnvMapLod(reflection.xyz, lod, envMap).rgb;
|
|
|
|
return sheenSample * sheenColor * brdf;
|
|
}
|
|
|
|
PBRInfo calculatePBRInputsMetallicRoughness(InputAttributes tc, vec4 albedo, vec4 mrSample, MetallicRoughnessDataGPU mat) {
|
|
PBRInfo pbrInputs;
|
|
|
|
bool isSpecularGlossiness = (getMaterialType(mat) & 2) != 0;
|
|
bool isSpecular = (getMaterialType(mat) & 0x10) != 0;
|
|
|
|
pbrInputs.ior = getIOR(mat);
|
|
vec3 f0 = isSpecularGlossiness ? getSpecularFactor(mat) * mrSample.rgb : vec3(pow((pbrInputs.ior - 1)/(pbrInputs.ior + 1), 2));
|
|
|
|
float metallic = getMetallicFactor(mat);
|
|
metallic = mrSample.b * metallic;
|
|
metallic = clamp(metallic, 0.0, 1.0);
|
|
|
|
pbrInputs.baseColor = albedo;
|
|
pbrInputs.metallic = metallic;
|
|
pbrInputs.specularWeight = 1.0;
|
|
|
|
if (isSpecular) {
|
|
vec3 dielectricSpecularF0 = min(f0 * getSpecularColorFactor(tc, mat), vec3(1.0));
|
|
f0 = mix(dielectricSpecularF0, pbrInputs.baseColor.rgb, metallic);
|
|
pbrInputs.specularWeight = getSpecularFactor(tc, mat);//u_KHR_materials_specular_specularFactor * specularTexture.a;
|
|
//pbrInputs.diffuseColor = mix(pbrInputs.baseColor.rgb, vec3(0), metallic);
|
|
}
|
|
|
|
float perceptualRoughness = isSpecularGlossiness ? getGlossinessFactor(mat): getRoughnessFactor(mat);
|
|
|
|
const float c_MinRoughness = 0.04;
|
|
|
|
// Metallic roughness:
|
|
// 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
|
|
// Specular Glossiness:
|
|
// Glossiness is stored in alpha channel
|
|
perceptualRoughness = isSpecularGlossiness ? 1.0 - mrSample.a * perceptualRoughness : clamp(mrSample.g * perceptualRoughness, c_MinRoughness, 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;
|
|
|
|
vec3 diffuseColor = isSpecularGlossiness ? pbrInputs.baseColor.rgb * (1.0 - max(max(f0.r, f0.g), f0.b)) : mix(pbrInputs.baseColor.rgb, vec3(0), metallic);
|
|
vec3 specularColor = isSpecularGlossiness ? f0 : mix(f0, pbrInputs.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);
|
|
vec3 specularEnvironmentR0 = specularColor.rgb;
|
|
vec3 specularEnvironmentR90 = vec3(1.0, 1.0, 1.0) * reflectance90;
|
|
|
|
pbrInputs.alphaRoughness = alphaRoughness;
|
|
|
|
pbrInputs.perceptualRoughness = perceptualRoughness;
|
|
pbrInputs.reflectance0 = specularEnvironmentR0;
|
|
pbrInputs.reflectance90 = specularEnvironmentR90;
|
|
|
|
pbrInputs.diffuseColor = diffuseColor;
|
|
pbrInputs.specularColor = specularColor;
|
|
|
|
return pbrInputs;
|
|
}
|
|
|
|
vec3 calculatePBRLightContribution( inout PBRInfo pbrInputs, vec3 lightDirection, vec3 lightColor ) {
|
|
vec3 n = pbrInputs.n;
|
|
vec3 v = pbrInputs.v;
|
|
vec3 l = normalize(lightDirection); // Vector from surface point to light
|
|
vec3 h = normalize(l+v); // Half vector between both l and v
|
|
|
|
float NdotV = pbrInputs.NdotV;
|
|
float NdotL = clamp(dot(n, l), 0.001, 1.0);
|
|
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);
|
|
|
|
vec3 color = vec3(0);
|
|
|
|
if (NdotL > 0.0 || NdotV > 0.0) {
|
|
pbrInputs.NdotL = NdotL;
|
|
pbrInputs.NdotH = NdotH;
|
|
pbrInputs.LdotH = LdotH;
|
|
pbrInputs.VdotH = VdotH;
|
|
|
|
// Calculate the shading terms for the microfacet specular shading model
|
|
vec3 F = specularReflection(pbrInputs);
|
|
float G = geometricOcclusion(pbrInputs);
|
|
float D = microfacetDistribution(pbrInputs);
|
|
|
|
// Calculation of analytical lighting contribution
|
|
vec3 diffuseContrib = (1.0 - F) * diffuseBurley(pbrInputs);
|
|
vec3 specContrib = F * G * D / (4.0 * NdotL * NdotV);
|
|
// Obtain final intensity as reflectance (BRDF) scaled by the energy of the light (cosine law)
|
|
color = NdotL * lightColor * (diffuseContrib + specContrib);
|
|
}
|
|
return color;
|
|
}
|
|
|
|
// http://www.thetenthplanet.de/archives/1180
|
|
// modified to fix handedness of the resulting cotangent frame
|
|
mat3 cotangentFrame( vec3 N, vec3 p, vec2 uv, inout PBRInfo pbrInputs ) {
|
|
// get edge vectors of the pixel triangle
|
|
vec3 dp1 = dFdx( p );
|
|
vec3 dp2 = dFdy( p );
|
|
vec2 duv1 = dFdx( uv );
|
|
vec2 duv2 = dFdy( uv );
|
|
|
|
if (length(duv1) <= 1e-2) {
|
|
duv1 = vec2(1.0, 0.0);
|
|
}
|
|
|
|
if (length(duv2) <= 1e-2) {
|
|
duv2 = vec2(0.0, 1.0);
|
|
}
|
|
|
|
// solve the linear system
|
|
vec3 dp2perp = cross( dp2, N );
|
|
vec3 dp1perp = cross( N, dp1 );
|
|
vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
|
|
vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;
|
|
|
|
// construct a scale-invariant frame
|
|
float invmax = inversesqrt( max( dot(T,T), dot(B,B) ) );
|
|
|
|
// calculate handedness of the resulting cotangent frame
|
|
float w = dot(cross(N, T), B) < 0.0 ? -1.0 : 1.0;
|
|
|
|
// adjust tangent if needed
|
|
T = T * w;
|
|
|
|
if (gl_FrontFacing == false) {
|
|
N *= -1.0f;
|
|
T *= -1.0f;
|
|
B *= -1.0f;
|
|
}
|
|
|
|
pbrInputs.t = T * invmax;
|
|
pbrInputs.b = B * invmax;
|
|
pbrInputs.ng = N;
|
|
|
|
return mat3( pbrInputs.t, pbrInputs.b, N );
|
|
}
|
|
|
|
void perturbNormal(vec3 n, vec3 v, vec3 normalSample, vec2 uv, inout PBRInfo pbrInputs) {
|
|
vec3 map = normalize( 2.0 * normalSample - vec3(1.0) );
|
|
mat3 TBN = cotangentFrame(n, v, uv, pbrInputs);
|
|
|
|
pbrInputs.n = normalize(TBN * map);
|
|
}
|
|
|
|
// Check normals - model has tangents and a normal map!
|
|
|
|
vec3 getVolumeTransmissionRay(vec3 n, vec3 v, float thickness, float ior, mat4 modelMatrix) {
|
|
// direction of refracted light
|
|
vec3 refractionVector = refract(-v, n, 1.0 / ior);
|
|
|
|
// compute rotation-independent scaling of the model matrix
|
|
vec3 modelScale = vec3(length(modelMatrix[0].xyz),
|
|
length(modelMatrix[1].xyz),
|
|
length(modelMatrix[2].xyz));
|
|
|
|
// the thickness is specified in local space
|
|
return normalize(refractionVector) * thickness * modelScale.xyz;
|
|
}
|
|
|
|
vec3 applyVolumeAttenuation(vec3 radiance, float transmissionDistance, vec3 attenuationColor, float attenuationDistance) {
|
|
if (attenuationDistance == 0.0) {
|
|
// Attenuation distance is +∞ (which we indicate by zero), i.e. the transmitted color is not attenuated at all.
|
|
return radiance;
|
|
}
|
|
|
|
// Compute light attenuation using Beer's law.
|
|
vec3 attenuationCoefficient = -log(attenuationColor) / attenuationDistance;
|
|
vec3 transmittance = exp(-attenuationCoefficient * transmissionDistance); // Beer's law
|
|
return transmittance * radiance;
|
|
}
|
|
|
|
float applyIorToRoughness(float roughness, float ior) {
|
|
// Scale roughness with IOR so that an IOR of 1.0 results in no microfacet refraction and
|
|
// an IOR of 1.5 results in the default amount of microfacet refraction.
|
|
return roughness * clamp(ior * 2.0 - 2.0, 0.0, 1.0);
|
|
}
|
|
|
|
|
|
vec3 getTransmissionSample(vec2 fragCoord, float roughness, float ior) {
|
|
const ivec2 size = textureBindlessSize2D(perFrame.transmissionFramebuffer);
|
|
const vec2 uv = fragCoord;
|
|
float framebufferLod = log2(float(size.x)) * applyIorToRoughness(roughness, ior);
|
|
vec3 transmittedLight = textureBindless2DLod(perFrame.transmissionFramebuffer, perFrame.transmissionFramebufferSampler, uv, framebufferLod).rgb;
|
|
// DEBUG:
|
|
// return vec3(uv, 0.0);
|
|
return transmittedLight;
|
|
}
|
|
|
|
vec3 getIBLVolumeRefraction(vec3 n, vec3 v, float perceptualRoughness, vec3 baseColor, vec3 f0, vec3 f90,
|
|
vec3 position, mat4 modelMatrix, mat4 viewProjMatrix, float ior, float thickness, vec3 attenuationColor, float attenuationDistance)
|
|
{
|
|
vec3 transmissionRay = getVolumeTransmissionRay(n, v, thickness, ior, modelMatrix);
|
|
vec3 refractedRayExit = position + transmissionRay;
|
|
|
|
// Project refracted vector on the framebuffer, while mapping to normalized device coordinates.
|
|
vec4 ndcPos = viewProjMatrix * vec4(refractedRayExit, 1.0);
|
|
vec2 refractionCoords = ndcPos.xy / ndcPos.w;
|
|
refractionCoords += 1.0;
|
|
refractionCoords /= 2.0;
|
|
refractionCoords.y = 1.0- refractionCoords.y;
|
|
|
|
// Sample framebuffer to get pixel the refracted ray hits.
|
|
vec3 transmittedLight = getTransmissionSample(refractionCoords, perceptualRoughness, ior);
|
|
|
|
vec3 attenuatedColor = applyVolumeAttenuation(transmittedLight, length(transmissionRay), attenuationColor, attenuationDistance);
|
|
|
|
// Sample GGX LUT to get the specular component.
|
|
float NdotV = clampedDot(n, v);
|
|
vec2 brdfSamplePoint = clamp(vec2(NdotV, perceptualRoughness), vec2(0.0, 0.0), vec2(1.0, 1.0));
|
|
vec2 brdf = sampleBRDF_LUT(brdfSamplePoint, getEnvironmentMap(getEnvironmentId())).rg;
|
|
vec3 specularColor = f0 * brdf.x + f90 * brdf.y;
|
|
|
|
return (1.0 - specularColor) * attenuatedColor * baseColor;
|
|
}
|
|
|
|
// https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md#range-property
|
|
float getRangeAttenuation(float range, float distance) {
|
|
if (range <= 0.0) {
|
|
// negative range means unlimited
|
|
return 1.0 / pow(distance, 2.0);
|
|
}
|
|
return max(min(1.0 - pow(distance / range, 4.0), 1.0), 0.0) / pow(distance, 2.0);
|
|
}
|
|
|
|
// https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md#inner-and-outer-cone-angles
|
|
float getSpotAttenuation(vec3 pointToLight, vec3 spotDirection, float outerConeCos, float innerConeCos) {
|
|
float actualCos = dot(normalize(spotDirection), normalize(-pointToLight));
|
|
|
|
if (actualCos > outerConeCos) {
|
|
if (actualCos < innerConeCos) {
|
|
return smoothstep(outerConeCos, innerConeCos, actualCos);
|
|
}
|
|
return 1.0;
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
vec3 getLightIntensity(Light light, vec3 pointToLight) {
|
|
float rangeAttenuation = 1.0;
|
|
float spotAttenuation = 1.0;
|
|
|
|
if (light.type != LightType_Directional) {
|
|
rangeAttenuation = getRangeAttenuation(light.range, length(pointToLight));
|
|
}
|
|
if (light.type == LightType_Spot) {
|
|
spotAttenuation = getSpotAttenuation(pointToLight, light.direction, light.outerConeCos, light.innerConeCos);
|
|
}
|
|
|
|
return rangeAttenuation * spotAttenuation * light.intensity * light.color;
|
|
}
|
|
|
|
// Estevez and Kulla http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf
|
|
float D_Charlie(float sheenRoughness, float NdotH) {
|
|
sheenRoughness = max(sheenRoughness, 0.000001); //clamp (0,1]
|
|
float alphaG = sheenRoughness * sheenRoughness;
|
|
float invR = 1.0 / alphaG;
|
|
float cos2h = NdotH * NdotH;
|
|
float sin2h = 1.0 - cos2h;
|
|
return (2.0 + invR) * pow(sin2h, invR * 0.5) / (2.0 * M_PI);
|
|
}
|
|
|
|
float lambdaSheenNumericHelper(float x, float alphaG) {
|
|
float oneMinusAlphaSq = (1.0 - alphaG) * (1.0 - alphaG);
|
|
float a = mix(21.5473, 25.3245, oneMinusAlphaSq);
|
|
float b = mix(3.82987, 3.32435, oneMinusAlphaSq);
|
|
float c = mix(0.19823, 0.16801, oneMinusAlphaSq);
|
|
float d = mix(-1.97760, -1.27393, oneMinusAlphaSq);
|
|
float e = mix(-4.32054, -4.85967, oneMinusAlphaSq);
|
|
return a / (1.0 + b * pow(x, c)) + d * x + e;
|
|
}
|
|
|
|
float lambdaSheen(float cosTheta, float alphaG) {
|
|
if (abs(cosTheta) < 0.5) {
|
|
return exp(lambdaSheenNumericHelper(cosTheta, alphaG));
|
|
}
|
|
|
|
return exp(2.0 * lambdaSheenNumericHelper(0.5, alphaG) - lambdaSheenNumericHelper(1.0 - cosTheta, alphaG));
|
|
}
|
|
|
|
float V_Sheen(float NdotL, float NdotV, float sheenRoughness) {
|
|
sheenRoughness = max(sheenRoughness, 0.000001); //clamp (0,1]
|
|
float alphaG = sheenRoughness * sheenRoughness;
|
|
|
|
return clamp(1.0 / ((1.0 + lambdaSheen(NdotV, alphaG) + lambdaSheen(NdotL, alphaG)) * (4.0 * NdotV * NdotL)), 0.0, 1.0);
|
|
}
|
|
|
|
// f_sheen
|
|
vec3 getBRDFSpecularSheen(vec3 sheenColor, float sheenRoughness, float NdotL, float NdotV, float NdotH) {
|
|
float sheenDistribution = D_Charlie(sheenRoughness, NdotH);
|
|
float sheenVisibility = V_Sheen(NdotL, NdotV, sheenRoughness);
|
|
return sheenColor * sheenDistribution * sheenVisibility;
|
|
}
|
|
|
|
vec3 getPunctualRadianceSheen(vec3 sheenColor, float sheenRoughness, float NdotL, float NdotV, float NdotH) {
|
|
return NdotL * getBRDFSpecularSheen(sheenColor, sheenRoughness, NdotL, NdotV, NdotH);
|
|
}
|
|
|
|
// https://github.com/DassaultSystemes-Technology/dspbr-pt/blob/e7cfa6e9aab2b99065a90694e1f58564d675c1a4/packages/lib/shader/bsdfs/sheen.glsl#L16C1-L20C2
|
|
float albedoSheenScalingFactor(float NdotV, float sheenRoughnessFactor) {
|
|
float c = 1.0 - NdotV;
|
|
float c3 = c * c * c;
|
|
return 0.65584461 * c3 + 1.0 / (4.16526551 + exp(-7.97291361 * sqrt(sheenRoughnessFactor) + 6.33516894));
|
|
}
|
|
|
|
float max3(vec3 v) {
|
|
return max(max(v.x, v.y), v.z);
|
|
}
|
|
|
|
vec3 getPunctualRadianceClearCoat(vec3 clearcoatNormal, vec3 v, vec3 l, vec3 h, float VdotH, vec3 f0, vec3 f90, float clearcoatRoughness) {
|
|
float NdotL = clampedDot(clearcoatNormal, l);
|
|
float NdotV = clampedDot(clearcoatNormal, v);
|
|
float NdotH = clampedDot(clearcoatNormal, h);
|
|
return NdotL * getBRDFSpecularGGX(f0, f90, clearcoatRoughness * clearcoatRoughness, 1.0, VdotH, NdotL, NdotV, NdotH);
|
|
}
|
|
|
|
vec3 getPunctualRadianceTransmission(vec3 normal, vec3 view, vec3 pointToLight, float alphaRoughness, vec3 f0, vec3 f90, vec3 baseColor, float ior) {
|
|
float transmissionRougness = applyIorToRoughness(alphaRoughness, ior);
|
|
|
|
vec3 n = normalize(normal); // Outward direction of surface point
|
|
vec3 v = normalize(view); // Direction from surface point to view
|
|
vec3 l = normalize(pointToLight);
|
|
vec3 l_mirror = normalize(l + 2.0*n*dot(-l, n)); // Mirror light reflection vector on surface
|
|
vec3 h = normalize(l_mirror + v); // Halfway vector between transmission light vector and v
|
|
|
|
float D = D_GGX(clamp(dot(n, h), 0.0, 1.0), transmissionRougness);
|
|
vec3 F = F_Schlick(f0, f90, clamp(dot(v, h), 0.0, 1.0));
|
|
float Vis = V_GGX(clamp(dot(n, l_mirror), 0.0, 1.0), clamp(dot(n, v), 0.0, 1.0), transmissionRougness);
|
|
|
|
// Transmission BTDF
|
|
return (1.0 - F) * baseColor * D * Vis;
|
|
}
|