UNPKG

playcanvas

Version:

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

372 lines (371 loc) 18.1 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { Debug } from "../../../core/debug.js"; import { BLEND_NONE, DITHER_NONE, ditherNames, FRESNEL_SCHLICK, SHADER_FORWARD, SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED } 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 { constructor() { super(...arguments); // Shared Standard Material option structures __publicField(this, "optionsContext", new StandardMaterialOptions()); __publicField(this, "optionsContextMin", new StandardMaterialOptions()); } 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: ${definesHash} ${props.map((prop) => prop + options[prop]).join("\n")}${LitOptionsUtils.generateKey(options.litOptions)}`; return key; } /** * Get the code with which to replace '*_TEXTURE_UV' in the map shader functions. * * @param {string} transformPropName - Name of the transform id in the options block. Usually "basenameTransform". * @param {string} uVPropName - Name of the UV channel in the options block. Usually "basenameUv". * @param {object} options - The options passed into createShaderDefinition. * @returns {string} The code used to replace '*_TEXTURE_UV' in the shader code. * @private */ _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(code, propName, chunkName, chunks) { Debug.call(() => { const requiredChangeStrings = []; const trackChange = (oldSyntax, newSyntax) => { if (code.includes(oldSyntax)) { requiredChangeStrings.push(` ${oldSyntax} -> ${newSyntax}`); } }; [ ["$UV", `{STD_${propName}_TEXTURE_UV}`], ["$CH", `{STD_${propName}_TEXTURE_CHANNEL}`], ["$SAMPLER", `{STD_${propName}_TEXTURE_NAME}`], ["$DECODE", `{STD_${propName}_TEXTURE_DECODE}`], ["$VC", `{STD_${propName}_VERTEX_CHANNEL}`], ["$DETAILMODE", `{STD_${propName}_DETAILMODE}`], ["unpackNormal(", `{STD_${propName}_TEXTURE_DECODE}(`] ].forEach(([oldSyntax, newSyntax]) => trackChange(oldSyntax, newSyntax)); [ ["MAPFLOAT", `STD_${propName}_CONSTANT`], ["MAPCOLOR", `STD_${propName}_CONSTANT`], ["MAPVERTEX", `STD_${propName}_VERTEX`], ["MAPTEXTURE", `STD_${propName}_TEXTURE`], ["MAPINVERT", `STD_${propName}_INVERT`] ].forEach(([oldSyntax, newSyntax]) => trackChange(oldSyntax, newSyntax)); if (code.includes("$texture2DSAMPLE")) { trackChange("$texture2DSAMPLE", "(Macro no longer supported - remove/refactor)"); } if (requiredChangeStrings.length > 0) { Debug.errorOnce(`Shader chunk ${chunkName} is no longer in a compatible format. Please make these replacements to bring it to the current version: ${requiredChangeStrings.join("\n")}`, { code }); } }); } /** * Add shader defines for a texture map. * * @param {Map<string, string>} fDefines - The fragment defines. * @param {string} propName - The base name of the map: diffuse | emissive | opacity | light | height | metalness | specular | gloss | ao. * @param {string} chunkName - The name of the chunk to use. Usually "basenamePS". * @param {object} options - The options passed into createShaderDefinition. * @param {Map<string, string>} chunks - The set of shader chunks to choose from. * @param {object} mapping - The mapping between chunk and sampler * @param {string|null} encoding - The texture's encoding * @private */ _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); Debug.assert(chunkCode, `Shader chunk ${chunkName} not found.`); Debug.call(() => { if (chunkCode) { const code = chunks.get(chunkName).replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, "$1"); this._validateMapChunk(code, propNameCaps, chunkName, chunks); if (vertexColorOption) { if (code.includes("gammaCorrectInputVec3(saturate3(vVertexColor.") || code.includes("gammaCorrectInput(saturate(vVertexColor.")) { Debug.errorOnce(`Shader chunk ${chunkName} contains gamma correction code which is incompatible with vertexColorGamma=true. Please remove gamma correction calls from the chunk.`, { code }); } } } }); 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, _matTex2D2) { if (_matTex2D2[p] > 0) { if (_matTex2D2[p] < chan.length) { return chan.substring(0, _matTex2D2[p]); } else if (_matTex2D2[p] > chan.length) { let str = chan; const chr = str.charAt(str.length - 1); const addLen = _matTex2D2[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] !== void 0 ? useUnmodifiedUv[1] : true; } litShader.generateVertexShader(useUv, useUnmodifiedUv, mapTransforms); } /** * @param {StandardMaterialOptions} options - The create options. * @param {Map<string, string>} fDefines - The fragment defines. * @param {ShaderPass} shaderPassInfo - The shader pass info. */ 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]); } /** * @param {GraphicsDevice} device - The graphics device. * @param {StandardMaterialOptions} options - The create options. * @returns {object} Returns the created shader definition. */ 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; } } const standard = new ShaderGeneratorStandard(); export { _matTex2D, standard };