<!// LightingModel.slh// fragment shader//// Created by Sam Gateau on 1/25/14.// Copyright 2013 High Fidelity, Inc.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html!><@if not SHADING_MODEL_SLH@><@def SHADING_MODEL_SLH@> <@include LightingModel.slh@> <@func declareBeckmannSpecular()@> TEXTURE(RENDER_UTILS_TEXTURE_SSSC_SPECULAR_BECKMANN, sampler2D, scatteringSpecularBeckmann); float fetchSpecularBeckmann(float ndoth, float roughness) { return pow(2.0 * texture(scatteringSpecularBeckmann, vec2(ndoth, roughness)).r, 10.0);} vec2 skinSpecular(SurfaceData surface, float intensity) { vec2 result = vec2(0.0, 1.0); if (surface.ndotl > 0.0) { float PH = fetchSpecularBeckmann(surface.ndoth, surface.roughness); float F = fresnelSchlickScalar(0.028, surface); float frSpec = max(PH * F / dot(surface.halfDir, surface.halfDir), 0.0); result.x = surface.ndotl * intensity * frSpec; result.y -= F; } return result;}<@endfunc@> <@func declareEvalPBRShading()@> float evalSmithInvG1(float roughness4, float ndotd) { return ndotd + sqrt(roughness4+ndotd*ndotd*(1.0-roughness4));} SurfaceData initSurfaceData(float roughness, vec3 normal, vec3 eyeDir) { SurfaceData surface; surface.eyeDir = eyeDir; surface.normal = normal; surface.roughness = mix(0.01, 1.0, roughness); surface.roughness2 = surface.roughness * surface.roughness; surface.roughness4 = surface.roughness2 * surface.roughness2; surface.ndotv = clamp(dot(normal, eyeDir), 0.0, 1.0); surface.smithInvG1NdotV = evalSmithInvG1(surface.roughness4, surface.ndotv); // These values will be set when we know the light direction, in updateSurfaceDataWithLight surface.ndoth = 0.0; surface.ndotl = 0.0; surface.ldoth = 0.0; surface.lightDir = vec3(0,0,1); surface.halfDir = vec3(0,0,1); return surface;} void updateSurfaceDataWithLight(inout SurfaceData surface, vec3 lightDir) { surface.lightDir = lightDir; surface.halfDir = normalize(surface.eyeDir + lightDir); vec3 dots; dots.x = dot(surface.normal, surface.halfDir); dots.y = dot(surface.normal, surface.lightDir); dots.z = dot(surface.halfDir, surface.lightDir); dots = clamp(dots, vec3(0), vec3(1)); surface.ndoth = dots.x; surface.ndotl = dots.y; surface.ldoth = dots.z;} vec3 fresnelSchlickColor(vec3 fresnelColor, SurfaceData surface) { float base = 1.0 - surface.ldoth; //float exponential = pow(base, 5.0); float base2 = base * base; float exponential = base * base2 * base2; return vec3(exponential) + fresnelColor * (1.0 - exponential);} float fresnelSchlickScalar(float fresnelScalar, SurfaceData surface) { float base = 1.0 - surface.ldoth; //float exponential = pow(base, 5.0); float base2 = base * base; float exponential = base * base2 * base2; return (exponential) + fresnelScalar * (1.0 - exponential);} float specularDistribution(SurfaceData surface) { // See https://www.khronos.org/assets/uploads/developers/library/2017-web3d/glTF-2.0-Launch_Jun17.pdf // for details of equations, especially page 20 float denom = (surface.ndoth*surface.ndoth * (surface.roughness4 - 1.0) + 1.0); denom *= denom; // Add geometric factors G1(n,l) and G1(n,v) float smithInvG1NdotL = evalSmithInvG1(surface.roughness4, surface.ndotl); denom *= surface.smithInvG1NdotV * smithInvG1NdotL; // Don't divide by PI as this is part of the light normalization factor float power = surface.roughness4 / denom; return power;} // Frag Shading returns the diffuse amount as W and the specular rgb as xyzvec4 evalPBRShading(float metallic, vec3 fresnel, SurfaceData surface) { // Incident angle attenuation float angleAttenuation = surface.ndotl; // Specular Lighting vec3 fresnelColor = fresnelSchlickColor(fresnel, surface); float power = specularDistribution(surface); vec3 specular = fresnelColor * power * angleAttenuation; float diffuse = (1.0 - metallic) * angleAttenuation * (1.0 - fresnelColor.x); // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf // page 23 paragraph "Punctual light sources") return vec4(specular, diffuse);} // Frag Shading returns the diffuse amount as W and the specular rgb as xyzvec4 evalPBRShadingDielectric(SurfaceData surface, float fresnel) { // Incident angle attenuation float angleAttenuation = surface.ndotl; // Specular Lighting float fresnelScalar = fresnelSchlickScalar(fresnel, surface); float power = specularDistribution(surface); vec3 specular = vec3(fresnelScalar) * power * angleAttenuation; float diffuse = angleAttenuation * (1.0 - fresnelScalar); // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf // page 23 paragraph "Punctual light sources") return vec4(specular, diffuse);} vec4 evalPBRShadingMetallic(SurfaceData surface, vec3 fresnel) { // Incident angle attenuation float angleAttenuation = surface.ndotl; // Specular Lighting vec3 fresnelColor = fresnelSchlickColor(fresnel, surface); float power = specularDistribution(surface); vec3 specular = fresnelColor * power * angleAttenuation; // We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that // we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit // by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means". // (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf // page 23 paragraph "Punctual light sources") return vec4(specular, 0.f);} <@endfunc@> <$declareEvalPBRShading()$> void evalFragShading(out vec3 diffuse, out vec3 specular, float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo) { vec4 shading = evalPBRShading(metallic, fresnel, surface); diffuse = vec3(shading.w); diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled()); specular = shading.xyz;} <$declareBeckmannSpecular()$><@include SubsurfaceScattering.slh@><$declareSubsurfaceScatteringBRDF()$> void evalFragShading(out vec3 diffuse, out vec3 specular, float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo, float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature) { if (scattering * isScatteringEnabled() > 0.0) { vec3 brdf = evalSkinBRDF(surface.lightDir, surface.normal, midNormalCurvature.xyz, lowNormalCurvature.xyz, lowNormalCurvature.w); diffuse = mix(vec3(surface.ndotl), brdf, scattering); // Specular Lighting vec2 specularBrdf = skinSpecular(surface, 1.0); diffuse *= specularBrdf.y; specular = vec3(specularBrdf.x); } else { vec4 shading = evalPBRShading(metallic, fresnel, surface); diffuse = vec3(shading.w); specular = shading.xyz; } diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled());} void evalFragShadingScattering(out vec3 diffuse, out vec3 specular, float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo, float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature) { vec3 brdf = evalSkinBRDF(surface.lightDir, surface.normal, midNormalCurvature.xyz, lowNormalCurvature.xyz, lowNormalCurvature.w); float NdotL = surface.ndotl; diffuse = mix(vec3(NdotL), brdf, scattering); // Specular Lighting vec2 specularBrdf = skinSpecular(surface, 1.0); diffuse *= specularBrdf.y; specular = vec3(specularBrdf.x); diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled());} void evalFragShadingGloss(out vec3 diffuse, out vec3 specular, float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo) { vec4 shading = evalPBRShading(metallic, fresnel, surface); diffuse = vec3(shading.w); diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled()); specular = shading.xyz;} vec3 evalSpecularWithOpacity(vec3 specular, float opacity) { return specular / mix(1.0, opacity, float(opacity > 0.0));} <@if not GETFRESNEL0@><@def GETFRESNEL0@>vec3 getFresnelF0(float metallic, vec3 metalF0) { // Enable continuous metallness value by lerping between dielectric // and metal fresnel F0 value based on the "metallic" parameter return mix(vec3(0.03), metalF0, metallic);}<@endif@> <@endif@>
<!
// LightingModel.slh
// fragment shader
//
// Created by Sam Gateau on 1/25/14.
// Copyright 2013 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
!>
<@if not SHADING_MODEL_SLH@>
<@def SHADING_MODEL_SLH@>
<@include LightingModel.slh@>
<@func declareBeckmannSpecular()@>
TEXTURE(RENDER_UTILS_TEXTURE_SSSC_SPECULAR_BECKMANN, sampler2D, scatteringSpecularBeckmann);
float fetchSpecularBeckmann(float ndoth, float roughness) {
return pow(2.0 * texture(scatteringSpecularBeckmann, vec2(ndoth, roughness)).r, 10.0);
}
vec2 skinSpecular(SurfaceData surface, float intensity) {
vec2 result = vec2(0.0, 1.0);
if (surface.ndotl > 0.0) {
float PH = fetchSpecularBeckmann(surface.ndoth, surface.roughness);
float F = fresnelSchlickScalar(0.028, surface);
float frSpec = max(PH * F / dot(surface.halfDir, surface.halfDir), 0.0);
result.x = surface.ndotl * intensity * frSpec;
result.y -= F;
}
return result;
}
<@endfunc@>
<@func declareEvalPBRShading()@>
float evalSmithInvG1(float roughness4, float ndotd) {
return ndotd + sqrt(roughness4+ndotd*ndotd*(1.0-roughness4));
}
SurfaceData initSurfaceData(float roughness, vec3 normal, vec3 eyeDir) {
SurfaceData surface;
surface.eyeDir = eyeDir;
surface.normal = normal;
surface.roughness = mix(0.01, 1.0, roughness);
surface.roughness2 = surface.roughness * surface.roughness;
surface.roughness4 = surface.roughness2 * surface.roughness2;
surface.ndotv = clamp(dot(normal, eyeDir), 0.0, 1.0);
surface.smithInvG1NdotV = evalSmithInvG1(surface.roughness4, surface.ndotv);
// These values will be set when we know the light direction, in updateSurfaceDataWithLight
surface.ndoth = 0.0;
surface.ndotl = 0.0;
surface.ldoth = 0.0;
surface.lightDir = vec3(0,0,1);
surface.halfDir = vec3(0,0,1);
return surface;
}
void updateSurfaceDataWithLight(inout SurfaceData surface, vec3 lightDir) {
surface.lightDir = lightDir;
surface.halfDir = normalize(surface.eyeDir + lightDir);
vec3 dots;
dots.x = dot(surface.normal, surface.halfDir);
dots.y = dot(surface.normal, surface.lightDir);
dots.z = dot(surface.halfDir, surface.lightDir);
dots = clamp(dots, vec3(0), vec3(1));
surface.ndoth = dots.x;
surface.ndotl = dots.y;
surface.ldoth = dots.z;
}
vec3 fresnelSchlickColor(vec3 fresnelColor, SurfaceData surface) {
float base = 1.0 - surface.ldoth;
//float exponential = pow(base, 5.0);
float base2 = base * base;
float exponential = base * base2 * base2;
return vec3(exponential) + fresnelColor * (1.0 - exponential);
}
float fresnelSchlickScalar(float fresnelScalar, SurfaceData surface) {
float base = 1.0 - surface.ldoth;
//float exponential = pow(base, 5.0);
float base2 = base * base;
float exponential = base * base2 * base2;
return (exponential) + fresnelScalar * (1.0 - exponential);
}
float specularDistribution(SurfaceData surface) {
// See https://www.khronos.org/assets/uploads/developers/library/2017-web3d/glTF-2.0-Launch_Jun17.pdf
// for details of equations, especially page 20
float denom = (surface.ndoth*surface.ndoth * (surface.roughness4 - 1.0) + 1.0);
denom *= denom;
// Add geometric factors G1(n,l) and G1(n,v)
float smithInvG1NdotL = evalSmithInvG1(surface.roughness4, surface.ndotl);
denom *= surface.smithInvG1NdotV * smithInvG1NdotL;
// Don't divide by PI as this is part of the light normalization factor
float power = surface.roughness4 / denom;
return power;
}
// Frag Shading returns the diffuse amount as W and the specular rgb as xyz
vec4 evalPBRShading(float metallic, vec3 fresnel, SurfaceData surface) {
// Incident angle attenuation
float angleAttenuation = surface.ndotl;
// Specular Lighting
vec3 fresnelColor = fresnelSchlickColor(fresnel, surface);
float power = specularDistribution(surface);
vec3 specular = fresnelColor * power * angleAttenuation;
float diffuse = (1.0 - metallic) * angleAttenuation * (1.0 - fresnelColor.x);
// We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that
// we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit
// by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means".
// (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf
// page 23 paragraph "Punctual light sources")
return vec4(specular, diffuse);
}
// Frag Shading returns the diffuse amount as W and the specular rgb as xyz
vec4 evalPBRShadingDielectric(SurfaceData surface, float fresnel) {
// Incident angle attenuation
float angleAttenuation = surface.ndotl;
// Specular Lighting
float fresnelScalar = fresnelSchlickScalar(fresnel, surface);
float power = specularDistribution(surface);
vec3 specular = vec3(fresnelScalar) * power * angleAttenuation;
float diffuse = angleAttenuation * (1.0 - fresnelScalar);
// We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that
// we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit
// by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means".
// (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf
// page 23 paragraph "Punctual light sources")
return vec4(specular, diffuse);
}
vec4 evalPBRShadingMetallic(SurfaceData surface, vec3 fresnel) {
// Incident angle attenuation
float angleAttenuation = surface.ndotl;
// Specular Lighting
vec3 fresnelColor = fresnelSchlickColor(fresnel, surface);
float power = specularDistribution(surface);
vec3 specular = fresnelColor * power * angleAttenuation;
// We don't divided by PI, as the "normalized" equations state we should, because we decide, as Naty Hoffman, that
// we wish to have a similar color as raw albedo on a perfectly diffuse surface perpendicularly lit
// by a white light of intensity 1. But this is an arbitrary normalization of what light intensity "means".
// (see http://blog.selfshadow.com/publications/s2013-shading-course/hoffman/s2013_pbs_physics_math_notes.pdf
// page 23 paragraph "Punctual light sources")
return vec4(specular, 0.f);
}
<@endfunc@>
<$declareEvalPBRShading()$>
void evalFragShading(out vec3 diffuse, out vec3 specular,
float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo) {
vec4 shading = evalPBRShading(metallic, fresnel, surface);
diffuse = vec3(shading.w);
diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled());
specular = shading.xyz;
}
<$declareBeckmannSpecular()$>
<@include SubsurfaceScattering.slh@>
<$declareSubsurfaceScatteringBRDF()$>
void evalFragShading(out vec3 diffuse, out vec3 specular,
float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo,
float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature) {
if (scattering * isScatteringEnabled() > 0.0) {
vec3 brdf = evalSkinBRDF(surface.lightDir, surface.normal, midNormalCurvature.xyz, lowNormalCurvature.xyz, lowNormalCurvature.w);
diffuse = mix(vec3(surface.ndotl), brdf, scattering);
// Specular Lighting
vec2 specularBrdf = skinSpecular(surface, 1.0);
diffuse *= specularBrdf.y;
specular = vec3(specularBrdf.x);
} else {
vec4 shading = evalPBRShading(metallic, fresnel, surface);
diffuse = vec3(shading.w);
specular = shading.xyz;
}
diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled());
}
void evalFragShadingScattering(out vec3 diffuse, out vec3 specular,
float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo,
float scattering, vec4 midNormalCurvature, vec4 lowNormalCurvature) {
vec3 brdf = evalSkinBRDF(surface.lightDir, surface.normal, midNormalCurvature.xyz, lowNormalCurvature.xyz, lowNormalCurvature.w);
float NdotL = surface.ndotl;
diffuse = mix(vec3(NdotL), brdf, scattering);
// Specular Lighting
vec2 specularBrdf = skinSpecular(surface, 1.0);
diffuse *= specularBrdf.y;
specular = vec3(specularBrdf.x);
diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled());
}
void evalFragShadingGloss(out vec3 diffuse, out vec3 specular,
float metallic, vec3 fresnel, SurfaceData surface, vec3 albedo) {
vec4 shading = evalPBRShading(metallic, fresnel, surface);
diffuse = vec3(shading.w);
diffuse *= mix(vec3(1.0), albedo, isAlbedoEnabled());
specular = shading.xyz;
}
vec3 evalSpecularWithOpacity(vec3 specular, float opacity) {
return specular / mix(1.0, opacity, float(opacity > 0.0));
}
<@if not GETFRESNEL0@>
<@def GETFRESNEL0@>
vec3 getFresnelF0(float metallic, vec3 metalF0) {
// Enable continuous metallness value by lerping between dielectric
// and metal fresnel F0 value based on the "metallic" parameter
return mix(vec3(0.03), metalF0, metallic);
}
<@endif@>
<@endif@>