src/viewer/scene/model/layer/programs/PBRProgram.js
import {LinearEncoding, sRGBEncoding} from "../../../constants/constants.js";
import {setupTexture} from "../../../webgl/WebGLRenderer.js";
export const PBRProgram = function(programVariables, geometry, scene, lightSetup, sao) {
const setup2dTexture = (name, isSrgb, getTexture) => {
return setupTexture(programVariables, "sampler2D", name, isSrgb ? sRGBEncoding : LinearEncoding, (set, state) => {
const texture = getTexture(state.layerTextureSet);
texture && set(texture.texture);
});
};
const attributes = geometry.attributes;
const getIrradiance = lightSetup.getIrradiance;
const colorMap = setup2dTexture("uColorMap", true, textureSet => textureSet.colorTexture);
const metallicRoughMap = setup2dTexture("uMetallicRoughMap", false, textureSet => textureSet.metallicRoughnessTexture);
const emissiveMap = setup2dTexture("uEmissiveMap", true, textureSet => textureSet.emissiveTexture);
const normalMap = setup2dTexture("uNormalMap", false, textureSet => textureSet.normalsTexture);
const aOMap = setup2dTexture("uAOMap", false, textureSet => textureSet.occlusionTexture);
const vViewPosition = programVariables.createVarying("vec3", "vViewPosition", () => `${attributes.position.view}.xyz`);
const vViewNormal = programVariables.createVarying("vec3", "vViewNormal", () => attributes.normal.view);
const vColor = programVariables.createVarying("vec4", "vColor", () => attributes.color);
const vUV = programVariables.createVarying("vec2", "vUV", () => attributes.uv);
const vMetallicRoughness = programVariables.createVarying("vec2", "vMetallicRoughness", () => attributes.metallicRoughness);
const vWorldNormal = programVariables.createVarying("vec3", "vWorldNormal", () => `${attributes.normal.world}.xyz`);
const outColor = programVariables.createOutput("vec4", "outColor");
const saturate = programVariables.createFragmentDefinition(
"saturate",
(name, src) => src.push(`float ${name}(const in float a) { return clamp(a, 0.0, 1.0); }`));
const BRDF_Specular_GGX = programVariables.createFragmentDefinition(
"BRDF_Specular_GGX",
(name, src) => {
src.push("vec3 F_Schlick(const in vec3 specularColor, const in float dotLH) {");
src.push(" float fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );");
src.push(" return ( 1.0 - specularColor ) * fresnel + specularColor;");
src.push("}");
src.push("float G_GGX_Smith(const in float alpha, const in float dotNL, const in float dotNV) {");
src.push(" float a2 = ( alpha * alpha );");
src.push(" float gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * ( dotNL * dotNL ) );");
src.push(" float gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * ( dotNV * dotNV ) );");
src.push(" return 1.0 / ( gl * gv );");
src.push("}");
src.push("float G_GGX_SmithCorrelated(const in float alpha, const in float dotNL, const in float dotNV) {");
src.push(" float a2 = ( alpha * alpha );");
src.push(" float gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * ( dotNV * dotNV ) );");
src.push(" float gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * ( dotNL * dotNL ) );");
src.push(" return 0.5 / max( gv + gl, 1e-6 );");
src.push("}");
src.push("float D_GGX(const in float alpha, const in float dotNH) {");
src.push(" float a2 = ( alpha * alpha );");
src.push(" float denom = ( dotNH * dotNH) * ( a2 - 1.0 ) + 1.0;");
src.push(" return 0.31830988618 * a2 / ( denom * denom);"); // 1/PI
src.push("}");
src.push(`vec3 ${name}(const in vec3 incidentLightDirection, const in vec3 viewNormal, const in vec3 viewEyeDir, const in vec3 specularColor, const in float roughness) {`);
src.push(" float alpha = ( roughness * roughness );");
src.push(" vec3 halfDir = normalize( incidentLightDirection + viewEyeDir );");
src.push(` float dotNL = ${saturate}( dot( viewNormal, incidentLightDirection ) );`);
src.push(` float dotNV = ${saturate}( dot( viewNormal, viewEyeDir ) );`);
src.push(` float dotNH = ${saturate}( dot( viewNormal, halfDir ) );`);
src.push(` float dotLH = ${saturate}( dot( incidentLightDirection, halfDir ) );`);
src.push(" vec3 F = F_Schlick( specularColor, dotLH );");
src.push(" float G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );");
src.push(" float D = D_GGX( alpha, dotNH );");
src.push(" return F * (G * D);");
src.push("}");
});
const BRDF_Specular_GGX_Environment = programVariables.createFragmentDefinition(
"BRDF_Specular_GGX_Environment",
(name, src) => {
src.push(`vec3 ${name}(const in vec3 viewNormal, const in vec3 viewEyeDir, const in vec3 specularColor, const in float roughness) {`);
src.push(` float dotNV = ${saturate}(dot(viewNormal, viewEyeDir));`);
src.push(" const vec4 c0 = vec4( -1, -0.0275, -0.572, 0.022);");
src.push(" const vec4 c1 = vec4( 1, 0.0425, 1.04, -0.04);");
src.push(" vec4 r = roughness * c0 + c1;");
src.push(" float a004 = min(r.x * r.x, exp2(-9.28 * dotNV)) * r.x + r.y;");
src.push(" vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;");
src.push(" return specularColor * AB.x + AB.y;");
src.push("}");
});
const perturbNormal2Arb = programVariables.createFragmentDefinition(
"perturbNormal2Arb",
(name, src) => {
src.push(`vec3 ${name}( vec3 eye_pos, vec3 surf_norm, vec2 uv, vec4 texel ) {`);
src.push(" if (texel.r == 0.0 && texel.g == 0.0 && texel.b == 0.0) {");
src.push(" return surf_norm;");
src.push(" }");
src.push(" vec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );");
src.push(" vec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );");
src.push(" vec2 st0 = dFdx( uv.st );");
src.push(" vec2 st1 = dFdy( uv.st );");
src.push(" vec3 S = normalize( q0 * st1.t - q1 * st0.t );");
src.push(" vec3 T = normalize( -q0 * st1.s + q1 * st0.s );");
src.push(" vec3 N = normalize( surf_norm );");
src.push(" vec3 mapN = texel.xyz * 2.0 - 1.0;");
src.push(" mat3 tsn = mat3( S, T, N );");
// src.push(" mapN *= 3.0;");
src.push(" return normalize( tsn * mapN );");
src.push("}");
});
return {
programName: "PBR",
getHash: () => [lightSetup.getHash(), sao ? "sao" : "nosao", !!scene.gammaOutput],
getLogDepth: scene.logarithmicDepthBufferEnabled && (vFragDepth => vFragDepth),
renderPassFlag: 0, // COLOR_OPAQUE | COLOR_TRANSPARENT
clippingCaps: scene._sectionPlanesState.clippingCaps && outColor,
incrementDrawState: true,
appendFragmentOutputs: (src, getGammaOutputExpression, gl_FragCoord) => {
src.push("const float PI = 3.14159265359;");
src.push("vec3 reflDiff = vec3(0.0);");
src.push("vec3 reflSpec = vec3(0.0);");
src.push(`vec3 rgb = ${vColor}.rgb;`);
src.push(`float opacity = ${vColor}.a;`);
src.push("vec3 baseColor = rgb;");
src.push("float specularF0 = 1.0;");
src.push(`float metallic = float(${vMetallicRoughness}.r) / 255.0;`);
src.push(`float roughness = float(${vMetallicRoughness}.g) / 255.0;`);
src.push("float dielectricSpecular = 0.16 * specularF0 * specularF0;");
src.push(`vec4 colorTexel = ${colorMap(vUV)};`);
src.push("baseColor *= colorTexel.rgb;");
// src.push("opacity *=/= colorTexel.a;"); // batching had "*=", instancing had "="
src.push(`vec3 metalRoughTexel = ${metallicRoughMap(vUV)}.rgb;`);
src.push("metallic *= metalRoughTexel.b;");
src.push("roughness *= metalRoughTexel.g;");
src.push("vec3 diffuseColor = baseColor * (1.0 - dielectricSpecular) * (1.0 - metallic);");
src.push("float specularRoughness = clamp(roughness, 0.04, 1.0);");
src.push("vec3 specularColor = mix(vec3(dielectricSpecular), baseColor, metallic);");
src.push(`vec3 viewNormal = -${perturbNormal2Arb}(${vViewPosition}, normalize(${vViewNormal}), ${vUV}, ${normalMap(vUV)});`);
src.push(`vec3 viewEyeDir = normalize(${vViewPosition});`);
getIrradiance && src.push(`reflDiff += ${getIrradiance(`normalize(${vWorldNormal})`)};`);
if (lightSetup.getReflection) {
const reflectVec = `reflect(viewEyeDir, viewNormal)`;
const viewReflectVec = `normalize((vec4(${reflectVec}, 0.0) * ${geometry.viewMatrix}).xyz)`;
const maxMIPLevel = "8.0";
const blinnExpFromRoughness = `(2.0 / pow(specularRoughness + 0.0001, 2.0) - 2.0)`;
const desiredMIPLevel = `${maxMIPLevel} - 0.79248 - 0.5 * log2(pow(${blinnExpFromRoughness}, 2.0) + 1.0)`;
const specularMIPLevel = `0.5 * clamp(${desiredMIPLevel}, 0.0, ${maxMIPLevel})`; // TODO: a random factor - fix this
const radiance = lightSetup.getReflection(viewReflectVec, specularMIPLevel);
const specularBRDFContrib = `${BRDF_Specular_GGX_Environment}(viewNormal, viewEyeDir, specularColor, specularRoughness)`;
src.push(`reflSpec += ${radiance} * ${specularBRDFContrib};`);
}
lightSetup.directionalLights.forEach((light, i) => {
src.push(`vec3 lightDirection${i} = -${light.getDirection(geometry.viewMatrix, vViewPosition)};`); // This "-" might be wrong, but it used to be like that
const dotNL = `${saturate}(dot(viewNormal, lightDirection${i}))`;
src.push(`vec3 irradiance${i} = ${dotNL} * ${light.getColor()};`);
src.push(`reflDiff += irradiance${i};`);
src.push(`reflSpec += irradiance${i} * PI * ${BRDF_Specular_GGX}(lightDirection${i}, viewNormal, viewEyeDir, specularColor, specularRoughness);`);
});
src.push(`vec3 emissiveColor = ${emissiveMap(vUV)}.rgb;`); // TODO: correct gamma function
src.push(`float aoFactor = ${aOMap(vUV)}.r;`);
const ambient = `${lightSetup.getAmbientColor()} * baseColor * opacity * rgb`;
src.push(`vec3 outgoingLight = emissiveColor + reflDiff * diffuseColor + reflSpec + ${ambient};`);
src.push(`${outColor} = vec4(outgoingLight * aoFactor${sao ? ` * ${sao.getAmbient(gl_FragCoord)}` : ""}, opacity);`);
getGammaOutputExpression && src.push(`${outColor} = ${getGammaOutputExpression(outColor)};`);
}
};
};