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.
325 lines • 15.3 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;
};
var NoiseBumpMaterialPlugin_1;
import { Vector2, Vector3, Vector4 } from 'three';
import { AViewerPluginSync } from '../../viewer';
import { uiFolderContainer, uiToggle } from 'uiconfig.js';
import { serialize } from 'ts-browser-helpers';
import { updateMaterialDefines } from '../../materials';
import { shaderReplaceString, ThreeSerialization } from '../../utils';
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
*/
let NoiseBumpMaterialPlugin = NoiseBumpMaterialPlugin_1 = class NoiseBumpMaterialPlugin extends AViewerPluginSync {
static AddNoiseBumpMaterial(material, params) {
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;
}
constructor() {
super();
this.enabled = true;
// private _defines: any = {
// }
this._uniforms = {
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 },
};
this.materialExtension = {
parsFragmentSnippet: (_, material) => {
if (this.isDisabled() || !material?.userData._noiseBumpMat?.hasBump)
return '';
return NoiseBumpMaterialPluginPars;
},
shaderExtender: (shader, material) => {
if (this.isDisabled() || !material?.userData._noiseBumpMat?.hasBump)
return;
shader.fragmentShader = shaderReplaceString(shader.fragmentShader, '#glMarker beforeAccumulation', NoiseBumpMaterialPluginPatch, { prepend: true });
shader.defines && (shader.defines.USE_UV = '');
shader.extensionDerivatives = true;
},
onObjectRender: (_, 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) => {
return (this.isDisabled() ? '0' : '1') + (material1.userData._noiseBumpMat?.hasBump ? '1' : '0');
},
isCompatible: (material1) => material1.isPhysicalMaterial,
getUiConfig: material => {
const viewer = this._viewer;
if (material.userData._noiseBumpMat === undefined)
material.userData._noiseBumpMat = {};
const state = material.userData._noiseBumpMat;
const config = {
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_1.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;
},
};
this.setDirty = () => {
this.materialExtension.setDirty?.();
this._viewer?.setDirty();
};
Object.assign(this.materialExtension.extraUniforms, this._uniforms);
}
onAdded(v) {
super.onAdded(v);
v.assetManager.materials.registerMaterialExtension(this.materialExtension);
v.assetManager.registerGltfExtension(noiseBumpMaterialGLTFExtension);
}
onRemove(v) {
v.assetManager.materials?.unregisterMaterialExtension(this.materialExtension);
v.assetManager.unregisterGltfExtension(noiseBumpMaterialGLTFExtension.name);
return super.onRemove(v);
}
};
NoiseBumpMaterialPlugin.PluginType = 'NoiseBumpMaterialPlugin';
/**
* @deprecated - use {@link noiseBumpMaterialGLTFExtension}
*/
NoiseBumpMaterialPlugin.NOISE_BUMP_MATERIAL_GLTF_EXTENSION = 'WEBGI_materials_noise_bump';
__decorate([
uiToggle('Enabled', (that) => ({ onChange: that.setDirty })),
serialize()
], NoiseBumpMaterialPlugin.prototype, "enabled", void 0);
NoiseBumpMaterialPlugin = NoiseBumpMaterialPlugin_1 = __decorate([
uiFolderContainer('Noise/Sparkle Bump (MatExt)')
], NoiseBumpMaterialPlugin);
export { NoiseBumpMaterialPlugin };
/**
* FragmentClipping Materials Extension
*
* Specification: https://threepipe.org/docs/gltf-extensions/WEBGI_materials_fragment_clipping_extension.html (todo - fix link)
*/
class GLTFMaterialsNoiseBumpMaterialImport {
constructor(parser) {
this.parser = parser;
this.name = noiseBumpMaterialGLTFExtension.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 = {};
NoiseBumpMaterialPlugin.AddNoiseBumpMaterial(materialParams);
ThreeSerialization.Deserialize(extension, materialParams.userData._noiseBumpMat);
}
}
const glTFMaterialsNoiseBumpMaterialExport = (w) => ({
writeMaterial: (material, materialDef) => {
if (!material.isMeshStandardMaterial || !material.userData._noiseBumpMat?.hasBump)
return;
materialDef.extensions = materialDef.extensions || {};
const extensionDef = ThreeSerialization.Serialize(material.userData._noiseBumpMat);
materialDef.extensions[noiseBumpMaterialGLTFExtension.name] = extensionDef;
w.extensionsUsed[noiseBumpMaterialGLTFExtension.name] = true;
},
});
export const noiseBumpMaterialGLTFExtension = {
name: 'WEBGI_materials_noise_bump',
import: (p) => new GLTFMaterialsNoiseBumpMaterialImport(p),
export: glTFMaterialsNoiseBumpMaterialExport,
textures: undefined,
};
//# sourceMappingURL=NoiseBumpMaterialPlugin.js.map