threepipe
Version:
A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.
274 lines • 12.8 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { Matrix3, SRGBColorSpace } from 'three';
import { AViewerPluginSync } from '../../viewer';
import { uiFolderContainer, uiToggle } from 'uiconfig.js';
import { serialize } from 'ts-browser-helpers';
import { updateMaterialDefines } from '../../materials';
import { shaderReplaceString } from '../../utils';
import CustomBumpMapPluginShader from './shaders/CustomBumpMapPlugin.glsl';
import { matDefine } from '../../three';
import { makeSamplerUi } from '../../ui/image-ui';
/**
* Custom Bump Map Plugin
* Adds a material extension to PhysicalMaterial to support custom bump maps.
* A Custom bump map is similar to the built-in bump map, but allows using an extra bump map and scale to give a combined effect.
* This plugin also has support for bicubic filtering of the custom bump map and is enabled by default.
* It also adds a UI to the material to edit the settings.
* It uses WEBGI_materials_custom_bump_map glTF extension to save the settings in glTF files.
* @category Plugins
*/
let CustomBumpMapPlugin = class CustomBumpMapPlugin extends AViewerPluginSync {
enableCustomBump(material, map, scale) {
const ud = material?.userData;
if (!ud)
return false;
if (ud._hasCustomBump === undefined) {
const meshes = material.appliedMeshes;
let possible = true;
if (meshes)
for (const { geometry } of meshes) {
if (geometry && (!geometry.attributes.position || !geometry.attributes.normal || !geometry.attributes.uv)) {
possible = false;
}
// if (possible && !geometry.attributes.tangent) {
// geometry.computeTangents()
// }
}
if (!possible) {
return false;
}
}
ud._hasCustomBump = true;
ud._customBumpScale = scale ?? ud._customBumpScale ?? 0.001;
ud._customBumpMap = map ?? ud._customBumpMap ?? null;
if (material.setDirty)
material.setDirty();
return true;
}
constructor() {
super();
this.enabled = true;
this.bicubicFiltering = true;
this._defines = {
['CUSTOM_BUMP_MAP_DEBUG']: false,
['CUSTOM_BUMP_MAP_BICUBIC']: true,
};
this._uniforms = {
customBumpUvTransform: { value: new Matrix3() },
customBumpScale: { value: 0.001 },
customBumpMap: { value: null },
};
this.materialExtension = {
parsFragmentSnippet: (_, material) => {
if (this.isDisabled() || !material?.userData._hasCustomBump)
return '';
return CustomBumpMapPluginShader;
},
shaderExtender: (shader, material) => {
if (this.isDisabled() || !material?.userData._hasCustomBump)
return;
const customBumpMap = material.userData._customBumpMap;
if (!customBumpMap)
return;
shader.fragmentShader = shaderReplaceString(shader.fragmentShader, '#glMarker beforeAccumulation', `
#if defined(CUSTOM_BUMP_MAP_ENABLED) && CUSTOM_BUMP_MAP_ENABLED > 0
normal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd_cb(), faceDirection );
#endif
`, { prepend: true });
shader.vertexShader = shaderReplaceString(shader.vertexShader, '#include <uv_pars_vertex>', `
#if defined(CUSTOM_BUMP_MAP_ENABLED) && CUSTOM_BUMP_MAP_ENABLED > 0
varying vec2 vCustomBumpUv;
uniform mat3 customBumpUvTransform;
#endif
`, { prepend: true });
shader.vertexShader = shaderReplaceString(shader.vertexShader, '#include <uv_vertex>', `
#if defined(CUSTOM_BUMP_MAP_ENABLED) && CUSTOM_BUMP_MAP_ENABLED > 0
vCustomBumpUv = ( customBumpUvTransform * vec3( uv, 1 ) ).xy;
#endif
`, { prepend: true });
shader.defines && (shader.defines.USE_UV = '');
},
onObjectRender: (object, material) => {
const userData = material.userData;
if (!userData?._hasCustomBump)
return;
if (!object.isMesh || !object.geometry)
return;
const tex = userData._customBumpMap?.isTexture ? userData._customBumpMap : null;
this._uniforms.customBumpMap.value = tex;
this._uniforms.customBumpScale.value = tex ? userData._customBumpScale ?? 0 : 0;
if (tex) {
tex.updateMatrix();
this._uniforms.customBumpUvTransform.value.copy(tex.matrix);
}
updateMaterialDefines({
...this._defines,
['CUSTOM_BUMP_MAP_ENABLED']: +this.enabled,
}, material);
},
extraUniforms: {
// ...this._uniforms, // done in constructor
},
computeCacheKey: (material1) => {
return (this.enabled ? '1' : '0') + (material1.userData._hasCustomBump ? '1' : '0') + material1.userData?._customBumpMap?.uuid;
},
isCompatible: (material1) => material1.isPhysicalMaterial,
getUiConfig: material => {
const viewer = this._viewer;
const enableCustomBump = this.enableCustomBump.bind(this);
const state = material.userData;
const config = {
type: 'folder',
label: 'CustomBumpMap',
onChange: (ev) => {
if (!ev.config)
return;
this.setDirty();
},
children: [
{
type: 'checkbox',
label: 'Enabled',
get value() {
return state._hasCustomBump || false;
},
set value(v) {
if (v === state._hasCustomBump)
return;
if (v) {
if (!enableCustomBump(material))
viewer.dialog.alert('CustomBumpMapPlugin - Cannot add CustomBumpMap.');
}
else {
state._hasCustomBump = false;
if (material.setDirty)
material.setDirty();
}
config.uiRefresh?.(true, 'postFrame');
},
},
{
type: 'slider',
label: 'Bump Scale',
bounds: [-1, 1],
hidden: () => !state._hasCustomBump,
property: [state, '_customBumpScale'],
// onChange: this.setDirty,
},
{
type: 'image',
label: 'Bump Map',
hidden: () => !state._hasCustomBump,
property: [state, '_customBumpMap'],
onChange: () => {
if (material.setDirty)
material.setDirty();
},
},
makeSamplerUi(state, '_customBumpMap', 'Sampler', () => !state._hasCustomBump, () => material.setDirty && material.setDirty()),
],
};
return config;
},
};
this.setDirty = () => {
this.materialExtension.setDirty?.();
this._viewer?.setDirty();
};
Object.assign(this.materialExtension.extraUniforms, this._uniforms);
}
onAdded(v) {
super.onAdded(v);
// v.addEventListener('preRender', this._preRender)
v.assetManager.materials.registerMaterialExtension(this.materialExtension);
v.assetManager.registerGltfExtension(customBumpMapGLTFExtension);
// v.getPlugin(GBufferPlugin)?.material?.registerMaterialExtensions([this.materialExtension])
}
onRemove(v) {
v.assetManager.materials?.unregisterMaterialExtension(this.materialExtension);
v.assetManager.unregisterGltfExtension(customBumpMapGLTFExtension.name);
// v.getPlugin(GBufferPlugin)?.material?.unregisterMaterialExtensions([this.materialExtension])
return super.onRemove(v);
}
};
CustomBumpMapPlugin.PluginType = 'CustomBumpMapPlugin';
/**
* @deprecated use {@link customBumpMapGLTFExtension}
*/
CustomBumpMapPlugin.CUSTOM_BUMP_MAP_GLTF_EXTENSION = 'WEBGI_materials_custom_bump_map';
__decorate([
uiToggle('Enabled', (that) => ({ onChange: that.setDirty })),
serialize()
], CustomBumpMapPlugin.prototype, "enabled", void 0);
__decorate([
uiToggle('Bicubic', (that) => ({ onChange: that.setDirty })),
matDefine('CUSTOM_BUMP_MAP_BICUBIC', undefined, true, CustomBumpMapPlugin.prototype.setDirty),
serialize()
], CustomBumpMapPlugin.prototype, "bicubicFiltering", void 0);
CustomBumpMapPlugin = __decorate([
uiFolderContainer('Custom BumpMap (MatExt)')
], CustomBumpMapPlugin);
export { CustomBumpMapPlugin };
/**
* FragmentClipping Materials Extension
*
* Specification: https://threepipe.org/docs/gltf-extensions/WEBGI_materials_fragment_clipping_extension.html
*/
class GLTFMaterialsCustomBumpMapImport {
constructor(parser) {
this.parser = parser;
this.name = customBumpMapGLTFExtension.name;
}
async extendMaterialParams(materialIndex, materialParams) {
const parser = this.parser;
const materialDef = parser.json.materials[materialIndex];
if (!materialDef.extensions || !materialDef.extensions[this.name])
return;
const extension = materialDef.extensions[this.name];
if (!materialParams.userData)
materialParams.userData = {};
materialParams.userData._hasCustomBump = true; // single _ so that its saved when cloning but not when saving
materialParams.userData._customBumpScale = extension.customBumpScale ?? 0.0;
const pending = [];
const tex = extension.customBumpMap;
if (tex) {
pending.push(parser.assignTexture(materialParams.userData, '_customBumpMap', tex).then((t) => {
// t.format = RGBFormat
t.colorSpace = SRGBColorSpace;
}));
}
return Promise.all(pending);
}
}
const glTFMaterialsCustomBumpMapExport = (w) => ({
writeMaterial: (material, materialDef) => {
if (!material.isMeshStandardMaterial || !material.userData._hasCustomBump)
return;
if ((material.userData._customBumpScale || 0) < 0.001)
return; // todo: is this correct?
materialDef.extensions = materialDef.extensions || {};
const extensionDef = {};
extensionDef.customBumpScale = material.userData._customBumpScale || 1.0;
if (w.checkEmptyMap(material.userData._customBumpMap)) {
const customBumpMapDef = { index: w.processTexture(material.userData._customBumpMap) };
w.applyTextureTransform(customBumpMapDef, material.userData._customBumpMap);
extensionDef.customBumpMap = customBumpMapDef;
}
materialDef.extensions[customBumpMapGLTFExtension.name] = extensionDef;
w.extensionsUsed[customBumpMapGLTFExtension.name] = true;
},
});
export const customBumpMapGLTFExtension = {
name: 'WEBGI_materials_custom_bump_map',
import: (p) => new GLTFMaterialsCustomBumpMapImport(p),
export: glTFMaterialsCustomBumpMapExport,
textures: {
customBumpMap: 'RGB',
},
};
//# sourceMappingURL=CustomBumpMapPlugin.js.map