| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328 |
- import {Vector2, Vector2Tuple, Vector3, Vector3Tuple, Vector4, Vector4Tuple} from 'three'
- import {AViewerPluginSync, ThreeViewer} from '../../viewer'
- import {uiFolderContainer, UiObjectConfig, uiToggle} from 'uiconfig.js'
- import {serialize} from 'ts-browser-helpers'
- import {IMaterial, IMaterialUserData, IObject3D, PhysicalMaterial} from '../../core'
- import {MaterialExtension, updateMaterialDefines} from '../../materials'
- import {shaderReplaceString, ThreeSerialization} from '../../utils'
- import {GLTFLoader2, GLTFWriter2} from '../../assetmanager'
- import type {GLTFLoaderPlugin, GLTFParser} from 'three/examples/jsm/loaders/GLTFLoader'
- import NoiseBumpMaterialPluginPars from './shaders/NoiseBumpMaterialPlugin.pars.glsl'
- import NoiseBumpMaterialPluginPatch from './shaders/NoiseBumpMaterialPlugin.patch.glsl'
-
- /**
- * NoiseBump Materials Extension
- * Adds a material extension to PhysicalMaterial to add support for sparkle bump / noise bump by creating procedural bump map from noise to simulate sparkle flakes.
- * It uses voronoise function from blender along with several additions to generate the noise for the generation.
- * It also adds a UI to the material to edit the settings.
- * It uses WEBGI_materials_noise_bump glTF extension to save the settings in glTF files.
- * @category Plugins
- */
- @uiFolderContainer('Noise/Sparkle Bump (MatExt)')
- export class NoiseBumpMaterialPlugin extends AViewerPluginSync<''> {
- static readonly PluginType = 'NoiseBumpMaterialPlugin'
-
- @uiToggle('Enabled', (that: NoiseBumpMaterialPlugin)=>({onChange: that.setDirty}))
- @serialize() enabled = true
-
- // private _defines: any = {
- // }
- private _uniforms: any = {
- noiseBumpParams: {value: new Vector2()}, // u scale, v scale,
- noiseBumpScale: {value: 0.05},
- noiseBumpFlakeScale: {value: 1000.0},
- noiseFlakeClamp: {value: 1.0},
- noiseFlakeRadius: {value: 0.5},
- flakeParams: {value: new Vector4(0, 1, 3, 0)},
- flakeFallOffParams: {value: new Vector3(0, 1, 0)},
- useColorFlakes: {value: false},
- }
-
- public static AddNoiseBumpMaterial(material: IMaterial, params?: IMaterialUserData['_noiseBumpMat']): boolean {
- const ud = material?.userData
- if (!ud) return false
- if (!ud._noiseBumpMat) {
- ud._noiseBumpMat = {}
- }
- const tf = ud._noiseBumpMat
- tf.hasBump = true
- if (tf.bumpNoiseParams === undefined) tf.bumpNoiseParams = new Vector2(0.5, 0.5)
- if (tf.bumpScale === undefined) tf.bumpScale = 0.05
- if (tf.flakeScale === undefined) tf.flakeScale = 0.05
- if (tf.flakeClamp === undefined) tf.flakeClamp = 1
- if (tf.flakeRadius === undefined) tf.flakeRadius = 0.3
- if (tf.useColorFlakes === undefined) tf.useColorFlakes = false
- if (tf.flakeParams === undefined) tf.flakeParams = new Vector4(0, 1, 3, 0)
- if (tf.flakeFallOffParams === undefined) tf.flakeFallOffParams = new Vector3(0, 1, 0)
- params && Object.assign(tf, params)
- if (material.setDirty) material.setDirty()
- return true
- }
-
- readonly materialExtension: MaterialExtension = {
- parsFragmentSnippet: (_, material: PhysicalMaterial)=>{
- if (this.isDisabled() || !material?.userData._noiseBumpMat?.hasBump) return ''
- return NoiseBumpMaterialPluginPars
- },
- shaderExtender: (shader, material: PhysicalMaterial) => {
- if (this.isDisabled() || !material?.userData._noiseBumpMat?.hasBump) return
- shader.fragmentShader = shaderReplaceString(shader.fragmentShader, '#glMarker beforeAccumulation', NoiseBumpMaterialPluginPatch, {prepend: true})
- shader.defines.USE_UV = ''
- shader.extensionDerivatives = true
- },
- onObjectRender: (_: IObject3D, material) => {
- const tfUd = material.userData._noiseBumpMat
- if (!tfUd?.hasBump) return
-
- if (Array.isArray(tfUd.bumpNoiseParams)) this._uniforms.noiseBumpParams.value.fromArray(tfUd.bumpNoiseParams)
- else this._uniforms.noiseBumpParams.value.copy(tfUd.bumpNoiseParams)
- this._uniforms.noiseBumpScale.value = tfUd.bumpScale
- this._uniforms.noiseBumpFlakeScale.value = tfUd.flakeScale
- this._uniforms.noiseFlakeClamp.value = tfUd.flakeClamp
- this._uniforms.noiseFlakeRadius.value = tfUd.flakeRadius
- if (Array.isArray(tfUd.flakeParams)) this._uniforms.flakeParams.value.fromArray(tfUd.flakeParams)
- else this._uniforms.flakeParams.value.copy(tfUd.flakeParams)
- if (Array.isArray(tfUd.flakeFallOffParams)) this._uniforms.flakeFallOffParams.value.fromArray(tfUd.flakeFallOffParams)
- else this._uniforms.flakeFallOffParams.value.copy(tfUd.flakeFallOffParams)
- this._uniforms.useColorFlakes.value = tfUd.useColorFlakes
-
- updateMaterialDefines({
- // ...this._defines,
- ['NOISE_BUMP_MATERIAL_ENABLED']: +!this.isDisabled(),
- }, material)
- },
- extraUniforms: {
- // ...this._uniforms, // done in constructor
- },
- computeCacheKey: (material1: PhysicalMaterial) => {
- return (this.isDisabled() ? '0' : '1') + (material1.userData._noiseBumpMat?.hasBump ? '1' : '0')
- },
- isCompatible: (material1: PhysicalMaterial) => material1.isPhysicalMaterial,
- getUiConfig: material => { // todo use uiConfigMaterialExtension
- const viewer = this._viewer!
- if (material.userData._noiseBumpMat === undefined) material.userData._noiseBumpMat = {}
- const state = material.userData._noiseBumpMat
- const config: UiObjectConfig = {
- type: 'folder',
- label: 'SparkleBump (NoiseBump)',
- onChange: (ev)=>{
- if (!ev.config) return
- this.setDirty()
- },
- children: [
- {
- type: 'checkbox',
- label: 'Enabled',
- get value() {
- return state.hasBump || false
- },
- set value(v) {
- if (v === state.hasBump) return
- if (v) {
- if (!NoiseBumpMaterialPlugin.AddNoiseBumpMaterial(material))
- viewer.dialog.alert('Cannot add NoiseBumpMaterial.')
- } else {
- state.hasBump = false
- if (material.setDirty) material.setDirty()
- }
- config.uiRefresh?.(true, 'postFrame')
- },
- },
- {
- type: 'vec4',
- label: 'Bump Noise Params',
- bounds: [0, 1],
- hidden: () => !state.hasBump,
- property: [state, 'bumpNoiseParams'],
- },
- {
- type: 'slider',
- label: 'Bump Scale',
- bounds: [0, 0.001],
- stepSize: 0.00001,
- hidden: () => !state.hasBump,
- property: [state, 'bumpScale'],
- },
- {
- type: 'slider',
- label: 'Flake Scale',
- bounds: [100, 10000],
- stepSize: 0.0001,
- hidden: () => !state.hasBump,
- property: [state, 'flakeScale'],
- },
- {
- type: 'slider',
- label: 'Flake Clamp',
- bounds: [0, 1],
- stepSize: 1,
- hidden: () => !state.hasBump,
- property: [state, 'flakeClamp'],
- },
- {
- type: 'slider',
- label: 'Flake Radius',
- bounds: [0.01, 1],
- stepSize: 0.001,
- hidden: () => !state.hasBump,
- property: [state, 'flakeRadius'],
- },
- {
- type: 'slider',
- label: 'Flake Roughness',
- bounds: [0., 1],
- stepSize: 0.01,
- hidden: () => !state.hasBump,
- property: [state.flakeParams, 'x'],
- },
- {
- type: 'slider',
- label: 'Flake Metalness',
- bounds: [0., 1],
- stepSize: 0.01,
- hidden: () => !state.hasBump,
- property: [state.flakeParams, 'y'],
- },
- {
- type: 'slider',
- label: 'Flake Strength',
- bounds: [0.0, 100],
- stepSize: 0.001,
- hidden: () => !state.hasBump,
- property: [state.flakeParams, 'z'],
- },
- {
- type: 'slider',
- label: 'Flake Threshold',
- bounds: [0.1, 10],
- stepSize: 0.001,
- hidden: () => !state.hasBump,
- property: [state.flakeParams, 'w'],
- },
- {
- type: 'slider',
- label: 'Falloff',
- stepSize: 1,
- bounds: [0, 1],
- hidden: () => !state.hasBump,
- property: [state.flakeFallOffParams, 'x'],
- },
- {
- type: 'slider',
- label: 'Linear falloff factor',
- bounds: [0., 10],
- stepSize: 0.001,
- hidden: () => !state.hasBump,
- property: [state.flakeFallOffParams, 'y'],
- },
- {
- type: 'slider',
- label: 'Quadratic falloff factor',
- bounds: [0., 10],
- stepSize: 0.001,
- hidden: () => !state.hasBump,
- property: [state.flakeFallOffParams, 'z'],
- },
- {
- type: 'checkbox',
- label: 'Colored Flakes',
- hidden: () => !state.hasBump,
- property: [state, 'useColorFlakes'],
- },
- ],
- }
- return config
- },
-
- }
-
- setDirty = (): void => {
- this.materialExtension.setDirty?.()
- this._viewer?.setDirty()
- }
-
- private _loaderCreate({loader}: {loader: GLTFLoader2}) {
- if (!loader.isGLTFLoader2) return
- loader.register((p) => new GLTFMaterialsNoiseBumpMaterialImport(p))
- }
-
- constructor() {
- super()
- this._loaderCreate = this._loaderCreate.bind(this)
- Object.assign(this.materialExtension.extraUniforms!, this._uniforms)
- }
-
- onAdded(v: ThreeViewer) {
- super.onAdded(v)
- v.assetManager.materials.registerMaterialExtension(this.materialExtension)
- v.assetManager.importer.addEventListener('loaderCreate', this._loaderCreate as any)
- v.assetManager.exporter.getExporter('gltf', 'glb')?.extensions?.push(glTFMaterialsNoiseBumpMaterialExport)
- }
-
- onRemove(v: ThreeViewer) {
- v.assetManager.materials?.unregisterMaterialExtension(this.materialExtension)
- v.assetManager.importer?.removeEventListener('loaderCreate', this._loaderCreate as any)
- const exporter = v.assetManager.exporter.getExporter('gltf', 'glb')
- if (exporter) {
- const index = exporter.extensions?.indexOf(glTFMaterialsNoiseBumpMaterialExport)
- if (index !== undefined && index >= 0) exporter.extensions?.splice(index, 1)
- }
- return super.onRemove(v)
- }
-
- public static readonly NOISE_BUMP_MATERIAL_GLTF_EXTENSION = 'WEBGI_materials_noise_bump'
-
- }
-
- declare module '../../core/IMaterial' {
- interface IMaterialUserData {
- _noiseBumpMat?: {
- hasBump?: boolean
- bumpNoiseParams?: Vector2Tuple | Vector2
- bumpScale?: number
- flakeScale?: number
- flakeClamp?: number
- flakeRadius?: number
- useColorFlakes?: boolean
- flakeParams?: Vector4Tuple | Vector4
- flakeFallOffParams?: Vector3Tuple | Vector3
- }
- }
- }
-
- /**
- * FragmentClipping Materials Extension
- *
- * Specification: https://webgi.xyz/docs/gltf-extensions/WEBGI_materials_fragment_clipping_extension.html
- */
- class GLTFMaterialsNoiseBumpMaterialImport implements GLTFLoaderPlugin {
- public name: string
- public parser: GLTFParser
-
- constructor(parser: GLTFParser) {
- this.parser = parser
- this.name = NoiseBumpMaterialPlugin.NOISE_BUMP_MATERIAL_GLTF_EXTENSION
- }
-
- async extendMaterialParams(materialIndex: number, materialParams: any) {
- 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 = {}
- NoiseBumpMaterialPlugin.AddNoiseBumpMaterial(materialParams)
- ThreeSerialization.Deserialize(extension, materialParams.userData._noiseBumpMat)
- }
- }
-
- const glTFMaterialsNoiseBumpMaterialExport = (w: GLTFWriter2)=> ({
- writeMaterial: (material: any, materialDef: any) => {
- if (!material.isMeshStandardMaterial || !material.userData._noiseBumpMat?.hasBump) return
- materialDef.extensions = materialDef.extensions || {}
-
- const extensionDef: any = ThreeSerialization.Serialize(material.userData._noiseBumpMat)
-
- materialDef.extensions[ NoiseBumpMaterialPlugin.NOISE_BUMP_MATERIAL_GLTF_EXTENSION ] = extensionDef
- w.extensionsUsed[ NoiseBumpMaterialPlugin.NOISE_BUMP_MATERIAL_GLTF_EXTENSION ] = true
- },
- })
|