playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
357 lines (356 loc) • 10.2 kB
JavaScript
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
};