src/viewer/scene/mesh/draw/DrawShaderSource.js
import {createLightSetup, setupTexture} from "../../webgl/WebGLRenderer.js";
import {math} from "../../math/math.js";
const tempVec4 = math.vec4();
export const DrawShaderSource = function(meshDrawHash, programVariables, geometry, material, scene) {
const materialState = material._state;
const phongMaterial = (materialState.type === "PhongMaterial");
const metallicMaterial = (materialState.type === "MetallicMaterial");
const specularMaterial = (materialState.type === "SpecularMaterial");
const attributes = geometry.attributes;
const lightSetup = createLightSetup(programVariables, scene._lightsState);
const colorize = programVariables.createUniform("vec4", "colorize", (set, state) => set(state.meshColorize));
const fresnel = programVariables.createFragmentDefinition(
"fresnel",
(name, src) => {
src.push(`float ${name}(vec3 eyeDir, vec3 normal, float edgeBias, float centerBias, float power) {`);
src.push(" float fr = abs(dot(eyeDir, normal));");
src.push(" float finalFr = clamp((fr - edgeBias) / (centerBias - edgeBias), 0.0, 1.0);");
src.push(" return pow(finalFr, power);");
src.push("}");
});
const setupFresnel = (name, colorSwizzle, getMaterialValue) => {
const fresnelUniform = (type, namePostfix, getParameterValue) => {
return programVariables.createUniform(type, name + "Fresnel" + namePostfix, (set, state) => set(getParameterValue(getMaterialValue(state.material))));
};
const edgeBias = fresnelUniform("float", "EdgeBias", (fresnelValue => fresnelValue.edgeBias));
const centerBias = fresnelUniform("float", "CenterBias", (fresnelValue => fresnelValue.centerBias));
const power = fresnelUniform("float", "Power", (fresnelValue => fresnelValue.power));
const edgeColor = fresnelUniform("vec3", "EdgeColor", (fresnelValue => fresnelValue.edgeColor));
const centerColor = fresnelUniform("vec3", "CenterColor", (fresnelValue => fresnelValue.centerColor));
return getMaterialValue(material) && ((viewEyeDir, viewNormal) => {
const f = `${fresnel}(${viewEyeDir}, ${viewNormal}, ${edgeBias}, ${centerBias}, ${power})`;
return `mix(${edgeColor + colorSwizzle}, ${centerColor + colorSwizzle}, ${f})`;
});
};
const diffuseFresnel = setupFresnel("diffuse", "", mtl => mtl._diffuseFresnel);
const specularFresnel = setupFresnel("specular", "", mtl => mtl._specularFresnel);
const emissiveFresnel = setupFresnel("emissive", "", mtl => mtl._emissiveFresnel);
const alphaFresnel = setupFresnel("alpha", ".r", mtl => mtl._alphaFresnel);
const setup2dTexture = (name, getMaterialValue) => {
const initTex = attributes.uv && getMaterialValue(material);
const tex = initTex && setupTexture(programVariables, "sampler2D", name, initTex.encoding, (set, state) => set(getMaterialValue(state.material)._state.texture));
return tex && (function() {
const matrix = initTex._state.matrix && programVariables.createUniform("mat4", name + "MapMatrix", (set, state) => set(getMaterialValue(state.material)._state.matrix));
const getTexCoordExpression = matrix ? (texPos => `(${matrix} * ${texPos}).st`) : (texPos => `${texPos}.st`);
const sample = (texturePos, bias) => tex(getTexCoordExpression(texturePos, bias));
sample.getTexCoordExpression = getTexCoordExpression;
return sample;
})();
};
const ambientMap = setup2dTexture("ambient", mtl => mtl._ambientMap);
const baseColorMap = setup2dTexture("baseColor", mtl => mtl._baseColorMap);
const diffuseMap = setup2dTexture("diffuse", mtl => mtl._diffuseMap);
const emissiveMap = setup2dTexture("emissive", mtl => mtl._emissiveMap);
const occlusionMap = setup2dTexture("occlusion", mtl => mtl._occlusionMap);
const alphaMap = setup2dTexture("alpha", mtl => mtl._alphaMap);
const metallicMap = setup2dTexture("metallic", mtl => mtl._metallicMap);
const roughnessMap = setup2dTexture("roughness", mtl => mtl._roughnessMap);
const metallicRoughnessMap = setup2dTexture("metallicRoughness", mtl => mtl._metallicRoughnessMap);
const specularMap = setup2dTexture("specular", mtl => mtl._specularMap);
const glossinessMap = setup2dTexture("glossiness", mtl => mtl._glossinessMap);
const specularGlossinessMap = setup2dTexture("specularGlossiness", mtl => mtl._specularGlossinessMap);
const normalMap = setup2dTexture("normal", mtl => mtl._normalMap);
const setupUniform = (name, type, getMaterialValue) => {
const initValue = getMaterialValue(material);
const isDefined = (type === "float") ? ((initValue !== undefined) && (initValue !== null)) : initValue;
return isDefined && programVariables.createUniform(type, name, (set, state) => set(getMaterialValue(state.material)));
};
const materialAmbient = setupUniform("materialAmbient", "vec3", mtl => mtl.ambient);
const materialDiffuse = setupUniform("materialDiffuse", "vec3", mtl => mtl.diffuse);
const materialBaseColor = setupUniform("materialBaseColor", "vec3", mtl => mtl.baseColor);
const materialEmissive = setupUniform("materialEmissive", "vec3", mtl => mtl.emissive);
const materialSpecular = setupUniform("materialSpecular", "vec3", mtl => mtl.specular);
const materialGlossiness = setupUniform("materialGlossiness", "float", mtl => mtl.glossiness);
const materialMetallic = setupUniform("materialMetallic", "float", mtl => mtl.metallic);
const materialRoughness = setupUniform("materialRoughness", "float", mtl => mtl.roughness);
const materialShininess = setupUniform("materialShininess", "float", mtl => mtl.shininess);
const materialSpecularF0 = setupUniform("materialSpecularF0", "float", mtl => mtl.specularF0);
const materialAlphaModeCutoff = setupUniform("materialAlphaModeCutoff", "vec4", mtl => {
const alpha = mtl.alpha;
if ((alpha !== undefined) && (alpha !== null)) {
tempVec4[0] = alpha;
tempVec4[1] = (mtl.alphaMode === 1 ? 1 : 0);
tempVec4[2] = mtl.alphaCutoff;
return tempVec4;
} else {
return null;
}
});
const vViewPosition = programVariables.createVarying("vec3", "vViewPosition", () => `${attributes.position.view}.xyz`);
const texturePos = programVariables.createVarying("vec4", "texturePos", () => `vec4(${attributes.uv}, 1.0, 1.0)`);
const vColor = attributes.color && programVariables.createVarying("vec4", "vColor", () => attributes.color);
const vViewNormal = programVariables.createVarying("vec3", "vViewNormal", () => attributes.normal.view);
const texUnitConverter = `mat4(0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.5, 1.0)`;
const vDirectionals = lightSetup.directionalLights.map((light, i) => ({
vViewLightReverseDir: programVariables.createVarying("vec3", `vViewLightReverseDir${i}`, () => light.getDirection(geometry.viewMatrix, null)),
vShadowPosFromLight: programVariables.createVarying("vec3", `vShadowPosFromLight${i}`, () => `(${texUnitConverter} * ${light.shadowParameters.getShadowProjMatrix()} * (${light.shadowParameters.getShadowViewMatrix()} * ${attributes.position.world})).xyz`)
}));
const vWorldNormal = programVariables.createVarying("vec3", "vWorldNormal", () => attributes.normal.world);
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(" 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 {
getHash: () => [
meshDrawHash,
scene.gammaOutput ? "go" : "",
lightSetup.getHash(),
material._state.hash
],
programName: "Draw",
canActAsBackground: true,
discardPoints: true,
setupPointSize: true,
setsLineWidth: true,
appendFragmentOutputs: (src, getGammaOutputExpression, gl_FragCoord) => {
const hasNonAmbientLighting = attributes.normal && ((lightSetup.directionalLights.length > 0) || lightSetup.getIrradiance || lightSetup.getReflection);
src.push(`vec3 diffuseColor = ${(hasNonAmbientLighting && (phongMaterial || specularMaterial) && materialDiffuse) || (metallicMaterial && materialBaseColor) || "vec3(1.0)"};`);
src.push(`vec4 alphaModeCutoff = ${((phongMaterial || metallicMaterial || specularMaterial) && materialAlphaModeCutoff) || "vec4(1.0, 0.0, 0.0, 0.0)"};`);
src.push("float alpha = alphaModeCutoff[0];");
(phongMaterial || metallicMaterial || specularMaterial) && alphaMap && src.push(`alpha *= ${alphaMap(texturePos)}.r;`);
if (vColor) {
src.push(`diffuseColor *= ${vColor}.rgb;`);
src.push(`alpha *= ${vColor}.a;`);
}
if (metallicMaterial && baseColorMap) {
src.push("vec4 baseColorTexel = " + baseColorMap(texturePos) + ";");
src.push("diffuseColor *= baseColorTexel.rgb;");
src.push("alpha *= baseColorTexel.a;");
}
if ((phongMaterial || specularMaterial) && diffuseMap) {
src.push("vec4 diffuseTexel = " + diffuseMap(texturePos) + ";");
src.push("diffuseColor *= diffuseTexel.rgb;");
src.push("alpha *= diffuseTexel.a;");
}
src.push(`vec3 emissiveColor = ${((phongMaterial || metallicMaterial || specularMaterial) && materialEmissive) || "vec3(0.0)"};`);
(phongMaterial || metallicMaterial || specularMaterial) && emissiveMap && src.push(`emissiveColor = ${emissiveMap(texturePos)}.rgb;`);
if (hasNonAmbientLighting) {
src.push(`vec3 specular = ${((phongMaterial || specularMaterial) && materialSpecular) || "vec3(1.0)"};`);
src.push(`float glossiness = ${(specularMaterial && materialGlossiness) || "1.0"};`);
src.push(`float metallic = ${(metallicMaterial && materialMetallic) || "1.0"};`);
src.push(`float roughness = ${((metallicMaterial || specularMaterial) && materialRoughness) || "1.0"};`);
src.push(`float shininess = ${(phongMaterial && materialShininess)|| "1.0"};`);
src.push(`float specularF0 = ${(metallicMaterial && materialSpecularF0) || "1.0"};`);
src.push(`float occlusion = ${((phongMaterial || metallicMaterial || specularMaterial) && occlusionMap) ? `${occlusionMap(texturePos)}.r` : "1.0"};`);
//--------------------------------------------------------------------------------
// SHADING
//--------------------------------------------------------------------------------
metallicMaterial && metallicMap && src.push(`metallic *= ${metallicMap(texturePos)}.r;`);
metallicMaterial && roughnessMap && src.push(`roughness *= ${roughnessMap(texturePos)}.r;`);
if (metallicMaterial && metallicRoughnessMap) {
src.push("vec4 metalRoughTexel = " + metallicRoughnessMap(texturePos) + ";");
src.push("metallic *= metalRoughTexel.b;");
src.push("roughness *= metalRoughTexel.g;");
}
(phongMaterial || specularMaterial) && specularMap && src.push(`specular *= ${specularMap(texturePos)}.rgb;`);
specularMaterial && glossinessMap && src.push(`glossiness *= ${glossinessMap(texturePos)}.r;`);
if (specularMaterial && specularGlossinessMap) {
src.push("vec4 specGlossTexel = " + specularGlossinessMap(texturePos) + ";"); // TODO: what if only RGB texture?
src.push("specular *= specGlossTexel.rgb;");
src.push("glossiness *= specGlossTexel.a;");
}
const vViewNormalized = `normalize(${vViewNormal})`;
const viewNormal = (((phongMaterial || metallicMaterial || specularMaterial) && normalMap)
? `${perturbNormal2Arb}(${vViewPosition}, ${vViewNormalized}, ${normalMap.getTexCoordExpression(texturePos)}, ${normalMap(texturePos)})`
: vViewNormalized);
src.push(`vec3 viewNormal = ${viewNormal};`);
src.push(`vec3 viewEyeDir = normalize(-${vViewPosition});`);
diffuseFresnel && src.push(`diffuseColor *= ${diffuseFresnel ("viewEyeDir", "viewNormal")};`);
specularFresnel && src.push(`specular *= ${specularFresnel("viewEyeDir", "viewNormal")};`);
emissiveFresnel && src.push(`emissiveColor *= ${emissiveFresnel("viewEyeDir", "viewNormal")};`);
alphaFresnel && src.push(`alpha *= ${alphaFresnel ("viewEyeDir", "viewNormal")};`);
src.push("if (alphaModeCutoff[1] == 1.0 && alpha < alphaModeCutoff[2]) {"); // ie. (alphaMode == "mask" && alpha < alphaCutoff)
src.push(" discard;"); // TODO: Discard earlier within this shader?
src.push("}");
src.push("vec3 material_diffuseColor;");
if (phongMaterial) {
src.push("material_diffuseColor = diffuseColor;");
src.push("roughness = 0.0;");
}
if (specularMaterial) {
src.push("float oneMinusSpecularStrength = 1.0 - max(max(specular.r, specular.g ),specular.b);"); // Energy conservation
src.push("material_diffuseColor = diffuseColor * oneMinusSpecularStrength;");
src.push("roughness = clamp(1.0 - glossiness, 0.04, 1.0);");
}
if (metallicMaterial) {
src.push("float dielectricSpecular = 0.16 * specularF0 * specularF0;");
src.push("material_diffuseColor = diffuseColor * (1.0 - dielectricSpecular) * (1.0 - metallic);");
src.push("roughness = clamp(roughness, 0.04, 1.0);");
src.push("specular = mix(vec3(dielectricSpecular), diffuseColor, metallic);");
}
// ENVIRONMENT AND REFLECTION MAP SHADING
if (phongMaterial || metallicMaterial || specularMaterial) {
src.push("const float PI = 3.14159265359;");
src.push("vec3 reflDiff = vec3(0.0);");
src.push("vec3 reflSpec = vec3(0.0);");
lightSetup.getIrradiance && src.push(`reflDiff += ${lightSetup.getIrradiance(`normalize(${vWorldNormal})`)};`);
if (lightSetup.getReflection) {
const reflectVec = `reflect(-viewEyeDir, viewNormal)`;
const spec = (phongMaterial
? `0.2 * PI * ${lightSetup.getReflection(reflectVec)}`
: (function() {
const blinnExpFromRoughness = `2.0 / pow(roughness + 0.0001, 2.0) - 2.0`;
const specularBRDFContrib = `${BRDF_Specular_GGX_Environment}(viewNormal, viewEyeDir, specular, roughness)`;
const viewReflectVec = `normalize((vec4(${reflectVec}, 0.0) * ${geometry.viewMatrix}).xyz)`;
const maxMIPLevelScalar = "4.0";
const desiredMIPLevel = `${maxMIPLevelScalar} - 0.39624 - 0.25 * log2(pow(${blinnExpFromRoughness}, 2.0) + 1.0)`;
const mipLevel = `clamp(${desiredMIPLevel}, 0.0, ${maxMIPLevelScalar})`; //TODO: a random factor - fix this
return `${specularBRDFContrib} * ${lightSetup.getReflection(viewReflectVec, mipLevel)}`;
})());
src.push(`reflSpec += ${spec};`);
}
if (lightSetup.directionalLights.some(ligth => ligth.shadowParameters)) {
src.push("const vec4 bitShift = vec4(1.0, 1.0/256.0, 1.0/(256.0 * 256.0), 1.0/(256.0*256.0*256.0));");
src.push("const float texelSize = 1.0 / 1024.0;");
src.push("float shadow;");
src.push("float fragmentDepth;");
}
lightSetup.directionalLights.forEach((light, i) => {
if (light.shadowParameters) {
const shadowAcneRemover = "0.007";
src.push(`fragmentDepth = ${vDirectionals[i].vShadowPosFromLight}.z - ${shadowAcneRemover};`);
src.push("shadow = 0.0;");
src.push("for (int x = -3; x <= 3; x++) {");
src.push(" for (int y = -3; y <= 3; y++) {");
src.push(` float texelDepth = dot(texture(${light.getShadowMap()}, ${vDirectionals[i].vShadowPosFromLight}.xy + vec2(x, y) * texelSize), bitShift);`);
src.push(` if (fragmentDepth < texelDepth) {`);
src.push(" shadow += 1.0;");
src.push(" }");
src.push(" }");
src.push("}");
src.push("shadow /= 9.0;");
}
src.push(`vec3 lightColor${i} = ${light.getColor()}${light.shadowParameters ? (" * " + "shadow") : ""};`);
src.push(`vec3 lightDirection${i} = ${light.isViewSpace ? light.getDirection(null, vViewPosition) : vDirectionals[i].vViewLightReverseDir};`);
const dotNL = `${saturate}(dot(viewNormal, lightDirection${i}))`;
src.push(`vec3 irradiance${i} = ${dotNL} * lightColor${i};`);
src.push(`reflDiff += irradiance${i};`);
const spec = (phongMaterial
? `lightColor${i} * specular * pow(max(dot(reflect(-lightDirection${i}, -viewNormal), viewEyeDir), 0.0), shininess)`
: `irradiance${i} * PI * ${BRDF_Specular_GGX}(lightDirection${i}, viewNormal, viewEyeDir, specular, roughness)`);
src.push(`reflSpec += ${spec};`);
});
}
const ambient = phongMaterial && `${lightSetup.getAmbientColor()} * diffuseColor`;
src.push("vec3 outgoingLight = emissiveColor + occlusion * (reflDiff * material_diffuseColor + reflSpec)" + (ambient ? (" + " + ambient) : "") + ";");
} else {
src.push(`vec3 ambientColor = ${(phongMaterial && materialAmbient) || "vec3(1.0)"};`);
phongMaterial && ambientMap && src.push(`ambientColor *= ${ambientMap(texturePos)}.rgb;`);
src.push(`ambientColor *= ${lightSetup.getAmbientColor()};`);
src.push("vec3 outgoingLight = emissiveColor + ambientColor;");
}
src.push(`vec4 fragColor = ${colorize} * vec4(outgoingLight, alpha);`);
src.push(`${outColor} = ${getGammaOutputExpression ? getGammaOutputExpression("fragColor") : "fragColor"};`);
}
};
};