Parcourir la source

Add ClearcoatTintPlugin, CustomBumpMapPlugin, FragmentClippingExtensionPlugin, NoiseBumpMaterialPlugin and examples

master
Palash Bansal il y a 2 ans
Parent
révision
9df924e254
Aucun compte lié à l'adresse e-mail de l'auteur

+ 36
- 0
examples/clearcoat-tint-plugin/index.html Voir le fichier

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Clearcoat Tint Plugin</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>

<script type="importmap">
{
"imports": {
"threepipe": "./../../dist/index.mjs",
"@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs"
}
}

</script>
<style id="example-style">
html, body, #canvas-container, #mcanvas {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
}
</style>
<script type="module" src="../examples-utils/simple-code-preview.mjs"></script>
<script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script>
</head>
<body>
<div id="canvas-container">
<canvas id="mcanvas"></canvas>
</div>

</body>

+ 44
- 0
examples/clearcoat-tint-plugin/script.ts Voir le fichier

import {_testFinish, ClearcoatTintPlugin, IObject3D, PhysicalMaterial, ThreeViewer} from 'threepipe'
import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane'

async function init() {

const viewer = new ThreeViewer({
canvas: document.getElementById('mcanvas') as HTMLCanvasElement,
msaa: true,
})

const clearcoatTint = viewer.addPluginSync(ClearcoatTintPlugin)
const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true))

await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', {
setBackground: true,
})
const result = await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', {
autoCenter: true,
autoScale: true,
})
const model = result?.getObjectByName('node_damagedHelmet_-6514')
const materials = (model?.materials || []) as PhysicalMaterial[]
for (const material of materials) {
material.clearcoat = 1
// add initial properties
ClearcoatTintPlugin.AddClearcoatTint(material, {
tintColor: '#ff0000',
thickness: 1,
})
// set properties like this or from the UI
// material.userData._clearcoatTint!.tintColor = '#ff0000'


// Add extra clearcoat tint ui mapped to this material.
// This is also added inside the material ui by default by the material extension automatically.
const config = material.uiConfig
if (!config) continue
ui.appendChild(clearcoatTint.materialExtension.getUiConfig?.(material), {expanded: true})
ui.appendChild(config)
}

}

init().then(_testFinish)

+ 36
- 0
examples/custom-bump-map-plugin/index.html Voir le fichier

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Custom Bump Map Plugin</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>

<script type="importmap">
{
"imports": {
"threepipe": "./../../dist/index.mjs",
"@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs"
}
}

</script>
<style id="example-style">
html, body, #canvas-container, #mcanvas {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
}
</style>
<script type="module" src="../examples-utils/simple-code-preview.mjs"></script>
<script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script>
</head>
<body>
<div id="canvas-container">
<canvas id="mcanvas"></canvas>
</div>

</body>

+ 41
- 0
examples/custom-bump-map-plugin/script.ts Voir le fichier

import {_testFinish, CustomBumpMapPlugin, ITexture, Mesh, PhysicalMaterial, PlaneGeometry, ThreeViewer} from 'threepipe'
import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane'

async function init() {
const viewer = new ThreeViewer({
canvas: document.getElementById('mcanvas') as HTMLCanvasElement,
msaa: true,
})

const customBump = viewer.addPluginSync(CustomBumpMapPlugin)
const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true))

await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr')

const model = new Mesh(new PlaneGeometry(4, 2), new PhysicalMaterial())
const material = model.material
viewer.scene.addObject(model)

const bumpMap1 = await viewer.load<ITexture>('https://threejs.org/examples/textures/brick_bump.jpg')
const bumpMap2 = await viewer.load<ITexture>('https://threejs.org/examples/textures/planets/earth_specular_2048.jpg')
customBump.enableCustomBump(material, bumpMap2, -0.2)
material.bumpMap = bumpMap1 || null
material.bumpScale = -0.01
material.setDirty()

// set properties like this or from the UI
// material.userData._customBumpMat = texture
// material.setDirty()

// to disable
// material.userData._hasCustomBump = false
// material.setDirty()

ui.setupPluginUi(CustomBumpMapPlugin)
const config = material.uiConfig!
ui.appendChild(customBump.materialExtension.getUiConfig?.(material), {expanded: true})
ui.appendChild(config)

}

init().then(_testFinish)

+ 36
- 0
examples/fragment-clipping-extension-plugin/index.html Voir le fichier

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fragment Clipping Extension Plugin</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>

<script type="importmap">
{
"imports": {
"threepipe": "./../../dist/index.mjs",
"@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs"
}
}

</script>
<style id="example-style">
html, body, #canvas-container, #mcanvas {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
}
</style>
<script type="module" src="../examples-utils/simple-code-preview.mjs"></script>
<script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script>
</head>
<body>
<div id="canvas-container">
<canvas id="mcanvas"></canvas>
</div>

</body>

+ 49
- 0
examples/fragment-clipping-extension-plugin/script.ts Voir le fichier

import {
_testFinish,
FragmentClippingExtensionPlugin,
IObject3D,
PhysicalMaterial,
ThreeViewer,
Vector4,
} from 'threepipe'
import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane'

async function init() {
const viewer = new ThreeViewer({
canvas: document.getElementById('mcanvas') as HTMLCanvasElement,
msaa: true,
})

const fragmentClipping = viewer.addPluginSync(FragmentClippingExtensionPlugin)
const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true))

await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', {
setBackground: true,
})
const result = await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', {
autoCenter: true,
autoScale: true,
})
const model = result?.getObjectByName('node_damagedHelmet_-6514')
const materials = (model?.materials || []) as PhysicalMaterial[]
for (const material of materials) {
FragmentClippingExtensionPlugin.AddFragmentClipping(material, {
clipPosition: new Vector4(0.5, 0.5, 0, 0),
clipParams: new Vector4(0.1, 0.05, 0, 1),
})
// set properties like this or from the UI
// material.userData._fragmentClipping!.clipPosition.set(0, 0, 0, 0)
// material.setDirty()


// Add extra fragment clipping extension ui mapped to this material.
// This is also added inside the material ui by default by the material extension automatically.
const config = material.uiConfig
if (!config) continue
ui.appendChild(fragmentClipping.materialExtension.getUiConfig?.(material), {expanded: true})
ui.appendChild(config)
}

}

