| @@ -18,7 +18,7 @@ import { | |||
| WebGLRenderTarget, | |||
| } from 'three' | |||
| import {GBufferRenderPass} from '../../postprocessing' | |||
| import {ThreeViewer} from '../../viewer' | |||
| import {ThreeViewer, ViewerRenderManager} from '../../viewer' | |||
| import {MaterialExtension} from '../../materials' | |||
| import {PipelinePassPlugin} from '../base/PipelinePassPlugin' | |||
| import {uiDropdown, uiFolderContainer, uiImage} from 'uiconfig.js' | |||
| @@ -31,7 +31,7 @@ import {IMaterial, PhysicalMaterial} from '../../core' | |||
| export type DepthBufferPluginEventTypes = '' | |||
| // type DepthBufferPluginTarget = WebGLMultipleRenderTargets | WebGLRenderTarget | |||
| export type DepthBufferPluginTarget = WebGLRenderTarget | |||
| export type DepthBufferPluginPass = GBufferRenderPass<'depth', DepthBufferPluginTarget> | |||
| export type DepthBufferPluginPass = GBufferRenderPass<'depth', DepthBufferPluginTarget|undefined> | |||
| /** | |||
| * Depth Buffer Plugin | |||
| @@ -102,12 +102,13 @@ export class DepthBufferPlugin | |||
| protected _createTarget(recreate = true) { | |||
| if (!this._viewer) return | |||
| if (recreate) this._disposeTarget() | |||
| const rm = this._viewer.renderManager | |||
| if (!this.target) | |||
| this.target = this._viewer.renderManager.createTarget<DepthBufferPluginTarget>( | |||
| { | |||
| depthBuffer: true, | |||
| samples: this._viewer.renderManager.zPrepass && this.isPrimaryGBuffer ? // requirement for zPrepass | |||
| this._viewer.renderManager.composerTarget.samples || 0 : 0, | |||
| 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, | |||
| // magFilter: NearestFilter, | |||
| // minFilter: NearestFilter, | |||
| @@ -118,7 +119,7 @@ export class DepthBufferPlugin | |||
| this.texture = this.target.texture | |||
| this.texture.name = 'depthBuffer' | |||
| if (this._pass) this._pass.target = this.target | |||
| // if (this._pass) this._pass.target = this.target | |||
| if (this.isPrimaryGBuffer) { | |||
| this._viewer.renderManager.gbufferTarget = this.target | |||
| @@ -147,7 +148,7 @@ export class DepthBufferPlugin | |||
| this._createTarget(true) | |||
| if (!this.target) throw new Error('DepthBufferPlugin: target not created') | |||
| this.material.userData.isGBufferMaterial = true | |||
| const pass = new GBufferRenderPass(this.passId, this.target, this.material, new Color(0, 0, 0), 1) | |||
| const pass = new GBufferRenderPass(this.passId, ()=>this.target, this.material, new Color(0, 0, 0), 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'] | |||
| @@ -29,7 +29,7 @@ import { | |||
| WebGLRenderTarget, | |||
| } from 'three' | |||
| import {GBufferRenderPass} from '../../postprocessing' | |||
| import {ThreeViewer} from '../../viewer' | |||
| import {ThreeViewer, ViewerRenderManager} from '../../viewer' | |||
| import {MaterialExtension, updateMaterialDefines} from '../../materials' | |||
| import {PipelinePassPlugin} from '../base/PipelinePassPlugin' | |||
| import {uiFolderContainer, uiImage} from 'uiconfig.js' | |||
| @@ -51,7 +51,7 @@ import { | |||
| export type GBufferPluginEventTypes = '' | |||
| export type GBufferPluginTarget = WebGLMultipleRenderTargets | WebGLRenderTarget | |||
| // export type GBufferPluginTarget = WebGLRenderTarget | |||
| export type GBufferPluginPass = GBufferRenderPass<'gbuffer', GBufferPluginTarget> | |||
| export type GBufferPluginPass = GBufferRenderPass<'gbuffer', GBufferPluginTarget|undefined> | |||
| export interface GBufferUpdaterContext { | |||
| material: IMaterial, renderer: WebGLRenderer, scene: Scene, camera: Camera, geometry: BufferGeometry, object: Object3D | |||
| @@ -118,6 +118,19 @@ export class GBufferPlugin | |||
| // } | |||
| unpackExtension: MaterialExtension = { | |||
| /** | |||
| * 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'] as const | |||
| const include = includes.find(i=>shader.fragmentShader.includes(`#include <${i}>`)) | |||
| @@ -149,11 +162,12 @@ export class GBufferPlugin | |||
| 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<GBufferPluginTarget>( | |||
| { | |||
| depthBuffer: true, | |||
| samples: this._viewer.renderManager.zPrepass && this.isPrimaryGBuffer ? // requirement for zPrepass | |||
| this._viewer.renderManager.composerTarget.samples || 0 : 0, | |||
| 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, | |||
| @@ -189,7 +203,7 @@ export class GBufferPlugin | |||
| }) | |||
| } | |||
| if (this._pass) this._pass.target = this.target | |||
| // if (this._pass) this._pass.target = this.target | |||
| if (this.isPrimaryGBuffer) { | |||
| this._viewer.renderManager.gbufferTarget = this.target | |||
| @@ -219,7 +233,7 @@ export class GBufferPlugin | |||
| 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 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'] | |||
| @@ -26,7 +26,7 @@ import {uiFolderContainer, uiImage} from 'uiconfig.js' | |||
| export type NormalBufferPluginEventTypes = '' | |||
| // type NormalBufferPluginTarget = WebGLMultipleRenderTargets | WebGLRenderTarget | |||
| export type NormalBufferPluginTarget = WebGLRenderTarget | |||
| export type NormalBufferPluginPass = GBufferRenderPass<'normal', NormalBufferPluginTarget> | |||
| export type NormalBufferPluginPass = GBufferRenderPass<'normal', NormalBufferPluginTarget|undefined> | |||
| /** | |||
| * Normal Buffer Plugin | |||
| * | |||
| @@ -54,10 +54,11 @@ export class NormalBufferPlugin | |||
| if (!this._viewer) return | |||
| if (recreate) this._disposeTarget() | |||
| // const rm = this._viewer.renderManager | |||
| if (!this.target) this.target = this._viewer.renderManager.createTarget<NormalBufferPluginTarget>( | |||
| { | |||
| depthBuffer: true, | |||
| // samples: v.renderManager.composerTarget.samples || 0, | |||
| // samples: rm.msaa ? typeof rm.msaa !== 'number' ? ViewerRenderManager.DEFAULT_MSAA_SAMPLES : rm.msaa : 0, | |||
| samples: 0, | |||
| type: this.bufferType, | |||
| magFilter: NearestFilter, | |||
| @@ -68,7 +69,7 @@ export class NormalBufferPlugin | |||
| this.texture = this.target.texture | |||
| this.texture.name = 'normalBuffer' | |||
| if (this._pass) this._pass.target = this.target | |||
| // if (this._pass) this._pass.target = this.target | |||
| } | |||
| protected _disposeTarget() { | |||
| if (!this._viewer) return | |||
| @@ -83,7 +84,7 @@ export class NormalBufferPlugin | |||
| this._createTarget(true) | |||
| if (!this.target) throw new Error('NormalBufferPlugin: target not created') | |||
| this.material.userData.isGBufferMaterial = true | |||
| const pass = new GBufferRenderPass(this.passId, this.target, this.material, new Color(0, 0, 0), 1) | |||
| const pass = new GBufferRenderPass(this.passId, ()=>this.target, this.material, new Color(0, 0, 0), 1) | |||
| const preprocessMaterial = pass.preprocessMaterial | |||
| pass.preprocessMaterial = (m) => preprocessMaterial(m, true) | |||
| pass.before = ['render'] | |||
| @@ -74,7 +74,7 @@ export class SSAOPlugin | |||
| this.texture = this.target.texture | |||
| this.texture.name = 'ssaoBuffer' | |||
| if (this._pass) this._pass.target = this.target | |||
| // if (this._pass) this._pass.target = this.target | |||
| } | |||
| protected _disposeTarget() { | |||
| @@ -102,8 +102,7 @@ export class SSAOPlugin | |||
| if (!this._viewer.renderManager.gbufferTarget || !this._viewer.renderManager.gbufferUnpackExtension) | |||
| throw new Error('SSAOPlugin: GBuffer target not created. GBufferPlugin or DepthBufferPlugin is required.') | |||
| this._createTarget(true) | |||
| // todo: send target as a func, so it works when changed | |||
| return new SSAOPluginPass(this.passId, this.target) | |||
| return new SSAOPluginPass(this.passId, ()=>this.target) | |||
| } | |||
| onAdded(viewer: ThreeViewer) { | |||
| @@ -299,7 +298,7 @@ export class SSAOPluginPass extends ExtendedShaderPass implements IPipelinePass | |||
| #include <simpleCameraHelpers> | |||
| `, | |||
| computeCacheKey: () => { | |||
| return this.enabled ? '1' : '0' + getOrCall(this.target)?.texture?.colorSpace | |||
| return (this.enabled ? '1' : '0') + getOrCall(this.target)?.texture?.colorSpace | |||
| }, | |||
| uuid: SSAOPlugin.PluginType, | |||
| ...uiConfigMaterialExtension(this._getUiConfig.bind(this), SSAOPlugin.PluginType), | |||
| @@ -33,9 +33,10 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| get transparentTarget(): IRenderTarget { | |||
| if (!this._transparentTarget) { | |||
| const msaa = this.renderManager.msaa | |||
| this._transparentTarget = this.renderManager.getTempTarget({ | |||
| sizeMultiplier: 1, | |||
| samples: this.renderManager.composerTarget.samples || 0, | |||
| samples: msaa ? typeof msaa !== 'number' ? ViewerRenderManager.DEFAULT_MSAA_SAMPLES : msaa : 0, | |||
| colorSpace: NoColorSpace, | |||
| type: this.renderManager.renderer.extensions.has('EXT_color_buffer_half_float') ? HalfFloatType : UnsignedByteType, | |||
| format: RGBAFormat, | |||
| @@ -46,12 +47,41 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| } | |||
| return this._transparentTarget | |||
| } | |||
| private _releaseTransparentTarget() { | |||
| if (this._transparentTarget) | |||
| this.renderManager.releaseTempTarget(this._transparentTarget) | |||
| this._transparentTarget = undefined | |||
| } | |||
| readonly preserveOpaqueTarget = false | |||
| private _opaqueTarget?: WebGLRenderTarget | |||
| get opaqueTarget(): WebGLRenderTarget { | |||
| if (!this._opaqueTarget) { | |||
| const composerTarget = this.renderManager.composerTarget as WebGLRenderTarget | |||
| const msaa = this.renderManager.msaa | |||
| this._opaqueTarget = this.renderManager.getTempTarget({ | |||
| sizeMultiplier: 1, | |||
| samples: msaa ? typeof msaa !== 'number' ? ViewerRenderManager.DEFAULT_MSAA_SAMPLES : msaa : 0, | |||
| colorSpace: composerTarget.texture.colorSpace, | |||
| type: this.renderManager.rgbm ? UnsignedByteType : HalfFloatType, | |||
| format: composerTarget.texture.format, | |||
| minFilter: composerTarget.texture.minFilter, | |||
| magFilter: composerTarget.texture.magFilter, | |||
| depthBuffer: composerTarget.depthBuffer, | |||
| generateMipmaps: composerTarget.texture.generateMipmaps, | |||
| }) | |||
| // console.log(this._opaqueTarget.samples) | |||
| } | |||
| return this._opaqueTarget | |||
| } | |||
| private _releaseOpaqueTarget() { | |||
| if (this._opaqueTarget) | |||
| this.renderManager.releaseTempTarget(this._opaqueTarget) | |||
| this._opaqueTarget = undefined | |||
| } | |||
| constructor(renderManager: ViewerRenderManager, overrideMaterial?: Material, clearColor = new Color(0, 0, 0), clearAlpha = 0) { | |||
| super(undefined, undefined, overrideMaterial, clearColor, clearAlpha) | |||
| this.renderManager = renderManager | |||
| @@ -59,6 +89,7 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| this.setDirty = this.setDirty.bind(this) | |||
| } | |||
| // names are incorrect. We read from `writeBuffer` and write to `readBuffer`. same in super class | |||
| render(renderer: IWebGLRenderer, writeBuffer?: WebGLMultipleRenderTargets|WebGLRenderTarget|null, readBuffer?: WebGLMultipleRenderTargets|WebGLRenderTarget, deltaTime?: number, maskActive?: boolean) { | |||
| if (!this.enabled) return | |||
| let needsSwap = false | |||
| @@ -72,7 +103,7 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| } | |||
| const ud = renderer.userData | |||
| if (!ud) console.error('threejs is not patched?') | |||
| if (!ud) console.error('threejs is not patched. Use the @repalash/three.js-modded to this functionality.') | |||
| const useGBufferDepth = (this.renderManager.zPrepass || !this.renderManager.depthBuffer) && this.renderManager.gbufferTarget | |||
| let depthRenderBuffer: WebGLRenderbuffer | undefined = undefined | |||
| @@ -90,6 +121,16 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| } | |||
| const lastReadBuffer = readBuffer | |||
| // if msaa we need to create a new multi sampled target to render to | |||
| // todo | |||
| readBuffer = this.renderManager.msaa ? this.opaqueTarget : readBuffer | |||
| // readBuffer = this.opaqueTarget | |||
| // if(readBuffer?.samples !== gbuffer?.samples) | |||
| // console.error('ExtendedRenderPass - readBuffer and gbuffer samples are not same', readBuffer?.samples, gbuffer?.samples) | |||
| let renderFn = ()=> { | |||
| super.render(renderer, null, readBuffer, deltaTime, maskActive, depthRenderBuffer) // read is write in super.render (RenderPass) | |||
| } | |||
| @@ -133,7 +174,7 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| // renderer.autoClearDepth = false | |||
| ud.transmissionRenderTarget = writeBuffer | |||
| ud.blurTransmissionTarget = this.blurTransmissionTarget && ud.transmissionRenderTarget.samples === 0 // todo: not working with msaa | |||
| ud.blurTransmissionTarget = this.blurTransmissionTarget && ud.transmissionRenderTarget.samples === 0 // todo: not working with msaa target. its fine now because writeBuffer is never multi sampled | |||
| renderer.renderWithModes({ | |||
| shadowMapRender: false, | |||
| @@ -178,7 +219,7 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| opaqueRender: true, | |||
| transparentRender: false, | |||
| transmissionRender: false, | |||
| }, renderFn) | |||
| }, renderFn) // render to readBuffer | |||
| renderer.autoClearDepth = curClearDepth | |||
| @@ -188,6 +229,9 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| const renderBufferProps2 = renderer.properties.get(readBuffer) | |||
| depthRenderBuffer = renderBufferProps2.__webglDepthRenderbuffer || renderBufferProps2.__webglDepthbuffer | |||
| } | |||
| // readBuffer has data | |||
| renderFn = ()=> { | |||
| super.render(renderer, null, this.transparentTarget as any, deltaTime, maskActive, depthRenderBuffer) | |||
| } | |||
| @@ -205,7 +249,7 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| opaqueRender: false, | |||
| transparentRender: true, | |||
| transmissionRender: false, | |||
| }, renderFn) | |||
| }, renderFn) // render to transparentTarget | |||
| this.clear = curClear | |||
| renderer.autoClearDepth = curClearDepth | |||
| @@ -213,12 +257,15 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| if (!renderer.info || renderer.info.render.calls > 0) { | |||
| // writeBuffer = transparentTarget + readBuffer | |||
| this._blendPass.uniforms.tDiffuse2.value = this.transparentTarget.texture | |||
| this._blendPass.render(renderer, writeBuffer, readBuffer, deltaTime, maskActive) | |||
| needsSwap = true | |||
| needsSwap = true // writeBuffer has the data now. | |||
| } | |||
| // if needsSwap, writeBuffer has data, else readBuffer | |||
| // Transmission | |||
| { | |||
| const curClear = this.clear | |||
| @@ -227,6 +274,7 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| // const curClearDepth = renderer.autoClearDepth | |||
| // renderer.autoClearDepth = false | |||
| // if needsSwap, writeBuffer has current data, else readBuffer | |||
| ud.transmissionRenderTarget = needsSwap ? writeBuffer : readBuffer | |||
| ud.blurTransmissionTarget = this.blurTransmissionTarget && ud.transmissionRenderTarget.samples === 0 // todo: not working with msaa | |||
| @@ -236,7 +284,7 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| opaqueRender: false, | |||
| transparentRender: false, | |||
| transmissionRender: true, | |||
| }, renderFn) | |||
| }, renderFn) // render to transparentTarget | |||
| ud.blurTransmissionTarget = undefined | |||
| ud.transmissionRenderTarget = undefined | |||
| @@ -250,12 +298,16 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| if (!renderer.info || renderer.info.render.calls > 0) { | |||
| // console.log('missive blit', renderer.info.render.frame) | |||
| // writeBuffer = transparentTarget + readBuffer. opaque will overwrite opaque pixels again | |||
| this._blendPass.uniforms.tDiffuse2.value = this.transparentTarget.texture | |||
| this._blendPass.render(renderer, writeBuffer, readBuffer, deltaTime, maskActive) | |||
| needsSwap = true | |||
| needsSwap = true // writeBuffer has the data now. | |||
| } | |||
| // if needsSwap, writeBuffer has data, else readBuffer | |||
| if (renderToScreen) { | |||
| this.renderToScreen = true | |||
| const tex = needsSwap ? writeBuffer?.texture : readBuffer?.texture | |||
| @@ -268,8 +320,22 @@ export class ExtendedRenderPass extends RenderPass implements IPipelinePass<'ren | |||
| } | |||
| // todo no need to do this if renderToScreen is true | |||
| // resolve msaa | |||
| if (!needsSwap && lastReadBuffer !== readBuffer && readBuffer) { | |||
| // copy from readBuffer to lastReadBuffer | |||
| const source = Array.isArray(readBuffer.texture) ? readBuffer.texture[0] : readBuffer.texture | |||
| source && this.renderManager.blit(lastReadBuffer, { | |||
| source: source, clear: true, | |||
| }) | |||
| readBuffer = lastReadBuffer | |||
| } | |||
| if (!this.preserveTransparentTarget) | |||
| this._releaseTransparentTarget() | |||
| if (!this.preserveOpaqueTarget) | |||
| this._releaseOpaqueTarget() | |||
| this.needsSwap = needsSwap | |||
| renderer.userData.mainRenderPass = undefined | |||
| } | |||
| @@ -1,12 +1,5 @@ | |||
| import {IRenderTarget, RenderManager} from '../rendering' | |||
| import { | |||
| HalfFloatType, | |||
| LinearFilter, | |||
| LinearMipMapLinearFilter, | |||
| NoColorSpace, | |||
| RGBM16ColorSpace, | |||
| UnsignedByteType, | |||
| } from 'three' | |||
| import {HalfFloatType, LinearFilter, NoColorSpace, RGBM16ColorSpace, UnsignedByteType} from 'three' | |||
| import {IRenderManagerEvent, IRenderManagerOptions, IScene} from '../core' | |||
| import {ExtendedRenderPass, ScreenPass, TViewerScreenShader} from '../postprocessing' | |||
| import {uiFolderContainer, UiObjectConfig} from 'uiconfig.js' | |||
| @@ -33,16 +26,19 @@ export class ViewerRenderManager extends RenderManager<IRenderManagerEvent, 'gbu | |||
| readonly screenPass: ScreenPass | |||
| declare uiConfig: UiObjectConfig | |||
| static DEFAULT_MSAA_SAMPLES = 4 | |||
| constructor({rgbm = true, msaa = false, depthBuffer = false, ...options}: ViewerRenderManagerOptions) { | |||
| super({ | |||
| ...options, | |||
| targetOptions: { | |||
| samples: msaa ? typeof msaa !== 'number' ? 4 : msaa : 0, | |||
| samples: 0, | |||
| // samples: msaa ? typeof msaa !== 'number' ? ViewerRenderManager.DEFAULT_MSAA_SAMPLES : msaa : 0, | |||
| colorSpace: rgbm ? RGBM16ColorSpace : NoColorSpace, | |||
| type: rgbm ? UnsignedByteType : HalfFloatType, | |||
| depthBuffer: depthBuffer, | |||
| generateMipmaps: msaa ? true : false, // todo: hack for now, fix blurTransmissionTarget in ExtendedRenderPass | |||
| minFilter: msaa ? LinearMipMapLinearFilter : LinearFilter, // todo: hack for now, fix blurTransmissionTarget in ExtendedRenderPass | |||
| generateMipmaps: /* msaa ? true : */false, // todo: hack for now, fix blurTransmissionTarget in ExtendedRenderPass | |||
| minFilter: /* msaa ? LinearMipMapLinearFilter : */LinearFilter, // todo: hack for now, fix blurTransmissionTarget in ExtendedRenderPass | |||
| }, | |||
| }) | |||
| this.rgbm = rgbm | |||