import Class from '../core/Class';
import math from '../math/math';
import semantic from './semantic';
import log from '../utils/log';
import constants from '../constants';
import capabilities from '../renderer/capabilities';
const {
LEQUAL,
BACK,
FRONT,
FRONT_AND_BACK,
ZERO,
FUNC_ADD,
ONE,
SRC_ALPHA,
ONE_MINUS_SRC_ALPHA,
CCW,
ALWAYS,
KEEP,
} = constants;
const blankInfo = {
isBlankInfo: true,
get() {
return undefined;
}
};
/**
* 材质基类,一般不直接使用
* @class
*/
const Material = Class.create(/** @lends Material.prototype */ {
/**
* @default true
* @type {boolean}
*/
isMaterial: true,
/**
* @default Material
* @type {string}
*/
className: 'Material',
/**
* name
* @type {string}
*/
name: null,
/**
* shader cache id
* @default null
* @type {string}
*/
shaderCacheId: null,
/**
* shader name,会在 shader 中加个 SHADER_NAME 宏,不填用 className 代替。
* @default null
* @type {string}
*/
shaderName: null,
/**
* 光照类型
* @default NONE
* @type {string}
*/
lightType: 'NONE',
/**
* 是否开启网格模式
* @default false
* @type {boolean}
*/
wireframe: false,
/**
* front face winding orientation
* @default CCW
* @type {GLenum}
*/
frontFace: CCW,
/**
* 是否开启深度测试
* @default true
* @type {boolean}
*/
depthTest: true,
/**
* SAMPLE_ALPHA_TO_COVERAGE
* @default false
* @type {Boolean}
*/
sampleAlphaToCoverage: false,
/**
* 是否开启depthMask
* @default true
* @type {boolean}
*/
depthMask: true,
/**
* 深度测试Range
* @default [0, 1]
* @type {Array}
*/
depthRange: [0, 1],
/**
* 深度测试方法
* @default LESS
* @type {GLenum}
*/
depthFunc: LEQUAL,
_cullFace: true,
/**
* 法线贴图
* @default null
* @type {Texture}
*/
normalMap: null,
/**
* 视差贴图
* @default null
* @type {Texture}
*/
parallaxMap: null,
/**
* 法线贴图scale
* @default 1
* @type {Number}
*/
normalMapScale: 1,
/**
* 是否忽略透明度
* @type {Boolean}
* @default false
*/
ignoreTranparent: false,
/**
* 是否开启 gamma 矫正
* @type {Boolean}
* @default false
*/
gammaCorrection: false,
/**
* 是否使用物理灯光
* @type {Boolean}
* @default false
*/
usePhysicsLight: false,
/**
* 是否环境贴图和环境光同时生效
* @type {Boolean}
* @default false
*/
isDiffuesEnvAndAmbientLightWorkTogether: false,
/**
* 用户数据
* @default null
* @type {any}
*/
userData: null,
/**
* 渲染顺序数字小的先渲染(透明物体和不透明在不同的队列)
* @default 0
* @type {Number}
*/
renderOrder: 0,
_premultiplyAlpha: true,
/**
* 是否预乘 alpha
* @type {Boolean}
* @default true
*/
premultiplyAlpha: {
get() {
return this._premultiplyAlpha;
},
set(value) {
this._premultiplyAlpha = value;
if (this.transparent) {
this.setDefaultTransparentBlend();
}
}
},
/**
* gammaOutput
* @type {Boolean}
* @deprecated
* @default false
*/
gammaOutput: {
get() {
log.warnOnce('Matrial.gammaOutput', 'material.gammaOutput has deprecated. Use material.gammaCorrection instead.');
return this.gammaCorrection;
},
set(value) {
log.warnOnce('Matrial.gammaOutput', 'material.gammaOutput has deprecated. Use material.gammaCorrection instead.');
this.gammaCorrection = value;
}
},
/**
* gamma值
* @type {Number}
* @default 2.2
*/
gammaFactor: 2.2,
/**
* 是否投射阴影
* @type {Boolean}
* @default true
*/
castShadows: true,
/**
* 是否接受阴影
* @type {Boolean}
* @default true
*/
receiveShadows: true,
/**
* uv transform eg:new Matrix3().fromRotationTranslationScale(Math.PI/2, 0, 0, 2, 2)
* @type {Matrix3}
*/
uvMatrix: null,
/**
* uv1 transform eg:new Matrix3().fromRotationTranslationScale(Math.PI/2, 0, 0, 2, 2)
* @type {Matrix3}
*/
uvMatrix1: null,
/**
* 是否开启 CullFace
* @default true
* @type {boolean}
*/
cullFace: {
get() {
return this._cullFace;
},
set(value) {
this._cullFace = value;
if (value) {
this.cullFaceType = this._cullFaceType;
} else {
this._side = FRONT_AND_BACK;
}
}
},
_cullFaceType: BACK,
/**
* CullFace 类型
* @default BACK
* @type {GLenum}
*/
cullFaceType: {
get() {
return this._cullFaceType;
},
set(value) {
this._cullFaceType = value;
if (this._cullFace) {
if (value === BACK) {
this._side = FRONT;
} else if (value === FRONT) {
this._side = BACK;
}
}
}
},
_side: FRONT,
/**
* 显示面,可选值 FRONT, BACK, FRONT_AND_BACK
* @type {GLenum}
* @default FRONT
*/
side: {
get() {
return this._side;
},
set(value) {
if (this._side !== value) {
this._side = value;
if (value === FRONT_AND_BACK) {
this._cullFace = false;
} else {
this._cullFace = true;
if (value === FRONT) {
this._cullFaceType = BACK;
} else if (value === BACK) {
this._cullFaceType = FRONT;
}
}
}
}
},
/**
* 是否开启颜色混合
* @default false
* @type {boolean}
*/
blend: false,
/**
* 颜色混合方式
* @default FUNC_ADD
* @type {GLenum}
*/
blendEquation: FUNC_ADD,
/**
* 透明度混合方式
* @default FUNC_ADD
* @type {GLenum}
*/
blendEquationAlpha: FUNC_ADD,
/**
* 颜色混合来源比例
* @default ONE
* @type {GLenum}
*/
blendSrc: ONE,
/**
* 颜色混合目标比例
* @default ZERO
* @type {GLenum}
*/
blendDst: ZERO,
/**
* 透明度混合来源比例
* @default ONE
* @type {GLenum}
*/
blendSrcAlpha: ONE,
/**
* 透明度混合目标比例
* @default ONE
* @type {GLenum}
*/
blendDstAlpha: ZERO,
/**
* stencilTest
* @type {boolean}
* @default 1
*/
stencilTest: false,
/**
* stencilMask
* @type {number}
* @default 0xff
*/
stencilMask: 0xff,
/**
* stencilFunc func
* @type {GLenum}
* @default ALWAYS
*/
stencilFunc: ALWAYS,
/**
* stencilFunc ref
* @type {number}
* @default 1
*/
stencilFuncRef: 1,
/**
* stencilFunc mask
* @type {number}
* @default 0xff
*/
stencilFuncMask: 0xff,
/**
* stencilOp fail
* @type {GLenum}
* @default KEEP
*/
stencilOpFail: KEEP,
/**
* stencilOp zfail
* @type {GLenum}
* @default KEEP
*/
stencilOpZFail: KEEP,
/**
* stencilOp zpass
* @type {GLenum}
* @default KEEP
*/
stencilOpZPass: KEEP,
/**
* 当前是否需要强制更新
* @default false
* @type {boolean}
*/
isDirty: false,
/**
* 透明度 0~1
* @default 1
* @type {number}
*/
transparency: 1,
_transparent: false,
/**
* 是否需要透明
* @default false
* @type {boolean}
*/
transparent: {
get() {
return this._transparent;
},
set(value) {
if (this._transparent !== value) {
this._transparent = value;
if (!value) {
this.blend = false;
this.depthMask = true;
} else {
this.setDefaultTransparentBlend();
}
}
}
},
setDefaultTransparentBlend() {
this.blend = true;
this.depthMask = false;
if (this.premultiplyAlpha) {
this.blendSrc = ONE;
this.blendDst = ONE_MINUS_SRC_ALPHA;
this.blendSrcAlpha = ONE;
this.blendDstAlpha = ONE_MINUS_SRC_ALPHA;
} else {
this.blendSrc = SRC_ALPHA;
this.blendDst = ONE_MINUS_SRC_ALPHA;
this.blendSrcAlpha = SRC_ALPHA;
this.blendDstAlpha = ONE_MINUS_SRC_ALPHA;
}
},
/**
* 透明度剪裁,如果渲染的颜色透明度大于等于这个值的话渲染为完全不透明,否则渲染为完全透明
* @default 0
* @type {number}
*/
alphaCutoff: 0,
/**
* 是否使用HDR
* @default false
* @type {Boolean}
*/
useHDR: false,
/**
* 曝光度,仅在 useHDR 为 true 时生效
* @default 1
* @type {Number}
*/
exposure: 1,
/**
* 是否开启 texture lod
* @default false
* @type {Boolean}
*/
enableTextureLod: false,
/**
* 是否开启 drawBuffers
* @default false
* @type {Boolean}
*/
enableDrawBuffers: false,
/**
* 是否需要加基础 uniforms
* @type {Boolean}
* @default true
*/
needBasicUnifroms: true,
/**
* 是否需要加基础 attributes
* @type {Boolean}
* @default true
*/
needBasicAttributes: true,
/**
* @constructs
* @param {object} [params] 初始化参数,所有params都会复制到实例上
*/
constructor(params) {
/**
* @type {string}
*/
this.id = math.generateUUID(this.className);
/**
* 可以通过指定,semantic来指定值的获取方式,或者自定义get方法
* @default {}
* @type {object}
*/
this.uniforms = {};
/**
* 可以通过指定,semantic来指定值的获取方式,或者自定义get方法
* @default {}
* @type {object}
*/
this.attributes = {};
Object.assign(this, params);
if (this.needBasicAttributes) {
this.addBasicAttributes();
}
if (this.needBasicUnifroms) {
this.addBasicUniforms();
}
},
/**
* 增加基础 attributes
*/
addBasicAttributes() {
const attributes = this.attributes;
this._copyProps(attributes, {
a_position: 'POSITION',
a_normal: 'NORMAL',
a_tangent: 'TANGENT',
a_texcoord0: 'TEXCOORD_0',
a_texcoord1: 'TEXCOORD_1',
a_color: 'COLOR_0',
a_skinIndices: 'SKININDICES',
a_skinWeights: 'SKINWEIGHTS'
});
['POSITION', 'NORMAL', 'TANGENT'].forEach((name) => {
const camelName = name.slice(0, 1) + name.slice(1).toLowerCase();
for (let i = 0; i < 8; i++) {
const morphAttributeName = 'a_morph' + camelName + i;
if (attributes[morphAttributeName] === undefined) {
attributes[morphAttributeName] = 'MORPH' + name + i;
}
}
});
},
/**
* 增加基础 uniforms
*/
addBasicUniforms() {
this._copyProps(this.uniforms, {
u_modelMatrix: 'MODEL',
u_viewMatrix: 'VIEW',
u_projectionMatrix: 'PROJECTION',
u_modelViewMatrix: 'MODELVIEW',
u_modelViewProjectionMatrix: 'MODELVIEWPROJECTION',
u_viewInverseNormalMatrix: 'VIEWINVERSEINVERSETRANSPOSE',
u_normalMatrix: 'MODELVIEWINVERSETRANSPOSE',
u_normalWorldMatrix: 'MODELINVERSETRANSPOSE',
u_cameraPosition: 'CAMERAPOSITION',
u_rendererSize: 'RENDERERSIZE',
u_logDepth: 'LOGDEPTH',
// light
u_ambientLightsColor: 'AMBIENTLIGHTSCOLOR',
u_directionalLightsColor: 'DIRECTIONALLIGHTSCOLOR',
u_directionalLightsInfo: 'DIRECTIONALLIGHTSINFO',
u_directionalLightsShadowMap: 'DIRECTIONALLIGHTSSHADOWMAP',
u_directionalLightsShadowMapSize: 'DIRECTIONALLIGHTSSHADOWMAPSIZE',
u_directionalLightsShadowBias: 'DIRECTIONALLIGHTSSHADOWBIAS',
u_directionalLightSpaceMatrix: 'DIRECTIONALLIGHTSPACEMATRIX',
u_pointLightsPos: 'POINTLIGHTSPOS',
u_pointLightsColor: 'POINTLIGHTSCOLOR',
u_pointLightsInfo: 'POINTLIGHTSINFO',
u_pointLightsRange: 'POINTLIGHTSRANGE',
u_pointLightsShadowBias: 'POINTLIGHTSSHADOWBIAS',
u_pointLightsShadowMap: 'POINTLIGHTSSHADOWMAP',
u_pointLightSpaceMatrix: 'POINTLIGHTSPACEMATRIX',
u_pointLightCamera: 'POINTLIGHTCAMERA',
u_spotLightsPos: 'SPOTLIGHTSPOS',
u_spotLightsDir: 'SPOTLIGHTSDIR',
u_spotLightsColor: 'SPOTLIGHTSCOLOR',
u_spotLightsCutoffs: 'SPOTLIGHTSCUTOFFS',
u_spotLightsInfo: 'SPOTLIGHTSINFO',
u_spotLightsRange: 'SPOTLIGHTSRANGE',
u_spotLightsShadowMap: 'SPOTLIGHTSSHADOWMAP',
u_spotLightsShadowMapSize: 'SPOTLIGHTSSHADOWMAPSIZE',
u_spotLightsShadowBias: 'SPOTLIGHTSSHADOWBIAS',
u_spotLightSpaceMatrix: 'SPOTLIGHTSPACEMATRIX',
u_areaLightsPos: 'AREALIGHTSPOS',
u_areaLightsColor: 'AREALIGHTSCOLOR',
u_areaLightsWidth: 'AREALIGHTSWIDTH',
u_areaLightsHeight: 'AREALIGHTSHEIGHT',
u_areaLightsLtcTexture1: 'AREALIGHTSLTCTEXTURE1',
u_areaLightsLtcTexture2: 'AREALIGHTSLTCTEXTURE2',
// joint
u_jointMat: 'JOINTMATRIX',
u_jointMatTexture: 'JOINTMATRIXTEXTURE',
u_jointMatTextureSize: 'JOINTMATRIXTEXTURESIZE',
// quantization
u_positionDecodeMat: 'POSITIONDECODEMAT',
u_normalDecodeMat: 'NORMALDECODEMAT',
u_uvDecodeMat: 'UVDECODEMAT',
u_uv1DecodeMat: 'UV1DECODEMAT',
// morph
u_morphWeights: 'MORPHWEIGHTS',
u_normalMapScale: 'NORMALMAPSCALE',
u_emission: 'EMISSION',
u_transparency: 'TRANSPARENCY',
// uv matrix
u_uvMatrix: 'UVMATRIX_0',
u_uvMatrix1: 'UVMATRIX_1',
// other info
u_fogColor: 'FOGCOLOR',
u_fogInfo: 'FOGINFO',
u_alphaCutoff: 'ALPHACUTOFF',
u_exposure: 'EXPOSURE',
u_gammaFactor: 'GAMMAFACTOR',
});
this.addTextureUniforms({
u_normalMap: 'NORMALMAP',
u_parallaxMap: 'PARALLAXMAP',
u_emission: 'EMISSION',
u_transparency: 'TRANSPARENCY'
});
},
/**
* 增加贴图 uniforms
* @param {Object} textureUniforms textureName:semanticName 键值对
*/
addTextureUniforms(textureUniforms) {
const uniforms = {};
for (const uniformName in textureUniforms) {
const semanticName = textureUniforms[uniformName];
uniforms[uniformName] = semanticName;
uniforms[`${uniformName}.texture`] = semanticName;
uniforms[`${uniformName}.uv`] = `${semanticName}UV`;
}
this._copyProps(this.uniforms, uniforms);
},
/**
* 获取渲染选项值
* @param {Object} [option={}] 渲染选项值
* @return {Object} 渲染选项值
*/
getRenderOption(option = {}) {
const lightType = this.lightType;
option[`LIGHT_TYPE_${lightType}`] = 1;
option.SIDE = this.side;
if (lightType !== 'NONE') {
option.HAS_LIGHT = 1;
}
if (this.premultiplyAlpha) {
option.PREMULTIPLY_ALPHA = 1;
}
if (capabilities.SHADER_TEXTURE_LOD && this.enableTextureLod) {
option.USE_SHADER_TEXTURE_LOD = 1;
}
if (capabilities.DRAW_BUFFERS && this.enableDrawBuffers) {
option.USE_DRAW_BUFFERS = 1;
}
const textureOption = this._textureOption.reset(option);
if (option.HAS_LIGHT) {
option.HAS_NORMAL = 1;
textureOption.add(this.normalMap, 'NORMAL_MAP', () => {
if (this.normalMapScale !== 1) {
option.NORMAL_MAP_SCALE = 1;
}
});
}
textureOption.add(this.parallaxMap, 'PARALLAX_MAP');
textureOption.add(this.emission, 'EMISSION_MAP');
textureOption.add(this.transparency, 'TRANSPARENCY_MAP');
if (this.ignoreTranparent) {
option.IGNORE_TRANSPARENT = 1;
}
if (this.alphaCutoff > 0) {
option.ALPHA_CUTOFF = 1;
}
if (this.useHDR) {
option.USE_HDR = 1;
}
if (this.gammaCorrection) {
option.GAMMA_CORRECTION = 1;
}
if (this.receiveShadows) {
option.RECEIVE_SHADOWS = 1;
}
if (this.castShadows) {
option.CAST_SHADOWS = 1;
}
if (this.uvMatrix) {
option.UV_MATRIX = 1;
}
if (this.uvMatrix1) {
option.UV_MATRIX1 = 1;
}
if (this.usePhysicsLight) {
option.USE_PHYSICS_LIGHT = 1;
}
if (this.isDiffuesEnvAndAmbientLightWorkTogether) {
option.IS_DIFFUESENV_AND_AMBIENTLIGHT_WORK_TOGETHER = 1;
}
textureOption.update();
return option;
},
_textureOption: {
uvTypes: null,
option: null,
reset(option) {
this.option = option;
this.uvTypes = {};
return this;
},
add(texture, optionName, callback) {
if (texture && texture.isTexture) {
const {
uvTypes,
option
} = this;
const uv = texture.uv || 0;
uvTypes[uv] = 1;
option[optionName] = uv;
if (texture.isCubeTexture) {
option[`${optionName}_CUBE`] = 1;
}
if (callback) {
callback(texture);
}
}
return this;
},
update() {
const supportUV = [0, 1];
const {
uvTypes,
option
} = this;
for (const type in uvTypes) {
if (supportUV.indexOf(Number(type)) !== -1) {
option[`HAS_TEXCOORD${type}`] = 1;
} else {
log.warnOnce(`Material._textureOption.update(${type})`, `uv_${type} not support!`);
option.HAS_TEXCOORD0 = 1;
}
}
return this;
}
},
/**
* 获取 instanced uniforms
* @private
* @return {Object}
*/
getInstancedUniforms() {
let instancedUniforms = this._instancedUniforms;
if (!this._instancedUniforms) {
const uniforms = this.uniforms;
instancedUniforms = this._instancedUniforms = [];
for (let name in uniforms) {
const info = this.getUniformInfo(name);
if (info.isDependMesh && !info.notSupportInstanced) {
instancedUniforms.push({
name,
info
});
}
}
}
return instancedUniforms;
},
getUniformData(name, mesh, programInfo) {
return this.getUniformInfo(name).get(mesh, this, programInfo);
},
getAttributeData(name, mesh, programInfo) {
return this.getAttributeInfo(name).get(mesh, this, programInfo);
},
getUniformInfo(name) {
return this.getInfo('uniforms', name);
},
getAttributeInfo(name) {
return this.getInfo('attributes', name);
},
getInfo(dataType, name) {
const dataDict = this[dataType];
let info = dataDict[name];
if (typeof info === 'string') {
info = semantic[info];
}
if (!info || !info.get) {
log.warnOnce('material.getInfo-' + name, 'Material.getInfo: no this semantic:' + name);
info = blankInfo;
}
return info;
},
/**
* clone 当前Material
* @return {Material} 返回clone的Material
*/
clone() {
const newMaterial = new this.constructor();
for (let key in this) {
if (key !== 'id') {
newMaterial[key] = this[key];
}
}
return newMaterial;
},
/**
* 销毁贴图
* @return {Material} this
*/
destroyTextures() {
this.getTextures().forEach((texture) => {
texture.destroy();
});
},
/**
* 获取材质全部贴图
* @return {Texture[]}
*/
getTextures() {
const textures = [];
for (const propName in this) {
const texture = this[propName];
if (texture && texture.isTexture) {
textures.push(texture);
}
}
return textures;
},
/**
* 复制属性,只有没属性时才会覆盖
* @private
* @param {Object} origin
* @param {Object} data
*/
_copyProps(origin, data) {
for (const key in data) {
if (origin[key] === undefined) {
origin[key] = data[key];
}
}
},
/**
* 获取阴影材质,子类可重写
* @param {Material} shadowMaterial 通用阴影材质
* @return {Material}
*/
getShadowMaterial(shadowMaterial) {
if (shadowMaterial.side !== this.side) {
shadowMaterial.side = this.side;
shadowMaterial.isDirty = true;
}
if (shadowMaterial.frontFace !== this.frontFace) {
shadowMaterial.frontFace = this.frontFace;
shadowMaterial.isDirty = true;
}
return shadowMaterial;
},
});
export default Material;