init().then(_testFinish)

+ 36
- 0
examples/noise-bump-material-plugin/index.html Voir le fichier

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>SparkleBump(NoiseBump) Material Plugin</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Import maps polyfill -->
<!-- Remove this when import maps will be widely supported -->
<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>

<script type="importmap">
{
"imports": {
"threepipe": "./../../dist/index.mjs",
"@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs"
}
}

</script>
<style id="example-style">
html, body, #canvas-container, #mcanvas {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
}
</style>
<script type="module" src="../examples-utils/simple-code-preview.mjs"></script>
<script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script>
</head>
<body>
<div id="canvas-container">
<canvas id="mcanvas"></canvas>
</div>

</body>

+ 42
- 0
examples/noise-bump-material-plugin/script.ts Voir le fichier

import {_testFinish, IObject3D, NoiseBumpMaterialPlugin, PhysicalMaterial, ThreeViewer} from 'threepipe'
import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane'

async function init() {

const viewer = new ThreeViewer({
canvas: document.getElementById('mcanvas') as HTMLCanvasElement,
msaa: true,
})

const noiseBump = viewer.addPluginSync(NoiseBumpMaterialPlugin)
const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true))

await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr', {
setBackground: true,
})
const result = await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf', {
autoCenter: true,
autoScale: true,
})
const model = result?.getObjectByName('node_damagedHelmet_-6514')
const materials = (model?.materials || []) as PhysicalMaterial[]
for (const material of materials) {
NoiseBumpMaterialPlugin.AddNoiseBumpMaterial(material, {
flakeScale: 300,
})
// set properties like this or from the UI
// material.userData._noiseBumpMat!.bumpNoiseParams = [1, 1]
// material.setDirty()


// Add extra noise bump extension ui mapped to this material.
// This is also added inside the material ui by default by the material extension automatically.
const config = material.uiConfig
if (!config) continue
ui.appendChild(noiseBump.materialExtension.getUiConfig?.(material), {expanded: true})
ui.appendChild(config)
}

}

init().then(_testFinish)

+ 6
- 0
src/plugins/index.ts Voir le fichier

export {PopmotionPlugin} from './animation/PopmotionPlugin' export {PopmotionPlugin} from './animation/PopmotionPlugin'
export {CameraViewPlugin, type CameraViewPluginOptions} from './animation/CameraViewPlugin' export {CameraViewPlugin, type CameraViewPluginOptions} from './animation/CameraViewPlugin'


// material
export {ClearcoatTintPlugin} from './material/ClearcoatTintPlugin'
export {NoiseBumpMaterialPlugin} from './material/NoiseBumpMaterialPlugin'
export {CustomBumpMapPlugin} from './material/CustomBumpMapPlugin'
export {FragmentClippingExtensionPlugin, FragmentClippingMode} from './material/FragmentClippingExtensionPlugin'

// extras // extras
export {HDRiGroundPlugin} from './extras/HDRiGroundPlugin' export {HDRiGroundPlugin} from './extras/HDRiGroundPlugin'

+ 244
- 0
src/plugins/material/ClearcoatTintPlugin.ts Voir le fichier

