UNPKG

@animech-public/playcanvas

Version:
1,225 lines (1,224 loc) 50.5 kB
import { SEMANTIC_POSITION, SEMANTIC_ATTR12, SEMANTIC_ATTR13, SEMANTIC_ATTR14, SEMANTIC_ATTR15, SEMANTIC_NORMAL, SEMANTIC_TANGENT, SEMANTIC_COLOR, SEMANTIC_ATTR8, SEMANTIC_ATTR9, SEMANTIC_ATTR10, SEMANTIC_ATTR11, SEMANTIC_BLENDWEIGHT, SEMANTIC_BLENDINDICES, PIXELFORMAT_RGBA8, SHADERTAG_MATERIAL, SEMANTIC_TEXCOORD0, SEMANTIC_TEXCOORD1 } from '../../../platform/graphics/constants.js'; import { shaderChunks } from '../chunks/chunks.js'; import { ChunkUtils } from '../chunk-utils.js'; import { SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED, LIGHTSHAPE_SPHERE, LIGHTSHAPE_DISK, LIGHTSHAPE_RECT, SHADER_DEPTH, SHADER_PREPASS_VELOCITY, LIGHTTYPE_DIRECTIONAL, SHADOW_VSM32, LIGHTTYPE_OMNI, SHADOW_PCSS, SHADOW_VSM8, LIGHTSHAPE_PUNCTUAL, LIGHTTYPE_SPOT, FRESNEL_SCHLICK, SPECOCC_GLOSSDEPENDENT, SPECOCC_AO, SHADOW_PCF3, SHADOW_PCF5, SHADOW_PCF1, SHADOW_VSM16, SPECULAR_PHONG, shadowTypeToString, LIGHTFALLOFF_LINEAR, BLEND_NORMAL, BLEND_PREMULTIPLIED, BLEND_ADDITIVEALPHA, SHADER_PICK } from '../../constants.js'; import { LightsBuffer } from '../../lighting/lights-buffer.js'; import { ShaderPass } from '../../shader-pass.js'; import { ShaderUtils } from '../../../platform/graphics/shader-utils.js'; import { ChunkBuilder } from '../chunk-builder.js'; import { ShaderGenerator } from './shader-generator.js'; const builtinAttributes = { vertex_normal: SEMANTIC_NORMAL, vertex_tangent: SEMANTIC_TANGENT, vertex_texCoord0: SEMANTIC_TEXCOORD0, vertex_texCoord1: SEMANTIC_TEXCOORD1, vertex_color: SEMANTIC_COLOR, vertex_boneWeights: SEMANTIC_BLENDWEIGHT, vertex_boneIndices: SEMANTIC_BLENDINDICES }; const builtinVaryings = { vVertexColor: 'vec4', vPositionW: 'vec3', vNormalV: 'vec3', vNormalW: 'vec3', vTangentW: 'vec3', vBinormalW: 'vec3', vObjectSpaceUpW: 'vec3', vUv0: 'vec2', vUv1: 'vec2' }; class LitShader { constructor(device, options) { this.device = device; this.options = options; this.attributes = { vertex_position: SEMANTIC_POSITION }; if (options.userAttributes) { for (const [semantic, name] of Object.entries(options.userAttributes)) { this.attributes[name] = semantic; } } if (options.chunks) { const userChunks = options.chunks; this.chunks = Object.create(shaderChunks); for (const chunkName in shaderChunks) { if (userChunks.hasOwnProperty(chunkName)) { const chunk = userChunks[chunkName]; for (const a in builtinAttributes) { if (builtinAttributes.hasOwnProperty(a) && chunk.indexOf(a) >= 0) { this.attributes[a] = builtinAttributes[a]; } } this.chunks[chunkName] = chunk; } } } else { this.chunks = shaderChunks; } this.shaderPassInfo = ShaderPass.get(this.device).getByIndex(options.pass); this.shadowPass = this.shaderPassInfo.isShadow; this.lighting = options.lights.length > 0 || options.dirLightMapEnabled || options.clusteredLightingEnabled; this.reflections = !!options.reflectionSource; this.needsNormal = this.lighting || this.reflections || options.useSpecular || options.ambientSH || options.useHeights || options.enableGGXSpecular || options.clusteredLightingEnabled && !this.shadowPass || options.useClearCoatNormals; this.needsNormal = this.needsNormal && !this.shadowPass; this.needsSceneColor = options.useDynamicRefraction; this.needsScreenSize = options.useDynamicRefraction; this.needsTransforms = options.useDynamicRefraction; this.varyings = ''; this.varyingDefines = ''; this.vshader = null; this.frontendDecl = null; this.frontendCode = null; this.frontendFunc = null; this.lightingUv = null; this.defines = []; this.fshader = null; } _vsAddBaseCode(code, chunks, options) { code += chunks.baseVS; if (options.nineSlicedMode === SPRITE_RENDERMODE_SLICED || options.nineSlicedMode === SPRITE_RENDERMODE_TILED) { code += chunks.baseNineSlicedVS; } return code; } _vsAddTransformCode(code, device, chunks, options) { code += this.chunks.transformVS; return code; } _setMapTransform(codes, name, id, uv) { const checkId = id + uv * 100; if (!codes[3][checkId]) { const varName = `texture_${name}MapTransform`; codes[0] += `uniform vec3 ${varName}0;\n`; codes[0] += `uniform vec3 ${varName}1;\n`; codes[1] += `varying vec2 vUV${uv}_${id};\n`; codes[2] += ` vUV${uv}_${id} = vec2(dot(vec3(uv${uv}, 1), ${varName}0), dot(vec3(uv${uv}, 1), ${varName}1));\n`; codes[3][checkId] = true; } return codes; } _fsGetBaseCode() { const options = this.options; const chunks = this.chunks; let result = this.chunks.basePS; if (options.nineSlicedMode === SPRITE_RENDERMODE_SLICED) { result += chunks.baseNineSlicedPS; } else if (options.nineSlicedMode === SPRITE_RENDERMODE_TILED) { result += chunks.baseNineSlicedTiledPS; } return result; } _fsGetStartCode(code, device, chunks, options) { let result = chunks.startPS; if (options.nineSlicedMode === SPRITE_RENDERMODE_SLICED) { result += chunks.startNineSlicedPS; } else if (options.nineSlicedMode === SPRITE_RENDERMODE_TILED) { result += chunks.startNineSlicedTiledPS; } return result; } _getLightSourceShapeString(shape) { switch (shape) { case LIGHTSHAPE_RECT: return 'Rect'; case LIGHTSHAPE_DISK: return 'Disk'; case LIGHTSHAPE_SPHERE: return 'Sphere'; default: return ''; } } generateVertexShader(useUv, useUnmodifiedUv, mapTransforms) { const device = this.device; const options = this.options; const chunks = this.chunks; let code = ''; let codeBody = ''; code = this._vsAddBaseCode(code, chunks, options); codeBody += ' vPositionW = getWorldPosition();\n'; if (this.options.pass === SHADER_DEPTH || this.options.pass === SHADER_PREPASS_VELOCITY) { code += 'varying float vDepth;\n'; code += '#ifndef VIEWMATRIX\n'; code += '#define VIEWMATRIX\n'; code += 'uniform mat4 matrix_view;\n'; code += '#endif\n'; code += '#ifndef CAMERAPLANES\n'; code += '#define CAMERAPLANES\n'; code += 'uniform vec4 camera_params;\n\n'; code += '#endif\n'; codeBody += ' vDepth = -(matrix_view * vec4(vPositionW,1.0)).z * camera_params.x;\n'; } if (this.options.pass === SHADER_PREPASS_VELOCITY) ; if (this.options.useInstancing) { this.attributes.instance_line1 = SEMANTIC_ATTR12; this.attributes.instance_line2 = SEMANTIC_ATTR13; this.attributes.instance_line3 = SEMANTIC_ATTR14; this.attributes.instance_line4 = SEMANTIC_ATTR15; code += chunks.instancingVS; } if (this.needsNormal) { this.attributes.vertex_normal = SEMANTIC_NORMAL; codeBody += ' vNormalW = getNormal();\n'; if (options.reflectionSource === 'sphereMap' && device.fragmentUniformsCount <= 16) { code += chunks.viewNormalVS; codeBody += ' vNormalV = getViewNormal();\n'; } if (options.hasTangents && (options.useHeights || options.useNormals || options.enableGGXSpecular)) { this.attributes.vertex_tangent = SEMANTIC_TANGENT; code += chunks.tangentBinormalVS; codeBody += ' vTangentW = getTangent();\n'; codeBody += ' vBinormalW = getBinormal();\n'; } else if (options.enableGGXSpecular || !device.extStandardDerivatives) { codeBody += ' vObjectSpaceUpW = normalize(dNormalMatrix * vec3(0, 1, 0));\n'; } } const maxUvSets = 2; for (let i = 0; i < maxUvSets; i++) { if (useUv[i]) { this.attributes[`vertex_texCoord${i}`] = `TEXCOORD${i}`; code += chunks[`uv${i}VS`]; codeBody += ` vec2 uv${i} = getUv${i}();\n`; } if (useUnmodifiedUv[i]) { codeBody += ` vUv${i} = uv${i};\n`; } } const codes = [code, this.varyings, codeBody, []]; mapTransforms.forEach(mapTransform => { this._setMapTransform(codes, mapTransform.name, mapTransform.id, mapTransform.uv); }); code = codes[0]; this.varyings = codes[1]; codeBody = codes[2]; if (options.vertexColors) { this.attributes.vertex_color = SEMANTIC_COLOR; codeBody += ' vVertexColor = vertex_color;\n'; } if (options.useMsdf && options.msdfTextAttribute) { this.attributes.vertex_outlineParameters = SEMANTIC_ATTR8; this.attributes.vertex_shadowParameters = SEMANTIC_ATTR9; codeBody += ' unpackMsdfParams();\n'; code += chunks.msdfVS; } if (options.useMorphPosition || options.useMorphNormal) { if (options.useMorphTextureBased) { code += '#define MORPHING_TEXTURE_BASED\n'; if (options.useMorphPosition) { code += '#define MORPHING_TEXTURE_BASED_POSITION\n'; } if (options.useMorphNormal) { code += '#define MORPHING_TEXTURE_BASED_NORMAL\n'; } this.attributes.morph_vertex_id = SEMANTIC_ATTR15; const morphIdType = device.isWebGPU ? 'uint' : 'float'; code += `attribute ${morphIdType} morph_vertex_id;\n`; } else { code += '#define MORPHING\n'; if (options.useMorphPosition) { this.attributes.morph_pos0 = SEMANTIC_ATTR8; this.attributes.morph_pos1 = SEMANTIC_ATTR9; this.attributes.morph_pos2 = SEMANTIC_ATTR10; this.attributes.morph_pos3 = SEMANTIC_ATTR11; code += '#define MORPHING_POS03\n'; code += 'attribute vec3 morph_pos0;\n'; code += 'attribute vec3 morph_pos1;\n'; code += 'attribute vec3 morph_pos2;\n'; code += 'attribute vec3 morph_pos3;\n'; } else if (options.useMorphNormal) { this.attributes.morph_nrm0 = SEMANTIC_ATTR8; this.attributes.morph_nrm1 = SEMANTIC_ATTR9; this.attributes.morph_nrm2 = SEMANTIC_ATTR10; this.attributes.morph_nrm3 = SEMANTIC_ATTR11; code += '#define MORPHING_NRM03\n'; code += 'attribute vec3 morph_nrm0;\n'; code += 'attribute vec3 morph_nrm1;\n'; code += 'attribute vec3 morph_nrm2;\n'; code += 'attribute vec3 morph_nrm3;\n'; } if (!options.useMorphNormal) { this.attributes.morph_pos4 = SEMANTIC_ATTR12; this.attributes.morph_pos5 = SEMANTIC_ATTR13; this.attributes.morph_pos6 = SEMANTIC_ATTR14; this.attributes.morph_pos7 = SEMANTIC_ATTR15; code += '#define MORPHING_POS47\n'; code += 'attribute vec3 morph_pos4;\n'; code += 'attribute vec3 morph_pos5;\n'; code += 'attribute vec3 morph_pos6;\n'; code += 'attribute vec3 morph_pos7;\n'; } else { this.attributes.morph_nrm4 = SEMANTIC_ATTR12; this.attributes.morph_nrm5 = SEMANTIC_ATTR13; this.attributes.morph_nrm6 = SEMANTIC_ATTR14; this.attributes.morph_nrm7 = SEMANTIC_ATTR15; code += '#define MORPHING_NRM47\n'; code += 'attribute vec3 morph_nrm4;\n'; code += 'attribute vec3 morph_nrm5;\n'; code += 'attribute vec3 morph_nrm6;\n'; code += 'attribute vec3 morph_nrm7;\n'; } } } if (options.skin) { this.attributes.vertex_boneWeights = SEMANTIC_BLENDWEIGHT; this.attributes.vertex_boneIndices = SEMANTIC_BLENDINDICES; code += ShaderGenerator.skinCode(device, chunks); code += '#define SKIN\n'; } else if (options.useInstancing) { code += '#define INSTANCING\n'; } if (options.screenSpace) { code += '#define SCREENSPACE\n'; } if (options.pixelSnap) { code += '#define PIXELSNAP\n'; } code = this._vsAddTransformCode(code, device, chunks, options); if (this.needsNormal) { code += chunks.normalVS; } code += '\n'; code += chunks.startVS; code += codeBody; code += chunks.endVS; code += '}'; Object.keys(builtinVaryings).forEach(v => { if (code.indexOf(v) >= 0) { this.varyings += `varying ${builtinVaryings[v]} ${v};\n`; this.varyingDefines += `#define VARYING_${v.toUpperCase()}\n`; } }); const shaderPassDefines = this.shaderPassInfo.shaderDefines; this.vshader = shaderPassDefines + this.varyings + code; } _fsGetBeginCode() { let code = this.shaderPassInfo.shaderDefines; for (let i = 0; i < this.defines.length; i++) { code += `#define ${this.defines[i]}\n`; } return code; } _fsGetPickPassCode() { let code = this._fsGetBeginCode(); code += 'uniform vec4 uColor;\n'; code += this.varyings; code += this.varyingDefines; code += this.frontendDecl; code += this.frontendCode; code += ShaderGenerator.begin(); code += this.frontendFunc; code += ' gl_FragColor = uColor;\n'; code += ShaderGenerator.end(); return code; } _fsGetDepthPassCode() { const chunks = this.chunks; let code = this._fsGetBeginCode(); code += 'varying float vDepth;\n'; code += this.varyings; code += this.varyingDefines; code += chunks.packDepthPS; code += this.frontendDecl; code += this.frontendCode; code += ShaderGenerator.begin(); code += this.frontendFunc; code += ' gl_FragColor = packFloat(vDepth);\n'; code += ShaderGenerator.end(); return code; } _fsGetPrePassVelocityCode() { return this._fsGetDepthPassCode(); } _fsGetShadowPassCode() { const device = this.device; const options = this.options; const chunks = this.chunks; const varyings = this.varyings; const lightType = this.shaderPassInfo.lightType; let shadowType = this.shaderPassInfo.shadowType; if (lightType !== LIGHTTYPE_DIRECTIONAL && options.clusteredLightingEnabled) { if (shadowType === SHADOW_VSM8 || shadowType === SHADOW_VSM16 || shadowType === SHADOW_VSM32 || shadowType === SHADOW_PCSS) { shadowType = SHADOW_PCF3; } } let code = this._fsGetBeginCode(); if (device.extStandardDerivatives && device.isWebGL1) { code += 'uniform vec2 polygonOffset;\n'; } if (shadowType === SHADOW_VSM32) { if (device.textureFloatHighPrecision) { code += '#define VSM_EXPONENT 15.0\n\n'; } else { code += '#define VSM_EXPONENT 5.54\n\n'; } } else if (shadowType === SHADOW_VSM16) { code += '#define VSM_EXPONENT 5.54\n\n'; } if (lightType !== LIGHTTYPE_DIRECTIONAL) { code += 'uniform vec3 view_position;\n'; code += 'uniform float light_radius;\n'; } code += varyings; code += this.varyingDefines; code += this.frontendDecl; code += this.frontendCode; const mayPackDepth = shadowType === SHADOW_PCF1 || shadowType === SHADOW_PCF3 || shadowType === SHADOW_PCF5 || shadowType === SHADOW_PCSS; const mustPackDepth = lightType === LIGHTTYPE_OMNI && shadowType !== SHADOW_PCSS && !options.clusteredLightingEnabled; const usePackedDepth = mayPackDepth && !device.supportsDepthShadow || mustPackDepth; if (usePackedDepth) { code += chunks.packDepthPS; } else if (shadowType === SHADOW_VSM8) { code += 'vec2 encodeFloatRG( float v ) {\n'; code += ' vec2 enc = vec2(1.0, 255.0) * v;\n'; code += ' enc = fract(enc);\n'; code += ' enc -= enc.yy * vec2(1.0/255.0, 1.0/255.0);\n'; code += ' return enc;\n'; code += '}\n\n'; } if (shadowType === SHADOW_PCSS) { code += shaderChunks.linearizeDepthPS; } code += ShaderGenerator.begin(); code += this.frontendFunc; const isVsm = shadowType === SHADOW_VSM8 || shadowType === SHADOW_VSM16 || shadowType === SHADOW_VSM32; const applySlopeScaleBias = device.isWebGL1 && device.extStandardDerivatives; const usePerspectiveDepth = lightType === LIGHTTYPE_DIRECTIONAL || !isVsm && lightType === LIGHTTYPE_SPOT; let hasModifiedDepth = false; if (usePerspectiveDepth) { code += ' float depth = gl_FragCoord.z;\n'; } else { code += ' float depth = min(distance(view_position, vPositionW) / light_radius, 0.99999);\n'; hasModifiedDepth = true; } if (applySlopeScaleBias) { code += ' float minValue = 2.3374370500153186e-10; //(1.0 / 255.0) / (256.0 * 256.0 * 256.0);\n'; code += ' depth += polygonOffset.x * max(abs(dFdx(depth)), abs(dFdy(depth))) + minValue * polygonOffset.y;\n'; hasModifiedDepth = true; } if (usePackedDepth) { code += ' gl_FragColor = packFloat(depth);\n'; } else if (!isVsm) { const exportR32 = shadowType === SHADOW_PCSS; if (exportR32) { code += ' gl_FragColor.r = depth;\n'; } else { if (hasModifiedDepth) { code += ' gl_FragDepth = depth;\n'; } code += ' gl_FragColor = vec4(1.0);\n'; } } else if (shadowType === SHADOW_VSM8) { code += ' gl_FragColor = vec4(encodeFloatRG(depth), encodeFloatRG(depth*depth));\n'; } else { code += chunks.storeEVSMPS; } code += ShaderGenerator.end(); return code; } _fsGetLitPassCode() { const device = this.device; const options = this.options; const chunks = this.chunks; const decl = new ChunkBuilder(); const func = new ChunkBuilder(); const backend = new ChunkBuilder(); const code = new ChunkBuilder(); if (options.opacityFadesSpecular === false) { decl.append('uniform float material_alphaFade;'); } if (options.useSpecular) { this.defines.push('LIT_SPECULAR'); if (this.reflections) { this.defines.push('LIT_REFLECTIONS'); } if (options.useClearCoat) { this.defines.push('LIT_CLEARCOAT'); } if (options.fresnelModel > 0) { this.defines.push('LIT_SPECULAR_FRESNEL'); } if (options.conserveEnergy) { this.defines.push('LIT_CONSERVE_ENERGY'); } if (options.useSheen) { this.defines.push('LIT_SHEEN'); } if (options.useIridescence) { this.defines.push('LIT_IRIDESCENCE'); } } const shadowTypeUsed = []; let numShadowLights = 0; let shadowedDirectionalLightUsed = false; let useVsm = false; let usePcss = false; let hasAreaLights = options.lights.some(light => { return light._shape && light._shape !== LIGHTSHAPE_PUNCTUAL; }); if (options.clusteredLightingEnabled && options.clusteredLightingAreaLightsEnabled) { hasAreaLights = true; } if (hasAreaLights || options.clusteredLightingEnabled) { let areaLutsPrecision = 'highp'; if (device.areaLightLutFormat === PIXELFORMAT_RGBA8) { decl.append('#define AREA_R8_G8_B8_A8_LUTS'); areaLutsPrecision = 'lowp'; } decl.append('#define AREA_LIGHTS'); decl.append(`uniform ${areaLutsPrecision} sampler2D areaLightsLutTex1;`); decl.append(`uniform ${areaLutsPrecision} sampler2D areaLightsLutTex2;`); } for (let i = 0; i < options.lights.length; i++) { const light = options.lights[i]; const lightType = light._type; if (options.clusteredLightingEnabled && lightType !== LIGHTTYPE_DIRECTIONAL) { continue; } const lightShape = hasAreaLights && light._shape ? light._shape : LIGHTSHAPE_PUNCTUAL; decl.append(`uniform vec3 light${i}_color;`); if (light._shadowType === SHADOW_PCSS && light.castShadows && !options.noShadow) { decl.append(`uniform float light${i}_shadowSearchArea;`); decl.append(`uniform vec4 light${i}_cameraParams;`); } if (lightType === LIGHTTYPE_DIRECTIONAL) { decl.append(`uniform vec3 light${i}_direction;`); } else { decl.append(`uniform vec3 light${i}_position;`); decl.append(`uniform float light${i}_radius;`); if (lightType === LIGHTTYPE_SPOT) { decl.append(`uniform vec3 light${i}_direction;`); decl.append(`uniform float light${i}_innerConeAngle;`); decl.append(`uniform float light${i}_outerConeAngle;`); } } if (lightShape !== LIGHTSHAPE_PUNCTUAL) { if (lightType === LIGHTTYPE_DIRECTIONAL) { decl.append(`uniform vec3 light${i}_position;`); } decl.append(`uniform vec3 light${i}_halfWidth;`); decl.append(`uniform vec3 light${i}_halfHeight;`); } if (light.castShadows && !options.noShadow) { decl.append(`uniform mat4 light${i}_shadowMatrix;`); decl.append(`uniform float light${i}_shadowIntensity;`); if (lightType === LIGHTTYPE_DIRECTIONAL) { decl.append(`uniform mat4 light${i}_shadowMatrixPalette[4];`); decl.append(`uniform float light${i}_shadowCascadeDistances[4];`); decl.append(`uniform float light${i}_shadowCascadeCount;`); } decl.append(`uniform vec4 light${i}_shadowParams;`); if (lightType === LIGHTTYPE_DIRECTIONAL) { shadowedDirectionalLightUsed = true; } if (lightType === LIGHTTYPE_OMNI) { decl.append(`uniform samplerCube light${i}_shadowMap;`); } else { if (light._isPcf && device.supportsDepthShadow) { decl.append(`uniform sampler2DShadow light${i}_shadowMap;`); } else { decl.append(`uniform sampler2D light${i}_shadowMap;`); } } numShadowLights++; shadowTypeUsed[light._shadowType] = true; if (light._isVsm) useVsm = true; if (light._shadowType === SHADOW_PCSS) usePcss = true; } if (light._cookie) { if (light._cookie._cubemap) { if (lightType === LIGHTTYPE_OMNI) { decl.append(`uniform samplerCube light${i}_cookie;`); decl.append(`uniform float light${i}_cookieIntensity;`); if (!light.castShadows || options.noShadow) { decl.append(`uniform mat4 light${i}_shadowMatrix;`); } } } else { if (lightType === LIGHTTYPE_SPOT) { decl.append(`uniform sampler2D light${i}_cookie;`); decl.append(`uniform float light${i}_cookieIntensity;`); if (!light.castShadows || options.noShadow) { decl.append(`uniform mat4 light${i}_shadowMatrix;`); } if (light._cookieTransform) { decl.append(`uniform vec4 light${i}_cookieMatrix;`); decl.append(`uniform vec2 light${i}_cookieOffset;`); } } } } } const hasTBN = this.needsNormal && (options.useNormals || options.useClearCoatNormals || options.enableGGXSpecular && !options.useHeights); if (hasTBN) { if (options.hasTangents) { func.append(options.fastTbn ? chunks.TBNfastPS : chunks.TBNPS); } else { if (device.extStandardDerivatives && (options.useNormals || options.useClearCoatNormals)) { func.append(chunks.TBNderivativePS.replace(/\$UV/g, this.lightingUv)); } else { func.append(chunks.TBNObjectSpacePS); } } if (options.twoSidedLighting) { func.append(chunks.twoSidedLightingPS); } } func.append(chunks.sphericalPS); func.append(chunks.decodePS); func.append(ShaderGenerator.gammaCode(options.gamma, chunks)); func.append(ShaderGenerator.tonemapCode(options.toneMap, chunks)); func.append(ShaderGenerator.fogCode(options.fog, chunks)); func.append(this.frontendCode); if (options.useCubeMapRotation) { decl.append('#define CUBEMAP_ROTATION'); } if (this.needsNormal) { func.append(chunks.cubeMapRotatePS); func.append(options.cubeMapProjection > 0 ? chunks.cubeMapProjectBoxPS : chunks.cubeMapProjectNonePS); func.append(options.skyboxIntensity ? chunks.envMultiplyPS : chunks.envConstPS); } if (this.lighting && options.useSpecular || this.reflections) { if (options.useMetalness) { func.append(chunks.metalnessModulatePS); } if (options.fresnelModel === FRESNEL_SCHLICK) { func.append(chunks.fresnelSchlickPS); } if (options.useIridescence) { func.append(chunks.iridescenceDiffractionPS); } } if (options.useAo) { func.append(chunks.aoDiffuseOccPS); switch (options.occludeSpecular) { case SPECOCC_AO: func.append(options.occludeSpecularFloat ? chunks.aoSpecOccSimplePS : chunks.aoSpecOccConstSimplePS); break; case SPECOCC_GLOSSDEPENDENT: func.append(options.occludeSpecularFloat ? chunks.aoSpecOccPS : chunks.aoSpecOccConstPS); break; } } if (options.reflectionSource === 'envAtlasHQ') { func.append(options.fixSeams ? chunks.fixCubemapSeamsStretchPS : chunks.fixCubemapSeamsNonePS); func.append(chunks.envAtlasPS); func.append(chunks.reflectionEnvHQPS.replace(/\$DECODE_CUBEMAP/g, ChunkUtils.decodeFunc(options.reflectionCubemapEncoding)).replace(/\$DECODE/g, ChunkUtils.decodeFunc(options.reflectionEncoding))); } else if (options.reflectionSource === 'envAtlas') { func.append(chunks.envAtlasPS); func.append(chunks.reflectionEnvPS.replace(/\$DECODE/g, ChunkUtils.decodeFunc(options.reflectionEncoding))); } else if (options.reflectionSource === 'cubeMap') { func.append(options.fixSeams ? chunks.fixCubemapSeamsStretchPS : chunks.fixCubemapSeamsNonePS); func.append(chunks.reflectionCubePS.replace(/\$DECODE/g, ChunkUtils.decodeFunc(options.reflectionEncoding))); } else if (options.reflectionSource === 'sphereMap') { func.append(chunks.reflectionSpherePS.replace(/\$DECODE/g, ChunkUtils.decodeFunc(options.reflectionEncoding))); } if (this.reflections) { if (options.useClearCoat) { func.append(chunks.reflectionCCPS); } if (options.useSheen) { func.append(chunks.reflectionSheenPS); } } if (options.useRefraction) { if (options.useDynamicRefraction) { if (options.dispersion) { decl.append('uniform float material_dispersion;'); decl.append('#define DISPERSION\n'); } func.append(chunks.refractionDynamicPS); } else if (this.reflections) { func.append(chunks.refractionCubePS); } } if (options.useSheen) { func.append(chunks.lightSheenPS); } if (options.clusteredLightingEnabled) { func.append(chunks.clusteredLightUtilsPS); if (options.clusteredLightingCookiesEnabled) { func.append(chunks.clusteredLightCookiesPS); } if (options.clusteredLightingShadowsEnabled && !options.noShadow) { shadowTypeUsed[SHADOW_PCF3] = true; shadowTypeUsed[SHADOW_PCF5] = true; shadowTypeUsed[SHADOW_PCSS] = true; } } if (numShadowLights > 0 || options.clusteredLightingEnabled) { if (shadowedDirectionalLightUsed) { func.append(chunks.shadowCascadesPS); } if (shadowTypeUsed[SHADOW_PCF1] || shadowTypeUsed[SHADOW_PCF3]) { func.append(chunks.shadowStandardPS); } if (shadowTypeUsed[SHADOW_PCF5] && !device.isWebGL1) { func.append(chunks.shadowStandardGL2PS); } if (useVsm) { func.append(chunks.shadowVSM_commonPS); if (shadowTypeUsed[SHADOW_VSM8]) { func.append(chunks.shadowVSM8PS); } if (shadowTypeUsed[SHADOW_VSM16]) { func.append(device.extTextureHalfFloatLinear ? chunks.shadowEVSMPS.replace(/\$/g, '16') : chunks.shadowEVSMnPS.replace(/\$/g, '16')); } if (shadowTypeUsed[SHADOW_VSM32]) { func.append(device.extTextureFloatLinear ? chunks.shadowEVSMPS.replace(/\$/g, '32') : chunks.shadowEVSMnPS.replace(/\$/g, '32')); } } if (usePcss) { func.append(chunks.linearizeDepthPS); func.append(chunks.shadowPCSSPS); } if (!(device.isWebGL2 || device.isWebGPU || device.extStandardDerivatives)) { func.append(chunks.biasConstPS); } } if (options.enableGGXSpecular) func.append('uniform float material_anisotropy;'); if (this.lighting) { func.append(chunks.lightDiffuseLambertPS); if (hasAreaLights || options.clusteredLightingAreaLightsEnabled) { func.append(chunks.ltcPS); } } let useOldAmbient = false; if (options.useSpecular) { if (this.lighting) { func.append(options.shadingModel === SPECULAR_PHONG ? chunks.lightSpecularPhongPS : options.enableGGXSpecular ? chunks.lightSpecularAnisoGGXPS : chunks.lightSpecularBlinnPS); } if (!options.fresnelModel && !this.reflections && !options.diffuseMapEnabled) { decl.append('uniform vec3 material_ambient;'); decl.append('#define LIT_OLD_AMBIENT'); useOldAmbient = true; } } func.append(chunks.combinePS); if (options.lightMapEnabled) { func.append(options.useSpecular && options.dirLightMapEnabled ? chunks.lightmapDirAddPS : chunks.lightmapAddPS); } const addAmbient = !options.lightMapEnabled || options.lightMapWithoutAmbient; if (addAmbient) { if (options.ambientSource === 'ambientSH') { func.append(chunks.ambientSHPS); } else if (options.ambientSource === 'envAtlas') { if (options.reflectionSource !== 'envAtlas' && options.reflectionSource !== 'envAtlasHQ') { func.append(chunks.envAtlasPS); } func.append(chunks.ambientEnvPS.replace(/\$DECODE/g, ChunkUtils.decodeFunc(options.ambientEncoding))); } else { func.append(chunks.ambientConstantPS); } } if (options.useAmbientTint && !useOldAmbient) { decl.append('uniform vec3 material_ambient;'); } if (options.useMsdf) { if (!options.msdfTextAttribute) { decl.append('#define UNIFORM_TEXT_PARAMETERS'); } func.append(chunks.msdfPS); } if (this.needsNormal) { func.append(chunks.viewDirPS); if (options.useSpecular) { func.append(options.enableGGXSpecular ? chunks.reflDirAnisoPS : chunks.reflDirPS); } } let hasPointLights = false; let usesLinearFalloff = false; let usesInvSquaredFalloff = false; let usesSpot = false; let usesCookie = false; let usesCookieNow; if (options.clusteredLightingEnabled && this.lighting) { usesSpot = true; hasPointLights = true; usesLinearFalloff = true; usesCookie = true; func.append(chunks.floatUnpackingPS); if (options.lightMaskDynamic) { decl.append('#define CLUSTER_MESH_DYNAMIC_LIGHTS'); } if (options.clusteredLightingCookiesEnabled) { decl.append('#define CLUSTER_COOKIES'); } if (options.clusteredLightingShadowsEnabled && !options.noShadow) { decl.append('#define CLUSTER_SHADOWS'); decl.append(`#define CLUSTER_SHADOW_TYPE_${shadowTypeToString[options.clusteredLightingShadowType]}`); } if (options.clusteredLightingAreaLightsEnabled) { decl.append('#define CLUSTER_AREALIGHTS'); } decl.append(LightsBuffer.getShaderDefines(device)); if (options.clusteredLightingShadowsEnabled && !options.noShadow) { func.append(chunks.clusteredLightShadowsPS); } func.append(chunks.clusteredLightPS); } code.append(this._fsGetStartCode(code, device, chunks, options)); if (this.needsNormal) { code.append(' dVertexNormalW = normalize(vNormalW);'); if ((options.useHeights || options.useNormals) && options.hasTangents) { code.append(' dTangentW = vTangentW;'); code.append(' dBinormalW = vBinormalW;'); } code.append(' getViewDir();'); if (hasTBN) { code.append(' getTBN(dTangentW, dBinormalW, dVertexNormalW);'); if (options.twoSidedLighting) { code.append(' handleTwoSidedLighting();'); } } } code.append(this.frontendFunc); if (this.needsNormal) { if (options.useSpecular) { backend.append(' getReflDir(litArgs_worldNormal, dViewDirW, litArgs_gloss, dTBN);'); } if (options.useClearCoat) { backend.append(' ccReflDirW = normalize(-reflect(dViewDirW, litArgs_clearcoat_worldNormal));'); } } if (this.lighting && options.useSpecular || this.reflections) { if (options.useMetalness) { backend.append(' float f0 = 1.0 / litArgs_ior; f0 = (f0 - 1.0) / (f0 + 1.0); f0 *= f0;'); backend.append(' litArgs_specularity = getSpecularModulate(litArgs_specularity, litArgs_albedo, litArgs_metalness, f0);'); backend.append(' litArgs_albedo = getAlbedoModulate(litArgs_albedo, litArgs_metalness);'); } if (options.useIridescence) { backend.append(' vec3 iridescenceFresnel = getIridescence(saturate(dot(dViewDirW, litArgs_worldNormal)), litArgs_specularity, litArgs_iridescence_thickness);'); } } if (addAmbient) { backend.append(' addAmbient(litArgs_worldNormal);'); if (options.conserveEnergy && options.useSpecular) { backend.append(' dDiffuseLight = dDiffuseLight * (1.0 - litArgs_specularity);'); } if (options.separateAmbient) { backend.append(` vec3 dAmbientLight = dDiffuseLight; dDiffuseLight = vec3(0); `); } } if (options.useAmbientTint && !useOldAmbient) { backend.append(' dDiffuseLight *= material_ambient;'); } if (options.useAo && !options.occludeDirect) { backend.append(' occludeDiffuse(litArgs_ao);'); } if (options.lightMapEnabled) { backend.append(` addLightMap( litArgs_lightmap, litArgs_lightmapDir, litArgs_worldNormal, dViewDirW, dReflDirW, litArgs_gloss, litArgs_specularity, dVertexNormalW, dTBN #if defined(LIT_IRIDESCENCE) , iridescenceFresnel, litArgs_iridescence_intensity #endif );`); } if (this.lighting || this.reflections) { if (this.reflections) { if (options.useClearCoat) { backend.append(' addReflectionCC(ccReflDirW, litArgs_clearcoat_gloss);'); if (options.fresnelModel > 0) { backend.append(' ccFresnel = getFresnelCC(dot(dViewDirW, litArgs_clearcoat_worldNormal));'); backend.append(' ccReflection.rgb *= ccFresnel;'); } else { backend.append(' ccFresnel = 0.0;'); } } if (options.useSpecularityFactor) { backend.append(' ccReflection.rgb *= litArgs_specularityFactor;'); } if (options.useSheen) { backend.append(' addReflectionSheen(litArgs_worldNormal, dViewDirW, litArgs_sheen_gloss);'); } backend.append(' addReflection(dReflDirW, litArgs_gloss);'); if (options.fresnelModel > 0) { backend.append(` dReflection.rgb *= getFresnel( dot(dViewDirW, litArgs_worldNormal), litArgs_gloss, litArgs_specularity #if defined(LIT_IRIDESCENCE) , iridescenceFresnel, litArgs_iridescence_intensity #endif );`); } else { backend.append(' dReflection.rgb *= litArgs_specularity;'); } if (options.useSpecularityFactor) { backend.append(' dReflection.rgb *= litArgs_specularityFactor;'); } } if (hasAreaLights) { backend.append(' dSpecularLight *= litArgs_specularity;'); if (options.useSpecular) { backend.append(' calcLTCLightValues(litArgs_gloss, litArgs_worldNormal, dViewDirW, litArgs_specularity, litArgs_clearcoat_gloss, litArgs_clearcoat_worldNormal, litArgs_clearcoat_specularity);'); } } for (let i = 0; i < options.lights.length; i++) { const light = options.lights[i]; const lightType = light._type; if (options.clusteredLightingEnabled && lightType !== LIGHTTYPE_DIRECTIONAL) { continue; } usesCookieNow = false; const lightShape = hasAreaLights && light._shape ? light.shape : LIGHTSHAPE_PUNCTUAL; const shapeString = hasAreaLights && light._shape ? this._getLightSourceShapeString(lightShape) : ''; if (lightShape !== LIGHTSHAPE_PUNCTUAL) { backend.append(` calc${shapeString}LightValues(light${i}_position, light${i}_halfWidth, light${i}_halfHeight);`); } if (lightType === LIGHTTYPE_DIRECTIONAL) { backend.append(` dLightDirNormW = light${i}_direction;`); backend.append(' dAtten = 1.0;'); } else { if (light._cookie) { if (lightType === LIGHTTYPE_SPOT && !light._cookie._cubemap) { usesCookie = true; usesCookieNow = true; } else if (lightType === LIGHTTYPE_OMNI && light._cookie._cubemap) { usesCookie = true; usesCookieNow = true; } } backend.append(` getLightDirPoint(light${i}_position);`); hasPointLights = true; if (usesCookieNow) { if (lightType === LIGHTTYPE_SPOT) { backend.append(` dAtten3 = getCookie2D${light._cookieFalloff ? '' : 'Clip'}${light._cookieTransform ? 'Xform' : ''}(light${i}_cookie, light${i}_shadowMatrix, light${i}_cookieIntensity${light._cookieTransform ? `, light${i}_cookieMatrix, light${i}_cookieOffset` : ''}).${light._cookieChannel};`); } else { backend.append(` dAtten3 = getCookieCube(light${i}_cookie, light${i}_shadowMatrix, light${i}_cookieIntensity).${light._cookieChannel};`); } } if (lightShape === LIGHTSHAPE_PUNCTUAL) { if (light._falloffMode === LIGHTFALLOFF_LINEAR) { backend.append(` dAtten = getFalloffLinear(light${i}_radius, dLightDirW);`); usesLinearFalloff = true; } else { backend.append(` dAtten = getFalloffInvSquared(light${i}_radius, dLightDirW);`); usesInvSquaredFalloff = true; } } else { backend.append(` dAtten = getFalloffWindow(light${i}_radius, dLightDirW);`); usesInvSquaredFalloff = true; } backend.append(' if (dAtten > 0.00001) {'); if (lightType === LIGHTTYPE_SPOT) { if (!(usesCookieNow && !light._cookieFalloff)) { backend.append(` dAtten *= getSpotEffect(light${i}_direction, light${i}_innerConeAngle, light${i}_outerConeAngle, dLightDirNormW);`); usesSpot = true; } } } if (lightShape !== LIGHTSHAPE_PUNCTUAL) { if (lightType === LIGHTTYPE_DIRECTIONAL) { backend.append(' dAttenD = getLightDiffuse(litArgs_worldNormal, dViewDirW, dLightDirW, dLightDirNormW);'); } else { backend.append(` dAttenD = get${shapeString}LightDiffuse(litArgs_worldNormal, dViewDirW, dLightDirW, dLightDirNormW) * 16.0;`); } } else { backend.append(' dAtten *= getLightDiffuse(litArgs_worldNormal, dViewDirW, dLightDirW, dLightDirNormW);'); } if (light.castShadows && !options.noShadow) { const pcssShadows = light._shadowType === SHADOW_PCSS; const vsmShadows = light._shadowType === SHADOW_VSM8 || light._shadowType === SHADOW_VSM16 || light._shadowType === SHADOW_VSM32; const pcfShadows = light._shadowType === SHADOW_PCF1 || light._shadowType === SHADOW_PCF3 || light._shadowType === SHADOW_PCF5; let shadowReadMode = null; let evsmExp; switch (light._shadowType) { case SHADOW_VSM8: shadowReadMode = 'VSM8'; evsmExp = '0.0'; break; case SHADOW_VSM16: shadowReadMode = 'VSM16'; evsmExp = '5.54'; break; case SHADOW_VSM32: shadowReadMode = 'VSM32'; if (device.textureFloatHighPrecision) { evsmExp = '15.0'; } else { evsmExp = '5.54'; } break; case SHADOW_PCF1: shadowReadMode = 'PCF1x1'; break; case SHADOW_PCF5: shadowReadMode = 'PCF5x5'; break; case SHADOW_PCSS: shadowReadMode = 'PCSS'; break; case SHADOW_PCF3: default: shadowReadMode = 'PCF3x3'; break; } if (shadowReadMode !== null) { if (light._normalOffsetBias && !light._isVsm) { func.append('#define SHADOW_SAMPLE_NORMAL_OFFSET'); } if (lightType === LIGHTTYPE_DIRECTIONAL) { func.append('#define SHADOW_SAMPLE_ORTHO'); } if ((pcfShadows || pcssShadows) && device.isWebGL2 || device.isWebGPU || device.extStandardDerivatives) { func.append('#define SHADOW_SAMPLE_SOURCE_ZBUFFER'); } if (lightType === LIGHTTYPE_OMNI) { func.append('#define SHADOW_SAMPLE_POINT'); } const coordCode = chunks.shadowSampleCoordPS; func.append(coordCode.replace('$LIGHT', i)); func.append('#undef SHADOW_SAMPLE_NORMAL_OFFSET'); func.append('#undef SHADOW_SAMPLE_ORTHO'); func.append('#undef SHADOW_SAMPLE_SOURCE_ZBUFFER'); func.append('#undef SHADOW_SAMPLE_POINT'); let shadowMatrix = `light${i}_shadowMatrix`; if (lightType === LIGHTTYPE_DIRECTIONAL && light.numCascades > 1) { backend.append(` getShadowCascadeMatrix(light${i}_shadowMatrixPalette, light${i}_shadowCascadeDistances, light${i}_shadowCascadeCount);`); shadowMatrix = 'cascadeShadowMat'; } backend.append(` dShadowCoord = getShadowSampleCoord${i}(${shadowMatrix}, light${i}_shadowParams, vPositionW, dLightPosW, dLightDirW, dLightDirNormW, dVertexNormalW);`); if (lightType === LIGHTTYPE_DIRECTIONAL) { backend.append(` fadeShadow(light${i}_shadowCascadeDistances);`); } let shadowCoordArgs = `SHADOWMAP_PASS(light${i}_shadowMap), dShadowCoord, light${i}_shadowParams`; if (vsmShadows) { shadowCoordArgs = `${shadowCoordArgs}, ${evsmExp}, dLightDirW`; } else if (pcssShadows) { let penumbraSizeArg = `vec2(light${i}_shadowSearchArea)`; if (lightShape !== LIGHTSHAPE_PUNCTUAL) { penumbraSizeArg = `vec2(length(light${i}_halfWidth), length(light${i}_halfHeight)) * light${i}_shadowSearchArea`; } shadowCoordArgs = `${shadowCoordArgs}, light${i}_cameraParams, ${penumbraSizeArg}, dLightDirW`; } if (lightType === LIGHTTYPE_OMNI) { shadowReadMode = `Point${shadowReadMode}`; if (!pcssShadows) { shadowCoordArgs = `${shadowCoordArgs}, dLightDirW`; } } else if (lightType === LIGHTTYPE_SPOT) { shadowReadMode = `Spot${shadowReadMode}`; } backend.append(` float shadow${i} = getShadow${shadowReadMode}(${shadowCoordArgs});`); backend.append(` dAtten *= mix(1.0, shadow${i}, light${i}_shadowIntensity);`); } } if (lightShape !== LIGHTSHAPE_PUNCTUAL) { if (options.conserveEnergy && options.useSpecular) { backend.append(` dDiffuseLight += ((dAttenD * dAtten) * light${i}_color${usesCookieNow ? ' * dAtten3' : ''}) * (1.0 - dLTCSpecFres);`); } else { backend.append(` dDiffuseLight += (dAttenD * dAtten) * light${i}_color${usesCookieNow ? ' * dAtten3' : ''};`); } } else { if (hasAreaLights && options.conserveEnergy && options.useSpecular) { backend.append(` dDiffuseLight += (dAtten * light${i}_color${usesCookieNow ? ' * dAtten3' : ''}) * (1.0 - litArgs_specularity);`); } else { backend.append(` dDiffuseLight += dAtten * light${i}_color${usesCookieNow ? ' * dAtten3' : ''};`); } } if (options.useSpecular) { backend.append(' dHalfDirW = normalize(-dLightDirNormW + dViewDirW);'); } if (light.affectSpecularity) { if (lightShape !== LIGHTSHAPE_PUNCTUAL) { if (options.useClearCoat) { backend.append(` ccSpecularLight += ccLTCSpecFres * get${shapeString}LightSpecular(litArgs_clearcoat_worldNormal, dViewDirW) * dAtten * light${i}_color${usesCookieNow ? ' * dAtten3' : ''};`); } if (options.useSpecular) { backend.append(` dSpecularLight += dLTCSpecFres * get${shapeString}LightSpecular(litArgs_worldNormal, dViewDirW) * dAtten * light${i}_color${usesCookieNow ? ' * dAtten3' : ''};`); } } else { let calcFresnel = false; if (lightType === LIGHTTYPE_DIRECTIONAL && options.fresnelModel > 0) { calcFresnel = true; } if (options.useClearCoat) { backend.append(` ccSpecularLight += getLightSpecular(dHalfDirW, ccReflDirW, litArgs_clearcoat_worldNormal, dViewDirW, dLightDirNormW, litArgs_clearcoat_gloss, dTBN) * dAtten * light${i}_color${usesCookieNow ? ' * dAtten3' : ''}${calcFresnel ? ' * getFresnelCC(dot(dViewDirW, dHalfDirW));' : ';'}`); } if (options.useSheen) { backend.append(` sSpecularLight += getLightSpecularSheen(dHalfDirW, litArgs_worldNormal, dViewDirW, dLightDirNormW, litArgs_sheen_gloss) * dAtten * light${i}_color${usesCookieNow ? ' * dAtten3;' : ';'}`); } if (options.useSpecular) { backend.append(` dSpecularLight += getLightSpecular(dHalfDirW, dReflDirW, litArgs_worldNormal, dViewDirW, dLightDirNormW, litArgs_gloss, dTBN) * dAtten * light${i}_color${usesCookieNow ? ' * dAtten3' : ''}${calcFresnel ? ` * getFresnel( dot(dViewDirW, dHalfDirW), litArgs_gloss, litArgs_specularity #if defined(LIT_IRIDESCENCE) , iridescenceFresnel, litArgs_iridescence_intensity #endif );` : '* litArgs_specularity;'}`); } } } if (lightType !== LIGHTTYPE_DIRECTIONAL) { backend.append(' }'); } } if (options.clusteredLightingEnabled && this.lighting) { usesLinearFalloff = true; usesInvSquaredFalloff = true; hasPointLights = true; backend.append(` addClusteredLights( litArgs_worldNormal, dViewDirW, dReflDirW, #if defined(LIT_CLEARCOAT) ccReflDirW, #endif litArgs_gloss, litArgs_specularity, dVertexNormalW, dTBN, #if defined(LIT_IRIDESCENCE) iridescenceFresnel, #endif litArgs_clearcoat_worldNormal, litArgs_clearcoat_gloss, litArgs_sheen_gloss, litArgs_iridescence_intensity );`); } if (hasAreaLights) { if (options.useClearCoat) { backend.append(' litArgs_clearcoat_specularity = 1.0;'); } if (options.useSpecular) { backend.append(' litArgs_specularity = vec3(1);'); } } if (options.useRefraction) { backend.append(` addRefraction( litArgs_worldNormal, dViewDirW, litArgs_thickness, litArgs_gloss, litArgs_specularity, litArgs_albedo, litArgs_transmission, litArgs_ior, litArgs_dispersion #if defined(LIT_IRIDESCENCE) , iridescenceFresnel, litArgs_iridescence_intensity #endif );`); } } if (options.useAo) { if (options.occludeDirect) { backend.append(' occludeDiffuse(litArgs_ao);'); } if (options.occludeSpecular === SPECOCC_AO || options.occludeSpecular === SPECOCC_GLOSSDEPENDENT) { backend.append(' occludeSpecular(litArgs_gloss, litArgs_ao, litArgs_worldNormal, dViewDirW);'); } } if (options.useSpecularityFactor) { backend.append(' dSpecularLight *= litArgs_specularityFactor;'); } if (options.opacityFadesSpecular === false) { if (options.blendType === BLEND_NORMAL || options.blendType === BLEND_PREMULTIPLIED) { backend.append('float specLum = dot((dSpecularLight + dReflection.rgb * dReflection.a), vec3( 0.2126, 0.7152, 0.0722 ));'); backend.append('#ifdef LIT_CLEARCOAT\n specLum += dot(ccSpecularLight * litArgs_clearcoat_specularity + ccReflection.rgb * litArgs_clearcoat_specularity, vec3( 0.2126, 0.7152, 0.0722 ));\n#endif'); backend.append('litArgs_opacity = clamp(litArgs_opacity + gammaCorrectInput(specLum), 0.0, 1.0);'); } backend.append('litArgs_opacity *= material_alphaFade;'); } backend.append(chunks.endPS); if (options.blendType === BLEND_NORMAL || options.blendType === BLEND_ADDITIVEALPHA || options.alphaToCoverage) { backend.append(chunks.outputAlphaPS); } else if (options.blendType === BLEND_PREMULTIPLIED) { backend.append(chunks.outputAlphaPremulPS); } else { backend.append(chunks.outputAlphaOpaquePS); } if (options.useMsdf) { backend.append(' gl_FragColor = applyMsdf(gl_FragColor);'); } backend.append(chunks.outputPS); backend.append(chunks.debugOutputPS); if (hasPointLights) { func.prepend(chunks.lightDirPointPS); } if (usesLinearFalloff) { func.prepend(chunks.falloffLinearPS); } if (usesInvSquaredFalloff) { func.prepend(chunks.falloffInvSquaredPS); } if (usesSpot) { func.prepend(chunks.spotPS); } if (usesCookie && !options.clusteredLightingEnabled) { func.prepend(chunks.cookiePS); } let structCode = ''; const backendCode = `void evaluateBackend() {\n${backend.code}\n}`; func.append(backendCode); code.append(chunks.debugProcessFrontendPS); code.append(' evaluateBackend();'); code.append(ShaderGenerator.end()); const mergedCode = decl.code + func.code + code.code; if (mergedCode.includes('dTBN')) structCode += 'mat3 dTBN;\n'; if (mergedCode.includes('dVertexNormalW')) structCode += 'vec3 dVertexNormalW;\n'; if (mergedCode.includes('dTangentW')) structCode += 'vec3 dTangentW;\n'; if (mergedCode.includes('dBinormalW')) structCode += 'vec3 dBinormalW;\n'; if (mergedCode.includes('dViewDirW')) structCode += 'vec3 dViewDirW;\n'; if (mergedCode.includes('dReflDirW')) structCode += 'vec3 dReflDirW;\n'; if (mergedCode.includes('dHalfDirW')) structCode += 'vec3 dHalfDirW;\n'; if (mergedCode.includes('ccReflDirW')) structCode += 'vec3 ccReflDirW;\n'; if (mergedCode.includes('dLightDirNormW')) structCode += 'vec3 dLightDirNormW;\n'; if (mergedCode.includes('dLightDirW')) structCode += 'vec3 dLightDirW;\n'; if (mergedCode.includes('dLightPosW')) structCode += 'vec3 dLightPosW;\n'; if (mergedCode.includes('dShadowCoord')) structCode += 'vec3 dShadowCoord;\n'; if (mergedCode.includes('dReflection')) structCode += 'vec4 dReflection;\n'; if (mergedCode.includes('dDiffuseLight')) structCode += 'vec3 dDiffuseLight;\n'; if (mergedCode.includes('dSpecularLight')) structCode += 'vec3 dSpecularLight;\n'; if (mergedCode.includes('dAtten')) structCode += 'float dAtten;\n'; if (mergedCode.includes('dAttenD')) structCode += 'float dAttenD;\n'; if (mergedCode.includes('dAtten3')) structCode += 'vec3 dAtten3;\n'; if (mergedCode.includes('dMsdf')) structCode += 'vec4 dMsdf;\n'; if (mergedCode.includes('ccFresnel')) structCode += 'float ccFresnel;\n'; if (mergedCode.includes('ccReflection')) structCode += 'vec3 ccReflection;\n'; if (mergedCode.includes('ccSpecularLight')) structCode += 'vec3 ccSpecularLight;\n'; if (mergedCode.includes('ccSpecularityNoFres')) structCode += 'float ccSpecularityNoFres;\n'; if (mergedCode.includes('sSpecularLight')) structCode += 'vec3 sSpecularLight;\n'; if (mergedCode.includes('sReflection')) structCode += 'vec3 sReflection;\n'; const result = this._fsGetBeginCode() + this.varyings + this.varyingDefines + this._fsGetBaseCode() + structCode + this.frontendDecl + mergedCode; return result; } generateFragmentShader(frontendDecl, frontendCode, frontendFunc, lightingUv) { var _this$handleCompatibi; const options = this.options; this.frontendDecl = frontendDecl; this.frontendCode = frontendCode; this.frontendFunc = frontendFunc; this.lightingUv = lightingUv; if (options.pass === SHADER_PICK) { this.fshader = this._fsGetPickPassCode(); } else if (options.pass === SHADER_DEPTH) { this.fshader = this._fsGetDepthPassCode(); } else if (options.pass === SHADER_PREPASS_VELOCITY) { this.fshader = this._fsGetPrePassVelocityCode(); } else if (this.shadowPass) { this.fshader = this._fsGetShadowPassCode(); } else if (options.customFragmentShader) { this.fshader = this._fsGetBeginCode() + options.customFragmentShader; } else { this.fshader = this._fsGetLit