threepipe
Version:
A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.
401 lines • 18.2 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 { ClampToEdgeWrapping, Color, DoubleSide, GLSL1, GLSL3, NoBlending, TangentSpaceNormalMap, UniformsLib, UniformsUtils, UnsignedByteType, UnsignedIntType, Vector2, Vector4, } from 'three';
import { GBufferRenderPass } from '../../postprocessing';
import { ViewerRenderManager } from '../../viewer';
import { updateMaterialDefines } from '../../materials';
import { PipelinePassPlugin } from '../base/PipelinePassPlugin';
import { uiFolderContainer, uiImage } from 'uiconfig.js';
import { shaderReplaceString } from '../../utils';
import GBufferUnpack from './shaders/GBufferPlugin.unpack.glsl';
import GBufferMatVert from './shaders/GBufferPlugin.mat.vert.glsl';
import GBufferMatFrag from './shaders/GBufferPlugin.mat.frag.glsl';
import { ShaderMaterial2, } from '../../core';
/**
* G-Buffer Plugin
*
* Adds a pre-render pass to render the g-buffer(depth+normal+flags) to render target(s) that can be used as gbuffer and for postprocessing.
* @category Plugins
*/
let GBufferPlugin = class GBufferPlugin extends PipelinePassPlugin {
get normalDepthTexture() {
return this.textures[0];
}
get flagsTexture() {
return this.textures[1];
}
get depthTexture() {
return this.target?.depthTexture;
}
_createTargetAndMaterial(recreateTarget = true) {
if (!this._viewer)
return;
if (recreateTarget)
this._disposeTarget();
const useMultiple = this._viewer?.renderManager.isWebGL2 && this.renderFlagsBuffer;
if (!this.target) {
const rm = this._viewer.renderManager;
this.target = this._viewer.renderManager.createTarget({
depthBuffer: true,
samples: this._viewer.renderManager.zPrepass && this.isPrimaryGBuffer && rm.msaa ? // requirement for zPrepass
typeof rm.msaa !== 'number' ? ViewerRenderManager.DEFAULT_MSAA_SAMPLES : rm.msaa : 0,
type: this.bufferType,
textureCount: useMultiple ? 2 : 1,
depthTexture: this.renderDepthTexture,
depthTextureType: this.depthTextureType,
// magFilter: NearestFilter,
// minFilter: NearestFilter,
// generateMipmaps: false,
// encoding: LinearEncoding,
wrapS: ClampToEdgeWrapping,
wrapT: ClampToEdgeWrapping,
});
if (Array.isArray(this.target.texture)) {
this.target.texture[0].name = 'gbufferDepthNormal';
this.target.texture[1].name = 'gbufferFlags';
this.textures = this.target.texture;
// todo flag buffer filtering?
// const flagTexture = this.flagsTexture
// flagTexture.generateMipmaps = false
// flagTexture.minFilter = NearestFilter
// flagTexture.magFilter = NearestFilter
}
else {
this.target.texture.name = 'gbufferDepthNormal';
this.textures.push(this.target.texture);
}
}
if (!this.material) {
this.material = new GBufferMaterial(useMultiple, {
blending: NoBlending,
transparent: true,
});
}
// if (this._pass) this._pass.target = this.target
if (this.isPrimaryGBuffer) {
this._viewer.renderManager.gbufferTarget = this.target;
this._viewer.renderManager.gbufferUnpackExtension = this.unpackExtension;
this._viewer.renderManager.screenPass.material.registerMaterialExtensions([this.unpackExtension]);
this._isPrimaryGBufferSet = true;
}
}
_disposeTarget() {
if (!this._viewer)
return;
if (this.target) {
this._viewer.renderManager.disposeTarget(this.target);
this.target = undefined;
}
this.textures = [];
if (this._isPrimaryGBufferSet) { // using a separate flag as when isPrimaryGBuffer is changed, we cannot check it.
this._viewer.renderManager.gbufferTarget = undefined;
this._viewer.renderManager.gbufferUnpackExtension = undefined;
// this._viewer.renderManager.screenPass.material.unregisterMaterialExtensions([this.unpackExtension]) // todo
this._isPrimaryGBufferSet = false;
}
}
_createPass() {
this._createTargetAndMaterial(true);
if (!this.target)
throw new Error('GBufferPlugin: target not created');
if (!this.material)
throw new Error('GBufferPlugin: material not created');
this.material.userData.isGBufferMaterial = true;
const pass = new GBufferRenderPass(this.passId, () => this.target, this.material, new Color(1, 1, 1), 1);
const preprocessMaterial = pass.preprocessMaterial;
pass.preprocessMaterial = (m) => preprocessMaterial(m, m.userData.renderToDepth); // if renderToDepth is undefined then renderToGbuffer is taken internally
pass.before = ['render'];
pass.after = [];
pass.required = ['render'];
return pass;
}
_beforeRender(scene, camera, renderManager) {
if (!super._beforeRender(scene, camera, renderManager) || !this.material)
return false;
camera.updateShaderProperties(this.material);
return true;
}
constructor(bufferType = UnsignedByteType, isPrimaryGBuffer = true, enabled = true, renderFlagsBuffer = true, renderDepthTexture = false, depthTextureType = UnsignedIntType) {
super();
this.renderFlagsBuffer = renderFlagsBuffer;
this.renderDepthTexture = renderDepthTexture;
this.depthTextureType = depthTextureType;
this.passId = 'gbuffer';
// @uiConfig(/* {readOnly: true}*/) // todo: fix bug in uiconfig or tpImageGenerator because of which 0 index is not showing in the UI, when we uncomment this
this.textures = [];
// protected _depthPackingChanged() {
// this.material.depthPacking = this.depthPacking
// this.material.needsUpdate = true
// if (this.unpackExtension && this.unpackExtension.extraDefines) {
// this.unpackExtension.extraDefines.DEPTH_PACKING = this.depthPacking
// this.unpackExtension.setDirty?.()
// }
// this.setDirty()
// }
this.unpackExtension = {
/**
* Use this in shader to get the snippet
* ```
* // for gbuffer
* #include <packing>
* #define THREE_PACKING_INCLUDED
* ```
* or if you don't need packing include
* ```
* #include <gbuffer_unpack>
* ```
* @param shader
*/
shaderExtender: (shader) => {
const includes = ['gbuffer_unpack', 'packing'];
const include = includes.find(i => shader.fragmentShader.includes(`#include <${i}>`));
shader.fragmentShader = shaderReplaceString(shader.fragmentShader, `#include <${include}>`, '\n' + GBufferUnpack + '\n', { append: include === 'packing' });
},
extraUniforms: {
tNormalDepth: () => ({ value: this.normalDepthTexture }),
tGBufferFlags: () => ({ value: this.flagsTexture }),
tGBufferDepthTexture: () => ({ value: this.depthTexture }),
},
extraDefines: {
// ['GBUFFER_PACKING']: BasicDepthPacking,
['HAS_NORMAL_DEPTH_BUFFER']: () => this.normalDepthTexture ? 1 : undefined,
['GBUFFER_HAS_DEPTH_TEXTURE']: () => this.depthTexture ? 1 : undefined,
['GBUFFER_HAS_FLAGS']: () => this.flagsTexture ? 1 : undefined,
// ['HAS_FLAGS_BUFFER']: ()=>this.flagsTexture ? 1 : undefined,
['HAS_GBUFFER']: () => this.isPrimaryGBuffer && this.normalDepthTexture ? 1 : undefined,
// LINEAR_DEPTH: 1, // to tell that the depth is linear. todo; see SSAOPlugin. also add support in DepthBufferPlugin?
},
priority: 100,
isCompatible: () => true,
};
this._isPrimaryGBufferSet = false;
this.enabled = enabled;
this.bufferType = bufferType;
this.isPrimaryGBuffer = isPrimaryGBuffer;
// this.depthPacking = depthPacking
}
registerGBufferUpdater(key, updater) {
if (this.material)
this.material.flagUpdaters.set(key, updater);
}
unregisterGBufferUpdater(key) {
if (this.material)
this.material.flagUpdaters.delete(key);
}
onRemove(viewer) {
this._disposeTarget();
this.material?.dispose();
this.material = undefined;
return super.onRemove(viewer);
}
/**
* @deprecated use {@link normalDepthTexture} instead
*/
getDepthNormal() {
return this.textures.length > 0 ? this.textures[0] : undefined;
}
/**
* @deprecated use {@link flagsTexture} instead
*/
getFlagsTexture() {
return this.textures.length > 1 ? this.textures[1] : undefined;
}
/**
* @deprecated use {@link target} instead
*/
getTarget() {
return this.target;
}
/**
* @deprecated use {@link unpackExtension} instead
*/
getUnpackSnippet() {
return GBufferUnpack;
}
/**
* @deprecated use {@link unpackExtension} instead, it adds the same uniforms and defines
* @param material
*/
updateShaderProperties(material) {
if (material.uniforms.tNormalDepth)
material.uniforms.tNormalDepth.value = this.normalDepthTexture ?? undefined;
else
this._viewer?.console.warn('BaseRenderer: no uniform: tNormalDepth');
if (material.uniforms.tGBufferFlags) {
material.uniforms.tGBufferFlags.value = this.flagsTexture ?? undefined;
const t = material.uniforms.tGBufferFlags.value ? 1 : 0;
if (t !== material.defines.GBUFFER_HAS_FLAGS) {
material.defines.GBUFFER_HAS_FLAGS = t;
material.needsUpdate = true;
}
}
return this;
}
};
GBufferPlugin.PluginType = 'GBuffer';
__decorate([
uiImage( /* {readOnly: true}*/)
], GBufferPlugin.prototype, "normalDepthTexture", null);
__decorate([
uiImage( /* {readOnly: true}*/)
], GBufferPlugin.prototype, "flagsTexture", null);
__decorate([
uiImage( /* {readOnly: true}*/)
], GBufferPlugin.prototype, "depthTexture", null);
GBufferPlugin = __decorate([
uiFolderContainer('G-Buffer Plugin')
], GBufferPlugin);
export { GBufferPlugin };
/**
* Renders DepthNormal to a texture and flags to another
*/
export class GBufferMaterial extends ShaderMaterial2 {
constructor(multipleRT = true, parameters) {
super({
vertexShader: GBufferMatVert,
fragmentShader: GBufferMatFrag,
uniforms: UniformsUtils.merge([
UniformsLib.common,
UniformsLib.bumpmap,
UniformsLib.normalmap,
UniformsLib.displacementmap,
{
cameraNearFar: { value: new Vector2(0.1, 1000) }, // this has to be set from outside
flags: { value: new Vector4(255, 255, 255, 255) },
},
]),
defines: {
// eslint-disable-next-line @typescript-eslint/naming-convention
IS_GLSL3: multipleRT ? '1' : '0',
},
glslVersion: multipleRT ? GLSL3 : GLSL1,
...parameters,
});
this.flagUpdaters = new Map();
this.normalMapType = TangentSpaceNormalMap;
this.flatShading = false;
this.reset();
}
onBeforeRender(renderer, scene, camera, geometry, object) {
super.onBeforeRender(renderer, scene, camera, geometry, object);
let material = object.material;
if (Array.isArray(material)) { // todo: add support for multi materials.
material = material[0];
}
if (!material)
return;
const setMap = (key) => {
const map = material[key];
if (!map)
return;
this.uniforms[key].value = map;
if (!this.uniforms[key + 'Transform'])
console.error('GBufferMaterial: ' + key + 'Transform is not defined in uniform');
else
renderer.materials.refreshTransformUniform(map, this.uniforms[key + 'Transform']);
};
setMap('map');
if (material.side !== undefined)
this.side = material.side ?? DoubleSide;
setMap('alphaMap');
if (material.alphaTest !== undefined)
this.alphaTest = material.alphaTest < 1e-4 ? 1e-4 : material.alphaTest;
setMap('bumpMap');
if (material.bumpScale !== undefined)
this.uniforms.bumpScale.value = material.bumpScale;
setMap('normalMap');
if (material.normalScale !== undefined)
this.uniforms.normalScale.value.copy(material.normalScale);
if (material.normalMapType !== undefined)
this.normalMapType = material.normalMapType;
if (material.flatShading !== undefined)
this.flatShading = material.flatShading;
setMap('displacementMap');
if (material.displacementScale !== undefined)
this.uniforms.displacementScale.value = material.displacementScale;
if (material.displacementBias !== undefined)
this.uniforms.displacementBias.value = material.displacementBias;
if (material.wireframe !== undefined)
this.wireframe = material.wireframe;
if (material.wireframeLinewidth !== undefined)
this.wireframeLinewidth = material.wireframeLinewidth;
/*
GBuffer Flags has the following data
1st Rendertarget has Depth and Normal buffers
2nd Render Target::
x : Empty
y : first 3 bits lut index, second 5 bits bevel radius
z : material id (userData.gBufferData?.materialId, userData.matId)
w : this field is for setting bits - lutEnable-0, tonemap-1, bloom-2, ssao(cast)-3, dof-4
*/
this.uniforms.flags.value.set(255, 255, 255, 255);
const materialId = material.userData.gBufferData?.materialId ?? material.userData.matId; // matId for backward compatibility
this.uniforms.flags.value.z = materialId || 0;
this.flagUpdaters.forEach((updater) => updater(this.uniforms.flags.value, { material, renderer, scene, camera, geometry, object }));
this.uniforms.flags.value.x /= 255;
this.uniforms.flags.value.y /= 255;
this.uniforms.flags.value.z /= 255;
this.uniforms.flags.value.w /= 255;
this.uniformsNeedUpdate = true;
updateMaterialDefines({
// ['USE_ALPHAMAP']: this.uniforms.alphaMap.value ? 1 : undefined,
['ALPHAMAP_UV']: this.uniforms.alphaMap.value ? 'uv' : undefined, // todo use getChannel, see WebGLPrograms.js
['USE_DISPLACEMENTMAP']: this.uniforms.displacementMap.value ? 1 : undefined,
['DISPLACEMENTMAP_UV']: this.uniforms.displacementMap.value ? 'uv' : undefined, // todo use getChannel, see WebGLPrograms.js
['ALPHA_I_RGBA_PACKING']: material.userData.ALPHA_I_RGBA_PACKING ? 1 : undefined,
['FORCED_LINEAR_DEPTH']: material.userData.forcedLinearDepth ?? undefined, // todo add to DepthBufferPlugin as well.
}, material);
// todo: do the same in DepthBufferPlugin and NormalBufferPlugin
// what about the material extension settings in the userData of the source materials?
if (material.materialExtensions?.length) {
this.registerMaterialExtensions(material.materialExtensions);
}
// this.transparent = true
this.needsUpdate = true;
}
onAfterRender(renderer, scene, camera, geometry, object) {
super.onAfterRender(renderer, scene, camera, geometry, object);
let material = object.material;
if (Array.isArray(material)) { // todo: add support for multi materials.
material = material[0];
}
if (!material)
return;
if (material.materialExtensions?.length) {
this.unregisterMaterialExtensions(material.materialExtensions);
}
this.reset();
}
reset() {
this.uniforms.map.value = null;
this.side = DoubleSide;
this.uniforms.alphaMap.value = null;
this.alphaTest = 0.001;
this.uniforms.bumpMap.value = null;
this.uniforms.bumpScale.value = 1;
this.uniforms.normalMap.value = null;
this.uniforms.normalScale.value.set(1, 1);
this.normalMapType = TangentSpaceNormalMap;
this.flatShading = false;
this.uniforms.displacementMap.value = null;
this.uniforms.displacementScale.value = 1;
this.uniforms.displacementBias.value = 0;
this.uniforms.flags.value.set(255, 255, 255, 255);
this.wireframe = false;
this.wireframeLinewidth = 1;
}
}
/**
* @deprecated use GBufferMaterial instead
*/
export class DepthNormalMaterial extends GBufferMaterial {
constructor(multipleRT, parameters) {
super(multipleRT, parameters);
console.warn('DepthNormalMaterial is deprecated, use GBufferMaterial instead');
}
}
//# sourceMappingURL=GBufferPlugin.js.map