import {Color} from 'three'
import {AViewerPluginSync, ThreeViewer} from '../../viewer'
import {uiFolderContainer, UiObjectConfig, uiToggle} from 'uiconfig.js'
import {glsl, serialize} from 'ts-browser-helpers'
import {IMaterialUserData, 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'

/**
* Clearcoat Tint Plugin
* Adds a material extension to PhysicalMaterial which adds tint and thickness to the built-in clearcoat properties.
* It also adds a UI to the material to edit the settings.
* It uses WEBGI_materials_clearcoat_tint glTF extension to save the settings in glTF files.
* @category Plugins
*/
@uiFolderContainer('ClearcoatTint Materials')
export class ClearcoatTintPlugin extends AViewerPluginSync<''> {
static readonly PluginType = 'ClearcoatTintPlugin'

@uiToggle('Enabled', (that: ClearcoatTintPlugin)=>({onChange: that.setDirty}))
@serialize() enabled = true

// private _defines: any = {
// // eslint-disable-next-line @typescript-eslint/naming-convention
// CLEARCOAT_TINT_DEBUG: false,
// }
private _uniforms: any = {
ccTintColor: {value: new Color()},
ccThickness: {value: 0.},
ccIor: {value: 0.},
}

static AddClearcoatTint(material: PhysicalMaterial, params?: IMaterialUserData['_clearcoatTint']): IMaterialUserData['_clearcoatTint']|null {
const ud = material?.userData
if (!ud) return null
if (!ud._clearcoatTint) ud._clearcoatTint = {}
const tf = ud._clearcoatTint!
tf.enableTint = true
if (tf.tintColor === undefined) tf.tintColor = '#ffffff'
if (tf.thickness === undefined) tf.thickness = 0.1
if (tf.ior === undefined) tf.ior = 1.5
Object.assign(tf, params)
if (material.setDirty) material.setDirty()
return tf
}

// private _multiplyPass?: MultiplyPass
readonly materialExtension: MaterialExtension = {
parsFragmentSnippet: (_, material: PhysicalMaterial)=>{
if (!this.enabled || !material?.userData._clearcoatTint?.enableTint || !(material.clearcoat > 0)) return ''
return glsl`
uniform vec3 ccTintColor;
uniform float ccThickness;
uniform float ccIor;
vec3 clearcoatTint(const in float dotNV, const in float dotNL, const in float clearcoat) {
vec3 tint = ( ccThickness > 0. ? 1. - ccTintColor : ccTintColor); // Set thickness < 0 for glow.
tint = exp(tint * -(ccThickness * ((dotNL + dotNV) / max(dotNL * dotNV, 1e-3)))); // beer's law
return mix(vec3(1.0), tint, clearcoat);
}
`
},
shaderExtender: (shader, material: PhysicalMaterial) => {
if (!this.enabled || !material?.userData._clearcoatTint?.enableTint || !(material.clearcoat > 0)) return

// Note: clearcoat only considers specular, not diffuse

shader.fragmentShader = shaderReplaceString(shader.fragmentShader,
'float dotNVcc = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );',
'float dotNVcc = saturate( dot( geometry.clearcoatNormal, -refract(geometry.viewDir, geometry.clearcoatNormal, 1./ccIor) ) );')

// todo: we are considering all light is coming from env map, but we should consider light coming from light sources by seperating light and env map attenuation
shader.fragmentShader = shaderReplaceString(shader.fragmentShader,
'outgoingLight = outgoingLight * ( 1.0 - material.clearcoat * Fcc ) + clearcoatSpecular * material.clearcoat;',
'outgoingLight *= clearcoatTint(dotNVcc, dotNVcc, material.clearcoat);\n', {prepend: true})

;(shader as any).defines.USE_UV = ''

},
onObjectRender: (_, material) => {
const tfUd = material.userData._clearcoatTint
if (!tfUd?.enableTint) return

this._uniforms.ccTintColor.value.set(tfUd.tintColor) // could be number or string also, apart from Color
this._uniforms.ccThickness.value = tfUd.thickness
this._uniforms.ccIor.value = tfUd.ior

updateMaterialDefines({
// ...this._defines,
['CLEARCOAT_TINT_ENABLED']: +this.enabled,
}, material)
},
extraUniforms: {
...this._uniforms,
},
computeCacheKey: (material1: PhysicalMaterial) => {
return (this.enabled ? '1' : '0') + (material1.userData._clearcoatTint?.enableTint ? '1' : '0') + (material1.clearcoat > 0 ? '1' : '0')
},
isCompatible: (material1: PhysicalMaterial) => {
return material1.isPhysicalMaterial
},
getUiConfig: (material: PhysicalMaterial) => {
const viewer = this._viewer!
if (material.userData._clearcoatTint === undefined) material.userData._clearcoatTint = {}
const state = material.userData._clearcoatTint
const config: UiObjectConfig = {
type: 'folder',
label: 'Clearcoat Tint',
onChange: (ev)=>{
if (!ev.config) return
this.setDirty()
},
children: [
{
type: 'checkbox',
label: 'Enabled',
get value() {
return state.enableTint || false
},
set value(v) {
if (v === state.enableTint) return
if (v) {
if (!ClearcoatTintPlugin.AddClearcoatTint(material))
viewer.dialog.alert('Cannot add clearcoat tint.')
} else {
state.enableTint = false
if (material.setDirty) material.setDirty()
}
config.uiRefresh?.(true, 'postFrame')
},
},
{
type: 'color',
label: 'Tint color',
hidden: () => !state.enableTint,
property: [state, 'tintColor'],
},
{
type: 'input',
label: 'Thickness',
hidden: () => !state.enableTint,
property: [state, 'thickness'],
},
{
type: 'slider',
bounds: [0.8, 2.5],
label: 'IOR',
hidden: () => !state.enableTint,
property: [state, 'ior'],
},
],
}
return config
},

}

setDirty = (): void => {
this.materialExtension.setDirty?.()
this._viewer?.setDirty()
}

private _loaderCreate({loader}: {loader: GLTFLoader2}) {
if (!loader.isGLTFLoader2) return
loader.register((p) => new GLTFMaterialsClearcoatTintExtensionImport(p))
}

constructor() {
super()
this._loaderCreate = this._loaderCreate.bind(this)
}

onAdded(v: ThreeViewer) {
super.onAdded(v)
// v.addEventListener('preRender', this._preRender)
v.assetManager.materials.registerMaterialExtension(this.materialExtension)
v.assetManager.importer.addEventListener('loaderCreate', this._loaderCreate as any)
v.assetManager.exporter.getExporter('gltf', 'glb')?.extensions?.push(glTFMaterialsClearcoatTintExtensionExport)

}

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(glTFMaterialsClearcoatTintExtensionExport)
if (index !== undefined && index >= 0) exporter.extensions?.splice(index, 1)
}
return super.onRemove(v)
}

public static readonly CLEARCOAT_TINT_GLTF_EXTENSION = 'WEBGI_materials_clearcoat_tint'

}

declare module '../../core/IMaterial' {
interface IMaterialUserData {
_clearcoatTint?: {
enableTint?: boolean
tintColor?: Color|number|string
thickness?: number
ior?: number
}
}
}

/**
* ClearcoatTint Materials Extension
*
* Specification: https://webgi.xyz/docs/gltf-extensions/WEBGI_materials_clearcoat_tint.html
*/
class GLTFMaterialsClearcoatTintExtensionImport implements GLTFLoaderPlugin {
public name: string
public parser: GLTFParser

constructor(parser: GLTFParser) {
this.parser = parser
this.name = ClearcoatTintPlugin.CLEARCOAT_TINT_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 = {}
ClearcoatTintPlugin.AddClearcoatTint(materialParams)
ThreeSerialization.Deserialize(extension, materialParams.userData._clearcoatTint)
}
}

const glTFMaterialsClearcoatTintExtensionExport = (w: GLTFWriter2)=> ({
writeMaterial: (material: any, materialDef: any) => {
if (!material.isMeshStandardMaterial || !material.userData._clearcoatTint?.enableTint) return
materialDef.extensions = materialDef.extensions || {}

const extensionDef: any = ThreeSerialization.Serialize(material.userData._clearcoatTint)

materialDef.extensions[ ClearcoatTintPlugin.CLEARCOAT_TINT_GLTF_EXTENSION ] = extensionDef
w.extensionsUsed[ ClearcoatTintPlugin.CLEARCOAT_TINT_GLTF_EXTENSION ] = true
},
})

