UNPKG

playcanvas

Version:

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

357 lines (356 loc) 10.2 kB
import { BLENDMODE_ZERO, BLENDMODE_ONE, BLENDMODE_SRC_COLOR, BLENDMODE_DST_COLOR, BLENDMODE_ONE_MINUS_DST_COLOR, BLENDMODE_SRC_ALPHA, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDEQUATION_ADD, BLENDEQUATION_REVERSE_SUBTRACT, BLENDEQUATION_MIN, BLENDEQUATION_MAX, CULLFACE_BACK, SHADERLANGUAGE_GLSL, FRONTFACE_CCW } from "../../platform/graphics/constants.js"; import { BlendState } from "../../platform/graphics/blend-state.js"; import { DepthState } from "../../platform/graphics/depth-state.js"; import { BLEND_ADDITIVE, BLEND_NORMAL, BLEND_NONE, BLEND_PREMULTIPLIED, BLEND_MULTIPLICATIVE, BLEND_ADDITIVEALPHA, BLEND_MULTIPLICATIVE2X, BLEND_SCREEN, BLEND_MIN, BLEND_MAX, BLEND_SUBTRACTIVE } from "../constants.js"; import { getDefaultMaterial } from "./default-material.js"; import { ShaderChunks } from "../shader-lib/shader-chunks.js"; const blendModes = []; blendModes[BLEND_SUBTRACTIVE] = { src: BLENDMODE_ONE, dst: BLENDMODE_ONE, op: BLENDEQUATION_REVERSE_SUBTRACT }; blendModes[BLEND_NONE] = { src: BLENDMODE_ONE, dst: BLENDMODE_ZERO, op: BLENDEQUATION_ADD }; blendModes[BLEND_NORMAL] = { src: BLENDMODE_SRC_ALPHA, dst: BLENDMODE_ONE_MINUS_SRC_ALPHA, op: BLENDEQUATION_ADD, alphaSrc: BLENDMODE_ONE }; blendModes[BLEND_PREMULTIPLIED] = { src: BLENDMODE_ONE, dst: BLENDMODE_ONE_MINUS_SRC_ALPHA, op: BLENDEQUATION_ADD }; blendModes[BLEND_ADDITIVE] = { src: BLENDMODE_ONE, dst: BLENDMODE_ONE, op: BLENDEQUATION_ADD }; blendModes[BLEND_ADDITIVEALPHA] = { src: BLENDMODE_SRC_ALPHA, dst: BLENDMODE_ONE, op: BLENDEQUATION_ADD }; blendModes[BLEND_MULTIPLICATIVE2X] = { src: BLENDMODE_DST_COLOR, dst: BLENDMODE_SRC_COLOR, op: BLENDEQUATION_ADD }; blendModes[BLEND_SCREEN] = { src: BLENDMODE_ONE_MINUS_DST_COLOR, dst: BLENDMODE_ONE, op: BLENDEQUATION_ADD }; blendModes[BLEND_MULTIPLICATIVE] = { src: BLENDMODE_DST_COLOR, dst: BLENDMODE_ZERO, op: BLENDEQUATION_ADD }; blendModes[BLEND_MIN] = { src: BLENDMODE_ONE, dst: BLENDMODE_ONE, op: BLENDEQUATION_MIN }; blendModes[BLEND_MAX] = { src: BLENDMODE_ONE, dst: BLENDMODE_ONE, op: BLENDEQUATION_MAX }; let id = 0; class Material { meshInstances = /* @__PURE__ */ new Set(); name = "Untitled"; userId = ""; id = id++; variants = /* @__PURE__ */ new Map(); defines = /* @__PURE__ */ new Map(); _definesDirty = false; parameters = {}; alphaTest = 0; alphaToCoverage = false; _blendState = new BlendState(); _depthState = new DepthState(); cull = CULLFACE_BACK; frontFace = FRONTFACE_CCW; stencilFront = null; stencilBack = null; _shaderChunks = null; // this is deprecated, keeping for backwards compatibility _oldChunks = {}; _dirtyShader = true; constructor() { if (new.target === Material) { } } get hasShaderChunks() { return this._shaderChunks != null; } get shaderChunks() { if (!this._shaderChunks) { this._shaderChunks = new ShaderChunks(); } return this._shaderChunks; } getShaderChunks(shaderLanguage = SHADERLANGUAGE_GLSL) { const chunks = this.shaderChunks; return shaderLanguage === SHADERLANGUAGE_GLSL ? chunks.glsl : chunks.wgsl; } set shaderChunksVersion(value) { this.shaderChunks.version = value; } get shaderChunksVersion() { return this.shaderChunks.version; } set chunks(value) { this._oldChunks = value; } get chunks() { Object.assign(this._oldChunks, Object.fromEntries(this.shaderChunks.glsl)); return this._oldChunks; } set depthBias(value) { this._depthState.depthBias = value; } get depthBias() { return this._depthState.depthBias; } set slopeDepthBias(value) { this._depthState.depthBiasSlope = value; } get slopeDepthBias() { return this._depthState.depthBiasSlope; } _shaderVersion = 0; _scene = null; dirty = true; set redWrite(value) { this._blendState.redWrite = value; } get redWrite() { return this._blendState.redWrite; } set greenWrite(value) { this._blendState.greenWrite = value; } get greenWrite() { return this._blendState.greenWrite; } set blueWrite(value) { this._blendState.blueWrite = value; } get blueWrite() { return this._blendState.blueWrite; } set alphaWrite(value) { this._blendState.alphaWrite = value; } get alphaWrite() { return this._blendState.alphaWrite; } // returns boolean depending on material being transparent get transparent() { return this._blendState.blend; } _updateTransparency() { for (const meshInstance of this.meshInstances) { meshInstance.transparent = this.transparent; } } set blendState(value) { this._blendState.copy(value); this._updateTransparency(); } get blendState() { return this._blendState; } set blendType(type) { const blendMode = blendModes[type]; this._blendState.setColorBlend(blendMode.op, blendMode.src, blendMode.dst); this._blendState.setAlphaBlend(blendMode.alphaOp ?? blendMode.op, blendMode.alphaSrc ?? blendMode.src, blendMode.alphaDst ?? blendMode.dst); const blend = type !== BLEND_NONE; if (this._blendState.blend !== blend) { this._blendState.blend = blend; this._updateTransparency(); } this._updateMeshInstanceKeys(); } get blendType() { if (!this.transparent) { return BLEND_NONE; } const { colorOp, colorSrcFactor, colorDstFactor, alphaOp, alphaSrcFactor, alphaDstFactor } = this._blendState; for (let i = 0; i < blendModes.length; i++) { const blendMode = blendModes[i]; if (blendMode.src === colorSrcFactor && blendMode.dst === colorDstFactor && blendMode.op === colorOp && blendMode.src === alphaSrcFactor && blendMode.dst === alphaDstFactor && blendMode.op === alphaOp) { return i; } } return BLEND_NORMAL; } set depthState(value) { this._depthState.copy(value); } get depthState() { return this._depthState; } set depthTest(value) { this._depthState.test = value; } get depthTest() { return this._depthState.test; } set depthFunc(value) { this._depthState.func = value; } get depthFunc() { return this._depthState.func; } set depthWrite(value) { this._depthState.write = value; } get depthWrite() { return this._depthState.write; } copy(source) { this.name = source.name; this.alphaTest = source.alphaTest; this.alphaToCoverage = source.alphaToCoverage; this._blendState.copy(source._blendState); this._depthState.copy(source._depthState); this.cull = source.cull; this.frontFace = source.frontFace; this.stencilFront = source.stencilFront?.clone(); if (source.stencilBack) { this.stencilBack = source.stencilFront === source.stencilBack ? this.stencilFront : source.stencilBack.clone(); } this.clearParameters(); for (const name in source.parameters) { if (source.parameters.hasOwnProperty(name)) { this._setParameterSimple(name, source.parameters[name].data); } } this.defines.clear(); source.defines.forEach((value, key) => this.defines.set(key, value)); this._shaderChunks = source.hasShaderChunks ? new ShaderChunks() : null; this._shaderChunks?.copy(source._shaderChunks); return this; } clone() { const clone = new this.constructor(); return clone.copy(this); } _updateMeshInstanceKeys() { for (const meshInstance of this.meshInstances) { meshInstance.updateKey(); } } updateUniforms(device, scene) { if (this._dirtyShader) { this.clearVariants(); this._dirtyShader = false; } } getShaderVariant(params) { } update() { if (Object.keys(this._oldChunks).length > 0) { for (const [key, value] of Object.entries(this._oldChunks)) { this.shaderChunks.glsl.set(key, value); delete this._oldChunks[key]; } } if (this._definesDirty || this._shaderChunks?.isDirty()) { this._definesDirty = false; this._shaderChunks?.resetDirty(); this.clearVariants(); } this.dirty = true; } // Parameter management clearParameters() { this.parameters = {}; } getParameters() { return this.parameters; } clearVariants() { this.variants.clear(); for (const meshInstance of this.meshInstances) { meshInstance.clearShaders(); } } getParameter(name) { return this.parameters[name]; } _setParameterSimple(name, data) { const param = this.parameters[name]; if (param) { param.data = data; } else { this.parameters[name] = { scopeId: null, data }; } } setParameter(name, data) { if (data === void 0 && typeof name === "object") { const uniformObject = name; if (uniformObject.length) { for (let i = 0; i < uniformObject.length; i++) { this.setParameter(uniformObject[i]); } return; } name = uniformObject.name; data = uniformObject.value; } this._setParameterSimple(name, data); } deleteParameter(name) { if (this.parameters[name]) { delete this.parameters[name]; } } // used to apply parameters from this material into scope of uniforms, called internally by forward-renderer // optional list of parameter names to be set can be specified, otherwise all parameters are set setParameters(device, names) { const parameters = this.parameters; if (names === void 0) names = parameters; for (const paramName in names) { const parameter = parameters[paramName]; if (parameter) { if (!parameter.scopeId) { parameter.scopeId = device.scope.resolve(paramName); } parameter.scopeId.setValue(parameter.data); } } } setDefine(name, value) { let modified = false; const { defines } = this; if (value !== void 0 && value !== false) { modified = !defines.has(name) || defines.get(name) !== value; defines.set(name, value); } else { modified = defines.has(name); defines.delete(name); } this._definesDirty || (this._definesDirty = modified); } getDefine(name) { return this.defines.has(name); } destroy() { this.variants.clear(); for (const meshInstance of this.meshInstances) { meshInstance.clearShaders(); meshInstance._material = null; if (meshInstance.mesh) { const defaultMaterial = getDefaultMaterial(meshInstance.mesh.device); if (this !== defaultMaterial) { meshInstance.material = defaultMaterial; } } else { } } this.meshInstances.clear(); } addMeshInstanceRef(meshInstance) { this.meshInstances.add(meshInstance); } removeMeshInstanceRef(meshInstance) { this.meshInstances.delete(meshInstance); } } export { Material };