UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

309 lines (306 loc) 16.6 kB
import { SEMANTIC_ATTR12, SEMANTIC_ATTR13, SEMANTIC_ATTR14, SEMANTIC_ATTR15, SEMANTIC_NORMAL, SEMANTIC_TANGENT, SEMANTIC_COLOR, SEMANTIC_ATTR8, SEMANTIC_ATTR9, SEMANTIC_BLENDINDICES, SEMANTIC_BLENDWEIGHT, SEMANTIC_POSITION, SEMANTIC_TEXCOORD1, SEMANTIC_TEXCOORD0 } from '../../../platform/graphics/constants.js'; import { SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED, shadowTypeInfo, LIGHTTYPE_DIRECTIONAL, LIGHTSHAPE_PUNCTUAL, lightTypeNames, lightShapeNames, lightFalloffNames, LIGHTTYPE_SPOT, LIGHTTYPE_OMNI, fresnelNames, spriteRenderModeNames, blendNames, cubemaProjectionNames, specularOcclusionNames, reflectionSrcNames, ambientSrcNames, SHADER_PICK, SHADER_DEPTH, SHADER_PREPASS, REFLECTIONSRC_NONE } from '../../constants.js'; import { shaderChunks } from '../chunks/chunks.js'; import { ChunkUtils } from '../chunk-utils.js'; import { ShaderPass } from '../../shader-pass.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 }; class LitShader { fDefineSet(condition, name, value) { if (value === void 0) value = ''; if (condition) { this.fDefines.set(name, value); } } generateVertexShader(useUv, useUnmodifiedUv, mapTransforms) { var { options, vDefines, attributes, chunks } = this; var varyings = new Map(); varyings.set('vPositionW', 'vec3'); if (options.nineSlicedMode === SPRITE_RENDERMODE_SLICED || options.nineSlicedMode === SPRITE_RENDERMODE_TILED) { vDefines.set('NINESLICED', true); } if (this.options.linearDepth) { vDefines.set('LINEAR_DEPTH', true); varyings.set('vLinearDepth', 'float'); } if (this.needsNormal) vDefines.set('NORMALS', true); if (this.options.useInstancing) { if (this.chunks.transformInstancingVS === shaderChunks.transformInstancingVS) { attributes.instance_line1 = SEMANTIC_ATTR12; attributes.instance_line2 = SEMANTIC_ATTR13; attributes.instance_line3 = SEMANTIC_ATTR14; attributes.instance_line4 = SEMANTIC_ATTR15; } } if (this.needsNormal) { attributes.vertex_normal = SEMANTIC_NORMAL; varyings.set('vNormalW', 'vec3'); if (options.hasTangents && (options.useHeights || options.useNormals || options.enableGGXSpecular)) { vDefines.set('TANGENTS', true); attributes.vertex_tangent = SEMANTIC_TANGENT; varyings.set('vTangentW', 'vec3'); varyings.set('vBinormalW', 'vec3'); } else if (options.enableGGXSpecular) { vDefines.set('GGX_SPECULAR', true); varyings.set('vObjectSpaceUpW', 'vec3'); } } var maxUvSets = 2; for(var i = 0; i < maxUvSets; i++){ if (useUv[i]) { vDefines.set("UV" + i, true); attributes["vertex_texCoord" + i] = "TEXCOORD" + i; } if (useUnmodifiedUv[i]) { vDefines.set("UV" + i + "_UNMODIFIED", true); varyings.set("vUv" + i, 'vec2'); } } var numTransforms = 0; var transformDone = new Set(); mapTransforms.forEach((mapTransform)=>{ var { id, uv, name } = mapTransform; var checkId = id + uv * 100; if (!transformDone.has(checkId)) { transformDone.add(checkId); varyings.set("vUV" + uv + "_" + id, 'vec2'); var varName = "texture_" + name + "MapTransform"; vDefines.set("{TRANSFORM_NAME_" + numTransforms + "}", varName); vDefines.set("{TRANSFORM_UV_" + numTransforms + "}", uv); vDefines.set("{TRANSFORM_ID_" + numTransforms + "}", id); numTransforms++; } }); vDefines.set('UV_TRANSFORMS_COUNT', numTransforms); if (options.vertexColors) { attributes.vertex_color = SEMANTIC_COLOR; vDefines.set('VERTEX_COLOR', true); varyings.set('vVertexColor', 'vec4'); } if (options.useMsdf && options.msdfTextAttribute) { attributes.vertex_outlineParameters = SEMANTIC_ATTR8; attributes.vertex_shadowParameters = SEMANTIC_ATTR9; vDefines.set('MSDF', true); } if (options.useMorphPosition || options.useMorphNormal) { vDefines.set('MORPHING', true); if (options.useMorphTextureBasedInt) vDefines.set('MORPHING_INT', true); if (options.useMorphPosition) vDefines.set('MORPHING_POSITION', true); if (options.useMorphNormal) vDefines.set('MORPHING_NORMAL', true); attributes.morph_vertex_id = SEMANTIC_ATTR15; } if (options.skin) { attributes.vertex_boneIndices = SEMANTIC_BLENDINDICES; if (options.batch) { vDefines.set('BATCH', true); } else { attributes.vertex_boneWeights = SEMANTIC_BLENDWEIGHT; vDefines.set('SKIN', true); } } if (options.useInstancing) vDefines.set('INSTANCING', true); if (options.screenSpace) vDefines.set('SCREENSPACE', true); if (options.pixelSnap) vDefines.set('PIXELSNAP', true); varyings.forEach((type, name)=>{ vDefines.set("VARYING_" + name.toUpperCase(), true); this.varyingsCode += "varying " + type + " " + name + ";\n"; }); this.vshader = this.varyingsCode + chunks.litMainVS; } _setupLightingDefines(hasAreaLights, clusteredLightingEnabled) { var fDefines = this.fDefines; var options = this.options; this.fDefines.set('LIGHT_COUNT', options.lights.length); if (hasAreaLights) fDefines.set('AREA_LIGHTS', true); if (clusteredLightingEnabled && this.lighting) { fDefines.set('LIT_CLUSTERED_LIGHTS', true); if (options.clusteredLightingCookiesEnabled) fDefines.set('CLUSTER_COOKIES', true); if (options.clusteredLightingAreaLightsEnabled) fDefines.set('CLUSTER_AREALIGHTS', true); if (options.lightMaskDynamic) fDefines.set('CLUSTER_MESH_DYNAMIC_LIGHTS', true); if (options.clusteredLightingShadowsEnabled && !options.noShadow) { var clusteredShadowInfo = shadowTypeInfo.get(options.clusteredLightingShadowType); fDefines.set('CLUSTER_SHADOWS', true); fDefines.set("SHADOW_KIND_" + clusteredShadowInfo.kind, true); fDefines.set("CLUSTER_SHADOW_TYPE_" + clusteredShadowInfo.kind, true); } } for(var i = 0; i < options.lights.length; i++){ var light = options.lights[i]; var lightType = light._type; if (clusteredLightingEnabled && lightType !== LIGHTTYPE_DIRECTIONAL) { continue; } var lightShape = hasAreaLights && light._shape ? light._shape : LIGHTSHAPE_PUNCTUAL; var shadowType = light._shadowType; var castShadow = light.castShadows && !options.noShadow; var shadowInfo = shadowTypeInfo.get(shadowType); fDefines.set("LIGHT" + i, true); fDefines.set("LIGHT" + i + "TYPE", "" + lightTypeNames[lightType]); fDefines.set("LIGHT" + i + "SHADOWTYPE", "" + shadowInfo.name); fDefines.set("LIGHT" + i + "SHAPE", "" + lightShapeNames[lightShape]); fDefines.set("LIGHT" + i + "FALLOFF", "" + lightFalloffNames[light._falloffMode]); if (light.affectSpecularity) fDefines.set("LIGHT" + i + "AFFECT_SPECULARITY", true); if (light._cookie) { if (lightType === LIGHTTYPE_SPOT && !light._cookie._cubemap || lightType === LIGHTTYPE_OMNI && light._cookie._cubemap) { fDefines.set("LIGHT" + i + "COOKIE", true); fDefines.set("{LIGHT" + i + "COOKIE_CHANNEL}", light._cookieChannel); if (lightType === LIGHTTYPE_SPOT) { if (light._cookieTransform) fDefines.set("LIGHT" + i + "COOKIE_TRANSFORM", true); if (light._cookieFalloff) fDefines.set("LIGHT" + i + "COOKIE_FALLOFF", true); } } } if (castShadow) { fDefines.set("LIGHT" + i + "CASTSHADOW", true); if (shadowInfo.pcf) fDefines.set("LIGHT" + i + "SHADOW_PCF", true); if (light._normalOffsetBias && !light._isVsm) fDefines.set("LIGHT" + i + "_SHADOW_SAMPLE_NORMAL_OFFSET", true); if (lightType === LIGHTTYPE_DIRECTIONAL) { fDefines.set("LIGHT" + i + "_SHADOW_SAMPLE_ORTHO", true); if (light.cascadeBlend > 0) fDefines.set("LIGHT" + i + "_SHADOW_CASCADE_BLEND", true); if (light.numCascades > 1) fDefines.set("LIGHT" + i + "_SHADOW_CASCADES", true); } if (shadowInfo.pcf || shadowInfo.pcss || this.device.isWebGPU) fDefines.set("LIGHT" + i + "_SHADOW_SAMPLE_SOURCE_ZBUFFER", true); if (lightType === LIGHTTYPE_OMNI) fDefines.set("LIGHT" + i + "_SHADOW_SAMPLE_POINT", true); } if (castShadow) { fDefines.set("SHADOW_KIND_" + shadowInfo.kind, true); if (lightType === LIGHTTYPE_DIRECTIONAL) fDefines.set('SHADOW_DIRECTIONAL', true); } } } prepareForwardPass(lightingUv) { var { options } = this; var clusteredAreaLights = options.clusteredLightingEnabled && options.clusteredLightingAreaLightsEnabled; var hasAreaLights = clusteredAreaLights || options.lights.some((light)=>{ return light._shape && light._shape !== LIGHTSHAPE_PUNCTUAL; }); var addAmbient = !options.lightMapEnabled || options.lightMapWithoutAmbient; var hasTBN = this.needsNormal && (options.useNormals || options.useClearCoatNormals || options.enableGGXSpecular && !options.useHeights); if (options.useSpecular) { this.fDefineSet(true, 'LIT_SPECULAR'); this.fDefineSet(this.reflections, 'LIT_REFLECTIONS'); this.fDefineSet(options.useClearCoat, 'LIT_CLEARCOAT'); this.fDefineSet(options.fresnelModel > 0, 'LIT_SPECULAR_FRESNEL'); this.fDefineSet(options.useSheen, 'LIT_SHEEN'); this.fDefineSet(options.useIridescence, 'LIT_IRIDESCENCE'); } this.fDefineSet(this.needsNormal, 'LIT_NEEDS_NORMAL'); this.fDefineSet(this.lighting, 'LIT_LIGHTING'); this.fDefineSet(options.useMetalness, 'LIT_METALNESS'); this.fDefineSet(options.enableGGXSpecular, 'LIT_GGX_SPECULAR'); this.fDefineSet(options.useSpecularityFactor, 'LIT_SPECULARITY_FACTOR'); this.fDefineSet(options.useCubeMapRotation, 'CUBEMAP_ROTATION'); this.fDefineSet(options.occludeSpecularFloat, 'LIT_OCCLUDE_SPECULAR_FLOAT'); this.fDefineSet(options.separateAmbient, 'LIT_SEPARATE_AMBIENT'); this.fDefineSet(options.twoSidedLighting, 'LIT_TWO_SIDED_LIGHTING'); this.fDefineSet(options.lightMapEnabled, 'LIT_LIGHTMAP'); this.fDefineSet(options.dirLightMapEnabled, 'LIT_DIR_LIGHTMAP'); this.fDefineSet(options.skyboxIntensity > 0, 'LIT_SKYBOX_INTENSITY'); this.fDefineSet(options.clusteredLightingShadowsEnabled, 'LIT_CLUSTERED_SHADOWS'); this.fDefineSet(options.clusteredLightingAreaLightsEnabled, 'LIT_CLUSTERED_AREA_LIGHTS'); this.fDefineSet(hasTBN, 'LIT_TBN'); this.fDefineSet(addAmbient, 'LIT_ADD_AMBIENT'); this.fDefineSet(options.hasTangents, 'LIT_TANGENTS'); this.fDefineSet(options.useNormals, 'LIT_USE_NORMALS'); this.fDefineSet(options.useClearCoatNormals, 'LIT_USE_CLEARCOAT_NORMALS'); this.fDefineSet(options.useRefraction, 'LIT_REFRACTION'); this.fDefineSet(options.useDynamicRefraction, 'LIT_DYNAMIC_REFRACTION'); this.fDefineSet(options.dispersion, 'LIT_DISPERSION'); this.fDefineSet(options.useHeights, 'LIT_HEIGHTS'); this.fDefineSet(options.opacityFadesSpecular, 'LIT_OPACITY_FADES_SPECULAR'); this.fDefineSet(options.alphaToCoverage, 'LIT_ALPHA_TO_COVERAGE'); this.fDefineSet(options.useMsdf, 'LIT_MSDF'); this.fDefineSet(options.ssao, 'LIT_SSAO'); this.fDefineSet(options.useAo, 'LIT_AO'); this.fDefineSet(options.occludeDirect, 'LIT_OCCLUDE_DIRECT'); this.fDefineSet(options.msdfTextAttribute, 'LIT_MSDF_TEXT_ATTRIBUTE'); this.fDefineSet(options.diffuseMapEnabled, 'LIT_DIFFUSE_MAP'); this.fDefineSet(options.shadowCatcher, 'LIT_SHADOW_CATCHER'); this.fDefineSet(true, 'LIT_FRESNEL_MODEL', fresnelNames[options.fresnelModel]); this.fDefineSet(true, 'LIT_NONE_SLICE_MODE', spriteRenderModeNames[options.nineSlicedMode]); this.fDefineSet(true, 'LIT_BLEND_TYPE', blendNames[options.blendType]); this.fDefineSet(true, 'LIT_CUBEMAP_PROJECTION', cubemaProjectionNames[options.cubeMapProjection]); this.fDefineSet(true, 'LIT_OCCLUDE_SPECULAR', specularOcclusionNames[options.occludeSpecular]); this.fDefineSet(true, 'LIT_REFLECTION_SOURCE', reflectionSrcNames[options.reflectionSource]); this.fDefineSet(true, 'LIT_AMBIENT_SOURCE', ambientSrcNames[options.ambientSource]); this.fDefineSet(true, '{lightingUv}', lightingUv != null ? lightingUv : ''); this.fDefineSet(true, '{reflectionDecode}', ChunkUtils.decodeFunc(options.reflectionEncoding)); this.fDefineSet(true, '{reflectionCubemapDecode}', ChunkUtils.decodeFunc(options.reflectionCubemapEncoding)); this.fDefineSet(true, '{ambientDecode}', ChunkUtils.decodeFunc(options.ambientEncoding)); this._setupLightingDefines(hasAreaLights, options.clusteredLightingEnabled); } prepareShadowPass() { var lightType = this.shaderPassInfo.lightType; var shadowType = this.shaderPassInfo.shadowType; var shadowInfo = shadowTypeInfo.get(shadowType); var usePerspectiveDepth = lightType === LIGHTTYPE_DIRECTIONAL || !shadowInfo.vsm && lightType === LIGHTTYPE_SPOT; this.fDefineSet(usePerspectiveDepth, 'PERSPECTIVE_DEPTH'); this.fDefineSet(true, 'LIGHT_TYPE', "" + lightTypeNames[lightType]); this.fDefineSet(true, 'SHADOW_TYPE', "" + shadowInfo.name); } generateFragmentShader(frontendDecl, frontendCode, lightingUv) { var options = this.options; if (options.pass === SHADER_PICK || options.pass === SHADER_DEPTH || options.pass === SHADER_PREPASS) { this.fshader = "\n\n " + this.varyingsCode + "\n " + frontendDecl + "\n " + frontendCode + '\n #include "litOtherMainPS"\n '; } else if (this.shadowPass) { this.prepareShadowPass(); this.fshader = "\n " + this.varyingsCode + "\n " + frontendDecl + "\n " + frontendCode + '\n #include "litShadowMainPS"\n '; } else if (options.customFragmentShader) { this.fshader = "\n " + options.customFragmentShader + "\n "; } else { this.prepareForwardPass(lightingUv); this.fshader = "\n " + this.varyingsCode + "\n " + frontendDecl + '\n #include "litForwardDeclarationPS"\n #include "litForwardPreCodePS"\n ' + frontendCode + '\n #include "litForwardPostCodePS"\n #include "litForwardBackendPS"\n #include "litForwardMainPS"\n '; } } constructor(device, options){ this.varyingsCode = ''; this.device = device; this.options = options; this.attributes = { vertex_position: SEMANTIC_POSITION }; if (options.userAttributes) { for (var [semantic, name] of Object.entries(options.userAttributes)){ this.attributes[name] = semantic; } } if (options.chunks) { var userChunks = options.chunks; this.chunks = Object.create(shaderChunks); for(var chunkName in shaderChunks){ if (userChunks.hasOwnProperty(chunkName)) { var chunk = userChunks[chunkName]; for(var 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 !== REFLECTIONSRC_NONE; 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.vshader = null; this.vDefines = new Map(); this.fDefines = new Map(); this.fshader = null; } } export { LitShader };