+ 304
- 0
src/plugins/material/CustomBumpMapPlugin.ts Voir le fichier

import {Matrix3, SRGBColorSpace, Texture} from 'three'
import {AViewerPluginSync, ThreeViewer} from '../../viewer'
import {uiFolderContainer, UiObjectConfig, uiToggle} from 'uiconfig.js'
import {serialize} from 'ts-browser-helpers'
import {IMaterial, IObject3D, ITexture, PhysicalMaterial} from '../../core'
import {MaterialExtension, updateMaterialDefines} from '../../materials'
import {shaderReplaceString} from '../../utils'
import {GLTFLoader2, GLTFWriter2} from '../../assetmanager'
import type {GLTFLoaderPlugin, GLTFParser} from 'three/examples/jsm/loaders/GLTFLoader'
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
*/
@uiFolderContainer('CustomBumpMap Materials')
export class CustomBumpMapPlugin extends AViewerPluginSync<''> {
static readonly PluginType = 'CustomBumpMapPlugin'

@uiToggle('Enabled', (that: CustomBumpMapPlugin)=>({onChange: that.setDirty}))
@serialize() enabled = true

@uiToggle('Bicubic', (that: CustomBumpMapPlugin)=>({onChange: that.setDirty}))
@matDefine('CUSTOM_BUMP_MAP_BICUBIC', undefined, true, CustomBumpMapPlugin.prototype.setDirty)
@serialize() bicubicFiltering = true

private _defines: any = {
['CUSTOM_BUMP_MAP_DEBUG']: false,
['CUSTOM_BUMP_MAP_BICUBIC']: true,
}
private _uniforms: any = {
customBumpUvTransform: {value: new Matrix3()},
customBumpScale: {value: 0.001},
customBumpMap: {value: null},
}

public enableCustomBump(material: IMaterial, map?: ITexture, scale?: number): boolean {
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
}

readonly materialExtension: MaterialExtension = {
parsFragmentSnippet: (_, material: PhysicalMaterial)=>{
if (!this.enabled || !material?.userData._hasCustomBump) return ''
return CustomBumpMapPluginShader
},
shaderExtender: (shader, material: PhysicalMaterial) => {
if (!this.enabled || !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 as any).defines.USE_UV = ''
},
onObjectRender: (object: IObject3D, 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,
},
computeCacheKey: (material1: PhysicalMaterial) => {
return (this.enabled ? '1' : '0') + (material1.userData._hasCustomBump ? '1' : '0') + material1.userData?._customBumpMap?.uuid
},
isCompatible: (material1: PhysicalMaterial) => material1.isPhysicalMaterial,
getUiConfig: material => {
const viewer = this._viewer!
const enableCustomBump = this.enableCustomBump.bind(this)
const state = material.userData
const config: UiObjectConfig = {
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('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: ()=>{
material.setDirty()
},
},
makeSamplerUi(state as any, '_customBumpMap'),
],
}
return config
},

}

setDirty = (): void => {
this.materialExtension.setDirty?.()
this._viewer?.setDirty()
}

private _loaderCreate({loader}: {loader: GLTFLoader2}) {
if (!loader.isGLTFLoader2) return
loader.register((p) => new GLTFMaterialsCustomBumpMapImport(p))
}

constructor() {
super()
this._loaderCreate = this._loaderCreate.bind(this)
}

onAdded(v: ThreeViewer) {
super.onAdded(v)
// v.addEventListener('preRender', this._preRender)
v.assetManager.materials.registerMaterialExtension(this.materialExtension)
v.assetManager.importer.addEventListener('loaderCreate', this._loaderCreate as any)
v.assetManager.exporter.getExporter('gltf', 'glb')?.extensions?.push(glTFMaterialsCustomBumpMapExport)
// v.getPlugin(GBufferPlugin)?.material?.registerMaterialExtensions([this.materialExtension])

}

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(glTFMaterialsCustomBumpMapExport)
if (index !== undefined && index >= 0) exporter.extensions?.splice(index, 1)
}
// v.getPlugin(GBufferPlugin)?.material?.unregisterMaterialExtensions([this.materialExtension])
return super.onRemove(v)
}

public static readonly CUSTOM_BUMP_MAP_GLTF_EXTENSION = 'WEBGI_materials_custom_bump_map'

}

declare module '../../core/IMaterial' {
interface IMaterialUserData {
_hasCustomBump?: boolean
_customBumpMap?: ITexture | null
_customBumpScale?: number
}
}


/**
* FragmentClipping Materials Extension
*
* Specification: https://webgi.xyz/docs/gltf-extensions/WEBGI_materials_fragment_clipping_extension.html
*/
class GLTFMaterialsCustomBumpMapImport implements GLTFLoaderPlugin {
public name: string
public parser: GLTFParser

constructor(parser: GLTFParser) {
this.parser = parser
this.name = CustomBumpMapPlugin.CUSTOM_BUMP_MAP_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 = {}
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: Texture) => {
// t.format = RGBFormat
t.colorSpace = SRGBColorSpace
}))
}
return Promise.all(pending)
}

// do any mesh or geometry processing here
// afterRoot(result: GLTF): Promise<void> | null {
// result.scene.traverse((object: any) => {
// const mat = object.material?.userData?._hasCustomBump
// if (!mat) return
// const geom = object.geometry
// if (!geom.attributes.tangent) {
// geom.computeTangents()
// geom.attributes.tangent.needsUpdate = true
// }
// })
// return null
// }
}

