UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

333 lines (330 loc) 17.3 kB
import { SEMANTIC_ATTR11, SEMANTIC_ATTR12, SEMANTIC_ATTR14, SEMANTIC_ATTR15, SEMANTIC_NORMAL, SEMANTIC_TANGENT, SEMANTIC_COLOR, SEMANTIC_ATTR8, SEMANTIC_ATTR9, SEMANTIC_BLENDINDICES, SEMANTIC_BLENDWEIGHT, SHADERLANGUAGE_WGSL, primitiveGlslToWgslTypeMap, SHADERLANGUAGE_GLSL, 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, ditherNames, SHADER_PICK, SHADER_PREPASS, REFLECTIONSRC_NONE } from '../../constants.js'; import { ChunkUtils } from '../chunk-utils.js'; import { ShaderPass } from '../../shader-pass.js'; import { ShaderChunks } from '../shader-chunks.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 }; class LitShader { fDefineSet(condition, name, value = '') { if (condition) { this.fDefines.set(name, value); } } generateVertexShader(useUv, useUnmodifiedUv, mapTransforms) { const { options, vDefines, attributes } = this; const 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) { const languageChunks = ShaderChunks.get(this.device, this.shaderLanguage); if (this.chunks.get('transformInstancingVS') === languageChunks.get('transformInstancingVS')) { attributes.instance_line1 = SEMANTIC_ATTR11; attributes.instance_line2 = SEMANTIC_ATTR12; 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.useClearCoatNormals || 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'); } } const maxUvSets = 2; for(let 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'); } } let numTransforms = 0; const transformDone = new Set(); mapTransforms.forEach((mapTransform)=>{ const { id, uv, name } = mapTransform; const checkId = id + uv * 100; if (!transformDone.has(checkId)) { transformDone.add(checkId); varyings.set(`vUV${uv}_${id}`, 'vec2'); const 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.useVertexColorGamma) { vDefines.set('STD_VERTEX_COLOR_GAMMA', ''); } } 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)=>{ this.varyingsCode += `#define VARYING_${name.toUpperCase()}\n`; this.varyingsCode += this.shaderLanguage === SHADERLANGUAGE_WGSL ? `varying ${name}: ${primitiveGlslToWgslTypeMap.get(type)};\n` : `varying ${type} ${name};\n`; }); this.includes.set('varyingsVS', this.varyingsCode); this.includes.set('varyingsPS', this.varyingsCode); this.vshader = ` #include "litMainVS" `; } _setupLightingDefines(hasAreaLights, clusteredLightingEnabled) { const fDefines = this.fDefines; const 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) { const 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(let i = 0; i < options.lights.length; i++){ const light = options.lights[i]; const lightType = light._type; if (clusteredLightingEnabled && lightType !== LIGHTTYPE_DIRECTIONAL) { continue; } const lightShape = hasAreaLights && light._shape ? light._shape : LIGHTSHAPE_PUNCTUAL; const shadowType = light._shadowType; const castShadow = light.castShadows && !options.noShadow; const 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) { const { options } = this; const clusteredAreaLights = options.clusteredLightingEnabled && options.clusteredLightingAreaLightsEnabled; const hasAreaLights = clusteredAreaLights || options.lights.some((light)=>{ return light._shape && light._shape !== LIGHTSHAPE_PUNCTUAL; }); const addAmbient = !options.lightMapEnabled || options.lightMapWithoutAmbient; const 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.lighting && options.useSpecular || this.reflections, 'LIT_SPECULAR_OR_REFLECTION'); this.fDefineSet(this.needsSceneColor, 'LIT_SCENE_COLOR'); this.fDefineSet(this.needsScreenSize, 'LIT_SCREEN_SIZE'); this.fDefineSet(this.needsTransforms, 'LIT_TRANSFORMS'); 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.useAnisotropy, 'LIT_ANISOTROPY'); 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.alphaTest, 'LIT_ALPHA_TEST'); 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 ?? ''); 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); } preparePrepassPass() { const { options } = this; this.fDefineSet(options.alphaTest, 'LIT_ALPHA_TEST'); this.fDefineSet(true, 'STD_OPACITY_DITHER', ditherNames[options.opacityShadowDither]); } prepareShadowPass() { const { options } = this; const lightType = this.shaderPassInfo.lightType; const shadowType = this.shaderPassInfo.shadowType; const shadowInfo = shadowTypeInfo.get(shadowType); const 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}`); this.fDefineSet(options.alphaTest, 'LIT_ALPHA_TEST'); } generateFragmentShader(frontendDecl, frontendCode, lightingUv) { const options = this.options; this.includes.set('frontendDeclPS', frontendDecl ?? ''); this.includes.set('frontendCodePS', frontendCode ?? ''); if (options.pass === SHADER_PICK) ; else if (options.pass === SHADER_PREPASS) { this.preparePrepassPass(); } else if (this.shadowPass) { this.prepareShadowPass(); } else { this.prepareForwardPass(lightingUv); } this.fshader = ` #include "litMainPS" `; } constructor(device, options, allowWGSL = true){ this.varyingsCode = ''; this.vDefines = new Map(); this.fDefines = new Map(); this.includes = new Map(); this.chunks = null; this.device = device; this.options = options; const userChunks = options.shaderChunks; this.shaderLanguage = device.isWebGPU && allowWGSL && (!userChunks || userChunks.useWGSL) ? SHADERLANGUAGE_WGSL : SHADERLANGUAGE_GLSL; if (device.isWebGPU && this.shaderLanguage === SHADERLANGUAGE_GLSL) { if (!device.hasTranspilers) ; } this.attributes = { vertex_position: SEMANTIC_POSITION }; if (options.userAttributes) { for (const [semantic, name] of Object.entries(options.userAttributes)){ this.attributes[name] = semantic; } } const engineChunks = ShaderChunks.get(device, this.shaderLanguage); this.chunks = new Map(engineChunks); if (userChunks) { const userChunkMap = this.shaderLanguage === SHADERLANGUAGE_GLSL ? userChunks.glsl : userChunks.wgsl; userChunkMap.forEach((chunk, chunkName)=>{ for(const a in builtinAttributes){ if (builtinAttributes.hasOwnProperty(a) && chunk.indexOf(a) >= 0) { this.attributes[a] = builtinAttributes[a]; } } this.chunks.set(chunkName, chunk); }); } 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.fshader = null; } } export { LitShader };