playcanvas
Version:
PlayCanvas WebGL game engine
339 lines (336 loc) • 19.7 kB
JavaScript
import { PIXELFORMAT_DXT5, TEXTURETYPE_SWIZZLEGGGR, PIXELFORMAT_RGBA8 } from '../../platform/graphics/constants.js';
import { SHADERDEF_TANGENTS, SHADERDEF_SCREENSPACE, SHADERDEF_SKIN, SHADERDEF_BATCH, SHADERDEF_INSTANCING, SHADERDEF_MORPH_POSITION, SHADERDEF_MORPH_NORMAL, SHADERDEF_MORPH_TEXTURE_BASED_INT, BLEND_NONE, DITHER_NONE, FOG_NONE, TONEMAP_NONE, SHADERDEF_NOSHADOW, SHADERDEF_LM, SHADERDEF_DIRLM, SHADERDEF_LMAMBIENT, MASK_AFFECT_DYNAMIC, LIGHTTYPE_DIRECTIONAL, LIGHTTYPE_OMNI, LIGHTTYPE_SPOT, SHADERDEF_UV0, SHADERDEF_UV1, SHADERDEF_VCOLOR, SHADER_PREPASS } from '../constants.js';
import { _matTex2D } from '../shader-lib/programs/standard.js';
import { LitMaterialOptionsBuilder } from './lit-material-options-builder.js';
var arraysEqual = (a, b)=>{
if (a.length !== b.length) {
return false;
}
for(var i = 0; i < a.length; ++i){
if (a[i] !== b[i]) {
return false;
}
}
return true;
};
var notWhite = (color)=>{
return color.r !== 1 || color.g !== 1 || color.b !== 1;
};
var notBlack = (color)=>{
return color.r !== 0 || color.g !== 0 || color.b !== 0;
};
class StandardMaterialOptionsBuilder {
// Minimal options for Depth and Shadow passes
updateMinRef(options, scene, stdMat, objDefs, pass, sortedLights) {
this._updateSharedOptions(options, scene, stdMat, objDefs, pass);
this._updateMinOptions(options, stdMat, pass);
this._updateUVOptions(options, stdMat, objDefs, true);
}
updateRef(options, scene, cameraShaderParams, stdMat, objDefs, pass, sortedLights) {
this._updateSharedOptions(options, scene, stdMat, objDefs, pass);
this._updateEnvOptions(options, stdMat, scene, cameraShaderParams);
this._updateMaterialOptions(options, stdMat);
options.litOptions.hasTangents = objDefs && (objDefs & SHADERDEF_TANGENTS) !== 0;
this._updateLightOptions(options, scene, stdMat, objDefs, sortedLights);
this._updateUVOptions(options, stdMat, objDefs, false, cameraShaderParams);
}
_updateSharedOptions(options, scene, stdMat, objDefs, pass) {
options.forceUv1 = stdMat.forceUv1;
// USER ATTRIBUTES
if (stdMat.userAttributes) {
options.litOptions.userAttributes = Object.fromEntries(stdMat.userAttributes.entries());
}
options.litOptions.chunks = stdMat.chunks || {};
options.litOptions.pass = pass;
options.litOptions.alphaTest = stdMat.alphaTest > 0;
options.litOptions.blendType = stdMat.blendType;
options.litOptions.screenSpace = objDefs && (objDefs & SHADERDEF_SCREENSPACE) !== 0;
options.litOptions.skin = objDefs && (objDefs & SHADERDEF_SKIN) !== 0;
options.litOptions.batch = objDefs && (objDefs & SHADERDEF_BATCH) !== 0;
options.litOptions.useInstancing = objDefs && (objDefs & SHADERDEF_INSTANCING) !== 0;
options.litOptions.useMorphPosition = objDefs && (objDefs & SHADERDEF_MORPH_POSITION) !== 0;
options.litOptions.useMorphNormal = objDefs && (objDefs & SHADERDEF_MORPH_NORMAL) !== 0;
options.litOptions.useMorphTextureBasedInt = objDefs && (objDefs & SHADERDEF_MORPH_TEXTURE_BASED_INT) !== 0;
options.litOptions.nineSlicedMode = stdMat.nineSlicedMode || 0;
// clustered lighting features (in shared options as shadow pass needs this too)
if (scene.clusteredLightingEnabled && stdMat.useLighting) {
options.litOptions.clusteredLightingEnabled = true;
options.litOptions.clusteredLightingCookiesEnabled = scene.lighting.cookiesEnabled;
options.litOptions.clusteredLightingShadowsEnabled = scene.lighting.shadowsEnabled;
options.litOptions.clusteredLightingShadowType = scene.lighting.shadowType;
options.litOptions.clusteredLightingAreaLightsEnabled = scene.lighting.areaLightsEnabled;
} else {
options.litOptions.clusteredLightingEnabled = false;
options.litOptions.clusteredLightingCookiesEnabled = false;
options.litOptions.clusteredLightingShadowsEnabled = false;
options.litOptions.clusteredLightingAreaLightsEnabled = false;
}
}
_updateUVOptions(options, stdMat, objDefs, minimalOptions, cameraShaderParams) {
var hasUv0 = false;
var hasUv1 = false;
var hasVcolor = false;
if (objDefs) {
hasUv0 = (objDefs & SHADERDEF_UV0) !== 0;
hasUv1 = (objDefs & SHADERDEF_UV1) !== 0;
hasVcolor = (objDefs & SHADERDEF_VCOLOR) !== 0;
}
options.litOptions.vertexColors = false;
this._mapXForms = [];
var uniqueTextureMap = {};
for(var p in _matTex2D){
this._updateTexOptions(options, stdMat, p, hasUv0, hasUv1, hasVcolor, minimalOptions, uniqueTextureMap);
}
this._mapXForms = null;
// true if ssao is applied directly in the lit shaders. Also ensure the AO part is generated in the front end
options.litOptions.ssao = cameraShaderParams == null ? undefined : cameraShaderParams.ssaoEnabled;
options.useAO = options.litOptions.ssao;
// All texture related lit options
options.litOptions.lightMapEnabled = options.lightMap;
options.litOptions.dirLightMapEnabled = options.dirLightMap;
options.litOptions.useHeights = options.heightMap;
options.litOptions.useNormals = options.normalMap;
options.litOptions.useClearCoatNormals = options.clearCoatNormalMap;
options.litOptions.useAo = options.aoMap || options.aoVertexColor || options.litOptions.ssao;
options.litOptions.diffuseMapEnabled = options.diffuseMap;
}
_updateTexOptions(options, stdMat, p, hasUv0, hasUv1, hasVcolor, minimalOptions, uniqueTextureMap) {
var isOpacity = p === 'opacity';
if (!minimalOptions || isOpacity) {
var mname = "" + p + "Map";
var vname = "" + p + "VertexColor";
var vcname = "" + p + "VertexColorChannel";
var cname = "" + mname + "Channel";
var tname = "" + mname + "Transform";
var uname = "" + mname + "Uv";
var iname = "" + mname + "Identifier";
// Avoid overriding previous lightMap properties
if (p !== 'light') {
options[mname] = false;
options[iname] = undefined;
options[cname] = '';
options[tname] = 0;
options[uname] = 0;
}
options[vname] = false;
options[vcname] = '';
if (isOpacity && stdMat.blendType === BLEND_NONE && stdMat.alphaTest === 0.0 && !stdMat.alphaToCoverage && stdMat.opacityDither === DITHER_NONE) {
return;
}
if (p !== 'height' && stdMat[vname]) {
if (hasVcolor) {
options[vname] = stdMat[vname];
options[vcname] = stdMat[vcname];
options.litOptions.vertexColors = true;
}
}
if (stdMat[mname]) {
var allow = true;
if (stdMat[uname] === 0 && !hasUv0) allow = false;
if (stdMat[uname] === 1 && !hasUv1) allow = false;
if (allow) {
// create an intermediate map between the textures and their slots
// to ensure the unique texture mapping isn't dependent on the texture id
// as that will change when textures are changed, even if the sharing is the same
var mapId = stdMat[mname].id;
var identifier = uniqueTextureMap[mapId];
if (identifier === undefined) {
uniqueTextureMap[mapId] = p;
identifier = p;
}
options[mname] = !!stdMat[mname];
options[iname] = identifier;
options[tname] = this._getMapTransformID(stdMat.getUniform(tname), stdMat[uname]);
options[cname] = stdMat[cname];
options[uname] = stdMat[uname];
}
}
}
}
_updateMinOptions(options, stdMat, pass) {
// pre-pass uses the same dither setting as forward pass, otherwise shadow dither
var isPrepass = pass === SHADER_PREPASS;
options.litOptions.opacityShadowDither = isPrepass ? stdMat.opacityDither : stdMat.opacityShadowDither;
options.litOptions.linearDepth = isPrepass;
options.litOptions.lights = [];
}
_updateMaterialOptions(options, stdMat) {
var _stdMat_diffuseMap, _stdMat_diffuseDetailMap, _stdMat_emissiveMap, _stdMat_lightMap;
var useSpecular = !!(stdMat.useMetalness || stdMat.specularMap || stdMat.sphereMap || stdMat.cubeMap || notBlack(stdMat.specular) || stdMat.specularityFactor > 0 && stdMat.useMetalness || stdMat.enableGGXSpecular || stdMat.clearCoat > 0);
var useSpecularColor = !stdMat.useMetalness || stdMat.useMetalnessSpecularColor;
var specularTint = useSpecular && (stdMat.specularTint || !stdMat.specularMap && !stdMat.specularVertexColor) && notWhite(stdMat.specular);
var specularityFactorTint = useSpecular && stdMat.useMetalnessSpecularColor && (stdMat.specularityFactorTint || stdMat.specularityFactor < 1 && !stdMat.specularityFactorMap);
var isPackedNormalMap = stdMat.normalMap ? stdMat.normalMap.format === PIXELFORMAT_DXT5 || stdMat.normalMap.type === TEXTURETYPE_SWIZZLEGGGR : false;
var equalish = (a, b)=>Math.abs(a - b) < 1e-4;
options.specularTint = specularTint ? 2 : 0;
options.specularityFactorTint = specularityFactorTint ? 1 : 0;
options.metalnessTint = stdMat.useMetalness && stdMat.metalness < 1 ? 1 : 0;
options.glossTint = 1;
options.diffuseEncoding = (_stdMat_diffuseMap = stdMat.diffuseMap) == null ? undefined : _stdMat_diffuseMap.encoding;
options.diffuseDetailEncoding = (_stdMat_diffuseDetailMap = stdMat.diffuseDetailMap) == null ? undefined : _stdMat_diffuseDetailMap.encoding;
options.emissiveEncoding = (_stdMat_emissiveMap = stdMat.emissiveMap) == null ? undefined : _stdMat_emissiveMap.encoding;
options.lightMapEncoding = (_stdMat_lightMap = stdMat.lightMap) == null ? undefined : _stdMat_lightMap.encoding;
options.packedNormal = isPackedNormalMap;
options.refractionTint = equalish(stdMat.refraction, 1.0) ? 0 : 1;
options.refractionIndexTint = equalish(stdMat.refractionIndex, 1.0 / 1.5) ? 0 : 1;
options.thicknessTint = stdMat.useDynamicRefraction && stdMat.thickness !== 1.0 ? 1 : 0;
options.specularEncoding = stdMat.specularEncoding || 'linear';
options.sheenEncoding = stdMat.sheenEncoding || 'linear';
options.aoMapUv = stdMat.aoUvSet; // backwards compatibility
options.aoDetail = !!stdMat.aoMap;
options.diffuseDetail = !!stdMat.diffuseMap;
options.normalDetail = !!stdMat.normalMap;
options.diffuseDetailMode = stdMat.diffuseDetailMode;
options.aoDetailMode = stdMat.aoDetailMode;
options.clearCoatTint = equalish(stdMat.clearCoat, 1.0) ? 0 : 1;
options.clearCoatGloss = !!stdMat.clearCoatGloss;
options.clearCoatGlossTint = stdMat.clearCoatGloss !== 1.0 ? 1 : 0;
options.iorTint = equalish(stdMat.refractionIndex, 1.0 / 1.5) ? 0 : 1;
options.iridescenceTint = stdMat.iridescence !== 1.0 ? 1 : 0;
options.glossInvert = stdMat.glossInvert;
options.sheenGlossInvert = stdMat.sheenGlossInvert;
options.clearCoatGlossInvert = stdMat.clearCoatGlossInvert;
options.useSpecularColor = useSpecularColor;
// LIT OPTIONS
options.litOptions.separateAmbient = false; // store ambient light color in separate variable, instead of adding it to diffuse directly
options.litOptions.customFragmentShader = stdMat.customFragmentShader;
options.litOptions.pixelSnap = stdMat.pixelSnap;
options.litOptions.ambientSH = !!stdMat.ambientSH;
options.litOptions.twoSidedLighting = stdMat.twoSidedLighting;
options.litOptions.occludeSpecular = stdMat.occludeSpecular;
options.litOptions.occludeSpecularFloat = stdMat.occludeSpecularIntensity !== 1.0;
options.litOptions.useMsdf = !!stdMat.msdfMap;
options.litOptions.msdfTextAttribute = !!stdMat.msdfTextAttribute;
options.litOptions.alphaToCoverage = stdMat.alphaToCoverage;
options.litOptions.opacityFadesSpecular = stdMat.opacityFadesSpecular;
options.litOptions.opacityDither = stdMat.opacityDither;
options.litOptions.cubeMapProjection = stdMat.cubeMapProjection;
options.litOptions.occludeDirect = stdMat.occludeDirect;
options.litOptions.useSpecular = useSpecular;
options.litOptions.useSpecularityFactor = (specularityFactorTint || !!stdMat.specularityFactorMap) && stdMat.useMetalnessSpecularColor;
options.litOptions.enableGGXSpecular = stdMat.enableGGXSpecular;
options.litOptions.fresnelModel = stdMat.fresnelModel;
options.litOptions.useRefraction = (stdMat.refraction || !!stdMat.refractionMap) && (stdMat.useDynamicRefraction || !!options.litOptions.reflectionSource);
options.litOptions.useClearCoat = !!stdMat.clearCoat;
options.litOptions.useSheen = stdMat.useSheen;
options.litOptions.useIridescence = stdMat.useIridescence && stdMat.iridescence !== 0.0;
options.litOptions.useMetalness = stdMat.useMetalness;
options.litOptions.useDynamicRefraction = stdMat.useDynamicRefraction;
options.litOptions.dispersion = stdMat.dispersion > 0;
}
_updateEnvOptions(options, stdMat, scene, cameraShaderParams) {
options.litOptions.fog = stdMat.useFog ? cameraShaderParams.fog : FOG_NONE;
options.litOptions.gamma = cameraShaderParams.shaderOutputGamma;
options.litOptions.toneMap = stdMat.useTonemap ? cameraShaderParams.toneMapping : TONEMAP_NONE;
var usingSceneEnv = false;
// source of environment reflections is as follows:
if (stdMat.envAtlas && stdMat.cubeMap) {
options.litOptions.reflectionSource = 'envAtlasHQ';
options.litOptions.reflectionEncoding = stdMat.envAtlas.encoding;
options.litOptions.reflectionCubemapEncoding = stdMat.cubeMap.encoding;
} else if (stdMat.envAtlas) {
options.litOptions.reflectionSource = 'envAtlas';
options.litOptions.reflectionEncoding = stdMat.envAtlas.encoding;
} else if (stdMat.cubeMap) {
options.litOptions.reflectionSource = 'cubeMap';
options.litOptions.reflectionEncoding = stdMat.cubeMap.encoding;
} else if (stdMat.sphereMap) {
options.litOptions.reflectionSource = 'sphereMap';
options.litOptions.reflectionEncoding = stdMat.sphereMap.encoding;
} else if (stdMat.useSkybox && scene.envAtlas && scene.skybox) {
options.litOptions.reflectionSource = 'envAtlasHQ';
options.litOptions.reflectionEncoding = scene.envAtlas.encoding;
options.litOptions.reflectionCubemapEncoding = scene.skybox.encoding;
usingSceneEnv = true;
} else if (stdMat.useSkybox && scene.envAtlas) {
options.litOptions.reflectionSource = 'envAtlas';
options.litOptions.reflectionEncoding = scene.envAtlas.encoding;
usingSceneEnv = true;
} else if (stdMat.useSkybox && scene.skybox) {
options.litOptions.reflectionSource = 'cubeMap';
options.litOptions.reflectionEncoding = scene.skybox.encoding;
usingSceneEnv = true;
} else {
options.litOptions.reflectionSource = null;
options.litOptions.reflectionEncoding = null;
}
// source of environment ambient is as follows:
if (stdMat.ambientSH) {
options.litOptions.ambientSource = 'ambientSH';
options.litOptions.ambientEncoding = null;
} else {
var envAtlas = stdMat.envAtlas || (stdMat.useSkybox && scene.envAtlas ? scene.envAtlas : null);
if (envAtlas && !stdMat.sphereMap) {
options.litOptions.ambientSource = 'envAtlas';
options.litOptions.ambientEncoding = envAtlas.encoding;
} else {
options.litOptions.ambientSource = 'constant';
options.litOptions.ambientEncoding = null;
}
}
// TODO: add a test for if non skybox cubemaps have rotation (when this is supported) - for now assume no non-skybox cubemap rotation
options.litOptions.skyboxIntensity = usingSceneEnv;
options.litOptions.useCubeMapRotation = usingSceneEnv && scene._skyboxRotationShaderInclude;
}
_updateLightOptions(options, scene, stdMat, objDefs, sortedLights) {
options.lightMap = false;
options.lightMapChannel = '';
options.lightMapUv = 0;
options.lightMapTransform = 0;
options.litOptions.lightMapWithoutAmbient = false;
options.dirLightMap = false;
if (objDefs) {
options.litOptions.noShadow = (objDefs & SHADERDEF_NOSHADOW) !== 0;
if ((objDefs & SHADERDEF_LM) !== 0) {
options.lightMapEncoding = scene.lightmapPixelFormat === PIXELFORMAT_RGBA8 ? 'rgbm' : 'linear';
options.lightMap = true;
options.lightMapChannel = 'rgb';
options.lightMapUv = 1;
options.lightMapTransform = 0;
options.litOptions.lightMapWithoutAmbient = !stdMat.lightMap;
if ((objDefs & SHADERDEF_DIRLM) !== 0) {
options.dirLightMap = true;
}
// if lightmaps contain baked ambient light, disable real-time ambient light
if ((objDefs & SHADERDEF_LMAMBIENT) !== 0) {
options.litOptions.lightMapWithoutAmbient = false;
}
}
}
if (stdMat.useLighting) {
var lightsFiltered = [];
var mask = objDefs ? objDefs >> 16 : MASK_AFFECT_DYNAMIC;
// mask to select lights (dynamic vs lightmapped) when using clustered lighting
options.litOptions.lightMaskDynamic = !!(mask & MASK_AFFECT_DYNAMIC);
if (sortedLights) {
LitMaterialOptionsBuilder.collectLights(LIGHTTYPE_DIRECTIONAL, sortedLights[LIGHTTYPE_DIRECTIONAL], lightsFiltered, mask);
LitMaterialOptionsBuilder.collectLights(LIGHTTYPE_OMNI, sortedLights[LIGHTTYPE_OMNI], lightsFiltered, mask);
LitMaterialOptionsBuilder.collectLights(LIGHTTYPE_SPOT, sortedLights[LIGHTTYPE_SPOT], lightsFiltered, mask);
}
options.litOptions.lights = lightsFiltered;
} else {
options.litOptions.lights = [];
}
if (options.litOptions.lights.length === 0) {
options.litOptions.noShadow = true;
}
}
_getMapTransformID(xform, uv) {
if (!xform) return 0;
var xforms = this._mapXForms[uv];
if (!xforms) {
xforms = [];
this._mapXForms[uv] = xforms;
}
for(var i = 0; i < xforms.length; i++){
if (arraysEqual(xforms[i][0].value, xform[0].value) && arraysEqual(xforms[i][1].value, xform[1].value)) {
return i + 1;
}
}
return xforms.push(xform);
}
constructor(){
this._mapXForms = null;
}
}
export { StandardMaterialOptionsBuilder };