const glTFMaterialsCustomBumpMapExport = (w: GLTFWriter2)=> ({
writeMaterial: (material: any, materialDef: any) => {
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: any = {}

extensionDef.customBumpScale = material.userData._customBumpScale || 1.0

if (material.userData._customBumpMap) {

const customBumpMapDef = {index: w.processTexture(material.userData._customBumpMap)}
w.applyTextureTransform(customBumpMapDef, material.userData._customBumpMap)
extensionDef.customBumpMap = customBumpMapDef

}

materialDef.extensions[ CustomBumpMapPlugin.CUSTOM_BUMP_MAP_GLTF_EXTENSION ] = extensionDef
w.extensionsUsed[ CustomBumpMapPlugin.CUSTOM_BUMP_MAP_GLTF_EXTENSION ] = true
},
})

+ 281
- 0
src/plugins/material/FragmentClippingExtensionPlugin.ts Voir le fichier

import {Matrix3, Plane as PlaneThree, 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 FragmentClippingExtensionPluginPars from './shaders/FragmentClippingExtensionPlugin.pars.glsl'
import FragmentClippingExtensionPluginPatch from './shaders/FragmentClippingExtensionPlugin.patch.glsl'

/**
* FragmentClipping Materials Extension
* Adds a material extension to PhysicalMaterial to add support for fragment clipping.
* Fragment clipping allows to clip fragments of the material in screen space or world space based on a circle, rectangle, plane, sphere, etc.
* It uses fixed SDFs with params defined by the user for clipping.
* It also adds a UI to the material to edit the settings.
* It uses WEBGI_materials_fragment_clipping_extension glTF extension to save the settings in glTF files.
* @category Plugins
*/
@uiFolderContainer('FragmentClipping Materials')
export class FragmentClippingExtensionPlugin extends AViewerPluginSync<''> {
static readonly PluginType = 'FragmentClippingExtensionPlugin1'

@uiToggle('Enabled', (that: FragmentClippingExtensionPlugin)=>({onChange: that.setDirty}))
@serialize() enabled = true

private _defines: any = {
['FRAG_CLIPPING_DEBUG']: 0,
}
private _uniforms: any = {
fragClippingPosition: {value: new Vector4()}, // point on plane, center of sphere, center of cylinder, etc
fragClippingParams: {value: new Vector4()}, // normal of plane, radius of sphere, radius of cylinder, etc
fragClippingCamAspect: {value: 1},
}

public static AddFragmentClipping(material: IMaterial, params?: IMaterialUserData['_fragmentClippingExt']): boolean {
const ud = material?.userData
if (!ud) return false
if (!ud._fragmentClippingExt) {
ud._fragmentClippingExt = {}
}
const tf = ud._fragmentClippingExt
tf.clipEnabled = true
if (tf.clipPosition === undefined) tf.clipPosition = [0, 0, 0, 0]
if (tf.clipParams === undefined) tf.clipParams = [0, 0, 0, 0]
if (tf.clipMode === undefined !== undefined) tf.clipMode = FragmentClippingMode.Circle
if (tf.clipInvert === undefined !== undefined) tf.clipInvert = false
Object.assign(tf, params)
if (material.setDirty) material.setDirty()
return true
}

private _plane = new PlaneThree()
private _viewNormalMatrix = new Matrix3()
private _v4 = new Vector4()

readonly materialExtension: MaterialExtension = {
parsFragmentSnippet: (_, material: PhysicalMaterial)=>{
if (!this.enabled || !material?.userData._fragmentClippingExt?.clipEnabled) return ''
return Object.entries(FragmentClippingMode)
.map(v=>['FragmentClippingMode.' + v[0], '' + v[1]])// replace enum with integer values in the shader
.reduce((a, v)=>a.replace(v[0], v[1]), FragmentClippingExtensionPluginPars)
},
shaderExtender: (shader, material: PhysicalMaterial) => {
if (!this.enabled || !material?.userData._fragmentClippingExt?.clipEnabled) return
shader.fragmentShader = shaderReplaceString(shader.fragmentShader, '#glMarker mainStart', Object.entries(FragmentClippingMode)
.map(v=>['FragmentClippingMode.' + v[0], '' + v[1]]) // replace enum with integer values in the shader
.reduce((a, v)=>a.replace(v[0], v[1]), '\n' + FragmentClippingExtensionPluginPatch), {append: true})
},
onObjectRender: (object: IObject3D, material) => {
let tfUd = material.userData._fragmentClippingExt
if (material.userData.isGBufferMaterial && object && object.material && !Array.isArray(object.material)) { // todo isGBufferMaterial
tfUd = object.material?.userData._fragmentClippingExt
}
if (!tfUd?.clipEnabled) return

if (Array.isArray(tfUd.clipPosition))
this._uniforms.fragClippingPosition.value.fromArray(tfUd.clipPosition)
else
this._uniforms.fragClippingPosition.value.copy(tfUd.clipPosition)

if (tfUd.clipMode === FragmentClippingMode.Plane && tfUd.clipParams) {
const clipParams = Array.isArray(tfUd.clipParams) ? this._v4.fromArray(tfUd.clipParams) : this._v4.copy(tfUd.clipParams)
const viewMatrix = this._viewer!.scene.mainCamera.matrixWorldInverse
this._plane.normal.set(clipParams.x, clipParams.y, clipParams.z)
this._plane.constant = clipParams.w
this._viewNormalMatrix.getNormalMatrix(viewMatrix)
this._plane.applyMatrix4(viewMatrix, this._viewNormalMatrix)
this._uniforms.fragClippingParams.value.set(this._plane.normal.x, this._plane.normal.y, this._plane.normal.z, this._plane.constant)
} else {
if (Array.isArray(tfUd.clipPosition))
this._uniforms.fragClippingParams.value.fromArray(tfUd.clipParams)
else
this._uniforms.fragClippingParams.value.copy(tfUd.clipParams)
}
if (this._viewer?.scene.mainCamera.isPerspectiveCamera)
this._uniforms.fragClippingCamAspect.value = this._viewer?.scene.mainCamera.aspect
else this._uniforms.fragClippingCamAspect.value = 1.0

updateMaterialDefines({
...this._defines,
// ['FRAGMENT_CLIPPING_EXTENSION_ENABLED']: this.enabled,
['FRAG_CLIPPING_MODE']: +(tfUd.clipMode ?? FragmentClippingMode.Circle),
['FRAG_CLIPPING_INVERSE']: +(tfUd.clipInvert ?? false),
}, material)
},
extraUniforms: {
...this._uniforms,
},
computeCacheKey: (material1: PhysicalMaterial) => {
return (this.enabled ? '1' : '0') + (material1.userData._fragmentClippingExt?.clipEnabled ? '1' : '0')
},
isCompatible: (material1: PhysicalMaterial) => {
return material1.isPhysicalMaterial || material1.userData.isGBufferMaterial // todo isGBufferMaterial
},
getUiConfig: material => {
const viewer = this._viewer!
if (material.userData._fragmentClippingExt === undefined) material.userData._fragmentClippingExt = {}
const state = material.userData._fragmentClippingExt
const config: UiObjectConfig = {
type: 'folder',
label: 'Fragment Clipping',
onChange: (ev)=>{
if (!ev.config) return
this.setDirty()
},
children: [
{
type: 'checkbox',
label: 'Enabled',
get value() {
return state.clipEnabled || false
},
set value(v) {
if (v === state.clipEnabled) return
if (v) {
if (!FragmentClippingExtensionPlugin.AddFragmentClipping(material))
viewer.dialog.alert('Cannot add FragmentClippingExtension.')
} else {
state.clipEnabled = false
if (material.setDirty) material.setDirty()
}
config.uiRefresh?.(true, 'postFrame')
},
},
{
type: 'dropdown',
label: 'Mode',
children: Object.entries(FragmentClippingMode)
// .filter(key => !isNaN(Number(FragmentClippingMode[key])))
.map(v => ({label: v[0], value: v[1]})),
hidden: () => !state.clipEnabled,
property: [state, 'clipMode'],
},
{
type: 'vec4',
label: 'Position',
bounds: [-1, 1],
hidden: () => !state.clipEnabled,
property: [state, 'clipPosition'],
},
{
type: 'vec4',
label: 'Params',
bounds: [0, 1],
hidden: () => !state.clipEnabled,
property: [state, 'clipParams'],
},
{
type: 'toggle',
label: 'Invert',
hidden: () => !state.clipEnabled,
property: [state, 'clipInvert'],
},
],
}
return config
},

}

setDirty = (): void => {
this.materialExtension.setDirty?.()
this._viewer?.setDirty()
}

private _loaderCreate({loader}: {loader: GLTFLoader2}) {
if (!loader.isGLTFLoader2) return
loader.register((p) => new GLTFMaterialsFragmentClippingExtensionImport(p))
}

constructor() {
super()
this._loaderCreate = this._loaderCreate.bind(this)
}

onAdded(v: ThreeViewer) {
super.onAdded(v)
// v.addEventListener('preRender', this._preRender)
v.assetManager.materials.registerMaterialExtension(this.materialExtension)
v.assetManager.importer.addEventListener('loaderCreate', this._loaderCreate as any)
v.assetManager.exporter.getExporter('gltf', 'glb')?.extensions?.push(glTFMaterialsFragmentClippingExtensionExport)
// v.getPlugin(GBufferPlugin)?.material?.registerMaterialExtensions([this.materialExtension])

}

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(glTFMaterialsFragmentClippingExtensionExport)
if (index !== undefined && index >= 0) exporter.extensions?.splice(index, 1)
}
// v.getPlugin(GBufferPlugin)?.material?.unregisterMaterialExtensions([this.materialExtension])
return super.onRemove(v)
}

