@luma.gl/shadertools
Version:
Shader module system for luma.gl
1,338 lines (1,200 loc) • 43.3 kB
text/typescript
// 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 pbrMaterialUniforms = /* wgsl */ `\
uniform Projection {
// Projection
vec3 u_Camera;
};
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
};
// Samplers
uniform sampler2D u_BaseColorSampler;
uniform sampler2D u_NormalSampler;
uniform sampler2D u_EmissiveSampler;
uniform sampler2D u_MetallicRoughnessSampler;
uniform sampler2D u_OcclusionSampler;
uniform sampler2D u_SpecularColorSampler;
uniform sampler2D u_SpecularIntensitySampler;
uniform sampler2D u_TransmissionSampler;
uniform sampler2D u_ThicknessSampler;
uniform sampler2D u_ClearcoatSampler;
uniform sampler2D u_ClearcoatRoughnessSampler;
uniform sampler2D u_ClearcoatNormalSampler;
uniform sampler2D u_SheenColorSampler;
uniform sampler2D u_SheenRoughnessSampler;
uniform sampler2D u_IridescenceSampler;
uniform sampler2D u_IridescenceThicknessSampler;
uniform sampler2D u_AnisotropySampler;
uniform samplerCube u_DiffuseEnvSampler;
uniform samplerCube u_SpecularEnvSampler;
uniform sampler2D u_brdfLUT;
`;
export const source = /* wgsl */ `\
struct PBRFragmentInputs {
pbr_vPosition: vec3f,
pbr_vUV0: vec2f,
pbr_vUV1: vec2f,
pbr_vTBN: mat3x3f,
pbr_vNormal: vec3f
};
var<private> fragmentInputs: PBRFragmentInputs;
fn pbr_setPositionNormalTangentUV(
position: vec4f,
normal: vec4f,
tangent: vec4f,
uv0: vec2f,
uv1: vec2f
)
{
var pos: vec4f = pbrProjection.modelMatrix * position;
fragmentInputs.pbr_vPosition = pos.xyz / pos.w;
fragmentInputs.pbr_vNormal = vec3f(0.0, 0.0, 1.0);
fragmentInputs.pbr_vTBN = mat3x3f(
vec3f(1.0, 0.0, 0.0),
vec3f(0.0, 1.0, 0.0),
vec3f(0.0, 0.0, 1.0)
);
fragmentInputs.pbr_vUV0 = vec2f(0.0, 0.0);
fragmentInputs.pbr_vUV1 = uv1;
let normalW: vec3f = normalize((pbrProjection.normalMatrix * vec4f(normal.xyz, 0.0)).xyz);
fragmentInputs.pbr_vNormal = normalW;
let tangentW: vec3f = normalize((pbrProjection.modelMatrix * vec4f(tangent.xyz, 0.0)).xyz);
let bitangentW: vec3f = cross(normalW, tangentW) * tangent.w;
fragmentInputs.pbr_vTBN = mat3x3f(tangentW, bitangentW, normalW);
fragmentInputs.pbr_vUV0 = uv0;
}
struct pbrMaterialUniforms {
// Material is unlit
unlit: u32,
// Base color map
baseColorMapEnabled: u32,
baseColorFactor: vec4f,
normalMapEnabled : u32,
normalScale: f32, // #ifdef HAS_NORMALMAP
emissiveMapEnabled: u32,
emissiveFactor: vec3f, // #ifdef HAS_EMISSIVEMAP
metallicRoughnessValues: vec2f,
metallicRoughnessMapEnabled: u32,
occlusionMapEnabled: i32,
occlusionStrength: f32, // #ifdef HAS_OCCLUSIONMAP
alphaCutoffEnabled: i32,
alphaCutoff: f32, // #ifdef ALPHA_CUTOFF
specularColorFactor: vec3f,
specularIntensityFactor: f32,
specularColorMapEnabled: i32,
specularIntensityMapEnabled: i32,
ior: f32,
transmissionFactor: f32,
transmissionMapEnabled: i32,
thicknessFactor: f32,
attenuationDistance: f32,
attenuationColor: vec3f,
clearcoatFactor: f32,
clearcoatRoughnessFactor: f32,
clearcoatMapEnabled: i32,
clearcoatRoughnessMapEnabled: i32,
sheenColorFactor: vec3f,
sheenRoughnessFactor: f32,
sheenColorMapEnabled: i32,
sheenRoughnessMapEnabled: i32,
iridescenceFactor: f32,
iridescenceIor: f32,
iridescenceThicknessRange: vec2f,
iridescenceMapEnabled: i32,
anisotropyStrength: f32,
anisotropyRotation: f32,
anisotropyDirection: vec2f,
anisotropyMapEnabled: i32,
emissiveStrength: f32,
// IBL
IBLenabled: i32,
scaleIBLAmbient: vec2f, // #ifdef USE_IBL
// debugging flags used for shader output of intermediate PBR variables
// #ifdef PBR_DEBUG
scaleDiffBaseMR: vec4f,
scaleFGDSpec: vec4f,
// #endif
baseColorUVSet: i32,
baseColorUVTransform: mat3x3f,
metallicRoughnessUVSet: i32,
metallicRoughnessUVTransform: mat3x3f,
normalUVSet: i32,
normalUVTransform: mat3x3f,
occlusionUVSet: i32,
occlusionUVTransform: mat3x3f,
emissiveUVSet: i32,
emissiveUVTransform: mat3x3f,
specularColorUVSet: i32,
specularColorUVTransform: mat3x3f,
specularIntensityUVSet: i32,
specularIntensityUVTransform: mat3x3f,
transmissionUVSet: i32,
transmissionUVTransform: mat3x3f,
thicknessUVSet: i32,
thicknessUVTransform: mat3x3f,
clearcoatUVSet: i32,
clearcoatUVTransform: mat3x3f,
clearcoatRoughnessUVSet: i32,
clearcoatRoughnessUVTransform: mat3x3f,
clearcoatNormalUVSet: i32,
clearcoatNormalUVTransform: mat3x3f,
sheenColorUVSet: i32,
sheenColorUVTransform: mat3x3f,
sheenRoughnessUVSet: i32,
sheenRoughnessUVTransform: mat3x3f,
iridescenceUVSet: i32,
iridescenceUVTransform: mat3x3f,
iridescenceThicknessUVSet: i32,
iridescenceThicknessUVTransform: mat3x3f,
anisotropyUVSet: i32,
anisotropyUVTransform: mat3x3f,
}
@group(3) @binding(auto) var<uniform> pbrMaterial : pbrMaterialUniforms;
// Samplers
@group(3) @binding(auto) var pbr_baseColorSampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_baseColorSamplerSampler: sampler;
@group(3) @binding(auto) var pbr_normalSampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_normalSamplerSampler: sampler;
@group(3) @binding(auto) var pbr_emissiveSampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_emissiveSamplerSampler: sampler;
@group(3) @binding(auto) var pbr_metallicRoughnessSampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_metallicRoughnessSamplerSampler: sampler;
@group(3) @binding(auto) var pbr_occlusionSampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_occlusionSamplerSampler: sampler;
@group(3) @binding(auto) var pbr_specularColorSampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_specularColorSamplerSampler: sampler;
@group(3) @binding(auto) var pbr_specularIntensitySampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_specularIntensitySamplerSampler: sampler;
@group(3) @binding(auto) var pbr_transmissionSampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_transmissionSamplerSampler: sampler;
@group(3) @binding(auto) var pbr_thicknessSampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_thicknessSamplerSampler: sampler;
@group(3) @binding(auto) var pbr_clearcoatSampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_clearcoatSamplerSampler: sampler;
@group(3) @binding(auto) var pbr_clearcoatRoughnessSampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_clearcoatRoughnessSamplerSampler: sampler;
@group(3) @binding(auto) var pbr_clearcoatNormalSampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_clearcoatNormalSamplerSampler: sampler;
@group(3) @binding(auto) var pbr_sheenColorSampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_sheenColorSamplerSampler: sampler;
@group(3) @binding(auto) var pbr_sheenRoughnessSampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_sheenRoughnessSamplerSampler: sampler;
@group(3) @binding(auto) var pbr_iridescenceSampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_iridescenceSamplerSampler: sampler;
@group(3) @binding(auto) var pbr_iridescenceThicknessSampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_iridescenceThicknessSamplerSampler: sampler;
@group(3) @binding(auto) var pbr_anisotropySampler: texture_2d<f32>;
@group(3) @binding(auto) var pbr_anisotropySamplerSampler: sampler;
// 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 {
NdotL: f32, // cos angle between normal and light direction
NdotV: f32, // cos angle between normal and view direction
NdotH: f32, // cos angle between normal and half vector
LdotH: f32, // cos angle between light direction and half vector
VdotH: f32, // cos angle between view direction and half vector
perceptualRoughness: f32, // roughness value, as authored by the model creator (input to shader)
metalness: f32, // metallic value at the surface
reflectance0: vec3f, // full reflectance color (normal incidence angle)
reflectance90: vec3f, // reflectance color at grazing angle
alphaRoughness: f32, // roughness mapped to a more linear change in the roughness (proposed by [2])
diffuseColor: vec3f, // color contribution from diffuse lighting
specularColor: vec3f, // color contribution from specular lighting
n: vec3f, // normal at surface point
v: vec3f, // vector from surface point to camera
};
const M_PI = 3.141592653589793;
const c_MinRoughness = 0.04;
fn SRGBtoLINEAR(srgbIn: vec4f ) -> vec4f
{
var linOut: vec3f = srgbIn.xyz;
let bLess: vec3f = step(vec3f(0.04045), srgbIn.xyz);
linOut = mix(
srgbIn.xyz / vec3f(12.92),
pow((srgbIn.xyz + vec3f(0.055)) / vec3f(1.055), vec3f(2.4)),
bLess
);
linOut = pow(srgbIn.xyz, vec3f(2.2));
return vec4f(linOut, srgbIn.w);
}
fn getMaterialUV(uvSet: i32, uvTransform: mat3x3f) -> vec2f
{
var baseUV = fragmentInputs.pbr_vUV0;
if (uvSet == 1) {
baseUV = fragmentInputs.pbr_vUV1;
}
return (uvTransform * vec3f(baseUV, 1.0)).xy;
}
// Build the tangent basis from interpolated attributes or screen-space derivatives.
fn getTBN(uv: vec2f) -> mat3x3f
{
let pos_dx: vec3f = dpdx(fragmentInputs.pbr_vPosition);
let pos_dy: vec3f = dpdy(fragmentInputs.pbr_vPosition);
let tex_dx: vec3f = dpdx(vec3f(uv, 0.0));
let tex_dy: vec3f = dpdy(vec3f(uv, 0.0));
var t: vec3f = (tex_dy.y * pos_dx - tex_dx.y * pos_dy) / (tex_dx.x * tex_dy.y - tex_dy.x * tex_dx.y);
var ng: vec3f = cross(pos_dx, pos_dy);
ng = normalize(fragmentInputs.pbr_vNormal);
t = normalize(t - ng * dot(ng, t));
var b: vec3f = normalize(cross(ng, t));
var tbn: mat3x3f = mat3x3f(t, b, ng);
tbn = fragmentInputs.pbr_vTBN;
return tbn;
}
// Find the normal for this fragment, pulling either from a predefined normal map
// or from the interpolated mesh normal and tangent attributes.
fn getMappedNormal(
normalSampler: texture_2d<f32>,
normalSamplerBinding: sampler,
tbn: mat3x3f,
normalScale: f32,
uv: vec2f
) -> vec3f
{
let n = textureSample(normalSampler, normalSamplerBinding, uv).rgb;
return normalize(tbn * ((2.0 * n - 1.0) * vec3f(normalScale, normalScale, 1.0)));
}
fn getNormal(tbn: mat3x3f, uv: vec2f) -> vec3f
{
// The tbn matrix is linearly interpolated, so we need to re-normalize
var n: vec3f = normalize(tbn[2].xyz);
n = getMappedNormal(
pbr_normalSampler,
pbr_normalSamplerSampler,
tbn,
pbrMaterial.normalScale,
uv
);
return n;
}
fn getClearcoatNormal(tbn: mat3x3f, baseNormal: vec3f, uv: vec2f) -> vec3f
{
return getMappedNormal(
pbr_clearcoatNormalSampler,
pbr_clearcoatNormalSamplerSampler,
tbn,
1.0,
uv
);
return baseNormal;
}
// 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.
fn getIBLContribution(pbrInfo: PBRInfo, n: vec3f, reflection: vec3f) -> vec3f
{
let mipCount: f32 = 9.0; // resolution of 512x512
let lod: f32 = pbrInfo.perceptualRoughness * mipCount;
// retrieve a scale and bias to F0. See [1], Figure 3
let brdf = SRGBtoLINEAR(
textureSampleLevel(
pbr_brdfLUT,
pbr_brdfLUTSampler,
vec2f(pbrInfo.NdotV, 1.0 - pbrInfo.perceptualRoughness),
0.0
)
).rgb;
let diffuseLight =
SRGBtoLINEAR(
textureSampleLevel(pbr_diffuseEnvSampler, pbr_diffuseEnvSamplerSampler, n, 0.0)
).rgb;
var specularLight = SRGBtoLINEAR(
textureSampleLevel(
pbr_specularEnvSampler,
pbr_specularEnvSamplerSampler,
reflection,
0.0
)
).rgb;
specularLight = SRGBtoLINEAR(
textureSampleLevel(
pbr_specularEnvSampler,
pbr_specularEnvSamplerSampler,
reflection,
lod
)
).rgb;
let diffuse = diffuseLight * pbrInfo.diffuseColor * pbrMaterial.scaleIBLAmbient.x;
let specular =
specularLight * (pbrInfo.specularColor * brdf.x + brdf.y) * pbrMaterial.scaleIBLAmbient.y;
return diffuse + specular;
}
// Basic Lambertian diffuse
// Implementation from Lambert's Photometria https://archive.org/details/lambertsphotome00lambgoog
// See also [1], Equation 1
fn diffuse(pbrInfo: PBRInfo) -> vec3<f32> {
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
fn specularReflection(pbrInfo: PBRInfo) -> vec3<f32> {
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].
fn geometricOcclusion(pbrInfo: PBRInfo) -> f32 {
let NdotL: f32 = pbrInfo.NdotL;
let NdotV: f32 = pbrInfo.NdotV;
let r: f32 = pbrInfo.alphaRoughness;
let attenuationL = 2.0 * NdotL / (NdotL + sqrt(r * r + (1.0 - r * r) * (NdotL * NdotL)));
let 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.
fn microfacetDistribution(pbrInfo: PBRInfo) -> f32 {
let roughnessSq = pbrInfo.alphaRoughness * pbrInfo.alphaRoughness;
let f = (pbrInfo.NdotH * roughnessSq - pbrInfo.NdotH) * pbrInfo.NdotH + 1.0;
return roughnessSq / (M_PI * f * f);
}
fn maxComponent(value: vec3f) -> f32 {
return max(max(value.r, value.g), value.b);
}
fn getDielectricF0(ior: f32) -> f32 {
let clampedIor = max(ior, 1.0);
let ratio = (clampedIor - 1.0) / (clampedIor + 1.0);
return ratio * ratio;
}
fn normalizeDirection(direction: vec2f) -> vec2f {
let directionLength = length(direction);
if (directionLength > 0.0001) {
return direction / directionLength;
}
return vec2f(1.0, 0.0);
}
fn rotateDirection(direction: vec2f, rotation: f32) -> vec2f {
let s = sin(rotation);
let c = cos(rotation);
return vec2f(direction.x * c - direction.y * s, direction.x * s + direction.y * c);
}
fn getIridescenceTint(iridescence: f32, thickness: f32, NdotV: f32) -> vec3f {
if (iridescence <= 0.0) {
return vec3f(1.0);
}
let phase = 0.015 * thickness * pbrMaterial.iridescenceIor + (1.0 - NdotV) * 6.0;
let thinFilmTint =
0.5 +
0.5 *
cos(vec3f(phase, phase + 2.0943951, phase + 4.1887902));
return mix(vec3f(1.0), thinFilmTint, iridescence);
}
fn getVolumeAttenuation(thickness: f32) -> vec3f {
if (thickness <= 0.0) {
return vec3f(1.0);
}
let attenuationCoefficient =
-log(max(pbrMaterial.attenuationColor, vec3f(0.0001))) /
max(pbrMaterial.attenuationDistance, 0.0001);
return exp(-attenuationCoefficient * thickness);
}
fn createClearcoatPBRInfo(
basePBRInfo: PBRInfo,
clearcoatNormal: vec3f,
clearcoatRoughness: f32
) -> PBRInfo {
let perceptualRoughness = clamp(clearcoatRoughness, c_MinRoughness, 1.0);
let alphaRoughness = perceptualRoughness * perceptualRoughness;
let 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,
vec3f(0.04),
vec3f(1.0),
alphaRoughness,
vec3f(0.0),
vec3f(0.04),
clearcoatNormal,
basePBRInfo.v
);
}
fn calculateClearcoatContribution(
pbrInfo: PBRInfo,
lightColor: vec3f,
clearcoatNormal: vec3f,
clearcoatFactor: f32,
clearcoatRoughness: f32
) -> vec3f {
if (clearcoatFactor <= 0.0) {
return vec3f(0.0);
}
let clearcoatPBRInfo = createClearcoatPBRInfo(pbrInfo, clearcoatNormal, clearcoatRoughness);
return calculateFinalColor(clearcoatPBRInfo, lightColor) * clearcoatFactor;
}
fn calculateClearcoatIBLContribution(
pbrInfo: PBRInfo,
clearcoatNormal: vec3f,
reflection: vec3f,
clearcoatFactor: f32,
clearcoatRoughness: f32
) -> vec3f {
if (clearcoatFactor <= 0.0) {
return vec3f(0.0);
}
let clearcoatPBRInfo = createClearcoatPBRInfo(pbrInfo, clearcoatNormal, clearcoatRoughness);
return getIBLContribution(clearcoatPBRInfo, clearcoatNormal, reflection) * clearcoatFactor;
}
fn calculateSheenContribution(
pbrInfo: PBRInfo,
lightColor: vec3f,
sheenColor: vec3f,
sheenRoughness: f32
) -> vec3f {
if (maxComponent(sheenColor) <= 0.0) {
return vec3f(0.0);
}
let sheenFresnel = pow(clamp(1.0 - pbrInfo.VdotH, 0.0, 1.0), 5.0);
let sheenVisibility = mix(1.0, pbrInfo.NdotL * pbrInfo.NdotV, sheenRoughness);
return pbrInfo.NdotL *
lightColor *
sheenColor *
(0.25 + 0.75 * sheenFresnel) *
sheenVisibility *
(1.0 - pbrInfo.metalness);
}
fn calculateAnisotropyBoost(
pbrInfo: PBRInfo,
anisotropyTangent: vec3f,
anisotropyStrength: f32
) -> f32 {
if (anisotropyStrength <= 0.0) {
return 1.0;
}
let anisotropyBitangent = normalize(cross(pbrInfo.n, anisotropyTangent));
let bitangentViewAlignment = abs(dot(pbrInfo.v, anisotropyBitangent));
return mix(1.0, 0.65 + 0.7 * bitangentViewAlignment, anisotropyStrength);
}
fn calculateMaterialLightColor(
pbrInfo: PBRInfo,
lightColor: vec3f,
clearcoatNormal: vec3f,
clearcoatFactor: f32,
clearcoatRoughness: f32,
sheenColor: vec3f,
sheenRoughness: f32,
anisotropyTangent: vec3f,
anisotropyStrength: f32
) -> vec3f {
let anisotropyBoost = calculateAnisotropyBoost(pbrInfo, anisotropyTangent, anisotropyStrength);
var color = calculateFinalColor(pbrInfo, lightColor) * anisotropyBoost;
color += calculateClearcoatContribution(
pbrInfo,
lightColor,
clearcoatNormal,
clearcoatFactor,
clearcoatRoughness
);
color += calculateSheenContribution(pbrInfo, lightColor, sheenColor, sheenRoughness);
return color;
}
fn PBRInfo_setAmbientLight(pbrInfo: ptr<function, PBRInfo>) {
(*pbrInfo).NdotL = 1.0;
(*pbrInfo).NdotH = 0.0;
(*pbrInfo).LdotH = 0.0;
(*pbrInfo).VdotH = 1.0;
}
fn PBRInfo_setDirectionalLight(pbrInfo: ptr<function, PBRInfo>, lightDirection: vec3<f32>) {
let n = (*pbrInfo).n;
let v = (*pbrInfo).v;
let l = normalize(lightDirection); // Vector from surface point to light
let 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);
}
fn PBRInfo_setPointLight(pbrInfo: ptr<function, PBRInfo>, pointLight: PointLight) {
let light_direction = normalize(pointLight.position - fragmentInputs.pbr_vPosition);
PBRInfo_setDirectionalLight(pbrInfo, light_direction);
}
fn PBRInfo_setSpotLight(pbrInfo: ptr<function, PBRInfo>, spotLight: SpotLight) {
let light_direction = normalize(spotLight.position - fragmentInputs.pbr_vPosition);
PBRInfo_setDirectionalLight(pbrInfo, light_direction);
}
fn calculateFinalColor(pbrInfo: PBRInfo, lightColor: vec3<f32>) -> vec3<f32> {
// Calculate the shading terms for the microfacet specular shading model
let F = specularReflection(pbrInfo);
let G = geometricOcclusion(pbrInfo);
let D = microfacetDistribution(pbrInfo);
// Calculation of analytical lighting contribution
let diffuseContrib = (1.0 - F) * diffuse(pbrInfo);
let 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);
}
fn pbr_filterColor(colorUnused: vec4<f32>) -> vec4<f32> {
let baseColorUV = getMaterialUV(pbrMaterial.baseColorUVSet, pbrMaterial.baseColorUVTransform);
let metallicRoughnessUV = getMaterialUV(
pbrMaterial.metallicRoughnessUVSet,
pbrMaterial.metallicRoughnessUVTransform
);
let normalUV = getMaterialUV(pbrMaterial.normalUVSet, pbrMaterial.normalUVTransform);
let occlusionUV = getMaterialUV(pbrMaterial.occlusionUVSet, pbrMaterial.occlusionUVTransform);
let emissiveUV = getMaterialUV(pbrMaterial.emissiveUVSet, pbrMaterial.emissiveUVTransform);
let specularColorUV = getMaterialUV(
pbrMaterial.specularColorUVSet,
pbrMaterial.specularColorUVTransform
);
let specularIntensityUV = getMaterialUV(
pbrMaterial.specularIntensityUVSet,
pbrMaterial.specularIntensityUVTransform
);
let transmissionUV = getMaterialUV(
pbrMaterial.transmissionUVSet,
pbrMaterial.transmissionUVTransform
);
let thicknessUV = getMaterialUV(pbrMaterial.thicknessUVSet, pbrMaterial.thicknessUVTransform);
let clearcoatUV = getMaterialUV(pbrMaterial.clearcoatUVSet, pbrMaterial.clearcoatUVTransform);
let clearcoatRoughnessUV = getMaterialUV(
pbrMaterial.clearcoatRoughnessUVSet,
pbrMaterial.clearcoatRoughnessUVTransform
);
let clearcoatNormalUV = getMaterialUV(
pbrMaterial.clearcoatNormalUVSet,
pbrMaterial.clearcoatNormalUVTransform
);
let sheenColorUV = getMaterialUV(
pbrMaterial.sheenColorUVSet,
pbrMaterial.sheenColorUVTransform
);
let sheenRoughnessUV = getMaterialUV(
pbrMaterial.sheenRoughnessUVSet,
pbrMaterial.sheenRoughnessUVTransform
);
let iridescenceUV = getMaterialUV(
pbrMaterial.iridescenceUVSet,
pbrMaterial.iridescenceUVTransform
);
let iridescenceThicknessUV = getMaterialUV(
pbrMaterial.iridescenceThicknessUVSet,
pbrMaterial.iridescenceThicknessUVTransform
);
let anisotropyUV = getMaterialUV(
pbrMaterial.anisotropyUVSet,
pbrMaterial.anisotropyUVTransform
);
// The albedo may be defined from a base texture or a flat color
var baseColor: vec4<f32> = pbrMaterial.baseColorFactor;
baseColor = SRGBtoLINEAR(
textureSample(pbr_baseColorSampler, pbr_baseColorSamplerSampler, baseColorUV)
) * pbrMaterial.baseColorFactor;
if (baseColor.a < pbrMaterial.alphaCutoff) {
discard;
}
var color = vec3<f32>(0.0, 0.0, 0.0);
var transmission = 0.0;
if (pbrMaterial.unlit != 0u) {
color = 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
var perceptualRoughness = pbrMaterial.metallicRoughnessValues.y;
var metallic = pbrMaterial.metallicRoughnessValues.x;
// 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
let mrSample = textureSample(
pbr_metallicRoughnessSampler,
pbr_metallicRoughnessSamplerSampler,
metallicRoughnessUV
);
perceptualRoughness = mrSample.g * perceptualRoughness;
metallic = mrSample.b * metallic;
perceptualRoughness = clamp(perceptualRoughness, c_MinRoughness, 1.0);
metallic = clamp(metallic, 0.0, 1.0);
let tbn = getTBN(normalUV);
let n = getNormal(tbn, normalUV); // normal at surface point
let v = normalize(pbrProjection.camera - fragmentInputs.pbr_vPosition); // Vector from surface point to camera
let NdotV = clamp(abs(dot(n, v)), 0.001, 1.0);
var useExtendedPBR = false;
useExtendedPBR =
pbrMaterial.specularColorMapEnabled != 0 ||
pbrMaterial.specularIntensityMapEnabled != 0 ||
abs(pbrMaterial.specularIntensityFactor - 1.0) > 0.0001 ||
maxComponent(abs(pbrMaterial.specularColorFactor - vec3f(1.0))) > 0.0001 ||
abs(pbrMaterial.ior - 1.5) > 0.0001 ||
pbrMaterial.transmissionMapEnabled != 0 ||
pbrMaterial.transmissionFactor > 0.0001 ||
pbrMaterial.clearcoatMapEnabled != 0 ||
pbrMaterial.clearcoatRoughnessMapEnabled != 0 ||
pbrMaterial.clearcoatFactor > 0.0001 ||
pbrMaterial.clearcoatRoughnessFactor > 0.0001 ||
pbrMaterial.sheenColorMapEnabled != 0 ||
pbrMaterial.sheenRoughnessMapEnabled != 0 ||
maxComponent(pbrMaterial.sheenColorFactor) > 0.0001 ||
pbrMaterial.sheenRoughnessFactor > 0.0001 ||
pbrMaterial.iridescenceMapEnabled != 0 ||
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 != 0 ||
pbrMaterial.anisotropyStrength > 0.0001 ||
abs(pbrMaterial.anisotropyRotation) > 0.0001 ||
length(pbrMaterial.anisotropyDirection - vec2f(1.0, 0.0)) > 0.0001;
if (!useExtendedPBR) {
let alphaRoughness = perceptualRoughness * perceptualRoughness;
let f0 = vec3<f32>(0.04);
var diffuseColor = baseColor.rgb * (vec3<f32>(1.0) - f0);
diffuseColor *= 1.0 - metallic;
let specularColor = mix(f0, baseColor.rgb, metallic);
let reflectance = max(max(specularColor.r, specularColor.g), specularColor.b);
let reflectance90 = clamp(reflectance * 25.0, 0.0, 1.0);
let specularEnvironmentR0 = specularColor;
let specularEnvironmentR90 = vec3<f32>(1.0, 1.0, 1.0) * reflectance90;
let reflection = -normalize(reflect(v, n));
var pbrInfo = PBRInfo(
0.0, // NdotL
NdotV,
0.0, // NdotH
0.0, // LdotH
0.0, // VdotH
perceptualRoughness,
metallic,
specularEnvironmentR0,
specularEnvironmentR90,
alphaRoughness,
diffuseColor,
specularColor,
n,
v
);
PBRInfo_setAmbientLight(&pbrInfo);
color += calculateFinalColor(pbrInfo, lighting.ambientColor);
for (var 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 (var i = 0; i < lighting.pointLightCount; i++) {
if (i < lighting.pointLightCount) {
PBRInfo_setPointLight(&pbrInfo, lighting_getPointLight(i));
let attenuation = getPointLightAttenuation(
lighting_getPointLight(i),
distance(lighting_getPointLight(i).position, fragmentInputs.pbr_vPosition)
);
color += calculateFinalColor(pbrInfo, lighting_getPointLight(i).color / attenuation);
}
}
for (var i = 0; i < lighting.spotLightCount; i++) {
if (i < lighting.spotLightCount) {
PBRInfo_setSpotLight(&pbrInfo, lighting_getSpotLight(i));
let attenuation = getSpotLightAttenuation(
lighting_getSpotLight(i),
fragmentInputs.pbr_vPosition
);
color += calculateFinalColor(pbrInfo, lighting_getSpotLight(i).color / attenuation);
}
}
if (pbrMaterial.IBLenabled != 0) {
color += getIBLContribution(pbrInfo, n, reflection);
}
if (pbrMaterial.occlusionMapEnabled != 0) {
let ao = textureSample(pbr_occlusionSampler, pbr_occlusionSamplerSampler, occlusionUV).r;
color = mix(color, color * ao, pbrMaterial.occlusionStrength);
}
var emissive = pbrMaterial.emissiveFactor;
if (pbrMaterial.emissiveMapEnabled != 0u) {
emissive *= SRGBtoLINEAR(
textureSample(pbr_emissiveSampler, pbr_emissiveSamplerSampler, emissiveUV)
).rgb;
}
color += emissive * pbrMaterial.emissiveStrength;
color = mix(color, baseColor.rgb, pbrMaterial.scaleDiffBaseMR.y);
color = mix(color, vec3<f32>(metallic), pbrMaterial.scaleDiffBaseMR.z);
color = mix(color, vec3<f32>(perceptualRoughness), pbrMaterial.scaleDiffBaseMR.w);
return vec4<f32>(pow(color, vec3<f32>(1.0 / 2.2)), baseColor.a);
}
var specularIntensity = pbrMaterial.specularIntensityFactor;
if (pbrMaterial.specularIntensityMapEnabled != 0) {
specularIntensity *= textureSample(
pbr_specularIntensitySampler,
pbr_specularIntensitySamplerSampler,
specularIntensityUV
).a;
}
var specularFactor = pbrMaterial.specularColorFactor;
if (pbrMaterial.specularColorMapEnabled != 0) {
specularFactor *= SRGBtoLINEAR(
textureSample(
pbr_specularColorSampler,
pbr_specularColorSamplerSampler,
specularColorUV
)
).rgb;
}
transmission = pbrMaterial.transmissionFactor;
if (pbrMaterial.transmissionMapEnabled != 0) {
transmission *= textureSample(
pbr_transmissionSampler,
pbr_transmissionSamplerSampler,
transmissionUV
).r;
}
transmission = clamp(transmission * (1.0 - metallic), 0.0, 1.0);
var thickness = max(pbrMaterial.thicknessFactor, 0.0);
thickness *= textureSample(
pbr_thicknessSampler,
pbr_thicknessSamplerSampler,
thicknessUV
).g;
var clearcoatFactor = pbrMaterial.clearcoatFactor;
var clearcoatRoughness = pbrMaterial.clearcoatRoughnessFactor;
if (pbrMaterial.clearcoatMapEnabled != 0) {
clearcoatFactor *= textureSample(
pbr_clearcoatSampler,
pbr_clearcoatSamplerSampler,
clearcoatUV
).r;
}
if (pbrMaterial.clearcoatRoughnessMapEnabled != 0) {
clearcoatRoughness *= textureSample(
pbr_clearcoatRoughnessSampler,
pbr_clearcoatRoughnessSamplerSampler,
clearcoatRoughnessUV
).g;
}
clearcoatFactor = clamp(clearcoatFactor, 0.0, 1.0);
clearcoatRoughness = clamp(clearcoatRoughness, c_MinRoughness, 1.0);
let clearcoatNormal = getClearcoatNormal(getTBN(clearcoatNormalUV), n, clearcoatNormalUV);
var sheenColor = pbrMaterial.sheenColorFactor;
var sheenRoughness = pbrMaterial.sheenRoughnessFactor;
if (pbrMaterial.sheenColorMapEnabled != 0) {
sheenColor *= SRGBtoLINEAR(
textureSample(
pbr_sheenColorSampler,
pbr_sheenColorSamplerSampler,
sheenColorUV
)
).rgb;
}
if (pbrMaterial.sheenRoughnessMapEnabled != 0) {
sheenRoughness *= textureSample(
pbr_sheenRoughnessSampler,
pbr_sheenRoughnessSamplerSampler,
sheenRoughnessUV
).a;
}
sheenRoughness = clamp(sheenRoughness, c_MinRoughness, 1.0);
var iridescence = pbrMaterial.iridescenceFactor;
if (pbrMaterial.iridescenceMapEnabled != 0) {
iridescence *= textureSample(
pbr_iridescenceSampler,
pbr_iridescenceSamplerSampler,
iridescenceUV
).r;
}
iridescence = clamp(iridescence, 0.0, 1.0);
var iridescenceThickness = mix(
pbrMaterial.iridescenceThicknessRange.x,
pbrMaterial.iridescenceThicknessRange.y,
0.5
);
iridescenceThickness = mix(
pbrMaterial.iridescenceThicknessRange.x,
pbrMaterial.iridescenceThicknessRange.y,
textureSample(
pbr_iridescenceThicknessSampler,
pbr_iridescenceThicknessSamplerSampler,
iridescenceThicknessUV
).g
);
var anisotropyStrength = clamp(pbrMaterial.anisotropyStrength, 0.0, 1.0);
var anisotropyDirection = normalizeDirection(pbrMaterial.anisotropyDirection);
if (pbrMaterial.anisotropyMapEnabled != 0) {
let anisotropySample = textureSample(
pbr_anisotropySampler,
pbr_anisotropySamplerSampler,
anisotropyUV
).rgb;
anisotropyStrength *= anisotropySample.b;
let mappedDirection = anisotropySample.rg * 2.0 - 1.0;
if (length(mappedDirection) > 0.0001) {
anisotropyDirection = normalize(mappedDirection);
}
}
anisotropyDirection = rotateDirection(anisotropyDirection, pbrMaterial.anisotropyRotation);
var anisotropyTangent =
normalize(tbn[0] * anisotropyDirection.x + tbn[1] * anisotropyDirection.y);
if (length(anisotropyTangent) < 0.0001) {
anisotropyTangent = normalize(tbn[0]);
}
let 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].
let alphaRoughness = perceptualRoughness * perceptualRoughness;
let dielectricF0 = getDielectricF0(pbrMaterial.ior);
var dielectricSpecularF0 = min(
vec3f(dielectricF0) * specularFactor * specularIntensity,
vec3f(1.0)
);
let iridescenceTint = getIridescenceTint(iridescence, iridescenceThickness, NdotV);
dielectricSpecularF0 = mix(
dielectricSpecularF0,
dielectricSpecularF0 * iridescenceTint,
iridescence
);
var diffuseColor = baseColor.rgb * (vec3f(1.0) - dielectricSpecularF0);
diffuseColor *= (1.0 - metallic) * (1.0 - transmission);
var specularColor = mix(dielectricSpecularF0, baseColor.rgb, metallic);
let baseLayerEnergy = 1.0 - clearcoatFactor * 0.25;
diffuseColor *= baseLayerEnergy;
specularColor *= baseLayerEnergy;
// Compute reflectance.
let 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 reflectance to 0%.
let reflectance90 = clamp(reflectance * 25.0, 0.0, 1.0);
let specularEnvironmentR0 = specularColor;
let specularEnvironmentR90 = vec3<f32>(1.0, 1.0, 1.0) * reflectance90;
let reflection = -normalize(reflect(v, n));
var pbrInfo = PBRInfo(
0.0, // NdotL
NdotV,
0.0, // NdotH
0.0, // LdotH
0.0, // VdotH
perceptualRoughness,
metallic,
specularEnvironmentR0,
specularEnvironmentR90,
alphaRoughness,
diffuseColor,
specularColor,
n,
v
);
// Apply ambient light
PBRInfo_setAmbientLight(&pbrInfo);
color += calculateMaterialLightColor(
pbrInfo,
lighting.ambientColor,
clearcoatNormal,
clearcoatFactor,
clearcoatRoughness,
sheenColor,
sheenRoughness,
anisotropyTangent,
anisotropyStrength
);
// Apply directional light
for (var 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 (var i = 0; i < lighting.pointLightCount; i++) {
if (i < lighting.pointLightCount) {
PBRInfo_setPointLight(&pbrInfo, lighting_getPointLight(i));
let attenuation = getPointLightAttenuation(
lighting_getPointLight(i),
distance(lighting_getPointLight(i).position, fragmentInputs.pbr_vPosition)
);
color += calculateMaterialLightColor(
pbrInfo,
lighting_getPointLight(i).color / attenuation,
clearcoatNormal,
clearcoatFactor,
clearcoatRoughness,
sheenColor,
sheenRoughness,
anisotropyTangent,
anisotropyStrength
);
}
}
for (var i = 0; i < lighting.spotLightCount; i++) {
if (i < lighting.spotLightCount) {
PBRInfo_setSpotLight(&pbrInfo, lighting_getSpotLight(i));
let attenuation = getSpotLightAttenuation(lighting_getSpotLight(i), fragmentInputs.pbr_vPosition);
color += calculateMaterialLightColor(
pbrInfo,
lighting_getSpotLight(i).color / attenuation,
clearcoatNormal,
clearcoatFactor,
clearcoatRoughness,
sheenColor,
sheenRoughness,
anisotropyTangent,
anisotropyStrength
);
}
}
// Calculate lighting contribution from image based lighting source (IBL)
if (pbrMaterial.IBLenabled != 0) {
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;
}
// Apply optional PBR terms for additional (optional) shading
if (pbrMaterial.occlusionMapEnabled != 0) {
let ao = textureSample(pbr_occlusionSampler, pbr_occlusionSamplerSampler, occlusionUV).r;
color = mix(color, color * ao, pbrMaterial.occlusionStrength);
}
var emissive = pbrMaterial.emissiveFactor;
if (pbrMaterial.emissiveMapEnabled != 0u) {
emissive *= SRGBtoLINEAR(
textureSample(pbr_emissiveSampler, pbr_emissiveSamplerSampler, emissiveUV)
).rgb;
}
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.
// 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<f32>(metallic), pbrMaterial.scaleDiffBaseMR.z);
color = mix(color, vec3<f32>(perceptualRoughness), pbrMaterial.scaleDiffBaseMR.w);
}
let alpha = clamp(baseColor.a * (1.0 - transmission), 0.0, 1.0);
return vec4<f32>(pow(color, vec3<f32>(1.0 / 2.2)), alpha);
}
`;