UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

279 lines (276 loc) 13.9 kB
import { SHADER_FORWARD, SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED, ditherNames, FRESNEL_SCHLICK, BLEND_NONE, DITHER_NONE } from '../../constants.js'; import { ShaderPass } from '../../shader-pass.js'; import { LitShader } from './lit-shader.js'; import { ChunkUtils } from '../chunk-utils.js'; import { StandardMaterialOptions } from '../../materials/standard-material-options.js'; import { LitOptionsUtils } from './lit-options-utils.js'; import { ShaderGenerator } from './shader-generator.js'; import { ShaderDefinitionUtils } from '../../../platform/graphics/shader-definition-utils.js'; import { SHADERTAG_MATERIAL } from '../../../platform/graphics/constants.js'; import { MapUtils } from '../../../core/map-utils.js'; const _matTex2D = []; const buildPropertiesList = (options)=>{ return Object.keys(options).filter((key)=>key !== 'litOptions').sort(); }; class ShaderGeneratorStandard extends ShaderGenerator { generateKey(options) { let props; if (options === this.optionsContextMin) { if (!this.propsMin) this.propsMin = buildPropertiesList(options); props = this.propsMin; } else if (options === this.optionsContext) { if (!this.props) this.props = buildPropertiesList(options); props = this.props; } else { props = buildPropertiesList(options); } const definesHash = ShaderGenerator.definesHash(options.defines); const key = `standard:\n${definesHash}\n${props.map((prop)=>prop + options[prop]).join('\n')}${LitOptionsUtils.generateKey(options.litOptions)}`; return key; } _getUvSourceExpression(transformPropName, uVPropName, options) { const transformId = options[transformPropName]; const uvChannel = options[uVPropName]; const isMainPass = options.litOptions.pass === SHADER_FORWARD; let expression; if (isMainPass && options.litOptions.nineSlicedMode === SPRITE_RENDERMODE_SLICED) { expression = 'nineSlicedUv'; } else if (isMainPass && options.litOptions.nineSlicedMode === SPRITE_RENDERMODE_TILED) { expression = 'nineSlicedUv'; } else { if (transformId === 0) { expression = `vUv${uvChannel}`; } else { expression = `vUV${uvChannel}_${transformId}`; } if (options.heightMap && transformPropName !== 'heightMapTransform') { expression += ' + dUvOffset'; } } return expression; } _validateMapChunk(propName, chunkName, chunks) {} _addMapDefines(fDefines, propName, chunkName, options, chunks, mapping, encoding = null) { const mapPropName = `${propName}Map`; const propNameCaps = propName.toUpperCase(); const uVPropName = `${mapPropName}Uv`; const identifierPropName = `${mapPropName}Identifier`; const transformPropName = `${mapPropName}Transform`; const channelPropName = `${mapPropName}Channel`; const vertexColorChannelPropName = `${propName}VertexColorChannel`; const tintPropName = `${propName}Tint`; const vertexColorPropName = `${propName}VertexColor`; const detailModePropName = `${propName}Mode`; const invertName = `${propName}Invert`; const tintOption = options[tintPropName]; const vertexColorOption = options[vertexColorPropName]; const textureOption = options[mapPropName]; const textureIdentifier = options[identifierPropName]; const detailModeOption = options[detailModePropName]; const chunkCode = chunks.get(chunkName); if (textureOption) { fDefines.set(`STD_${propNameCaps}_TEXTURE`, ''); const uv = this._getUvSourceExpression(transformPropName, uVPropName, options); fDefines.set(`{STD_${propNameCaps}_TEXTURE_UV}`, uv); fDefines.set(`{STD_${propNameCaps}_TEXTURE_CHANNEL}`, options[channelPropName]); const textureId = `{STD_${propNameCaps}_TEXTURE_NAME}`; if (chunkCode.includes(textureId)) { let samplerName = `texture_${mapPropName}`; const alias = mapping[textureIdentifier]; if (alias) { samplerName = alias; } else { mapping[textureIdentifier] = samplerName; fDefines.set(`STD_${propNameCaps}_TEXTURE_ALLOCATE`, ''); } fDefines.set(textureId, samplerName); } if (encoding) { const textureDecode = options[channelPropName] === 'aaa' ? 'passThrough' : ChunkUtils.decodeFunc(encoding); fDefines.set(`{STD_${propNameCaps}_TEXTURE_DECODE}`, textureDecode); } } if (vertexColorOption) { fDefines.set(`STD_${propNameCaps}_VERTEX`, ''); fDefines.set(`{STD_${propNameCaps}_VERTEX_CHANNEL}`, options[vertexColorChannelPropName]); } if (detailModeOption) { fDefines.set(`{STD_${propNameCaps}_DETAILMODE}`, detailModeOption); } if (tintOption) { fDefines.set(`STD_${propNameCaps}_CONSTANT`, ''); } if (!!options[invertName]) { fDefines.set(`STD_${propNameCaps}_INVERT`, ''); } } _correctChannel(p, chan, _matTex2D) { if (_matTex2D[p] > 0) { if (_matTex2D[p] < chan.length) { return chan.substring(0, _matTex2D[p]); } else if (_matTex2D[p] > chan.length) { let str = chan; const chr = str.charAt(str.length - 1); const addLen = _matTex2D[p] - str.length; for(let i = 0; i < addLen; i++)str += chr; return str; } return chan; } } createVertexShader(litShader, options) { const useUv = []; const useUnmodifiedUv = []; const mapTransforms = []; const maxUvSets = 2; for(const p in _matTex2D){ const mapName = `${p}Map`; if (options[`${p}VertexColor`]) { const colorChannelName = `${p}VertexColorChannel`; options[colorChannelName] = this._correctChannel(p, options[colorChannelName], _matTex2D); } if (options[mapName]) { const channelName = `${mapName}Channel`; const transformName = `${mapName}Transform`; const uvName = `${mapName}Uv`; options[uvName] = Math.min(options[uvName], maxUvSets - 1); options[channelName] = this._correctChannel(p, options[channelName], _matTex2D); const uvSet = options[uvName]; useUv[uvSet] = true; useUnmodifiedUv[uvSet] = useUnmodifiedUv[uvSet] || options[mapName] && !options[transformName]; if (options[transformName]) { mapTransforms.push({ name: p, id: options[transformName], uv: options[uvName] }); } } } if (options.forceUv1) { useUv[1] = true; useUnmodifiedUv[1] = useUnmodifiedUv[1] !== undefined ? useUnmodifiedUv[1] : true; } litShader.generateVertexShader(useUv, useUnmodifiedUv, mapTransforms); } prepareFragmentDefines(options, fDefines, shaderPassInfo) { const fDefineSet = (condition, name, value = '')=>{ if (condition) { fDefines.set(name, value); } }; fDefineSet(options.lightMap, 'STD_LIGHTMAP', ''); fDefineSet(options.lightVertexColor, 'STD_LIGHT_VERTEX_COLOR', ''); fDefineSet(options.dirLightMap && options.litOptions.useSpecular, 'STD_LIGHTMAP_DIR', ''); fDefineSet(options.heightMap, 'STD_HEIGHT_MAP', ''); fDefineSet(options.useSpecularColor, 'STD_SPECULAR_COLOR', ''); fDefineSet(options.aoMap || options.aoVertexColor || options.useAO, 'STD_AO', ''); fDefineSet(true, 'STD_OPACITY_DITHER', ditherNames[shaderPassInfo.isForward ? options.litOptions.opacityDither : options.litOptions.opacityShadowDither]); } createShaderDefinition(device, options) { const shaderPassInfo = ShaderPass.get(device).getByIndex(options.litOptions.pass); const isForwardPass = shaderPassInfo.isForward; const litShader = new LitShader(device, options.litOptions); this.createVertexShader(litShader, options); const textureMapping = {}; options.litOptions.fresnelModel = options.litOptions.fresnelModel === 0 ? FRESNEL_SCHLICK : options.litOptions.fresnelModel; const fDefines = litShader.fDefines; this.prepareFragmentDefines(options, fDefines, shaderPassInfo); let lightingUv = ''; if (isForwardPass) { if (options.heightMap) { this._addMapDefines(fDefines, 'height', 'parallaxPS', options, litShader.chunks, textureMapping); } if (options.litOptions.blendType !== BLEND_NONE || options.litOptions.alphaTest || options.litOptions.alphaToCoverage || options.litOptions.opacityDither !== DITHER_NONE) { this._addMapDefines(fDefines, 'opacity', 'opacityPS', options, litShader.chunks, textureMapping); } if (litShader.needsNormal) { if (options.normalMap || options.clearCoatNormalMap) { if (!options.litOptions.hasTangents) { const baseName = options.normalMap ? 'normalMap' : 'clearCoatNormalMap'; lightingUv = this._getUvSourceExpression(`${baseName}Transform`, `${baseName}Uv`, options); } } this._addMapDefines(fDefines, 'normalDetail', 'normalMapPS', options, litShader.chunks, textureMapping, options.normalDetailPackedNormal ? 'xy' : 'xyz'); this._addMapDefines(fDefines, 'normal', 'normalMapPS', options, litShader.chunks, textureMapping, options.packedNormal ? 'xy' : 'xyz'); } if (options.diffuseDetail) { this._addMapDefines(fDefines, 'diffuseDetail', 'diffusePS', options, litShader.chunks, textureMapping, options.diffuseDetailEncoding); } this._addMapDefines(fDefines, 'diffuse', 'diffusePS', options, litShader.chunks, textureMapping, options.diffuseEncoding); if (options.litOptions.useRefraction) { this._addMapDefines(fDefines, 'refraction', 'transmissionPS', options, litShader.chunks, textureMapping); this._addMapDefines(fDefines, 'thickness', 'thicknessPS', options, litShader.chunks, textureMapping); } if (options.litOptions.useIridescence) { this._addMapDefines(fDefines, 'iridescence', 'iridescencePS', options, litShader.chunks, textureMapping); this._addMapDefines(fDefines, 'iridescenceThickness', 'iridescenceThicknessPS', options, litShader.chunks, textureMapping); } if (litShader.lighting && options.litOptions.useSpecular || litShader.reflections) { if (options.litOptions.useSheen) { this._addMapDefines(fDefines, 'sheen', 'sheenPS', options, litShader.chunks, textureMapping, options.sheenEncoding); this._addMapDefines(fDefines, 'sheenGloss', 'sheenGlossPS', options, litShader.chunks, textureMapping); } if (options.litOptions.useMetalness) { this._addMapDefines(fDefines, 'metalness', 'metalnessPS', options, litShader.chunks, textureMapping); this._addMapDefines(fDefines, 'ior', 'iorPS', options, litShader.chunks, textureMapping); } if (options.litOptions.useSpecularityFactor) { this._addMapDefines(fDefines, 'specularityFactor', 'specularityFactorPS', options, litShader.chunks, textureMapping); } if (options.useSpecularColor) { this._addMapDefines(fDefines, 'specular', 'specularPS', options, litShader.chunks, textureMapping, options.specularEncoding); } this._addMapDefines(fDefines, 'gloss', 'glossPS', options, litShader.chunks, textureMapping); } if (options.aoDetail) { this._addMapDefines(fDefines, 'aoDetail', 'aoPS', options, litShader.chunks, textureMapping); } if (options.aoMap || options.aoVertexColor || options.useAO) { this._addMapDefines(fDefines, 'ao', 'aoPS', options, litShader.chunks, textureMapping); } this._addMapDefines(fDefines, 'emissive', 'emissivePS', options, litShader.chunks, textureMapping, options.emissiveEncoding); if (options.litOptions.useClearCoat) { this._addMapDefines(fDefines, 'clearCoat', 'clearCoatPS', options, litShader.chunks, textureMapping); this._addMapDefines(fDefines, 'clearCoatGloss', 'clearCoatGlossPS', options, litShader.chunks, textureMapping); this._addMapDefines(fDefines, 'clearCoatNormal', 'clearCoatNormalPS', options, litShader.chunks, textureMapping, options.clearCoatPackedNormal ? 'xy' : 'xyz'); } if (options.litOptions.enableGGXSpecular) { this._addMapDefines(fDefines, 'anisotropy', 'anisotropyPS', options, litShader.chunks, textureMapping); } if (options.lightMap || options.lightVertexColor) { this._addMapDefines(fDefines, 'light', 'lightmapPS', options, litShader.chunks, textureMapping, options.lightMapEncoding); } } else { const opacityShadowDither = options.litOptions.opacityShadowDither; if (options.litOptions.alphaTest || opacityShadowDither) { this._addMapDefines(fDefines, 'opacity', 'opacityPS', options, litShader.chunks, textureMapping); } } litShader.generateFragmentShader(litShader.chunks.get('stdDeclarationPS'), litShader.chunks.get('stdFrontEndPS'), lightingUv); const includes = MapUtils.merge(litShader.chunks, litShader.includes); const vDefines = litShader.vDefines; options.defines.forEach((value, key)=>vDefines.set(key, value)); options.defines.forEach((value, key)=>fDefines.set(key, value)); const definition = ShaderDefinitionUtils.createDefinition(device, { name: 'StandardShader', attributes: litShader.attributes, shaderLanguage: litShader.shaderLanguage, vertexCode: litShader.vshader, fragmentCode: litShader.fshader, vertexIncludes: includes, fragmentIncludes: includes, fragmentDefines: fDefines, vertexDefines: vDefines }); if (litShader.shaderPassInfo.isForward) { definition.tag = SHADERTAG_MATERIAL; } return definition; } constructor(...args){ super(...args), this.optionsContext = new StandardMaterialOptions(), this.optionsContextMin = new StandardMaterialOptions(); } } const standard = new ShaderGeneratorStandard(); export { _matTex2D, standard };