public static readonly FRAGMENT_CLIPPING_EXTENSION_GLTF_EXTENSION = 'WEBGI_materials_fragment_clipping_extension'

}

declare module '../../core/IMaterial' {
interface IMaterialUserData {
_fragmentClippingExt?: {
clipEnabled?: boolean
clipPosition?: Vector4|Vector4Tuple
clipParams?: Vector4|Vector4Tuple
clipMode?: FragmentClippingMode
clipInvert?: boolean
}
}
}


export enum FragmentClippingMode {
Circle = 0,
Ellipse = 1,
Rectangle = 2,
Plane = 3,
Sphere = 4
}

/**
* FragmentClipping Materials Extension
*
* Specification: https://webgi.xyz/docs/gltf-extensions/WEBGI_materials_fragment_clipping_extension.html
*/
class GLTFMaterialsFragmentClippingExtensionImport implements GLTFLoaderPlugin {
public name: string
public parser: GLTFParser

constructor(parser: GLTFParser) {
this.parser = parser
this.name = FragmentClippingExtensionPlugin.FRAGMENT_CLIPPING_EXTENSION_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 = {}
FragmentClippingExtensionPlugin.AddFragmentClipping(materialParams)
ThreeSerialization.Deserialize(extension, materialParams.userData._fragmentClippingExt)
}
}

const glTFMaterialsFragmentClippingExtensionExport = (w: GLTFWriter2)=> ({
writeMaterial: (material: any, materialDef: any) => {
if (!material.isMeshStandardMaterial || !material.userData._fragmentClippingExt?.clipEnabled) return
materialDef.extensions = materialDef.extensions || {}

const extensionDef: any = ThreeSerialization.Serialize(material.userData._fragmentClippingExt)

materialDef.extensions[ FragmentClippingExtensionPlugin.FRAGMENT_CLIPPING_EXTENSION_GLTF_EXTENSION ] = extensionDef
w.extensionsUsed[ FragmentClippingExtensionPlugin.FRAGMENT_CLIPPING_EXTENSION_GLTF_EXTENSION ] = true
},
})

+ 327
- 0
src/plugins/material/NoiseBumpMaterialPlugin.ts Voir le fichier

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('NoiseBump Materials')
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)
Object.assign(tf, params)
if (material.setDirty) material.setDirty()
return true
}

