// luma.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors // Attribution: // MIT license, Copyright (c) 2016-2017 Mohamad Moneimne and Contributors // This fragment shader defines a reference implementation for Physically Based Shading of // a microfacet surface material defined by a glTF model. // TODO - better do the checks outside of shader export const vs = /* glsl */ `\ out vec3 pbr_vPosition; out vec2 pbr_vUV0; out vec2 pbr_vUV1; #ifdef HAS_NORMALS # ifdef HAS_TANGENTS out mat3 pbr_vTBN; # else out vec3 pbr_vNormal; # endif #endif void pbr_setPositionNormalTangentUV( vec4 position, vec4 normal, vec4 tangent, vec2 uv0, vec2 uv1 ) { vec4 pos = pbrProjection.modelMatrix * position; pbr_vPosition = vec3(pos.xyz) / pos.w; #ifdef HAS_NORMALS #ifdef HAS_TANGENTS vec3 normalW = normalize(vec3(pbrProjection.normalMatrix * vec4(normal.xyz, 0.0))); vec3 tangentW = normalize(vec3(pbrProjection.modelMatrix * vec4(tangent.xyz, 0.0))); vec3 bitangentW = cross(normalW, tangentW) * tangent.w; pbr_vTBN = mat3(tangentW, bitangentW, normalW); #else // HAS_TANGENTS != 1 pbr_vNormal = normalize(vec3(pbrProjection.modelMatrix * vec4(normal.xyz, 0.0))); #endif #endif #ifdef HAS_UV pbr_vUV0 = uv0; #else pbr_vUV0 = vec2(0.,0.); #endif pbr_vUV1 = uv1; } `; export const fs = /* glsl */ `\ precision highp float; layout(std140) uniform pbrMaterialUniforms { // Material is unlit bool unlit; // Base color map bool baseColorMapEnabled; vec4 baseColorFactor; bool normalMapEnabled; float normalScale; // #ifdef HAS_NORMALMAP bool emissiveMapEnabled; vec3 emissiveFactor; // #ifdef HAS_EMISSIVEMAP vec2 metallicRoughnessValues; bool metallicRoughnessMapEnabled; bool occlusionMapEnabled; float occlusionStrength; // #ifdef HAS_OCCLUSIONMAP bool alphaCutoffEnabled; float alphaCutoff; // #ifdef ALPHA_CUTOFF vec3 specularColorFactor; float specularIntensityFactor; bool specularColorMapEnabled; bool specularIntensityMapEnabled; float ior; float transmissionFactor; bool transmissionMapEnabled; float thicknessFactor; float attenuationDistance; vec3 attenuationColor; float clearcoatFactor; float clearcoatRoughnessFactor; bool clearcoatMapEnabled; bool clearcoatRoughnessMapEnabled; vec3 sheenColorFactor; float sheenRoughnessFactor; bool sheenColorMapEnabled; bool sheenRoughnessMapEnabled; float iridescenceFactor; float iridescenceIor; vec2 iridescenceThicknessRange; bool iridescenceMapEnabled; float anisotropyStrength; float anisotropyRotation; vec2 anisotropyDirection; bool anisotropyMapEnabled; float emissiveStrength; // IBL bool IBLenabled; vec2 scaleIBLAmbient; // #ifdef USE_IBL // debugging flags used for shader output of intermediate PBR variables // #ifdef PBR_DEBUG vec4 scaleDiffBaseMR; vec4 scaleFGDSpec; // #endif int baseColorUVSet; mat3 baseColorUVTransform; int metallicRoughnessUVSet; mat3 metallicRoughnessUVTransform; int normalUVSet; mat3 normalUVTransform; int occlusionUVSet; mat3 occlusionUVTransform; int emissiveUVSet; mat3 emissiveUVTransform; int specularColorUVSet; mat3 specularColorUVTransform; int specularIntensityUVSet; mat3 specularIntensityUVTransform; int transmissionUVSet; mat3 transmissionUVTransform; int thicknessUVSet; mat3 thicknessUVTransform; int clearcoatUVSet; mat3 clearcoatUVTransform; int clearcoatRoughnessUVSet; mat3 clearcoatRoughnessUVTransform; int clearcoatNormalUVSet; mat3 clearcoatNormalUVTransform; int sheenColorUVSet; mat3 sheenColorUVTransform; int sheenRoughnessUVSet; mat3 sheenRoughnessUVTransform; int iridescenceUVSet; mat3 iridescenceUVTransform; int iridescenceThicknessUVSet; mat3 iridescenceThicknessUVTransform; int anisotropyUVSet; mat3 anisotropyUVTransform; } pbrMaterial; // Samplers #ifdef HAS_BASECOLORMAP uniform sampler2D pbr_baseColorSampler; #endif #ifdef HAS_NORMALMAP uniform sampler2D pbr_normalSampler; #endif #ifdef HAS_EMISSIVEMAP uniform sampler2D pbr_emissiveSampler; #endif #ifdef HAS_METALROUGHNESSMAP uniform sampler2D pbr_metallicRoughnessSampler; #endif #ifdef HAS_OCCLUSIONMAP uniform sampler2D pbr_occlusionSampler; #endif #ifdef HAS_SPECULARCOLORMAP uniform sampler2D pbr_specularColorSampler; #endif #ifdef HAS_SPECULARINTENSITYMAP uniform sampler2D pbr_specularIntensitySampler; #endif #ifdef HAS_TRANSMISSIONMAP uniform sampler2D pbr_transmissionSampler; #endif #ifdef HAS_THICKNESSMAP uniform sampler2D pbr_thicknessSampler; #endif #ifdef HAS_CLEARCOATMAP uniform sampler2D pbr_clearcoatSampler; #endif #ifdef HAS_CLEARCOATROUGHNESSMAP uniform sampler2D pbr_clearcoatRoughnessSampler; #endif #ifdef HAS_CLEARCOATNORMALMAP uniform sampler2D pbr_clearcoatNormalSampler; #endif #ifdef HAS_SHEENCOLORMAP uniform sampler2D pbr_sheenColorSampler; #endif #ifdef HAS_SHEENROUGHNESSMAP uniform sampler2D pbr_sheenRoughnessSampler; #endif #ifdef HAS_IRIDESCENCEMAP uniform sampler2D pbr_iridescenceSampler; #endif #ifdef HAS_IRIDESCENCETHICKNESSMAP uniform sampler2D pbr_iridescenceThicknessSampler; #endif #ifdef HAS_ANISOTROPYMAP uniform sampler2D pbr_anisotropySampler; #endif // Inputs from vertex shader in vec3 pbr_vPosition; in vec2 pbr_vUV0; in vec2 pbr_vUV1; #ifdef HAS_NORMALS #ifdef HAS_TANGENTS in mat3 pbr_vTBN; #else in vec3 pbr_vNormal; #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 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 vec3 n; // normal at surface point vec3 v; // vector from surface point to camera }; const float M_PI = 3.141592653589793; const float c_MinRoughness = 0.04; vec3 calculateFinalColor(PBRInfo pbrInfo, vec3 lightColor); vec4 SRGBtoLINEAR(vec4 srgbIn) { #ifdef MANUAL_SRGB #ifdef SRGB_FAST_APPROXIMATION vec3 linOut = pow(srgbIn.xyz,vec3(2.2)); #else // SRGB_FAST_APPROXIMATION vec3 bLess = step(vec3(0.04045),srgbIn.xyz); vec3 linOut = mix( srgbIn.xyz/vec3(12.92), pow((srgbIn.xyz+vec3(0.055))/vec3(1.055),vec3(2.4)), bLess ); #endif //SRGB_FAST_APPROXIMATION return vec4(linOut,srgbIn.w);; #else //MANUAL_SRGB return srgbIn; #endif //MANUAL_SRGB } vec2 getMaterialUV(int uvSet, mat3 uvTransform) { vec2 baseUV = uvSet == 1 ? pbr_vUV1 : pbr_vUV0; return (uvTransform * vec3(baseUV, 1.0)).xy; } // Build the tangent basis from interpolated attributes or screen-space derivatives. mat3 getTBN(vec2 uv) { #ifndef HAS_TANGENTS vec3 pos_dx = dFdx(pbr_vPosition); vec3 pos_dy = dFdy(pbr_vPosition); vec3 tex_dx = dFdx(vec3(uv, 0.0)); vec3 tex_dy = dFdy(vec3(uv, 0.0)); vec3 t = (tex_dy.t * pos_dx - tex_dx.t * pos_dy) / (tex_dx.s * tex_dy.t - tex_dy.s * tex_dx.t); #ifdef HAS_NORMALS vec3 ng = normalize(pbr_vNormal); #else vec3 ng = cross(pos_dx, pos_dy); #endif t = normalize(t - ng * dot(ng, t)); vec3 b = normalize(cross(ng, t)); mat3 tbn = mat3(t, b, ng); #else // HAS_TANGENTS mat3 tbn = pbr_vTBN; #endif return tbn; } // Find the normal for this fragment, pulling either from a predefined normal map // or from the interpolated mesh normal and tangent attributes. vec3 getMappedNormal(sampler2D normalSampler, mat3 tbn, float normalScale, vec2 uv) { vec3 n = texture(normalSampler, uv).rgb; return normalize(tbn * ((2.0 * n - 1.0) * vec3(normalScale, normalScale, 1.0))); } vec3 getNormal(mat3 tbn, vec2 uv) { #ifdef HAS_NORMALMAP vec3 n = getMappedNormal(pbr_normalSampler, tbn, pbrMaterial.normalScale, uv); #else // The tbn matrix is linearly interpolated, so we need to re-normalize vec3 n = normalize(tbn[2].xyz); #endif return n; } vec3 getClearcoatNormal(mat3 tbn, vec3 baseNormal, vec2 uv) { #ifdef HAS_CLEARCOATNORMALMAP return getMappedNormal(pbr_clearcoatNormalSampler, tbn, 1.0, uv); #else return baseNormal; #endif } // 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. #ifdef USE_IBL vec3 getIBLContribution(PBRInfo pbrInfo, vec3 n, vec3 reflection) { float mipCount = 9.0; // resolution of 512x512 float lod = (pbrInfo.perceptualRoughness * mipCount); // retrieve a scale and bias to F0. See [1], Figure 3 vec3 brdf = SRGBtoLINEAR(texture(pbr_brdfLUT, vec2(pbrInfo.NdotV, 1.0 - pbrInfo.perceptualRoughness))).rgb; vec3 diffuseLight = SRGBtoLINEAR(texture(pbr_diffuseEnvSampler, n)).rgb; #ifdef USE_TEX_LOD vec3 specularLight = SRGBtoLINEAR(texture(pbr_specularEnvSampler, reflection, lod)).rgb; #else vec3 specularLight = SRGBtoLINEAR(texture(pbr_specularEnvSampler, reflection)).rgb; #endif vec3 diffuse = diffuseLight * pbrInfo.diffuseColor; vec3 specular = specularLight * (pbrInfo.specularColor * brdf.x + brdf.y); // For presentation, this allows us to disable IBL terms diffuse *= pbrMaterial.scaleIBLAmbient.x; specular *= pbrMaterial.scaleIBLAmbient.y; return diffuse + specular; } #endif // Basic Lambertian diffuse // Implementation from Lambert's Photometria https://archive.org/details/lambertsphotome00lambgoog // See also [1], Equation 1 vec3 diffuse(PBRInfo pbrInfo) { return pbrInfo.diffuseColor / M_PI; } // The following equation models the Fresnel reflectance term of the spec equation (aka F()) // Implementation of fresnel from [4], Equation 15 vec3 specularReflection(PBRInfo pbrInfo) { return pbrInfo.reflectance0 + (pbrInfo.reflectance90 - pbrInfo.reflectance0) * pow(clamp(1.0 - pbrInfo.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 pbrInfo) { float NdotL = pbrInfo.NdotL; float NdotV = pbrInfo.NdotV; float r = pbrInfo.alphaRoughness; float attenuationL = 2.0 * NdotL / (NdotL + sqrt(r * r + (1.0 - r * r) * (NdotL * NdotL))); float attenuationV = 2.0 * NdotV / (NdotV + sqrt(r * r + (1.0 - r * r) * (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 pbrInfo) { float roughnessSq = pbrInfo.alphaRoughness * pbrInfo.alphaRoughness; float f = (pbrInfo.NdotH * roughnessSq - pbrInfo.NdotH) * pbrInfo.NdotH + 1.0; return roughnessSq / (M_PI * f * f); } float maxComponent(vec3 value) { return max(max(value.r, value.g), value.b); } float getDielectricF0(float ior) { float clampedIor = max(ior, 1.0); float ratio = (clampedIor - 1.0) / (clampedIor + 1.0); return ratio * ratio; } vec2 normalizeDirection(vec2 direction) { float directionLength = length(direction); return directionLength > 0.0001 ? direction / directionLength : vec2(1.0, 0.0); } vec2 rotateDirection(vec2 direction, float rotation) { float s = sin(rotation); float c = cos(rotation); return vec2(direction.x * c - direction.y * s, direction.x * s + direction.y * c); } vec3 getIridescenceTint(float iridescence, float thickness, float NdotV) { if (iridescence <= 0.0) { return vec3(1.0); } float phase = 0.015 * thickness * pbrMaterial.iridescenceIor + (1.0 - NdotV) * 6.0; vec3 thinFilmTint = 0.5 + 0.5 * cos(vec3(phase, phase + 2.0943951, phase + 4.1887902)); return mix(vec3(1.0), thinFilmTint, iridescence); } vec3 getVolumeAttenuation(float thickness) { if (thickness <= 0.0) { return vec3(1.0); } vec3 attenuationCoefficient = -log(max(pbrMaterial.attenuationColor, vec3(0.0001))) / max(pbrMaterial.attenuationDistance, 0.0001); return exp(-attenuationCoefficient * thickness); } PBRInfo createClearcoatPBRInfo(PBRInfo basePBRInfo, vec3 clearcoatNormal, float clearcoatRoughness) { float perceptualRoughness = clamp(clearcoatRoughness, c_MinRoughness, 1.0); float alphaRoughness = perceptualRoughness * perceptualRoughness; float NdotV = clamp(abs(dot(clearcoatNormal, basePBRInfo.v)), 0.001, 1.0); return PBRInfo( basePBRInfo.NdotL, NdotV, basePBRInfo.NdotH, basePBRInfo.LdotH, basePBRInfo.VdotH, perceptualRoughness, 0.0, vec3(0.04), vec3(1.0), alphaRoughness, vec3(0.0), vec3(0.04), clearcoatNormal, basePBRInfo.v ); } vec3 calculateClearcoatContribution( PBRInfo pbrInfo, vec3 lightColor, vec3 clearcoatNormal, float clearcoatFactor, float clearcoatRoughness ) { if (clearcoatFactor <= 0.0) { return vec3(0.0); } PBRInfo clearcoatPBRInfo = createClearcoatPBRInfo(pbrInfo, clearcoatNormal, clearcoatRoughness); return calculateFinalColor(clearcoatPBRInfo, lightColor) * clearcoatFactor; } #ifdef USE_IBL vec3 calculateClearcoatIBLContribution( PBRInfo pbrInfo, vec3 clearcoatNormal, vec3 reflection, float clearcoatFactor, float clearcoatRoughness ) { if (clearcoatFactor <= 0.0) { return vec3(0.0); } PBRInfo clearcoatPBRInfo = createClearcoatPBRInfo(pbrInfo, clearcoatNormal, clearcoatRoughness); return getIBLContribution(clearcoatPBRInfo, clearcoatNormal, reflection) * clearcoatFactor; } #endif vec3 calculateSheenContribution( PBRInfo pbrInfo, vec3 lightColor, vec3 sheenColor, float sheenRoughness ) { if (maxComponent(sheenColor) <= 0.0) { return vec3(0.0); } float sheenFresnel = pow(clamp(1.0 - pbrInfo.VdotH, 0.0, 1.0), 5.0); float sheenVisibility = mix(1.0, pbrInfo.NdotL * pbrInfo.NdotV, sheenRoughness); return pbrInfo.NdotL * lightColor * sheenColor * (0.25 + 0.75 * sheenFresnel) * sheenVisibility * (1.0 - pbrInfo.metalness); } float calculateAnisotropyBoost( PBRInfo pbrInfo, vec3 anisotropyTangent, float anisotropyStrength ) { if (anisotropyStrength <= 0.0) { return 1.0; } vec3 anisotropyBitangent = normalize(cross(pbrInfo.n, anisotropyTangent)); float bitangentViewAlignment = abs(dot(pbrInfo.v, anisotropyBitangent)); return mix(1.0, 0.65 + 0.7 * bitangentViewAlignment, anisotropyStrength); } vec3 calculateMaterialLightColor( PBRInfo pbrInfo, vec3 lightColor, vec3 clearcoatNormal, float clearcoatFactor, float clearcoatRoughness, vec3 sheenColor, float sheenRoughness, vec3 anisotropyTangent, float anisotropyStrength ) { float anisotropyBoost = calculateAnisotropyBoost(pbrInfo, anisotropyTangent, anisotropyStrength); vec3 color = calculateFinalColor(pbrInfo, lightColor) * anisotropyBoost; color += calculateClearcoatContribution( pbrInfo, lightColor, clearcoatNormal, clearcoatFactor, clearcoatRoughness ); color += calculateSheenContribution(pbrInfo, lightColor, sheenColor, sheenRoughness); return color; } void PBRInfo_setAmbientLight(inout PBRInfo pbrInfo) { pbrInfo.NdotL = 1.0; pbrInfo.NdotH = 0.0; pbrInfo.LdotH = 0.0; pbrInfo.VdotH = 1.0; } void PBRInfo_setDirectionalLight(inout PBRInfo pbrInfo, vec3 lightDirection) { vec3 n = pbrInfo.n; vec3 v = pbrInfo.v; vec3 l = normalize(lightDirection); // Vector from surface point to light vec3 h = normalize(l+v); // Half vector between both l and v pbrInfo.NdotL = clamp(dot(n, l), 0.001, 1.0); pbrInfo.NdotH = clamp(dot(n, h), 0.0, 1.0); pbrInfo.LdotH = clamp(dot(l, h), 0.0, 1.0); pbrInfo.VdotH = clamp(dot(v, h), 0.0, 1.0); } void PBRInfo_setPointLight(inout PBRInfo pbrInfo, PointLight pointLight) { vec3 light_direction = normalize(pointLight.position - pbr_vPosition); PBRInfo_setDirectionalLight(pbrInfo, light_direction); } void PBRInfo_setSpotLight(inout PBRInfo pbrInfo, SpotLight spotLight) { vec3 light_direction = normalize(spotLight.position - pbr_vPosition); PBRInfo_setDirectionalLight(pbrInfo, light_direction); } vec3 calculateFinalColor(PBRInfo pbrInfo, vec3 lightColor) { // Calculate the shading terms for the microfacet specular shading model vec3 F = specularReflection(pbrInfo); float G = geometricOcclusion(pbrInfo); float D = microfacetDistribution(pbrInfo); // Calculation of analytical lighting contribution vec3 diffuseContrib = (1.0 - F) * diffuse(pbrInfo); vec3 specContrib = F * G * D / (4.0 * pbrInfo.NdotL * pbrInfo.NdotV); // Obtain final intensity as reflectance (BRDF) scaled by the energy of the light (cosine law) return pbrInfo.NdotL * lightColor * (diffuseContrib + specContrib); } vec4 pbr_filterColor(vec4 colorUnused) { vec2 baseColorUV = getMaterialUV(pbrMaterial.baseColorUVSet, pbrMaterial.baseColorUVTransform); vec2 metallicRoughnessUV = getMaterialUV( pbrMaterial.metallicRoughnessUVSet, pbrMaterial.metallicRoughnessUVTransform ); vec2 normalUV = getMaterialUV(pbrMaterial.normalUVSet, pbrMaterial.normalUVTransform); vec2 occlusionUV = getMaterialUV(pbrMaterial.occlusionUVSet, pbrMaterial.occlusionUVTransform); vec2 emissiveUV = getMaterialUV(pbrMaterial.emissiveUVSet, pbrMaterial.emissiveUVTransform); vec2 specularColorUV = getMaterialUV( pbrMaterial.specularColorUVSet, pbrMaterial.specularColorUVTransform ); vec2 specularIntensityUV = getMaterialUV( pbrMaterial.specularIntensityUVSet, pbrMaterial.specularIntensityUVTransform ); vec2 transmissionUV = getMaterialUV( pbrMaterial.transmissionUVSet, pbrMaterial.transmissionUVTransform ); vec2 thicknessUV = getMaterialUV(pbrMaterial.thicknessUVSet, pbrMaterial.thicknessUVTransform); vec2 clearcoatUV = getMaterialUV(pbrMaterial.clearcoatUVSet, pbrMaterial.clearcoatUVTransform); vec2 clearcoatRoughnessUV = getMaterialUV( pbrMaterial.clearcoatRoughnessUVSet, pbrMaterial.clearcoatRoughnessUVTransform ); vec2 clearcoatNormalUV = getMaterialUV( pbrMaterial.clearcoatNormalUVSet, pbrMaterial.clearcoatNormalUVTransform ); vec2 sheenColorUV = getMaterialUV( pbrMaterial.sheenColorUVSet, pbrMaterial.sheenColorUVTransform ); vec2 sheenRoughnessUV = getMaterialUV( pbrMaterial.sheenRoughnessUVSet, pbrMaterial.sheenRoughnessUVTransform ); vec2 iridescenceUV = getMaterialUV( pbrMaterial.iridescenceUVSet, pbrMaterial.iridescenceUVTransform ); vec2 iridescenceThicknessUV = getMaterialUV( pbrMaterial.iridescenceThicknessUVSet, pbrMaterial.iridescenceThicknessUVTransform ); vec2 anisotropyUV = getMaterialUV( pbrMaterial.anisotropyUVSet, pbrMaterial.anisotropyUVTransform ); // The albedo may be defined from a base texture or a flat color #ifdef HAS_BASECOLORMAP vec4 baseColor = SRGBtoLINEAR(texture(pbr_baseColorSampler, baseColorUV)) * pbrMaterial.baseColorFactor; #else vec4 baseColor = pbrMaterial.baseColorFactor; #endif #ifdef ALPHA_CUTOFF if (baseColor.a < pbrMaterial.alphaCutoff) { discard; } #endif vec3 color = vec3(0, 0, 0); float transmission = 0.0; if(pbrMaterial.unlit){ color.rgb = baseColor.rgb; } else{ // 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 = pbrMaterial.metallicRoughnessValues.y; float metallic = pbrMaterial.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 vec4 mrSample = texture(pbr_metallicRoughnessSampler, metallicRoughnessUV); perceptualRoughness = mrSample.g * perceptualRoughness; metallic = mrSample.b * metallic; #endif perceptualRoughness = clamp(perceptualRoughness, c_MinRoughness, 1.0); metallic = clamp(metallic, 0.0, 1.0); mat3 tbn = getTBN(normalUV); vec3 n = getNormal(tbn, normalUV); // normal at surface point vec3 v = normalize(pbrProjection.camera - pbr_vPosition); // Vector from surface point to camera float NdotV = clamp(abs(dot(n, v)), 0.001, 1.0); #ifdef USE_MATERIAL_EXTENSIONS bool useExtendedPBR = pbrMaterial.specularColorMapEnabled || pbrMaterial.specularIntensityMapEnabled || abs(pbrMaterial.specularIntensityFactor - 1.0) > 0.0001 || maxComponent(abs(pbrMaterial.specularColorFactor - vec3(1.0))) > 0.0001 || abs(pbrMaterial.ior - 1.5) > 0.0001 || pbrMaterial.transmissionMapEnabled || pbrMaterial.transmissionFactor > 0.0001 || pbrMaterial.clearcoatMapEnabled || pbrMaterial.clearcoatRoughnessMapEnabled || pbrMaterial.clearcoatFactor > 0.0001 || pbrMaterial.clearcoatRoughnessFactor > 0.0001 || pbrMaterial.sheenColorMapEnabled || pbrMaterial.sheenRoughnessMapEnabled || maxComponent(pbrMaterial.sheenColorFactor) > 0.0001 || pbrMaterial.sheenRoughnessFactor > 0.0001 || pbrMaterial.iridescenceMapEnabled || pbrMaterial.iridescenceFactor > 0.0001 || abs(pbrMaterial.iridescenceIor - 1.3) > 0.0001 || abs(pbrMaterial.iridescenceThicknessRange.x - 100.0) > 0.0001 || abs(pbrMaterial.iridescenceThicknessRange.y - 400.0) > 0.0001 || pbrMaterial.anisotropyMapEnabled || pbrMaterial.anisotropyStrength > 0.0001 || abs(pbrMaterial.anisotropyRotation) > 0.0001 || length(pbrMaterial.anisotropyDirection - vec2(1.0, 0.0)) > 0.0001; #else bool useExtendedPBR = false; #endif if (!useExtendedPBR) { // Keep the baseline metallic-roughness implementation byte-for-byte equivalent in behavior. float alphaRoughness = perceptualRoughness * perceptualRoughness; vec3 f0 = vec3(0.04); vec3 diffuseColor = baseColor.rgb * (vec3(1.0) - f0); diffuseColor *= 1.0 - metallic; vec3 specularColor = mix(f0, baseColor.rgb, metallic); float reflectance = max(max(specularColor.r, specularColor.g), specularColor.b); float reflectance90 = clamp(reflectance * 25.0, 0.0, 1.0); vec3 specularEnvironmentR0 = specularColor.rgb; vec3 specularEnvironmentR90 = vec3(1.0, 1.0, 1.0) * reflectance90; vec3 reflection = -normalize(reflect(v, n)); PBRInfo pbrInfo = PBRInfo( 0.0, // NdotL NdotV, 0.0, // NdotH 0.0, // LdotH 0.0, // VdotH perceptualRoughness, metallic, specularEnvironmentR0, specularEnvironmentR90, alphaRoughness, diffuseColor, specularColor, n, v ); #ifdef USE_LIGHTS PBRInfo_setAmbientLight(pbrInfo); color += calculateFinalColor(pbrInfo, lighting.ambientColor); for(int i = 0; i < lighting.directionalLightCount; i++) { if (i < lighting.directionalLightCount) { PBRInfo_setDirectionalLight(pbrInfo, lighting_getDirectionalLight(i).direction); color += calculateFinalColor(pbrInfo, lighting_getDirectionalLight(i).color); } } for(int i = 0; i < lighting.pointLightCount; i++) { if (i < lighting.pointLightCount) { PBRInfo_setPointLight(pbrInfo, lighting_getPointLight(i)); float attenuation = getPointLightAttenuation(lighting_getPointLight(i), distance(lighting_getPointLight(i).position, pbr_vPosition)); color += calculateFinalColor(pbrInfo, lighting_getPointLight(i).color / attenuation); } } for(int i = 0; i < lighting.spotLightCount; i++) { if (i < lighting.spotLightCount) { PBRInfo_setSpotLight(pbrInfo, lighting_getSpotLight(i)); float attenuation = getSpotLightAttenuation(lighting_getSpotLight(i), pbr_vPosition); color += calculateFinalColor(pbrInfo, lighting_getSpotLight(i).color / attenuation); } } #endif #ifdef USE_IBL if (pbrMaterial.IBLenabled) { color += getIBLContribution(pbrInfo, n, reflection); } #endif #ifdef HAS_OCCLUSIONMAP if (pbrMaterial.occlusionMapEnabled) { float ao = texture(pbr_occlusionSampler, occlusionUV).r; color = mix(color, color * ao, pbrMaterial.occlusionStrength); } #endif vec3 emissive = pbrMaterial.emissiveFactor; #ifdef HAS_EMISSIVEMAP if (pbrMaterial.emissiveMapEnabled) { emissive *= SRGBtoLINEAR(texture(pbr_emissiveSampler, emissiveUV)).rgb; } #endif color += emissive * pbrMaterial.emissiveStrength; #ifdef PBR_DEBUG color = mix(color, baseColor.rgb, pbrMaterial.scaleDiffBaseMR.y); color = mix(color, vec3(metallic), pbrMaterial.scaleDiffBaseMR.z); color = mix(color, vec3(perceptualRoughness), pbrMaterial.scaleDiffBaseMR.w); #endif return vec4(pow(color, vec3(1.0 / 2.2)), baseColor.a); } float specularIntensity = pbrMaterial.specularIntensityFactor; #ifdef HAS_SPECULARINTENSITYMAP if (pbrMaterial.specularIntensityMapEnabled) { specularIntensity *= texture(pbr_specularIntensitySampler, specularIntensityUV).a; } #endif vec3 specularFactor = pbrMaterial.specularColorFactor; #ifdef HAS_SPECULARCOLORMAP if (pbrMaterial.specularColorMapEnabled) { specularFactor *= SRGBtoLINEAR(texture(pbr_specularColorSampler, specularColorUV)).rgb; } #endif transmission = pbrMaterial.transmissionFactor; #ifdef HAS_TRANSMISSIONMAP if (pbrMaterial.transmissionMapEnabled) { transmission *= texture(pbr_transmissionSampler, transmissionUV).r; } #endif transmission = clamp(transmission * (1.0 - metallic), 0.0, 1.0); float thickness = max(pbrMaterial.thicknessFactor, 0.0); #ifdef HAS_THICKNESSMAP thickness *= texture(pbr_thicknessSampler, thicknessUV).g; #endif float clearcoatFactor = pbrMaterial.clearcoatFactor; float clearcoatRoughness = pbrMaterial.clearcoatRoughnessFactor; #ifdef HAS_CLEARCOATMAP if (pbrMaterial.clearcoatMapEnabled) { clearcoatFactor *= texture(pbr_clearcoatSampler, clearcoatUV).r; } #endif #ifdef HAS_CLEARCOATROUGHNESSMAP if (pbrMaterial.clearcoatRoughnessMapEnabled) { clearcoatRoughness *= texture(pbr_clearcoatRoughnessSampler, clearcoatRoughnessUV).g; } #endif clearcoatFactor = clamp(clearcoatFactor, 0.0, 1.0); clearcoatRoughness = clamp(clearcoatRoughness, c_MinRoughness, 1.0); vec3 clearcoatNormal = getClearcoatNormal(getTBN(clearcoatNormalUV), n, clearcoatNormalUV); vec3 sheenColor = pbrMaterial.sheenColorFactor; float sheenRoughness = pbrMaterial.sheenRoughnessFactor; #ifdef HAS_SHEENCOLORMAP if (pbrMaterial.sheenColorMapEnabled) { sheenColor *= SRGBtoLINEAR(texture(pbr_sheenColorSampler, sheenColorUV)).rgb; } #endif #ifdef HAS_SHEENROUGHNESSMAP if (pbrMaterial.sheenRoughnessMapEnabled) { sheenRoughness *= texture(pbr_sheenRoughnessSampler, sheenRoughnessUV).a; } #endif sheenRoughness = clamp(sheenRoughness, c_MinRoughness, 1.0); float iridescence = pbrMaterial.iridescenceFactor; #ifdef HAS_IRIDESCENCEMAP if (pbrMaterial.iridescenceMapEnabled) { iridescence *= texture(pbr_iridescenceSampler, iridescenceUV).r; } #endif iridescence = clamp(iridescence, 0.0, 1.0); float iridescenceThickness = mix( pbrMaterial.iridescenceThicknessRange.x, pbrMaterial.iridescenceThicknessRange.y, 0.5 ); #ifdef HAS_IRIDESCENCETHICKNESSMAP iridescenceThickness = mix( pbrMaterial.iridescenceThicknessRange.x, pbrMaterial.iridescenceThicknessRange.y, texture(pbr_iridescenceThicknessSampler, iridescenceThicknessUV).g ); #endif float anisotropyStrength = clamp(pbrMaterial.anisotropyStrength, 0.0, 1.0); vec2 anisotropyDirection = normalizeDirection(pbrMaterial.anisotropyDirection); #ifdef HAS_ANISOTROPYMAP if (pbrMaterial.anisotropyMapEnabled) { vec3 anisotropySample = texture(pbr_anisotropySampler, anisotropyUV).rgb; anisotropyStrength *= anisotropySample.b; vec2 mappedDirection = anisotropySample.rg * 2.0 - 1.0; if (length(mappedDirection) > 0.0001) { anisotropyDirection = normalize(mappedDirection); } } #endif anisotropyDirection = rotateDirection(anisotropyDirection, pbrMaterial.anisotropyRotation); vec3 anisotropyTangent = normalize(tbn[0] * anisotropyDirection.x + tbn[1] * anisotropyDirection.y); if (length(anisotropyTangent) < 0.0001) { anisotropyTangent = normalize(tbn[0]); } float anisotropyViewAlignment = abs(dot(v, anisotropyTangent)); perceptualRoughness = mix( perceptualRoughness, clamp(perceptualRoughness * (1.0 - 0.6 * anisotropyViewAlignment), c_MinRoughness, 1.0), anisotropyStrength ); // Roughness is authored as perceptual roughness; as is convention, // convert to material roughness by squaring the perceptual roughness [2]. float alphaRoughness = perceptualRoughness * perceptualRoughness; float dielectricF0 = getDielectricF0(pbrMaterial.ior); vec3 dielectricSpecularF0 = min( vec3(dielectricF0) * specularFactor * specularIntensity, vec3(1.0) ); vec3 iridescenceTint = getIridescenceTint(iridescence, iridescenceThickness, NdotV); dielectricSpecularF0 = mix( dielectricSpecularF0, dielectricSpecularF0 * iridescenceTint, iridescence ); vec3 diffuseColor = baseColor.rgb * (vec3(1.0) - dielectricSpecularF0); diffuseColor *= (1.0 - metallic) * (1.0 - transmission); vec3 specularColor = mix(dielectricSpecularF0, baseColor.rgb, metallic); float baseLayerEnergy = 1.0 - clearcoatFactor * 0.25; diffuseColor *= baseLayerEnergy; specularColor *= baseLayerEnergy; // 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; vec3 reflection = -normalize(reflect(v, n)); PBRInfo pbrInfo = PBRInfo( 0.0, // NdotL NdotV, 0.0, // NdotH 0.0, // LdotH 0.0, // VdotH perceptualRoughness, metallic, specularEnvironmentR0, specularEnvironmentR90, alphaRoughness, diffuseColor, specularColor, n, v ); #ifdef USE_LIGHTS // Apply ambient light PBRInfo_setAmbientLight(pbrInfo); color += calculateMaterialLightColor( pbrInfo, lighting.ambientColor, clearcoatNormal, clearcoatFactor, clearcoatRoughness, sheenColor, sheenRoughness, anisotropyTangent, anisotropyStrength ); // Apply directional light for(int i = 0; i < lighting.directionalLightCount; i++) { if (i < lighting.directionalLightCount) { PBRInfo_setDirectionalLight(pbrInfo, lighting_getDirectionalLight(i).direction); color += calculateMaterialLightColor( pbrInfo, lighting_getDirectionalLight(i).color, clearcoatNormal, clearcoatFactor, clearcoatRoughness, sheenColor, sheenRoughness, anisotropyTangent, anisotropyStrength ); } } // Apply point light for(int i = 0; i < lighting.pointLightCount; i++) { if (i < lighting.pointLightCount) { PBRInfo_setPointLight(pbrInfo, lighting_getPointLight(i)); float attenuation = getPointLightAttenuation(lighting_getPointLight(i), distance(lighting_getPointLight(i).position, pbr_vPosition)); color += calculateMaterialLightColor( pbrInfo, lighting_getPointLight(i).color / attenuation, clearcoatNormal, clearcoatFactor, clearcoatRoughness, sheenColor, sheenRoughness, anisotropyTangent, anisotropyStrength ); } } for(int i = 0; i < lighting.spotLightCount; i++) { if (i < lighting.spotLightCount) { PBRInfo_setSpotLight(pbrInfo, lighting_getSpotLight(i)); float attenuation = getSpotLightAttenuation(lighting_getSpotLight(i), pbr_vPosition); color += calculateMaterialLightColor( pbrInfo, lighting_getSpotLight(i).color / attenuation, clearcoatNormal, clearcoatFactor, clearcoatRoughness, sheenColor, sheenRoughness, anisotropyTangent, anisotropyStrength ); } } #endif // Calculate lighting contribution from image based lighting source (IBL) #ifdef USE_IBL if (pbrMaterial.IBLenabled) { color += getIBLContribution(pbrInfo, n, reflection) * calculateAnisotropyBoost(pbrInfo, anisotropyTangent, anisotropyStrength); color += calculateClearcoatIBLContribution( pbrInfo, clearcoatNormal, -normalize(reflect(v, clearcoatNormal)), clearcoatFactor, clearcoatRoughness ); color += sheenColor * pbrMaterial.scaleIBLAmbient.x * (1.0 - sheenRoughness) * 0.25; } #endif // Apply optional PBR terms for additional (optional) shading #ifdef HAS_OCCLUSIONMAP if (pbrMaterial.occlusionMapEnabled) { float ao = texture(pbr_occlusionSampler, occlusionUV).r; color = mix(color, color * ao, pbrMaterial.occlusionStrength); } #endif vec3 emissive = pbrMaterial.emissiveFactor; #ifdef HAS_EMISSIVEMAP if (pbrMaterial.emissiveMapEnabled) { emissive *= SRGBtoLINEAR(texture(pbr_emissiveSampler, emissiveUV)).rgb; } #endif color += emissive * pbrMaterial.emissiveStrength; if (transmission > 0.0) { color = mix(color, color * getVolumeAttenuation(thickness), transmission); } // This section uses mix to override final color for reference app visualization // of various parameters in the lighting equation. #ifdef PBR_DEBUG // TODO: Figure out how to debug multiple lights // color = mix(color, F, pbr_scaleFGDSpec.x); // color = mix(color, vec3(G), pbr_scaleFGDSpec.y); // color = mix(color, vec3(D), pbr_scaleFGDSpec.z); // color = mix(color, specContrib, pbr_scaleFGDSpec.w); // color = mix(color, diffuseContrib, pbr_scaleDiffBaseMR.x); color = mix(color, baseColor.rgb, pbrMaterial.scaleDiffBaseMR.y); color = mix(color, vec3(metallic), pbrMaterial.scaleDiffBaseMR.z); color = mix(color, vec3(perceptualRoughness), pbrMaterial.scaleDiffBaseMR.w); #endif } float alpha = clamp(baseColor.a * (1.0 - transmission), 0.0, 1.0); return vec4(pow(color,vec3(1.0/2.2)), alpha); } `;