UNPKG

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
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