readonly materialExtension: MaterialExtension = {
parsFragmentSnippet: (_, material: PhysicalMaterial)=>{
if (!this.enabled || !material?.userData._noiseBumpMat?.hasBump) return ''
return NoiseBumpMaterialPluginPars
},
shaderExtender: (shader, material: PhysicalMaterial) => {
if (!this.enabled || !material?.userData._noiseBumpMat?.hasBump) return
shader.fragmentShader = shaderReplaceString(shader.fragmentShader, '#glMarker beforeAccumulation', NoiseBumpMaterialPluginPatch, {prepend: true})
;(shader as any).defines.USE_UV = ''
;(shader as any).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.enabled,
}, material)
},
extraUniforms: {
...this._uniforms,
},
computeCacheKey: (material1: PhysicalMaterial) => {
return (this.enabled ? '1' : '0') + (material1.userData._noiseBumpMat?.hasBump ? '1' : '0')
},
isCompatible: (material1: PhysicalMaterial) => material1.isPhysicalMaterial,
getUiConfig: material => {
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)
}

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
},
})

+ 88
- 0
src/plugins/material/shaders/CustomBumpMapPlugin.glsl Voir le fichier

#if defined(CUSTOM_BUMP_MAP_ENABLED) && CUSTOM_BUMP_MAP_ENABLED > 0

#if CUSTOM_BUMP_MAP_BICUBIC > 0 // from http://www.java-gaming.org/index.php?topic=35123.0
vec4 cubic_cb(float v){
vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v;
vec4 s = n * n * n;
float x = s.x;
float y = s.y - 4.0 * s.x;
float z = s.z - 4.0 * s.y + 6.0 * s.x;
float w = 6.0 - x - y - z;
return vec4(x, y, z, w) * (1.0/6.0);
}

vec4 textureBicubic_cb(sampler2D sampler, vec2 texCoords){

vec2 texSize = vec2(textureSize(sampler, 0));
vec2 invTexSize = 1.0 / texSize;

texCoords = texCoords * texSize - 0.5;

vec2 fxy = fract(texCoords);
texCoords -= fxy;

vec4 xcubic = cubic_cb(fxy.x);
vec4 ycubic = cubic_cb(fxy.y);

vec4 c = texCoords.xxyy + vec2 (-0.5, +1.5).xyxy;

vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);
vec4 offset = c + vec4 (xcubic.yw, ycubic.yw) / s;

offset *= invTexSize.xxyy;

vec4 sample0 = texture(sampler, offset.xz);
vec4 sample1 = texture(sampler, offset.yz);
vec4 sample2 = texture(sampler, offset.xw);
vec4 sample3 = texture(sampler, offset.yw);

float sx = s.x / (s.x + s.y);
float sy = s.z / (s.z + s.w);

return mix(
mix(sample3, sample2, sx), mix(sample1, sample0, sx)
, sy);
}
#endif

varying vec2 vCustomBumpUv;
uniform sampler2D customBumpMap;
uniform float customBumpScale;

// same as bumpmap_pars_fragment, but with customBumpMap, customBumpUv and bicubic
vec2 dHdxy_fwd_cb() {

vec2 dSTdx = dFdx( vCustomBumpUv );
vec2 dSTdy = dFdy( vCustomBumpUv );

#if CUSTOM_BUMP_MAP_BICUBIC > 0
float Hll = customBumpScale * textureBicubic_cb( customBumpMap, vCustomBumpUv ).x;
float dBx = customBumpScale * textureBicubic_cb( customBumpMap, vCustomBumpUv + dSTdx ).x - Hll;
float dBy = customBumpScale * textureBicubic_cb( customBumpMap, vCustomBumpUv + dSTdy ).x - Hll;
#else
float Hll = customBumpScale * texture2D( customBumpMap, vCustomBumpUv ).x;
float dBx = customBumpScale * texture2D( customBumpMap, vCustomBumpUv + dSTdx ).x - Hll;
float dBy = customBumpScale * texture2D( customBumpMap, vCustomBumpUv + dSTdy ).x - Hll;
#endif

return vec2( dBx, dBy );

}
#ifndef USE_BUMPMAP
vec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {

vec3 vSigmaX = dFdx( surf_pos.xyz );
vec3 vSigmaY = dFdy( surf_pos.xyz );
vec3 vN = surf_norm; // normalized

vec3 R1 = cross( vSigmaY, vN );
vec3 R2 = cross( vN, vSigmaX );

float fDet = dot( vSigmaX, R1 ) * faceDirection;

vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );
return normalize( abs( fDet ) * surf_norm - vGrad );

}
#endif
#endif

+ 49
- 0
src/plugins/material/shaders/FragmentClippingExtensionPlugin.pars.glsl Voir le fichier

#include <simpleCameraHelpers>
uniform vec4 fragClippingPosition;
uniform vec4 fragClippingParams;
uniform float fragClippingCamAspect;
#if FRAG_CLIPPING_MODE == FragmentClippingMode.Circle
float fragClippingCircle(){
vec2 pos = viewToScreen(vViewPosition.xyz).xy;
float radius = fragClippingParams.x;
vec2 center = fragClippingPosition.xy;
pos.y /= fragClippingCamAspect;
center.y /= fragClippingCamAspect;
return length(pos - center) - radius;
}
#elif FRAG_CLIPPING_MODE == FragmentClippingMode.Ellipse
float fragClippingEllipse(){
vec2 pos = viewToScreen(vViewPosition.xyz).xy;
vec2 radius = fragClippingParams.xy;
vec2 center = fragClippingPosition.xy;
pos.y /= fragClippingCamAspect;
center.y /= fragClippingCamAspect;
return length((pos - center) / radius) - 1.0;
}
#elif FRAG_CLIPPING_MODE == FragmentClippingMode.Rectangle
float fragClippingRectangle(){
vec2 pos = viewToScreen(vViewPosition.xyz).xy;
vec2 radius = fragClippingParams.xy;
vec2 center = fragClippingPosition.xy;
pos.y /= fragClippingCamAspect;
center.y /= fragClippingCamAspect;
vec2 d = abs(pos - center) - radius;
return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}
#elif FRAG_CLIPPING_MODE == FragmentClippingMode.Plane
float fragClippingPlane(){
vec3 pos = vViewPosition.xyz;
vec3 normal = fragClippingParams.xyz;
float d = dot(pos, normal) - fragClippingParams.w;
return d;
}
#elif FRAG_CLIPPING_MODE == FragmentClippingMode.Sphere
float fragClippingSphere(){
vec3 pos = vViewPosition.xyz;
vec3 center = fragClippingPosition.xyz;
float radius = fragClippingParams.x;
pos.y /= fragClippingCamAspect;
center.y /= fragClippingCamAspect;
return length(pos - center) - radius;
}
#endif

