| @@ -16,13 +16,13 @@ export abstract class PipelinePassPlugin<T extends IPipelinePass, TPassId extend | |||
| set enabled(value: boolean) { | |||
| if (this._pass) this._pass.enabled = value | |||
| this._enabledTemp = value | |||
| this.setDirty() | |||
| } | |||
| @uiConfig() | |||
| @serialize('pass') | |||
| protected _pass?: T | |||
| abstract createPass(v:TViewer):T | |||
| protected abstract _createPass():T | |||
| /** | |||
| * This function is called every frame before composer render, if this pass is being used in the pipeline | |||
| @@ -40,7 +40,7 @@ export abstract class PipelinePassPlugin<T extends IPipelinePass, TPassId extend | |||
| onAdded(viewer: TViewer): void { | |||
| super.onAdded(viewer) | |||
| this._pass = this.createPass(viewer) | |||
| this._pass = this._createPass() | |||
| this._pass.onDirty?.push(viewer.setDirty) | |||
| this._pass.beforeRender = wrapThisFunction(this._beforeRender, this._pass.beforeRender) | |||
| viewer.renderManager.registerPass(this._pass) | |||
| @@ -66,6 +66,11 @@ export abstract class PipelinePassPlugin<T extends IPipelinePass, TPassId extend | |||
| return super.fromJSON(data, meta) | |||
| } | |||
| setDirty() { | |||
| this._viewer?.setDirty() | |||
| this.uiConfig?.uiRefresh?.(true, 'postFrame', 100) // adding delay for a few frames, so render target(if any can update) | |||
| } | |||
| } | |||
| function wrapThisFunction<T extends AnyFunction, T2>(f1: ()=>void, f2?: T): T { | |||
| @@ -1,7 +1,7 @@ | |||
| import { | |||
| BasicDepthPacking, | |||
| Color, | |||
| IUniform, | |||
| DepthPackingStrategies, | |||
| MeshDepthMaterial, | |||
| NoBlending, | |||
| Texture, | |||
| @@ -11,9 +11,13 @@ import { | |||
| } from 'three' | |||
| import {GBufferRenderPass} from '../../postprocessing' | |||
| import {ThreeViewer} from '../../viewer' | |||
| import {IShaderPropertiesUpdater} from '../../materials' | |||
| import {MaterialExtension} from '../../materials' | |||
| import {PipelinePassPlugin} from '../base/PipelinePassPlugin' | |||
| import {uiFolderContainer, uiImage} from 'uiconfig.js' | |||
| import {uiDropdown, uiFolderContainer, uiImage} from 'uiconfig.js' | |||
| import {shaderReplaceString} from '../../utils' | |||
| import {onChange} from 'ts-browser-helpers' | |||
| import DepthBufferUnpack from './shaders/DepthBufferPlugin.unpack.glsl' | |||
| import {threeConstMappings} from '../../three' | |||
| export type DepthBufferPluginEventTypes = '' | |||
| // type DepthBufferPluginTarget = WebGLMultipleRenderTargets | WebGLRenderTarget | |||
| @@ -22,8 +26,7 @@ export type DepthBufferPluginPass = GBufferRenderPass<'depth', DepthBufferPlugin | |||
| @uiFolderContainer('Depth Buffer Plugin') | |||
| export class DepthBufferPlugin | |||
| extends PipelinePassPlugin<DepthBufferPluginPass, 'depth', DepthBufferPluginEventTypes> | |||
| implements IShaderPropertiesUpdater { | |||
| extends PipelinePassPlugin<DepthBufferPluginPass, 'depth', DepthBufferPluginEventTypes> { | |||
| readonly passId = 'depth' | |||
| public static readonly PluginType = 'DepthBufferPlugin' | |||
| @@ -36,24 +39,89 @@ export class DepthBufferPlugin | |||
| depthPacking: BasicDepthPacking, | |||
| blending: NoBlending, | |||
| }) | |||
| // private _gbufferPass?: IFilter<GBufferRenderPass<WebGLMultipleRenderTargets> | |||
| createPass(v: ThreeViewer) { | |||
| if (!this.target) this.target = v.renderManager.createTarget<DepthBufferPluginTarget>( | |||
| { | |||
| depthBuffer: true, | |||
| samples: v.renderManager.composerTarget.samples || 0, | |||
| type: this.bufferType, | |||
| // magFilter: NearestFilter, | |||
| // minFilter: NearestFilter, | |||
| // generateMipmaps: false, | |||
| // encoding: LinearEncoding, | |||
| }) | |||
| @onChange(DepthBufferPlugin.prototype._depthPackingChanged) | |||
| @uiDropdown('Depth Packing', threeConstMappings.DepthPackingStrategies.uiConfig) depthPacking: DepthPackingStrategies | |||
| // @onChange2(DepthBufferPlugin.prototype._createTarget) | |||
| // @uiDropdown('Buffer Type', threeConstMappings.TextureDataType.uiConfig) | |||
| readonly bufferType: TextureDataType // cannot be changed after creation (for now) | |||
| // @uiToggle() | |||
| // @onChange2(DepthBufferPlugin.prototype._createTarget) | |||
| readonly isPrimaryGBuffer: boolean // cannot be changed after creation (for now) | |||
| 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() | |||
| } | |||
| unpackExtension: MaterialExtension = { | |||
| shaderExtender: (shader)=>{ | |||
| shader.fragmentShader = shaderReplaceString(shader.fragmentShader, | |||
| '#include <packing>', | |||
| '\n' + DepthBufferUnpack + '\n', {append: true}) | |||
| }, | |||
| extraUniforms: { | |||
| tDepthBuffer: ()=>({value: this.target?.texture}), | |||
| }, | |||
| extraDefines: { | |||
| ['DEPTH_PACKING']: BasicDepthPacking, | |||
| ['HAS_DEPTH_BUFFER']: ()=>this.target?.texture ? 1 : undefined, | |||
| ['HAS_GBUFFER']: ()=>this.isPrimaryGBuffer && this.target?.texture ? 1 : undefined, | |||
| }, | |||
| priority: 100, | |||
| isCompatible: () => true, | |||
| } | |||
| private _isPrimaryGBufferSet = false | |||
| protected _createTarget(recreate = true) { | |||
| if (!this._viewer) return | |||
| if (recreate) this._disposeTarget() | |||
| if (!this.target) | |||
| this.target = this._viewer.renderManager.createTarget<DepthBufferPluginTarget>( | |||
| { | |||
| depthBuffer: true, | |||
| samples: this._viewer.renderManager.composerTarget.samples || 0, | |||
| type: this.bufferType, | |||
| // magFilter: NearestFilter, | |||
| // minFilter: NearestFilter, | |||
| // generateMipmaps: false, | |||
| // encoding: LinearEncoding, | |||
| }) | |||
| this.texture = this.target.texture | |||
| this.texture.name = 'depthBuffer' | |||
| if (this.isPrimaryGBuffer) v.renderManager.gbufferTarget = this.target | |||
| if (this.isPrimaryGBuffer) { | |||
| this._viewer.renderManager.gbufferTarget = this.target | |||
| this._viewer.renderManager.screenPass.material.registerMaterialExtensions([this.unpackExtension]) | |||
| this._isPrimaryGBufferSet = true | |||
| } | |||
| } | |||
| protected _disposeTarget() { | |||
| if (!this._viewer) return | |||
| if (this.target) { | |||
| this._viewer.renderManager.disposeTarget(this.target) | |||
| this.target = undefined | |||
| } | |||
| this.texture = undefined | |||
| if (this._isPrimaryGBufferSet) { // using a separate flag as when isPrimaryGBuffer is changed, we cannot check it. | |||
| this._viewer.renderManager.gbufferTarget = undefined | |||
| // this._viewer.renderManager.screenPass.material.unregisterMaterialExtensions([this.unpackExtension]) // todo | |||
| this._isPrimaryGBufferSet = false | |||
| } | |||
| } | |||
| protected _createPass() { | |||
| this._createTarget(true) | |||
| if (!this.target) throw new Error('DepthBufferPlugin: target not created') | |||
| this.material.userData.isGBufferMaterial = true | |||
| const pass = new GBufferRenderPass('depth', this.target, this.material, new Color(0, 0, 0), 1) | |||
| const preprocessMaterial = pass.preprocessMaterial | |||
| @@ -65,27 +133,22 @@ export class DepthBufferPlugin | |||
| } | |||
| constructor( | |||
| public readonly bufferType: TextureDataType = UnsignedByteType, | |||
| public readonly isPrimaryGBuffer = false, | |||
| bufferType: TextureDataType = UnsignedByteType, | |||
| isPrimaryGBuffer = false, | |||
| enabled = true, | |||
| depthPacking: DepthPackingStrategies = BasicDepthPacking, | |||
| ) { | |||
| super() | |||
| this.enabled = enabled | |||
| this.depthPacking = depthPacking | |||
| this.bufferType = bufferType | |||
| this.isPrimaryGBuffer = isPrimaryGBuffer | |||
| } | |||
| onRemove(viewer: ThreeViewer): void { | |||
| if (this.target) { | |||
| viewer.renderManager.disposeTarget(this.target) | |||
| this.target = undefined | |||
| } | |||
| this._disposeTarget() | |||
| return super.onRemove(viewer) | |||
| } | |||
| updateShaderProperties(material: {defines: Record<string, string | number | undefined>; uniforms: {[p: string]: IUniform}}): this { | |||
| if (material.uniforms.tDepth) material.uniforms.tDepth.value = this.texture ?? undefined | |||
| else this._viewer?.console.warn('BaseRenderer: no uniform: tDepth') | |||
| return this | |||
| } | |||
| } | |||
| @@ -4,7 +4,6 @@ import { | |||
| Color, | |||
| FrontSide, | |||
| HalfFloatType, | |||
| IUniform, | |||
| LinearSRGBColorSpace, | |||
| MeshNormalMaterial, | |||
| NearestFilter, | |||
| @@ -19,7 +18,6 @@ import { | |||
| } from 'three' | |||
| import {GBufferRenderPass} from '../../postprocessing' | |||
| import {ThreeViewer} from '../../viewer' | |||
| import {IShaderPropertiesUpdater} from '../../materials' | |||
| import {PipelinePassPlugin} from '../base/PipelinePassPlugin' | |||
| import type {IMaterial, PhysicalMaterial} from '../../core' | |||
| import {uiFolderContainer, uiImage} from 'uiconfig.js' | |||
| @@ -30,8 +28,7 @@ export type NormalBufferPluginTarget = WebGLRenderTarget | |||
| export type NormalBufferPluginPass = GBufferRenderPass<'normal', NormalBufferPluginTarget> | |||
| @uiFolderContainer('Normal Buffer Plugin') | |||
| export class NormalBufferPlugin | |||
| extends PipelinePassPlugin<NormalBufferPluginPass, 'normal', NormalBufferPluginEventTypes> | |||
| implements IShaderPropertiesUpdater { | |||
| extends PipelinePassPlugin<NormalBufferPluginPass, 'normal', NormalBufferPluginEventTypes> { | |||
| readonly passId = 'normal' | |||
| public static readonly PluginType = 'NormalBufferPlugin' | |||
| @@ -41,10 +38,16 @@ export class NormalBufferPlugin | |||
| readonly material: MeshNormalMaterial = new MeshNormalMaterial2({ | |||
| blending: NoBlending, | |||
| }) | |||
| // private _gbufferPass?: IFilter<GBufferRenderPass<WebGLMultipleRenderTargets> | |||
| createPass(v: ThreeViewer) { | |||
| if (!this.target) this.target = v.renderManager.createTarget<NormalBufferPluginTarget>( | |||
| // @onChange2(NormalBufferPlugin.prototype._createTarget) | |||
| // @uiDropdown('Buffer Type', threeConstMappings.TextureDataType.uiConfig) | |||
| readonly bufferType: TextureDataType // cannot be changed after creation (for now) | |||
| protected _createTarget(recreate = true) { | |||
| if (!this._viewer) return | |||
| if (recreate) this._disposeTarget() | |||
| if (!this.target) this.target = this._viewer.renderManager.createTarget<NormalBufferPluginTarget>( | |||
| { | |||
| depthBuffer: true, | |||
| // samples: v.renderManager.composerTarget.samples || 0, | |||
| @@ -57,7 +60,19 @@ export class NormalBufferPlugin | |||
| }) | |||
| this.texture = this.target.texture | |||
| this.texture.name = 'normalBuffer' | |||
| } | |||
| protected _disposeTarget() { | |||
| if (!this._viewer) return | |||
| if (this.target) { | |||
| this._viewer.renderManager.disposeTarget(this.target) | |||
| this.target = undefined | |||
| } | |||
| this.texture = undefined | |||
| } | |||
| protected _createPass() { | |||
| this._createTarget(true) | |||
| if (!this.target) throw new Error('NormalBufferPlugin: target not created') | |||
| this.material.userData.isGBufferMaterial = true | |||
| const pass = new GBufferRenderPass('normal', this.target, this.material, new Color(0, 0, 0), 1) | |||
| const preprocessMaterial = pass.preprocessMaterial | |||
| @@ -69,27 +84,19 @@ export class NormalBufferPlugin | |||
| } | |||
| constructor( | |||
| public readonly bufferType: TextureDataType = HalfFloatType, | |||
| bufferType: TextureDataType = HalfFloatType, | |||
| enabled = true, | |||
| ) { | |||
| super() | |||
| this.enabled = enabled | |||
| this.bufferType = bufferType | |||
| } | |||
| onRemove(viewer: ThreeViewer): void { | |||
| if (this.target) { | |||
| viewer.renderManager.disposeTarget(this.target) | |||
| this.target = undefined | |||
| } | |||
| this._disposeTarget() | |||
| return super.onRemove(viewer) | |||
| } | |||
| updateShaderProperties(material: {defines: Record<string, string | number | undefined>; uniforms: {[p: string]: IUniform}}): this { | |||
| if (material.uniforms.tNormal) material.uniforms.tNormal.value = this.texture ?? undefined | |||
| else this._viewer?.console.warn('BaseRenderer: no uniform: tNormal') | |||
| return this | |||
| } | |||
| } | |||
| class MeshNormalMaterial2 extends MeshNormalMaterial { | |||
| @@ -0,0 +1,10 @@ | |||
| #if defined(HAS_DEPTH_BUFFER) | |||
| #if DEPTH_PACKING == 3200 | |||
| #define unpackDepth(rgba_depth) (1.0 - rgba_depth.r) | |||
| #elif DEPTH_PACKING == 3201 | |||
| #define unpackDepth(rgba_depth) unpackRGBAToDepth(rgba_depth) | |||
| #endif | |||
| uniform sampler2D tDepthBuffer; | |||
| #define getDepth(uv) unpackDepth(texture2D(tDepthBuffer, uv)) | |||
| #endif | |||
| @@ -0,0 +1,43 @@ | |||
| #include <packing> | |||
| varying vec2 vUv; | |||
| #include <alphatest_pars_fragment> | |||
| void main() { | |||
| vec4 diffuseColor = tDiffuseTexelToLinear (texture2D(tDiffuse, vUv)); | |||
| #ifdef HAS_TRANSPARENT_TARGET | |||
| vec4 transparentColor = tTransparentTexelToLinear (texture2D(tTransparent, vUv)); | |||
| #else | |||
| vec4 transparentColor = vec4(0.0); | |||
| #endif | |||
| #ifdef HAS_GBUFFER | |||
| float depth = getDepth(vUv); | |||
| bool isBackground = depth>0.99 && transparentColor.a < 0.001; | |||
| #endif | |||
| #glMarker | |||
| #ifdef HAS_GBUFFER | |||
| #ifdef CLIP_BACKGROUND | |||
| if(isBackground) diffuseColor.a = 0.0; | |||
| if(depth>0.99 && transparentColor.a >= 0.001) diffuseColor.a = transparentColor.a; | |||
| #endif | |||
| if(depth < 0.00001) diffuseColor.a = 0.0; | |||
| #endif | |||
| #include <alphatest_fragment> | |||
| #ifdef OPAQUE | |||
| diffuseColor.a = 1.0; | |||
| #endif | |||
| gl_FragColor = diffuseColor; | |||
| //gl_FragColor = isBackground ? vec4(0, 0, 0, 1) : gl_FragColor; | |||
| // gl_FragColor = vec4(depth, 0, 0, 1); | |||
| #include <encodings_fragment> | |||
| } | |||
| @@ -8,10 +8,14 @@ import { | |||
| WebGLMultipleRenderTargets, | |||
| WebGLRenderTarget, | |||
| } from 'three' | |||
| import {IWebGLRenderer, ShaderMaterial2} from '../core' | |||
| import {ICamera, IRenderManager, IScene, IWebGLRenderer, ShaderMaterial2} from '../core' | |||
| import {CopyShader} from 'three/examples/jsm/shaders/CopyShader.js' | |||
| import {IPassID, IPipelinePass} from './Pass' | |||
| import {uiFolderContainer} from 'uiconfig.js' | |||
| import {uiFolderContainer, uiToggle} from 'uiconfig.js' | |||
| import {ViewerRenderManager} from '../viewer' | |||
| import {matDefine} from '../three' | |||
| import ScreenPassShader from './ScreenPass.glsl' | |||
| import {shaderReplaceString} from '../utils' | |||
| export type TViewerScreenShaderFrag = string | [string, string] | {pars?: string, main: string} | |||
| export type TViewerScreenShader = TViewerScreenShaderFrag | ShaderMaterialParameters | ShaderMaterial2 | |||
| @@ -22,11 +26,12 @@ export class ScreenPass extends ExtendedShaderPass implements IPipelinePass<'scr | |||
| after: IPassID[] = ['render'] | |||
| required: IPassID[] = ['render'] | |||
| constructor(shader: TViewerScreenShader, ...textureID: string[]) { | |||
| constructor(shader: TViewerScreenShader = '', ...textureID: string[]) { | |||
| super( | |||
| (<any>shader)?.fragmentShader || (<ShaderMaterial2>shader)?.isShaderMaterial ? <ShaderMaterialParameters|ShaderMaterial2>shader : | |||
| makeScreenShader(shader), | |||
| ...textureID.length ? textureID : ['tDiffuse']) | |||
| ...textureID.length ? textureID : ['tDiffuse', 'tTransparent', 'tGBuffer']) | |||
| this.material.addEventListener('materialUpdate', this.setDirty) | |||
| } | |||
| outputColorSpace: ColorSpace = SRGBColorSpace | |||
| @@ -56,34 +61,32 @@ export class ScreenPass extends ExtendedShaderPass implements IPipelinePass<'scr | |||
| this._lastReadBuffer = undefined | |||
| super.dispose() | |||
| } | |||
| @matDefine('CLIP_BACKGROUND', undefined, undefined, ScreenPass.prototype.setDirty, (v)=>v ? '1' : undefined, (v)=>!!v) | |||
| @uiToggle() clipBackground = false | |||
| beforeRender(_: IScene, _1: ICamera, renderManager: ViewerRenderManager) { | |||
| this.material.uniforms.tTransparent.value = renderManager.renderPass.preserveTransparentTarget ? renderManager.renderPass.transparentTarget?.texture || null : null | |||
| this.material.defines.HAS_TRANSPARENT_TARGET = this.material.uniforms.tTransparent.value ? 1 : undefined | |||
| if (!this.material.defines.HAS_TRANSPARENT_TARGET) delete this.material.defines.HAS_TRANSPARENT_TARGET | |||
| } | |||
| setDirty() { | |||
| super.setDirty() | |||
| this._needsReRender = true | |||
| } | |||
| } | |||
| function makeScreenShader(shader: string | [string, string] | {pars?: string; main: string} | ShaderMaterialParameters | ShaderMaterial2) { | |||
| return { | |||
| ...CopyShader, | |||
| fragmentShader: ` | |||
| varying vec2 vUv; | |||
| #include <alphatest_pars_fragment> | |||
| ${Array.isArray(shader) ? shader[0] : (<any>shader)?.pars || ''} | |||
| void main() { | |||
| vec4 diffuseColor = tDiffuseTexelToLinear (texture2D(tDiffuse, vUv)); | |||
| #glMarker | |||
| ${Array.isArray(shader) ? shader[1] : typeof shader === 'string' ? shader : (shader as any)?.main || ''} | |||
| #include <alphatest_fragment> | |||
| #ifdef OPAQUE | |||
| diffuseColor.a = 1.0; | |||
| #endif | |||
| gl_FragColor = diffuseColor; | |||
| #include <encodings_fragment> | |||
| }`, | |||
| fragmentShader: | |||
| shaderReplaceString(shaderReplaceString(ScreenPassShader, | |||
| 'void main()', (Array.isArray(shader) ? shader[0] : (<any>shader)?.pars || '') + '\n', {prepend: true}), | |||
| '#glMarker', (Array.isArray(shader) ? shader[1] : typeof shader === 'string' ? shader : (shader as any)?.main || '') + '\n', {prepend: true}), | |||
| uniforms: { | |||
| tDiffuse: {value: null}, | |||
| tTransparent: {value: null}, | |||
| }, | |||
| transparent: true, | |||
| blending: NoBlending, | |||
| @@ -19,10 +19,9 @@ export {imageToCanvas, imageBitmapToBase64, imageUrlToImageData, imageDataToCanv | |||
| export {absMax, clearBit, updateBit} from 'ts-browser-helpers' | |||
| export {includesAll} from 'ts-browser-helpers' | |||
| export {copyProps, getOrCall, getPropertyDescriptor, isPropertyWritable, safeSetProperty} from 'ts-browser-helpers' | |||
| export {deepAccessObject, getKeyByValue, objectHasOwn} from 'ts-browser-helpers' | |||
| export {deepAccessObject, getKeyByValue, objectHasOwn, objectMap2, objectMap} from 'ts-browser-helpers' | |||
| export {makeColorSvg, makeTextSvg, makeColorSvgCircle, svgToCanvas, svgToPng} from 'ts-browser-helpers' | |||
| export {timeout, now} from 'ts-browser-helpers' | |||
| export {pathJoin, getUrlQueryParam, setUrlQueryParam, remoteWorkerURL} from 'ts-browser-helpers' | |||
| export {css, glsl, html, svgUrl} from 'ts-browser-helpers' | |||
| export {Serialization} from 'ts-browser-helpers' | |||