UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

373 lines (370 loc) 10.7 kB
import { CULLFACE_BACK, SHADERLANGUAGE_GLSL, BLENDEQUATION_REVERSE_SUBTRACT, BLENDMODE_ONE, BLENDEQUATION_ADD, BLENDMODE_ZERO, BLENDMODE_ONE_MINUS_SRC_ALPHA, BLENDMODE_SRC_ALPHA, BLENDMODE_SRC_COLOR, BLENDMODE_DST_COLOR, BLENDMODE_ONE_MINUS_DST_COLOR, BLENDEQUATION_MIN, BLENDEQUATION_MAX } from '../../platform/graphics/constants.js'; import { BlendState } from '../../platform/graphics/blend-state.js'; import { DepthState } from '../../platform/graphics/depth-state.js'; import { BLEND_NONE, BLEND_NORMAL, BLEND_SUBTRACTIVE, BLEND_PREMULTIPLIED, BLEND_ADDITIVE, BLEND_ADDITIVEALPHA, BLEND_MULTIPLICATIVE2X, BLEND_SCREEN, BLEND_MULTIPLICATIVE, BLEND_MIN, BLEND_MAX } 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 { constructor(){ this.meshInstances = []; this.name = 'Untitled'; this.userId = ''; this.id = id++; this.variants = new Map(); this.defines = new Map(); this._definesDirty = false; this.parameters = {}; this.alphaTest = 0; this.alphaToCoverage = false; this._blendState = new BlendState(); this._depthState = new DepthState(); this.cull = CULLFACE_BACK; this.stencilFront = null; this.stencilBack = null; this._shaderChunks = null; this._oldChunks = {}; this._dirtyShader = true; this._shaderVersion = 0; this._scene = null; this.dirty = true; } 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; } 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; } get transparent() { return this._blendState.blend; } _updateTransparency() { const transparent = this.transparent; const meshInstances = this.meshInstances; for(let i = 0; i < meshInstances.length; i++){ meshInstances[i].transparent = 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.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() { const meshInstances = this.meshInstances; for(let i = 0; i < meshInstances.length; i++){ meshInstances[i].updateKey(); } } updateUniforms(device, scene) { if (this._dirtyShader) { this.clearVariants(); } } 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; } clearParameters() { this.parameters = {}; } getParameters() { return this.parameters; } clearVariants() { this.variants.clear(); const meshInstances = this.meshInstances; const count = meshInstances.length; for(let i = 0; i < count; i++){ meshInstances[i].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: data }; } } setParameter(name, data) { if (data === undefined && 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]; } } setParameters(device, names) { const parameters = this.parameters; if (names === undefined) 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 !== undefined && value !== false) { modified = !defines.has(name) || defines.get(name) !== value; defines.set(name, value); } else { modified = defines.has(name); defines.delete(name); } this._definesDirty ||= modified; } getDefine(name) { return this.defines.has(name); } destroy() { this.variants.clear(); for(let i = 0; i < this.meshInstances.length; i++){ const meshInstance = this.meshInstances[i]; meshInstance.clearShaders(); meshInstance._material = null; if (meshInstance.mesh) { const defaultMaterial = getDefaultMaterial(meshInstance.mesh.device); if (this !== defaultMaterial) { meshInstance.material = defaultMaterial; } } } this.meshInstances.length = 0; } addMeshInstanceRef(meshInstance) { this.meshInstances.push(meshInstance); } removeMeshInstanceRef(meshInstance) { const meshInstances = this.meshInstances; const i = meshInstances.indexOf(meshInstance); if (i !== -1) { meshInstances.splice(i, 1); } } } export { Material };