+ 24
- 0
src/plugins/material/shaders/FragmentClippingExtensionPlugin.patch.glsl Voir le fichier

float fragClippingDist = 0.0;
#if FRAG_CLIPPING_MODE == FragmentClippingMode.Circle
fragClippingDist = fragClippingCircle();
#elif FRAG_CLIPPING_MODE == FragmentClippingMode.Ellipse
fragClippingDist = fragClippingEllipse();
#elif FRAG_CLIPPING_MODE == FragmentClippingMode.Rectangle
fragClippingDist = fragClippingRectangle();
#elif FRAG_CLIPPING_MODE == FragmentClippingMode.Plane
fragClippingDist = fragClippingPlane();
#elif FRAG_CLIPPING_MODE == FragmentClippingMode.Sphere
fragClippingDist = fragClippingSphere();
#endif
#if FRAG_CLIPPING_DEBUG
gl_FragColor = vec4(max(fragClippingDist, 0.0), 0.0, 0.0, 1.0);
// gl_FragColor = vec4(vViewPosition.xyz, 1.0);
#include <encodings_fragment>
return;
#endif

#if FRAG_CLIPPING_INVERSE == 1
if (fragClippingDist > 0.0) discard;
#else
if (fragClippingDist < 0.0) discard;
#endif

+ 27
- 0
src/plugins/material/shaders/NoiseBumpMaterialPlugin.pars.glsl Voir le fichier

#include <randomHelpers>
#include <voronoiNoise>

uniform vec2 noiseBumpParams;
uniform float noiseBumpScale;
uniform float noiseBumpFlakeScale;
uniform float noiseFlakeClamp;
uniform float noiseFlakeRadius;
uniform bool useColorFlakes;
uniform vec4 flakeParams; // Roughness, Metalness, Strength, Threshold
uniform vec3 flakeFallOffParams; // useFallOff, fallOffFactor

vec3 perturbNormalArb_nb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy, float faceDirection ) {

vec3 vSigmaX = dFdx( surf_pos.xyz );
vec3 vSigmaY = dFdy( surf_pos.xyz );
vec3 vN = surf_norm; // normalized

vec3 R1 = cross( vSigmaY, vN );
vec3 R2 = cross( vN, vSigmaX );

float fDet = dot( vSigmaX, R1 ) * faceDirection;

vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );
return normalize( abs( fDet ) * surf_norm - vGrad );

}

+ 38
- 0
src/plugins/material/shaders/NoiseBumpMaterialPlugin.patch.glsl Voir le fichier

vec3 outColor, outColor1, outColor2, outColor3, outColor4, outColor5;
float distFac = length(vViewPosition.xyz);
/*float e = floor( log2( 0.3 * distFac + 3.0 ) / 0.3785116);
float level_z = 0.1 * pow( 1.3 , e ) - 0.2;*/
float level = 1.;//0.15 / level_z;
vec2 uvMod = noiseBumpFlakeScale * noiseBumpParams.xy * vUv * level;
float voronoiDist = clamp(voronoi_f1_2d( uvMod, 1., noiseFlakeClamp, noiseFlakeRadius, outColor ), 0.0, 1.0);

vec3 oldNormal = normal;
normal = perturbNormalArb_nb( - vViewPosition, normal, (2. * outColor.xy - 1.) * noiseBumpScale, faceDirection );

float oldRoughnessFactor = roughnessFactor;
float oldMetalnessFactor = metalnessFactor;
roughnessFactor = mix(roughnessFactor, flakeParams.x, 1. - voronoiDist);
metalnessFactor = mix(metalnessFactor, flakeParams.y, 1. - voronoiDist);


#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )

vec3 sparkleRadiance = getIBLRadiance( normalize(vViewPosition), normal, roughnessFactor );
float sparkleIntensity = length(sparkleRadiance);
float sparkleIntensityMultiplier = sparkleIntensity > 1.3 ? flakeParams.z : 1.;
vec3 oldDiffuseColor = diffuseColor.rgb;
vec2 cellPosition_ = floor(uvMod);
vec3 colorRGB = useColorFlakes ? hash3(cellPosition_) : vec3(1.);
float fallOff_ = mix(1., 1. / (1. + flakeFallOffParams.y * distFac + flakeFallOffParams.z * distFac * distFac), flakeFallOffParams.x);
diffuseColor.rgb *= mix(vec3(1.), sparkleIntensityMultiplier * colorRGB * fallOff_, vec3(1. - voronoiDist));

if(sparkleIntensity < flakeParams.w) {
float mixFactor = 1.;
roughnessFactor = mix(roughnessFactor, oldRoughnessFactor, mixFactor);
metalnessFactor = mix(metalnessFactor, oldMetalnessFactor, mixFactor);
normal = normalize(mix(normal, oldNormal, mixFactor));
diffuseColor.rgb = mix(diffuseColor.rgb, oldDiffuseColor, mixFactor);
}
#endif

Chargement…
Annuler
Enregistrer