UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

1,025 lines (1,023 loc) 53 kB
import { SEMANTIC_ATTR12, SEMANTIC_ATTR13, SEMANTIC_ATTR14, SEMANTIC_ATTR15, SEMANTIC_NORMAL, SEMANTIC_TANGENT, SEMANTIC_COLOR, SEMANTIC_ATTR8, SEMANTIC_ATTR9, SEMANTIC_BLENDINDICES, SEMANTIC_BLENDWEIGHT, SHADERTAG_MATERIAL, SEMANTIC_POSITION, SEMANTIC_TEXCOORD0, SEMANTIC_TEXCOORD1 } from '../../../platform/graphics/constants.js'; import { SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED, LIGHTSHAPE_SPHERE, LIGHTSHAPE_DISK, LIGHTSHAPE_RECT, LIGHTTYPE_DIRECTIONAL, shadowTypeInfo, SHADOW_PCSS_32F, LIGHTSHAPE_PUNCTUAL, LIGHTTYPE_SPOT, LIGHTTYPE_OMNI, FRESNEL_SCHLICK, SPECOCC_GLOSSDEPENDENT, SPECOCC_AO, SHADOW_PCF3_32F, SHADOW_PCF5_32F, SHADOW_PCF1_32F, SHADOW_PCF1_16F, SHADOW_PCF3_16F, SHADOW_PCF5_16F, SHADOW_VSM_16F, SHADOW_VSM_32F, LIGHTFALLOFF_LINEAR, BLEND_NORMAL, BLEND_PREMULTIPLIED, BLEND_ADDITIVEALPHA, SHADER_PICK, SHADER_DEPTH, SHADER_PREPASS } from '../../constants.js'; import { shaderChunks } from '../chunks/chunks.js'; import { ChunkUtils } from '../chunk-utils.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'; var 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 }; var builtinVaryings = { vVertexColor: 'vec4', vPositionW: 'vec3', vNormalV: 'vec3', vNormalW: 'vec3', vTangentW: 'vec3', vBinormalW: 'vec3', vObjectSpaceUpW: 'vec3', vUv0: 'vec2', vUv1: 'vec2', vLinearDepth: 'float' }; class LitShader { _vsAddBaseCode(code, chunks, options) { code += chunks.baseVS; if (options.nineSlicedMode === SPRITE_RENDERMODE_SLICED || options.nineSlicedMode === SPRITE_RENDERMODE_TILED) { code += chunks.baseNineSlicedVS; } return code; } _setMapTransform(codes, name, id, uv) { var checkId = id + uv * 100; if (!codes[3][checkId]) { var 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() { var options = this.options; var chunks = this.chunks; var 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) { var 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) { var device = this.device; var options = this.options; var chunks = this.chunks; var code = ''; var codeBody = ''; var codeDefines = ''; code = this._vsAddBaseCode(code, chunks, options); codeBody += ' vPositionW = getWorldPosition();\n'; if (this.options.linearDepth) { codeDefines += "\n #ifndef VIEWMATRIX\n #define VIEWMATRIX\n uniform mat4 matrix_view;\n #endif\n "; codeBody += "\n // linear depth from the worldPosition, see getLinearDepth\n vLinearDepth = -(matrix_view * vec4(vPositionW, 1.0)).z;\n "; } if (this.options.useInstancing) { if (this.chunks.transformInstancingVS === shaderChunks.transformInstancingVS) { 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.transformVS; if (this.needsNormal) { code += chunks.normalCoreVS; code += chunks.normalVS; } 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) { codeBody += ' vObjectSpaceUpW = normalize(dNormalMatrix * vec3(0, 1, 0));\n'; } } var maxUvSets = 2; for(var 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"; } } var 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) { codeDefines += '#define MORPHING\n'; if (options.useMorphTextureBasedInt) { codeDefines += '#define MORPHING_INT\n'; } if (options.useMorphPosition) { codeDefines += '#define MORPHING_POSITION\n'; } if (options.useMorphNormal) { codeDefines += '#define MORPHING_NORMAL\n'; } this.attributes.morph_vertex_id = SEMANTIC_ATTR15; } if (options.skin) { this.attributes.vertex_boneIndices = SEMANTIC_BLENDINDICES; if (options.batch) { codeDefines += '#define BATCH\n'; } else { this.attributes.vertex_boneWeights = SEMANTIC_BLENDWEIGHT; codeDefines += '#define SKIN\n'; } } else if (options.useInstancing) { codeDefines += '#define INSTANCING\n'; } if (options.screenSpace) { codeDefines += '#define SCREENSPACE\n'; } if (options.pixelSnap) { codeDefines += '#define PIXELSNAP\n'; } 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"; } }); var shaderPassDefines = this.shaderPassInfo.shaderDefines; this.vshader = shaderPassDefines + codeDefines + this.varyings + code; } _fsGetBeginCode() { var code = this.shaderPassInfo.shaderDefines; for(var i = 0; i < this.defines.length; i++){ code += "#define " + this.defines[i] + "\n"; } return code; } _fsGetPickPassCode() { return "\n " + this._fsGetBeginCode() + "\n " + this.varyings + "\n " + this.varyingDefines + "\n " + this.frontendDecl + "\n " + this.frontendCode + "\n " + this.chunks.pickPS + "\n\n void main(void) {\n " + this.frontendFunc + "\n gl_FragColor = getPickOutput();\n }\n "; } _fsGetDepthPassCode() { var code = this._fsGetBeginCode(); code += this.varyings; code += this.varyingDefines; code += this.frontendDecl; code += this.frontendCode; code += ShaderGenerator.begin(); code += this.frontendFunc; code += ' gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n'; code += ShaderGenerator.end(); return code; } _fsGetPrePassCode() { var code = this._fsGetBeginCode(); code += this.varyings; code += this.varyingDefines; code += this.chunks.floatAsUintPS; code += this.frontendDecl; code += this.frontendCode; code += ShaderGenerator.begin(); code += this.frontendFunc; code += this.device.textureFloatRenderable ? "\n gl_FragColor = vec4(vLinearDepth, 1.0, 1.0, 1.0);\n " : "\n gl_FragColor = float2uint(vLinearDepth);\n "; code += ShaderGenerator.end(); return code; } _fsGetShadowPassCode() { var options = this.options; var chunks = this.chunks; var varyings = this.varyings; var lightType = this.shaderPassInfo.lightType; var shadowType = this.shaderPassInfo.shadowType; if (lightType !== LIGHTTYPE_DIRECTIONAL && options.clusteredLightingEnabled) { if (shadowType === SHADOW_VSM_16F || shadowType === SHADOW_VSM_32F || shadowType === SHADOW_PCSS_32F) { shadowType = SHADOW_PCF3_32F; } } var shadowInfo = shadowTypeInfo.get(shadowType); var _shadowInfo_vsm; var isVsm = (_shadowInfo_vsm = shadowInfo == null ? undefined : shadowInfo.vsm) != null ? _shadowInfo_vsm : false; var _shadowInfo_pcss; var isPcss = (_shadowInfo_pcss = shadowInfo == null ? undefined : shadowInfo.pcss) != null ? _shadowInfo_pcss : false; var code = this._fsGetBeginCode(); if (shadowType === SHADOW_VSM_32F) { code += '#define VSM_EXPONENT 15.0\n\n'; } else if (shadowType === SHADOW_VSM_16F) { 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; if (shadowType === SHADOW_PCSS_32F) { code += shaderChunks.linearizeDepthPS; } code += ShaderGenerator.begin(); code += this.frontendFunc; var usePerspectiveDepth = lightType === LIGHTTYPE_DIRECTIONAL || !isVsm && lightType === LIGHTTYPE_SPOT; var hasModifiedDepth = false; if (usePerspectiveDepth) { code += ' float depth = gl_FragCoord.z;\n'; if (isPcss) { if (lightType !== LIGHTTYPE_DIRECTIONAL) { code += ' depth = linearizeDepth(depth, camera_params);\n'; } } } else { code += ' float depth = min(distance(view_position, vPositionW) / light_radius, 0.99999);\n'; hasModifiedDepth = true; } if (!isVsm) { var exportR32 = isPcss; if (exportR32) { code += ' gl_FragColor.r = depth;\n'; } else { if (hasModifiedDepth) { code += ' gl_FragDepth = depth;\n'; } code += ' gl_FragColor = vec4(1.0);\n'; } } else { code += chunks.storeEVSMPS; } code += ShaderGenerator.end(); return code; } _fsGetLitPassCode() { var device = this.device; var options = this.options; var chunks = this.chunks; var decl = new ChunkBuilder(); var func = new ChunkBuilder(); var backend = new ChunkBuilder(); var 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.useSheen) { this.defines.push('LIT_SHEEN'); } if (options.useIridescence) { this.defines.push('LIT_IRIDESCENCE'); } } var shadowTypeUsed = []; var numShadowLights = 0; var shadowedDirectionalLightUsed = false; var useVsm = false; var usePcss = false; var hasAreaLights = options.lights.some((light)=>{ return light._shape && light._shape !== LIGHTSHAPE_PUNCTUAL; }); if (options.clusteredLightingEnabled && options.clusteredLightingAreaLightsEnabled) { hasAreaLights = true; } if (hasAreaLights || options.clusteredLightingEnabled) { decl.append('#define AREA_LIGHTS'); decl.append('uniform highp sampler2D areaLightsLutTex1;'); decl.append('uniform highp sampler2D areaLightsLutTex2;'); } for(var i = 0; i < options.lights.length; i++){ var light = options.lights[i]; var lightType = light._type; if (options.clusteredLightingEnabled && lightType !== LIGHTTYPE_DIRECTIONAL) { continue; } var lightShape = hasAreaLights && light._shape ? light._shape : LIGHTSHAPE_PUNCTUAL; decl.append("uniform vec3 light" + i + "_color;"); if (light._shadowType === SHADOW_PCSS_32F && 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 vec4 light" + i + "_softShadowParams;"); } } 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 vec4 light" + i + "_shadowCascadeDistances;"); decl.append("uniform int light" + i + "_shadowCascadeCount;"); decl.append("uniform float light" + i + "_shadowCascadeBlend;"); } decl.append("uniform vec4 light" + i + "_shadowParams;"); if (lightType === LIGHTTYPE_DIRECTIONAL) { shadowedDirectionalLightUsed = true; } if (lightType === LIGHTTYPE_OMNI) { decl.append("uniform " + (light._isPcf ? 'samplerCubeShadow' : 'samplerCube') + " light" + i + "_shadowMap;"); } else { decl.append("uniform " + (light._isPcf ? 'sampler2DShadow' : 'sampler2D') + " light" + i + "_shadowMap;"); } numShadowLights++; shadowTypeUsed[light._shadowType] = true; if (light._isVsm) useVsm = true; if (light._shadowType === SHADOW_PCSS_32F) 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;"); } } } } } var hasTBN = this.needsNormal && (options.useNormals || options.useClearCoatNormals || options.enableGGXSpecular && !options.useHeights); if (hasTBN) { if (options.hasTangents) { func.append(chunks.TBNPS); } else { if (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(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(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_32F] = true; shadowTypeUsed[SHADOW_PCF5_32F] = true; shadowTypeUsed[SHADOW_PCSS_32F] = true; } } if (numShadowLights > 0 || options.clusteredLightingEnabled) { if (shadowedDirectionalLightUsed) { func.append(chunks.shadowCascadesPS); } if (shadowTypeUsed[SHADOW_PCF1_32F] || shadowTypeUsed[SHADOW_PCF3_32F] || shadowTypeUsed[SHADOW_PCF1_16F] || shadowTypeUsed[SHADOW_PCF3_16F]) { func.append(chunks.shadowStandardPS); } if (shadowTypeUsed[SHADOW_PCF5_32F] || shadowTypeUsed[SHADOW_PCF5_16F]) { func.append(chunks.shadowStandardGL2PS); } if (useVsm) { func.append(chunks.shadowVSM_commonPS); if (shadowTypeUsed[SHADOW_VSM_16F]) { func.append(chunks.shadowEVSMPS.replace(/\$/g, '16')); } if (shadowTypeUsed[SHADOW_VSM_32F]) { func.append(device.extTextureFloatLinear ? chunks.shadowEVSMPS.replace(/\$/g, '32') : chunks.shadowEVSMnPS.replace(/\$/g, '32')); } } if (usePcss) { func.append(chunks.linearizeDepthPS); func.append(chunks.shadowPCSSPS); func.append(chunks.shadowSoftPS); } } if (options.enableGGXSpecular) func.append('uniform float material_anisotropy;'); if (this.lighting) { func.append(chunks.lightDiffuseLambertPS); if (hasAreaLights || options.clusteredLightingAreaLightsEnabled) { func.append(chunks.ltcPS); } } var useOldAmbient = false; if (options.useSpecular) { if (this.lighting) { func.append(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); } var 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 (!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); } } var hasPointLights = false; var usesLinearFalloff = false; var usesInvSquaredFalloff = false; var usesSpot = false; var usesCookie = false; var 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) { var _shadowTypeInfo_get; var shadowTypeName = (_shadowTypeInfo_get = shadowTypeInfo.get(options.clusteredLightingShadowType)) == null ? undefined : _shadowTypeInfo_get.name; var clusteredSampleType = shadowTypeName.substring(0, 4); decl.append('#define CLUSTER_SHADOWS'); decl.append("#define CLUSTER_SHADOW_TYPE_" + clusteredSampleType); } if (options.clusteredLightingAreaLightsEnabled) { decl.append('#define CLUSTER_AREALIGHTS'); } decl.append(LightsBuffer.getShaderDefines()); 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 (options.ssao) { func.append("\n uniform sampler2D ssaoTexture;\n uniform vec2 ssaoTextureSizeInv;\n "); backend.append('litArgs_ao *= texture2DLod(ssaoTexture, gl_FragCoord.xy * ssaoTextureSizeInv, 0.0).r;'); } 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.useSpecular) { backend.append(' dDiffuseLight = dDiffuseLight * (1.0 - litArgs_specularity);'); } if (options.separateAmbient) { backend.append("\n vec3 dAmbientLight = dDiffuseLight;\n dDiffuseLight = vec3(0);\n "); } } if (!useOldAmbient) { backend.append(' dDiffuseLight *= material_ambient;'); } if (options.useAo && !options.occludeDirect) { backend.append(' occludeDiffuse(litArgs_ao);'); } if (options.lightMapEnabled) { backend.append(" addLightMap(\n litArgs_lightmap, \n litArgs_lightmapDir, \n litArgs_worldNormal, \n dViewDirW, \n dReflDirW, \n litArgs_gloss, \n litArgs_specularity, \n dVertexNormalW,\n dTBN\n #if defined(LIT_IRIDESCENCE)\n , iridescenceFresnel,\n litArgs_iridescence_intensity\n #endif\n );"); } 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 *= \n getFresnel(\n dot(dViewDirW, litArgs_worldNormal), \n litArgs_gloss, \n litArgs_specularity\n #if defined(LIT_IRIDESCENCE)\n , iridescenceFresnel,\n litArgs_iridescence_intensity\n #endif\n );"); } 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(var i1 = 0; i1 < options.lights.length; i1++){ var light1 = options.lights[i1]; var lightType1 = light1._type; if (options.clusteredLightingEnabled && lightType1 !== LIGHTTYPE_DIRECTIONAL) { continue; } usesCookieNow = false; var lightShape1 = hasAreaLights && light1._shape ? light1.shape : LIGHTSHAPE_PUNCTUAL; var shapeString = hasAreaLights && light1._shape ? this._getLightSourceShapeString(lightShape1) : ''; if (lightShape1 !== LIGHTSHAPE_PUNCTUAL) { backend.append(" calc" + shapeString + "LightValues(light" + i1 + "_position, light" + i1 + "_halfWidth, light" + i1 + "_halfHeight);"); } if (lightType1 === LIGHTTYPE_DIRECTIONAL) { backend.append(" dLightDirNormW = light" + i1 + "_direction;"); backend.append(' dAtten = 1.0;'); } else { if (light1._cookie) { if (lightType1 === LIGHTTYPE_SPOT && !light1._cookie._cubemap) { usesCookie = true; usesCookieNow = true; } else if (lightType1 === LIGHTTYPE_OMNI && light1._cookie._cubemap) { usesCookie = true; usesCookieNow = true; } } backend.append(" getLightDirPoint(light" + i1 + "_position);"); hasPointLights = true; if (usesCookieNow) { if (lightType1 === LIGHTTYPE_SPOT) { backend.append(" dAtten3 = getCookie2D" + (light1._cookieFalloff ? '' : 'Clip') + (light1._cookieTransform ? 'Xform' : '') + "(light" + i1 + "_cookie, light" + i1 + "_shadowMatrix, light" + i1 + "_cookieIntensity" + (light1._cookieTransform ? ", light" + i1 + "_cookieMatrix, light" + i1 + "_cookieOffset" : '') + ")." + light1._cookieChannel + ";"); } else { backend.append(" dAtten3 = getCookieCube(light" + i1 + "_cookie, light" + i1 + "_shadowMatrix, light" + i1 + "_cookieIntensity)." + light1._cookieChannel + ";"); } } if (lightShape1 === LIGHTSHAPE_PUNCTUAL) { if (light1._falloffMode === LIGHTFALLOFF_LINEAR) { backend.append(" dAtten = getFalloffLinear(light" + i1 + "_radius, dLightDirW);"); usesLinearFalloff = true; } else { backend.append(" dAtten = getFalloffInvSquared(light" + i1 + "_radius, dLightDirW);"); usesInvSquaredFalloff = true; } } else { backend.append(" dAtten = getFalloffWindow(light" + i1 + "_radius, dLightDirW);"); usesInvSquaredFalloff = true; } backend.append(' if (dAtten > 0.00001) {'); if (lightType1 === LIGHTTYPE_SPOT) { if (!(usesCookieNow && !light1._cookieFalloff)) { backend.append(" dAtten *= getSpotEffect(light" + i1 + "_direction, light" + i1 + "_innerConeAngle, light" + i1 + "_outerConeAngle, dLightDirNormW);"); usesSpot = true; } } } if (lightShape1 !== LIGHTSHAPE_PUNCTUAL) { if (lightType1 === 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 (light1.castShadows && !options.noShadow) { var shadowInfo = shadowTypeInfo.get(light1._shadowType); var pcssShadows = light1._shadowType === SHADOW_PCSS_32F; var vsmShadows = shadowInfo == null ? undefined : shadowInfo.vsm; var pcfShadows = shadowInfo == null ? undefined : shadowInfo.pcf; var shadowReadMode = null; var evsmExp = undefined; switch(light1._shadowType){ case SHADOW_VSM_16F: shadowReadMode = 'VSM16'; evsmExp = '5.54'; break; case SHADOW_VSM_32F: shadowReadMode = 'VSM32'; evsmExp = '15.0'; break; case SHADOW_PCF1_32F: case SHADOW_PCF1_16F: shadowReadMode = 'PCF1x1'; break; case SHADOW_PCF5_32F: case SHADOW_PCF5_16F: shadowReadMode = 'PCF5x5'; break; case SHADOW_PCSS_32F: shadowReadMode = 'PCSS'; break; case SHADOW_PCF3_32F: case SHADOW_PCF3_16F: default: shadowReadMode = 'PCF3x3'; break; } if (shadowReadMode !== null) { if (light1._normalOffsetBias && !light1._isVsm) { func.append('#define SHADOW_SAMPLE_NORMAL_OFFSET'); } if (lightType1 === LIGHTTYPE_DIRECTIONAL) { func.append('#define SHADOW_SAMPLE_ORTHO'); } if (pcfShadows || pcssShadows || device.isWebGPU) { func.append('#define SHADOW_SAMPLE_SOURCE_ZBUFFER'); } if (lightType1 === LIGHTTYPE_OMNI) { func.append('#define SHADOW_SAMPLE_POINT'); } var coordCode = chunks.shadowSampleCoordPS; func.append(coordCode.replace('$LIGHT', i1)); 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'); var shadowMatrix = "light" + i1 + "_shadowMatrix"; if (lightType1 === LIGHTTYPE_DIRECTIONAL && light1.numCascades > 1) { backend.append("int cascadeIndex = getShadowCascadeIndex(light" + i1 + "_shadowCascadeDistances, light" + i1 + "_shadowCascadeCount);"); if (light1.cascadeBlend > 0) { backend.append("cascadeIndex = ditherShadowCascadeIndex(cascadeIndex, light" + i1 + "_shadowCascadeDistances, light" + i1 + "_shadowCascadeCount, light" + i1 + "_shadowCascadeBlend);"); } backend.append("mat4 cascadeShadowMat = light" + i1 + "_shadowMatrixPalette[cascadeIndex];"); shadowMatrix = 'cascadeShadowMat'; } backend.append(" dShadowCoord = getShadowSampleCoord" + i1 + "(" + shadowMatrix + ", light" + i1 + "_shadowParams, vPositionW, dLightPosW, dLightDirW, dLightDirNormW, dVertexNormalW);"); if (lightType1 === LIGHTTYPE_DIRECTIONAL) { backend.append(" fadeShadow(light" + i1 + "_shadowCascadeDistances);"); } var shadowCoordArgs = "SHADOWMAP_PASS(light" + i1 + "_shadowMap), dShadowCoord, light" + i1 + "_shadowParams"; if (vsmShadows) { shadowCoordArgs = shadowCoordArgs + ", " + evsmExp + ", dLightDirW"; } else if (pcssShadows) { var penumbraSizeArg = lightType1 === LIGHTTYPE_DIRECTIONAL ? "light" + i1 + "_softShadowParams" : "vec2(light" + i1 + "_shadowSearchArea)"; if (lightShape1 !== LIGHTSHAPE_PUNCTUAL) { penumbraSizeArg = "vec2(length(light" + i1 + "_halfWidth), length(light" + i1 + "_halfHeight)) * light" + i1 + "_shadowSearchArea"; } shadowCoordArgs = shadowCoordArgs + ", light" + i1 + "_cameraParams, " + penumbraSizeArg + ", dLightDirW"; } if (lightType1 === LIGHTTYPE_OMNI) { shadowReadMode = "Point" + shadowReadMode; if (!pcssShadows) { shadowCoordArgs = "" + shadowCoordArgs + ", dLightDirW"; } } else if (lightType1 === LIGHTTYPE_SPOT) { shadowReadMode = "Spot" + shadowReadMode; } backend.append(" float shadow" + i1 + " = getShadow" + shadowReadMode + "(" + shadowCoordArgs + ");"); backend.append(" dAtten *= mix(1.0, shadow" + i1 + ", light" + i1 + "_shadowIntensity);"); } } if (lightShape1 !== LIGHTSHAPE_PUNCTUAL) { if (options.useSpecular) { backend.append(" dDiffuseLight += ((dAttenD * dAtten) * light" + i1 + "_color" + (usesCookieNow ? ' * dAtten3' : '') + ") * (1.0 - dLTCSpecFres);"); } else { backend.append(" dDiffuseLight += (dAttenD * dAtten) * light" + i1 + "_color" + (usesCookieNow ? ' * dAtten3' : '') + ";"); } } else { if (hasAreaLights && options.useSpecular) { backend.append(" dDiffuseLight += (dAtten * light" + i1 + "_color" + (usesCookieNow ? ' * dAtten3' : '') + ") * (1.0 - litArgs_specularity);"); } else { backend.append(" dDiffuseLight += dAtten * light" + i1 + "_color" + (usesCookieNow ? ' * dAtten3' : '') + ";"); } } if (options.useSpecular) { backend.append(' dHalfDirW = normalize(-dLightDirNormW + dViewDirW);'); } if (light1.affectSpecularity) { if (lightShape1 !== LIGHTSHAPE_PUNCTUAL) { if (options.useClearCoat) { backend.append(" ccSpecularLight += ccLTCSpecFres * get" + shapeString + "LightSpecular(litArgs_clearcoat_worldNormal, dViewDirW) * dAtten * light" + i1 + "_color" + (usesCookieNow ? ' * dAtten3' : '') + ";"); } if (options.useSpecular) { backend.append(" dSpecularLight += dLTCSpecFres * get" + shapeString + "LightSpecular(litArgs_worldNormal, dViewDirW) * dAtten * light" + i1 + "_color" + (usesCookieNow ? ' * dAtten3' : '') + ";"); } } else { var calcFresnel = false; if (lightType1 === 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" + i1 + "_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" + i1 + "_color" + (usesCookieNow ? ' * dAtten3;' : ';')); } if (options.useSpecular) { backend.append(" dSpecularLight += getLightSpecular(dHalfDirW, dReflDirW, litArgs_worldNormal, dViewDirW, dLightDirNormW, litArgs_gloss, dTBN) * dAtten * light" + i1 + "_color" + (usesCookieNow ? ' * dAtten3' : '') + (calcFresnel ? " \n * getFresnel(\n dot(dViewDirW, dHalfDirW), \n litArgs_gloss, \n litArgs_specularity\n #if defined(LIT_IRIDESCENCE)\n , iridescenceFresnel, \n litArgs_iridescence_intensity\n #endif\n );" : '* litArgs_specularity;')); } } } if (lightType1 !== LIGHTTYPE_DIRECTIONAL) { backend.append(' }'); } } if (options.clusteredLightingEnabled && this.lighting) { usesLinearFalloff = true; usesInvSquaredFalloff = true; hasPointLights = true; backend.append(" addClusteredLights(\n litArgs_worldNormal, \n dViewDirW, \n dReflDirW,\n #if defined(LIT_CLEARCOAT)\n ccReflDirW,\n #endif\n litArgs_gloss, \n litArgs_specularity, \n dVertexNormalW, \n dTBN, \n #if defined(LIT_IRIDESCENCE)\n iridescenceFresnel,\n #endif\n litArgs_clearcoat_worldNormal, \n litArgs_clearcoat_gloss,\n litArgs_sheen_gloss,\n litArgs_iridescence_intensity\n );"); } 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(\n litArgs_worldNormal, \n dViewDirW, \n litArgs_thickness, \n litArgs_gloss, \n litArgs_specularity, \n litArgs_albedo, \n litArgs_transmission,\n litArgs_ior,\n litArgs_dispersion\n #if defined(LIT_IRIDESCENCE)\n , iridescenceFresnel, \n litArgs_iridescence_intensity\n #endif\n );"); } } 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); } var structCode = ''; var backendCode = "void evaluateBackend() {\n" + backend.code + "\n}"; func.append(backendCode); code.append(chunks.debugProcessFrontendPS); code.append(' evaluateBackend();'); code.append(ShaderGenerator.end()); var 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'; var result = this._fsGetBeginCode() + this.varyings + this.varyingDefines + this._fsGetBaseCode() + structCode + this.frontendDecl + mergedCode; return result; } generateFragmentShader(frontendDecl, frontendCode, frontendFunc, lightingUv) { var 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) {