<@if not SHADING_MODEL_SLH@> <@def SHADING_MODEL_SLH@> <@include LightingModel.slh@> <@func declareBeckmannSpecular()@> LAYOUT(binding=RENDER_UTILS_TEXTURE_SSSC_SPECULAR_BECKMANN) uniform 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@>