From b5cfe00c04edafb2ca5ff91d009293e255661ea3 Mon Sep 17 00:00:00 2001 From: AlexandruPopovici Date: Fri, 14 Oct 2022 00:36:33 +0300 Subject: [PATCH] A lot of changes. Pipeline rework is more or less complete. Added individual output for some pipeline passes that we might want to inspect inside the sandbox. --- packages/viewer-sandbox/src/Sandbox.ts | 261 ++++++++++-------- .../viewer/src/modules/SpeckleRenderer.ts | 55 ++-- packages/viewer/src/modules/Viewer.ts | 4 +- .../shaders/speckle-copy-output-frag.ts | 30 ++ .../shaders/speckle-copy-output-vert.ts | 6 + .../materials/shaders/speckle-sao-frag.ts | 15 + .../src/modules/pipeline/ApplySAOPass.ts | 29 +- .../src/modules/pipeline/CopyOutputPass.ts | 56 ++++ .../viewer/src/modules/pipeline/DepthPass.ts | 14 +- ...kleDynamicSAOPass.ts => DynamicSAOPass.ts} | 58 ++-- .../src/modules/pipeline/NormalsPass.ts | 95 +++++++ .../viewer/src/modules/pipeline/Pipeline.ts | 200 +++++++++----- .../src/modules/pipeline/SpecklePass.ts | 17 ++ 13 files changed, 583 insertions(+), 257 deletions(-) create mode 100644 packages/viewer/src/modules/materials/shaders/speckle-copy-output-frag.ts create mode 100644 packages/viewer/src/modules/materials/shaders/speckle-copy-output-vert.ts create mode 100644 packages/viewer/src/modules/pipeline/CopyOutputPass.ts rename packages/viewer/src/modules/pipeline/{SpeckleDynamicSAOPass.ts => DynamicSAOPass.ts} (90%) create mode 100644 packages/viewer/src/modules/pipeline/NormalsPass.ts create mode 100644 packages/viewer/src/modules/pipeline/SpecklePass.ts diff --git a/packages/viewer-sandbox/src/Sandbox.ts b/packages/viewer-sandbox/src/Sandbox.ts index 3a8eeb973..1dadd4c7e 100644 --- a/packages/viewer-sandbox/src/Sandbox.ts +++ b/packages/viewer-sandbox/src/Sandbox.ts @@ -30,26 +30,20 @@ export default class Sandbox { tonemapping: 4 //'ACESFilmicToneMapping' } - public static postParams = { - saoEnabled: true, - saoParams: { - saoBias: 0.15, - saoIntensity: 1.25, - saoScale: 434, - saoKernelRadius: 15, - saoMinResolution: 0, - saoBlur: true, - saoBlurRadius: 4, - saoBlurStdDev: 4, - saoBlurDepthCutoff: 0.0007 - }, - saoScaleOffset: 0, - saoNormalsRendering: 2, - minDistance: 0.0, - maxDistance: 0.008, - ssaoKernelRadius: 0.5, - progressiveAO: 0, - progressive: true + public static pipelineParams = { + pipelineOutput: 8, + dynamicAoEnabled: true, + dynamicAoParams: { + intensity: 1.25, + scale: 0, + kernelRadius: 10, + bias: 0.15, + normalsType: 2, + blurEnabled: true, + blurRadius: 4, + blurStdDev: 4, + blurDepthCutoff: 0.0007 + } } public static lightParams: SunLightConfiguration = { @@ -343,33 +337,88 @@ export default class Sandbox { this.viewer.getRenderer().renderer.toneMapping = Sandbox.sceneParams.tonemapping this.viewer.requestRender() }) - postFolder - .addInput(Sandbox.postParams, 'saoEnabled', { label: 'SAO-ENABLED' }) - .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - this.viewer.requestRender() - }) - postFolder - .addInput(Sandbox.postParams.saoParams, 'saoBias', { - min: -1, - max: 1 + + const pipelineFolder = this.tabs.pages[1].addFolder({ + title: 'Pipeline', + expanded: true + }) + pipelineFolder + .addInput(Sandbox.pipelineParams, 'pipelineOutput', { + options: { + DEPTH_RGBA: 0, + DEPTH: 1, + COLOR: 2, + GEOMETRY_NORMALS: 3, + RECONSTRUCTED_NORMALS: 4, + DYNAMIC_AO: 5, + DYNAMIC_AO_BLURED: 6, + PROGRESSIVE_AO: 7, + FINAL: 8 + } }) .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - this.viewer.requestRender() - }) - postFolder - .addInput(Sandbox.postParams.saoParams, 'saoIntensity', { - min: 0, - max: 5 - }) - .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + this.viewer.getRenderer().pipelineOptions = Sandbox.pipelineParams this.viewer.requestRender() }) + // postFolder + // .addInput(Sandbox.postParams, 'saoEnabled', { label: 'SAO-ENABLED' }) + // .on('change', () => { + // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + // this.viewer.requestRender() + // }) + // postFolder + // .addInput(Sandbox.postParams.saoParams, 'saoBias', { + // min: -1, + // max: 1 + // }) + // .on('change', () => { + // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + // this.viewer.requestRender() + // }) + // postFolder + // .addInput(Sandbox.postParams.saoParams, 'saoIntensity', { + // min: 0, + // max: 5 + // }) + // .on('change', () => { + // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + // this.viewer.requestRender() + // }) + + // // postFolder + // // .addInput(Sandbox.postParams.saoParams, 'saoScale', { + // // min: 0, + // // max: 100 + // // }) + // // .on('change', () => { + // // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + // // this.viewer.requestRender() + // // }) + // postFolder + // .addInput(Sandbox.postParams, 'saoScaleOffset', { + // min: -100, + // max: 100 + // }) + // .on('change', () => { + // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + // this.viewer.requestRender() + // }) // postFolder - // .addInput(Sandbox.postParams.saoParams, 'saoScale', { + // .addInput(Sandbox.postParams, 'saoNormalsRendering', { + // options: { + // DEFAULT: 0, + // ADVANCED: 1, + // ACCURATE: 2 + // } + // }) + // .on('change', () => { + // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + // this.viewer.requestRender() + // }) + + // postFolder + // .addInput(Sandbox.postParams.saoParams, 'saoKernelRadius', { // min: 0, // max: 100 // }) @@ -377,97 +426,65 @@ export default class Sandbox { // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams // this.viewer.requestRender() // }) - postFolder - .addInput(Sandbox.postParams, 'saoScaleOffset', { - min: -100, - max: 100 - }) - .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - this.viewer.requestRender() - }) - postFolder - .addInput(Sandbox.postParams, 'saoNormalsRendering', { - options: { - DEFAULT: 0, - ADVANCED: 1, - ACCURATE: 2 - } - }) - .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - this.viewer.requestRender() - }) - - postFolder - .addInput(Sandbox.postParams.saoParams, 'saoKernelRadius', { - min: 0, - max: 100 - }) - .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - this.viewer.requestRender() - }) + // // postFolder + // // .addInput(Sandbox.postParams.saoParams, 'saoMinResolution', { + // // min: 0, + // // max: 1 + // // }) + // // .on('change', () => { + // // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + // // this.viewer.requestRender() + // // }) // postFolder - // .addInput(Sandbox.postParams.saoParams, 'saoMinResolution', { - // min: 0, - // max: 1 - // }) + // .addInput(Sandbox.postParams.saoParams, 'saoBlur', {}) // .on('change', () => { // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams // this.viewer.requestRender() // }) - postFolder - .addInput(Sandbox.postParams.saoParams, 'saoBlur', {}) - .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - this.viewer.requestRender() - }) + // postFolder + // .addInput(Sandbox.postParams.saoParams, 'saoBlurRadius', { min: 0, max: 10 }) + // .on('change', () => { + // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + // this.viewer.requestRender() + // }) - postFolder - .addInput(Sandbox.postParams.saoParams, 'saoBlurRadius', { min: 0, max: 10 }) - .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - this.viewer.requestRender() - }) + // postFolder + // .addInput(Sandbox.postParams, 'minDistance', { min: 0, max: 100, step: 0.000001 }) + // .on('change', () => { + // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + // this.viewer.requestRender() + // }) - postFolder - .addInput(Sandbox.postParams, 'minDistance', { min: 0, max: 100, step: 0.000001 }) - .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - this.viewer.requestRender() - }) - - postFolder - .addInput(Sandbox.postParams, 'maxDistance', { min: 0, max: 100, step: 0.000001 }) - .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - this.viewer.requestRender() - }) - postFolder - .addInput(Sandbox.postParams, 'ssaoKernelRadius', { min: 0, max: 100 }) - .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - this.viewer.requestRender() - }) - postFolder.addInput(Sandbox.postParams, 'progressive', {}).on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - this.viewer.requestRender() - }) - postFolder - .addInput(Sandbox.postParams, 'progressiveAO', { - options: { - SAO: 0, - SSAO: 1 - } - }) - .on('change', () => { - this.viewer.getRenderer().pipelineOptions = Sandbox.postParams - this.viewer.requestRender() - }) + // postFolder + // .addInput(Sandbox.postParams, 'maxDistance', { min: 0, max: 100, step: 0.000001 }) + // .on('change', () => { + // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + // this.viewer.requestRender() + // }) + // postFolder + // .addInput(Sandbox.postParams, 'ssaoKernelRadius', { min: 0, max: 100 }) + // .on('change', () => { + // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + // this.viewer.requestRender() + // }) + // postFolder.addInput(Sandbox.postParams, 'progressive', {}).on('change', () => { + // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + // this.viewer.requestRender() + // }) + // postFolder + // .addInput(Sandbox.postParams, 'progressiveAO', { + // options: { + // SAO: 0, + // SSAO: 1 + // } + // }) + // .on('change', () => { + // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + // this.viewer.requestRender() + // }) // postFolder // .addInput(Sandbox.postParams.saoParams, 'saoBlurStdDev', { diff --git a/packages/viewer/src/modules/SpeckleRenderer.ts b/packages/viewer/src/modules/SpeckleRenderer.ts index 4c18d0ff8..0f01cd9e8 100644 --- a/packages/viewer/src/modules/SpeckleRenderer.ts +++ b/packages/viewer/src/modules/SpeckleRenderer.ts @@ -50,7 +50,7 @@ import { DefaultPipelineOptions, Pipeline, PipelineOptions } from './pipeline/Pi export default class SpeckleRenderer { private readonly SHOW_HELPERS = false private _renderer: WebGLRenderer - public scene: Scene + public _scene: Scene private rootGroup: Group private batcher: Batcher private intersections: Intersections @@ -62,19 +62,12 @@ export default class SpeckleRenderer { private filterBatchRecording: string[] private pipeline: Pipeline - private lastAzimuth: number - private lastPolar: number - private lastDistance: number - private lastTarget: Vector3 = new Vector3() - private lasMaxCameraMotion: number - private readonly CAMERA_MOTION_EPSILON: number = 0.0001 - public get renderer(): WebGLRenderer { return this._renderer } public set indirectIBL(texture: Texture) { - this.scene.environment = texture + this._scene.environment = texture } public set indirectIBLIntensity(value: number) { @@ -92,11 +85,11 @@ export default class SpeckleRenderer { /** TEMPORARY for backwards compatibility */ public get allObjects() { - return this.scene.getObjectByName('ContentGroup') + return this._scene.getObjectByName('ContentGroup') } public subtree(subtreeId: string) { - return this.scene.getObjectByName(subtreeId) + return this._scene.getObjectByName(subtreeId) } public get sceneBox() { @@ -115,15 +108,23 @@ export default class SpeckleRenderer { return this.sun } + public get camera() { + return this.viewer.cameraHandler.activeCam.camera + } + + public get scene() { + return this._scene + } + public set pipelineOptions(value: PipelineOptions) { this.pipeline.pipelineOptions = value } public constructor(viewer: Viewer /** TEMPORARY */) { - this.scene = new Scene() + this._scene = new Scene() this.rootGroup = new Group() this.rootGroup.name = 'ContentGroup' - this.scene.add(this.rootGroup) + this._scene.add(this.rootGroup) this.batcher = new Batcher() this.intersections = new Intersections() @@ -151,7 +152,7 @@ export default class SpeckleRenderer { container.appendChild(this._renderer.domElement) this.pipeline = new Pipeline(this._renderer, this.batcher) - this.pipeline.configure(this.scene, this.viewer.cameraHandler.activeCam.camera) + this.pipeline.configure(this._scene, this.viewer.cameraHandler.activeCam.camera) this.pipeline.pipelineOptions = DefaultPipelineOptions this.input = new Input(this._renderer.domElement, InputOptionsDefault) @@ -163,7 +164,7 @@ export default class SpeckleRenderer { if (this.SHOW_HELPERS) { const helpers = new Group() helpers.name = 'Helpers' - this.scene.add(helpers) + this._scene.add(helpers) const sceneBoxHelper = new Box3Helper(this.sceneBox, new Color(0x0000ff)) sceneBoxHelper.name = 'SceneBoxHelper' @@ -287,7 +288,8 @@ export default class SpeckleRenderer { this.viewer.cameraHandler.activeCam.camera.far = d this.viewer.cameraHandler.activeCam.camera.updateProjectionMatrix() this.viewer.cameraHandler.camera.updateProjectionMatrix() - this.pipeline.pipelineOptions = { saoParams: { saoScale: d } } + + this.pipeline.update(this) // const currentAzimuth = this.viewer.cameraHandler.controls.azimuthAngle // const currentPolar = this.viewer.cameraHandler.controls.polarAngle @@ -321,8 +323,9 @@ export default class SpeckleRenderer { } public render(camera: Camera): boolean { + camera this.batcher.render(this.renderer) - const needsRender = this.pipeline.render(this.scene, camera) + const needsRender = this.pipeline.render() return needsRender // this.renderer.render(this.scene, camera) } @@ -431,7 +434,7 @@ export default class SpeckleRenderer { private addDirectLights() { this.sun = new DirectionalLight(0xffffff, 5) this.sun.name = 'sun' - this.scene.add(this.sun) + this._scene.add(this.sun) this.sun.castShadow = true @@ -450,7 +453,7 @@ export default class SpeckleRenderer { this.sun.shadow.radius = 2 this.sunTarget = new Object3D() - this.scene.add(this.sunTarget) + this._scene.add(this.sunTarget) this.sunTarget.position.copy(this.sceneCenter) this.sun.target = this.sunTarget } @@ -529,12 +532,14 @@ export default class SpeckleRenderer { public updateHelpers() { if (this.SHOW_HELPERS) { - ;(this.scene.getObjectByName('CamHelper') as CameraHelper).update() + ;(this._scene.getObjectByName('CamHelper') as CameraHelper).update() // Thank you prettier, this looks so much better - ;(this.scene.getObjectByName('SceneBoxHelper') as Box3Helper).box.copy( + ;(this._scene.getObjectByName('SceneBoxHelper') as Box3Helper).box.copy( this.sceneBox ) - ;(this.scene.getObjectByName('DirLightHelper') as DirectionalLightHelper).update() + ;( + this._scene.getObjectByName('DirLightHelper') as DirectionalLightHelper + ).update() } } @@ -575,7 +580,7 @@ export default class SpeckleRenderer { private onObjectClick(e) { const results: Array = this.intersections.intersect( - this.scene, + this._scene, this.viewer.cameraHandler.activeCam.camera, e, true, @@ -614,7 +619,7 @@ export default class SpeckleRenderer { private onObjectDoubleClick(e) { const results: Array = this.intersections.intersect( - this.scene, + this._scene, this.viewer.cameraHandler.activeCam.camera, e, true, @@ -899,7 +904,7 @@ export default class SpeckleRenderer { /** DEBUG */ public onObjectClickDebug(e) { const results: Array = this.intersections.intersect( - this.scene, + this._scene, this.viewer.cameraHandler.activeCam.camera, e, true, diff --git a/packages/viewer/src/modules/Viewer.ts b/packages/viewer/src/modules/Viewer.ts index 8e816a122..d474201cd 100644 --- a/packages/viewer/src/modules/Viewer.ts +++ b/packages/viewer/src/modules/Viewer.ts @@ -135,7 +135,9 @@ export class Viewer extends EventEmitter implements IViewer { } public resize() { - this.speckleRenderer.resize(this.container.offsetWidth, this.container.offsetHeight) + const width = this.container.offsetWidth + const height = this.container.offsetHeight + this.speckleRenderer.resize(width, height) this.needsRender = true } diff --git a/packages/viewer/src/modules/materials/shaders/speckle-copy-output-frag.ts b/packages/viewer/src/modules/materials/shaders/speckle-copy-output-frag.ts new file mode 100644 index 000000000..f696ba58d --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-copy-output-frag.ts @@ -0,0 +1,30 @@ +export const speckleCopyOutputFrag = ` + uniform float opacity; + uniform sampler2D tDiffuse; + varying vec2 vUv; + + const float UnpackDownscale = 255. / 256.; + const vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. ); + const vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. ); + + float unpackRGBAToDepth( const in vec4 v ) { + return dot( v, UnpackFactors ); + } + + vec3 unpackRGBToNormal( const in vec3 rgb ) { + return 2.0 * rgb.xyz - 1.0; + } + + void main() { + vec4 inSample = texture2D( tDiffuse, vUv ); + vec3 outSample = inSample.rgb; + #if OUTPUT_TYPE == 1 + outSample.rgb = vec3(unpackRGBAToDepth(inSample)); + #endif + #if OUTPUT_TYPE == 3 + outSample.rgb = unpackRGBToNormal(inSample.rgb); + #endif + + gl_FragColor.rgb = outSample; + gl_FragColor.a = 1.; + }` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-copy-output-vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-copy-output-vert.ts new file mode 100644 index 000000000..7a083de37 --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-copy-output-vert.ts @@ -0,0 +1,6 @@ +export const speckleCopyOutputVert = ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-sao-frag.ts b/packages/viewer/src/modules/materials/shaders/speckle-sao-frag.ts index 53dc6b550..66293cd88 100644 --- a/packages/viewer/src/modules/materials/shaders/speckle-sao-frag.ts +++ b/packages/viewer/src/modules/materials/shaders/speckle-sao-frag.ts @@ -209,6 +209,21 @@ export const speckleSaoFrag = /* glsl */ ` } float centerViewZ = getViewZ( centerDepth ); vec3 viewPosition = getViewPosition( vUv, centerDepth, centerViewZ ); + + #ifdef OUTPUT_RECONSTRUCTED_NORMALS + vec3 normal; + #if IMPROVED_NORMAL_RECONSTRUCTION == 1 + normal = viewNormalImproved(vUv, viewPosition); + #elif ACCURATE_NORMAL_RECONSTRUCTION == 1 + normal = viewNormalAccurate(vUv, viewPosition, centerDepth); + #else + normal = normalize( cross( dFdx( viewPosition ), dFdy( viewPosition ) ) ); + #endif + gl_FragColor.rgb = packNormalToRGB(viewNormalImproved(vUv, viewPosition)); + gl_FragColor.a = 1.; + return; + #endif + float ambientOcclusion = getAmbientOcclusion( viewPosition, centerDepth ); gl_FragColor = getDefaultColor( vUv ); gl_FragColor.xyz *= 1. - ambientOcclusion; diff --git a/packages/viewer/src/modules/pipeline/ApplySAOPass.ts b/packages/viewer/src/modules/pipeline/ApplySAOPass.ts index 65b2cf55e..a378f3c45 100644 --- a/packages/viewer/src/modules/pipeline/ApplySAOPass.ts +++ b/packages/viewer/src/modules/pipeline/ApplySAOPass.ts @@ -11,8 +11,9 @@ import { } from 'three' import { FullScreenQuad, Pass } from 'three/examples/jsm/postprocessing/Pass' import { CopyShader } from 'three/examples/jsm/shaders/CopyShader.js' +import { InputColorTextureUniform, SpecklePass } from './SpecklePass' -export class ApplySAOPass extends Pass { +export class ApplySAOPass extends Pass implements SpecklePass { private fsQuad: FullScreenQuad public materialCopy: ShaderMaterial @@ -35,26 +36,34 @@ export class ApplySAOPass extends Pass { this.materialCopy.blendDstAlpha = ZeroFactor this.materialCopy.blendEquationAlpha = AddEquation - // this.materialCopy.blending = CustomBlending - // this.materialCopy.blendSrc = OneFactor - // this.materialCopy.blendDst = OneFactor - // this.materialCopy.blendEquation = ReverseSubtractEquation - // this.materialCopy.blendSrcAlpha = OneFactor - // this.materialCopy.blendDstAlpha = OneFactor - // this.materialCopy.blendEquationAlpha = AddEquation this.materialCopy.needsUpdate = true this.fsQuad = new FullScreenQuad(this.materialCopy) } - public setAoTexture(texture: Texture) { - this.materialCopy.uniforms['tDiffuse'].value = texture + public setTexture(uName: InputColorTextureUniform, texture: Texture) { + this.materialCopy.uniforms[uName].value = texture this.materialCopy.needsUpdate = true } + get displayName(): string { + return 'APPLYSAO' + } + + get outputTexture(): Texture { + return null + } + + setParams(params: unknown) { + params + } + render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive*/) { writeBuffer readBuffer renderer.setRenderTarget(null) + const rendereAutoClear = renderer.autoClear + renderer.autoClear = false this.fsQuad.render(renderer) + renderer.autoClear = rendereAutoClear } } diff --git a/packages/viewer/src/modules/pipeline/CopyOutputPass.ts b/packages/viewer/src/modules/pipeline/CopyOutputPass.ts new file mode 100644 index 000000000..076b213e0 --- /dev/null +++ b/packages/viewer/src/modules/pipeline/CopyOutputPass.ts @@ -0,0 +1,56 @@ +import { NoBlending, ShaderMaterial, Texture, UniformsUtils } from 'three' +import { FullScreenQuad, Pass } from 'three/examples/jsm/postprocessing/Pass' +import { CopyShader } from 'three/examples/jsm/shaders/CopyShader.js' +import { speckleCopyOutputFrag } from '../materials/shaders/speckle-copy-output-frag' +import { speckleCopyOutputVert } from '../materials/shaders/speckle-copy-output-vert' +import { PipelineOutputType } from './Pipeline' +import { InputColorTextureUniform, SpecklePass } from './SpecklePass' + +export class CopyOutputPass extends Pass implements SpecklePass { + private fsQuad: FullScreenQuad + public materialCopy: ShaderMaterial + + constructor() { + super() + this.materialCopy = new ShaderMaterial({ + defines: { + INPUT_TYPE: 0 + }, + uniforms: UniformsUtils.clone(CopyShader.uniforms), + vertexShader: speckleCopyOutputVert, + fragmentShader: speckleCopyOutputFrag, + blending: NoBlending + }) + + this.materialCopy.needsUpdate = true + this.fsQuad = new FullScreenQuad(this.materialCopy) + } + + public setOutputType(type: PipelineOutputType) { + this.materialCopy.defines['OUTPUT_TYPE'] = type + this.materialCopy.needsUpdate = true + } + + public setTexture(uName: InputColorTextureUniform, texture: Texture) { + this.materialCopy.uniforms[uName].value = texture + this.materialCopy.needsUpdate = true + } + + get displayName(): string { + return 'COPY-OUTPUT' + } + + get outputTexture(): Texture { + return null + } + + render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive*/) { + writeBuffer + readBuffer + renderer.setRenderTarget(null) + const rendereAutoClear = renderer.autoClear + renderer.autoClear = false + this.fsQuad.render(renderer) + renderer.autoClear = rendereAutoClear + } +} diff --git a/packages/viewer/src/modules/pipeline/DepthPass.ts b/packages/viewer/src/modules/pipeline/DepthPass.ts index 6b1397f81..71db4eb07 100644 --- a/packages/viewer/src/modules/pipeline/DepthPass.ts +++ b/packages/viewer/src/modules/pipeline/DepthPass.ts @@ -3,6 +3,7 @@ import { Color, DoubleSide, NoBlending, + Plane, RGBADepthPacking, Scene, Texture, @@ -10,7 +11,7 @@ import { } from 'three' import { Pass } from 'three/examples/jsm/postprocessing/Pass' import SpeckleDepthMaterial from '../materials/SpeckleDepthMaterial' -import { SpecklePass } from './Pipeline' +import { SpecklePass } from './SpecklePass' export class DepthPass extends Pass implements SpecklePass { private renderTarget: WebGLRenderTarget @@ -20,6 +21,9 @@ export class DepthPass extends Pass implements SpecklePass { private colorBuffer: Color = new Color() + public onBeforeRender: () => void = null + public onAfterRender: () => void = null + get displayName(): string { return 'DEPTH' } @@ -49,7 +53,11 @@ export class DepthPass extends Pass implements SpecklePass { this.depthMaterial.side = DoubleSide } - public update(camera: Camera, scene: Scene) { + public setClippingPlanes(planes: Plane[]) { + this.depthMaterial.clippingPlanes = planes + } + + public update(scene: Scene, camera: Camera) { this.camera = camera this.scene = scene } @@ -58,6 +66,7 @@ export class DepthPass extends Pass implements SpecklePass { writeBuffer readBuffer + this.onBeforeRender() renderer.getClearColor(this.colorBuffer) const originalClearAlpha = renderer.getClearAlpha() const originalAutoClear = renderer.autoClear @@ -83,6 +92,7 @@ export class DepthPass extends Pass implements SpecklePass { renderer.autoClear = originalAutoClear renderer.setClearColor(this.colorBuffer) renderer.setClearAlpha(originalClearAlpha) + this.onAfterRender() } public setSize(width: number, height: number) { diff --git a/packages/viewer/src/modules/pipeline/SpeckleDynamicSAOPass.ts b/packages/viewer/src/modules/pipeline/DynamicSAOPass.ts similarity index 90% rename from packages/viewer/src/modules/pipeline/SpeckleDynamicSAOPass.ts rename to packages/viewer/src/modules/pipeline/DynamicSAOPass.ts index c29a0c1e1..d29490af6 100644 --- a/packages/viewer/src/modules/pipeline/SpeckleDynamicSAOPass.ts +++ b/packages/viewer/src/modules/pipeline/DynamicSAOPass.ts @@ -17,7 +17,7 @@ import { speckleSaoVert } from '../materials/shaders/speckle-sao-vert' import { SAOShader } from 'three/examples/jsm/shaders/SAOShader.js' import { DepthLimitedBlurShader } from 'three/examples/jsm/shaders/DepthLimitedBlurShader.js' import { BlurShaderUtils } from 'three/examples/jsm/shaders/DepthLimitedBlurShader.js' -import { SpecklePass } from './Pipeline' +import { InputDepthTextureUniform, SpecklePass } from './SpecklePass' export enum NormalsType { DEFAULT = 0, @@ -49,12 +49,8 @@ export const DefaultSpeckleDynamicSAOPassParams = { blurDepthCutoff: 0.0007 } -/** - * SAO implementation inspired from bhouston previous SAO work - */ - -export class SpeckleDynamicSAOPass extends Pass implements SpecklePass { - private params: SpeckleDynamicSAOPassParams +export class DynamicSAOPass extends Pass implements SpecklePass { + private params: SpeckleDynamicSAOPassParams = DefaultSpeckleDynamicSAOPassParams private colorBuffer: Color = new Color() private saoMaterial: ShaderMaterial = null private vBlurMaterial: ShaderMaterial = null @@ -66,32 +62,22 @@ export class SpeckleDynamicSAOPass extends Pass implements SpecklePass { private prevStdDev: number private prevNumSamples: number - get displayName(): string { + public get displayName(): string { return 'SAO' } - get outputTexture(): Texture { + public get outputTexture(): Texture { return this.saoRenderTarget.texture } - public setDepthTexture(texture: Texture) { - this.saoMaterial.uniforms['tDepth'].value = texture - this.vBlurMaterial.uniforms['tDepth'].value = texture - this.hBlurMaterial.uniforms['tDepth'].value = texture - this.saoMaterial.needsUpdate = true - this.vBlurMaterial.needsUpdate = true - this.hBlurMaterial.needsUpdate = true + public set outputReconstructedNormals(value: boolean) { + if (value) this.saoMaterial.defines['OUTPUT_RECONSTRUCTED_NORMALS'] = '' + else delete this.saoMaterial.defines['OUTPUT_RECONSTRUCTED_NORMALS'] } constructor() { super() - // this.normalRenderTarget.depthBuffer = true - // this.normalRenderTarget.stencilBuffer = true - - // this.normalMaterial = new SpeckleNormalMaterial({}, ['USE_RTE']) - // this.normalMaterial.blending = NoBlending - // this.normalMaterial.side = DoubleSide this.saoRenderTarget = new WebGLRenderTarget(256, 256) this.blurIntermediateRenderTarget = new WebGLRenderTarget(256, 256) this.saoMaterial = new ShaderMaterial({ @@ -145,7 +131,21 @@ export class SpeckleDynamicSAOPass extends Pass implements SpecklePass { this.fsQuad = new FullScreenQuad(this.saoMaterial) } + public setParams(params: unknown) { + Object.assign(this.params, params) + } + + public setTexture(uName: InputDepthTextureUniform, texture: Texture) { + this.saoMaterial.uniforms['tDepth'].value = texture + this.vBlurMaterial.uniforms['tDepth'].value = texture + this.hBlurMaterial.uniforms['tDepth'].value = texture + this.saoMaterial.needsUpdate = true + this.vBlurMaterial.needsUpdate = true + this.hBlurMaterial.needsUpdate = true + } + public update(scene: Scene, camera: Camera) { + this.params.scale = (camera as PerspectiveCamera | OrthographicCamera).far /** SAO DEFINES */ this.saoMaterial.defines['PERSPECTIVE_CAMERA'] = (camera as PerspectiveCamera) .isPerspectiveCamera @@ -235,15 +235,9 @@ export class SpeckleDynamicSAOPass extends Pass implements SpecklePass { this.hBlurMaterial.needsUpdate = true } - public render(renderer, writeBuffer, readBuffer) { - writeBuffer - readBuffer - - // const restoreVisibility = this.batcher.saveVisiblity() - // const opaque = this.batcher.getOpaque() - // this.batcher.applyVisibility(opaque) - // this.batcher.applyVisibility(restoreVisibility) - + public render(renderer) { + const outputNormals = + this.saoMaterial.defines['OUTPUT_RECONSTRUCTED_NORMALS'] !== undefined // Rendering SAO texture renderer.getClearColor(this.colorBuffer) const originalClearAlpha = renderer.getClearAlpha() @@ -259,7 +253,7 @@ export class SpeckleDynamicSAOPass extends Pass implements SpecklePass { this.fsQuad.material = this.saoMaterial this.fsQuad.render(renderer) - if (this.params.blurEnabled) { + if (this.params.blurEnabled && !outputNormals) { renderer.setRenderTarget(this.blurIntermediateRenderTarget) renderer.setClearColor(0xffffff) renderer.setClearAlpha(1.0) diff --git a/packages/viewer/src/modules/pipeline/NormalsPass.ts b/packages/viewer/src/modules/pipeline/NormalsPass.ts new file mode 100644 index 000000000..771d0b165 --- /dev/null +++ b/packages/viewer/src/modules/pipeline/NormalsPass.ts @@ -0,0 +1,95 @@ +import { + Camera, + Color, + DoubleSide, + NoBlending, + Plane, + Scene, + Texture, + WebGLRenderTarget +} from 'three' +import { Pass } from 'three/examples/jsm/postprocessing/Pass' +import SpeckleNormalMaterial from '../materials/SpeckleNormalMaterial' +import { SpecklePass } from './SpecklePass' + +export class NormalsPass extends Pass implements SpecklePass { + private renderTarget: WebGLRenderTarget + private normalsMaterial: SpeckleNormalMaterial = null + private scene: Scene + private camera: Camera + + private colorBuffer: Color = new Color() + + public onBeforeRender: () => void = null + public onAfterRender: () => void = null + + get displayName(): string { + return 'GEOMETRY-NORMALS' + } + + get outputTexture(): Texture { + return this.renderTarget.texture + } + + constructor() { + super() + + this.renderTarget = new WebGLRenderTarget(256, 256) + /** On Chromium, on MacOS the 16 bit depth render buffer appears broken. + * We're not really using a stencil buffer at all, we're just forcing + * three.js to use a 24 bit depth render buffer + */ + this.renderTarget.depthBuffer = true + this.renderTarget.stencilBuffer = true + + this.normalsMaterial = new SpeckleNormalMaterial({}, ['USE_RTE']) + this.normalsMaterial.blending = NoBlending + this.normalsMaterial.side = DoubleSide + } + + public setClippingPlanes(planes: Plane[]) { + this.normalsMaterial.clippingPlanes = planes + } + + public update(scene: Scene, camera: Camera) { + this.camera = camera + this.scene = scene + } + + public render(renderer, writeBuffer, readBuffer) { + writeBuffer + readBuffer + + this.onBeforeRender() + renderer.getClearColor(this.colorBuffer) + const originalClearAlpha = renderer.getClearAlpha() + const originalAutoClear = renderer.autoClear + + renderer.setRenderTarget(this.renderTarget) + renderer.autoClear = false + + renderer.setClearColor(0x000000) + renderer.setClearAlpha(1.0) + renderer.clear() + + const shadowmapEnabled = renderer.shadowMap.enabled + const shadowmapNeedsUpdate = renderer.shadowMap.needsUpdate + this.scene.overrideMaterial = this.normalsMaterial + renderer.shadowMap.enabled = false + renderer.shadowMap.needsUpdate = false + renderer.render(this.scene, this.camera) + renderer.shadowMap.enabled = shadowmapEnabled + renderer.shadowMap.needsUpdate = shadowmapNeedsUpdate + this.scene.overrideMaterial = null + + // restore original state + renderer.autoClear = originalAutoClear + renderer.setClearColor(this.colorBuffer) + renderer.setClearAlpha(originalClearAlpha) + this.onAfterRender() + } + + public setSize(width: number, height: number) { + this.renderTarget.setSize(width, height) + } +} diff --git a/packages/viewer/src/modules/pipeline/Pipeline.ts b/packages/viewer/src/modules/pipeline/Pipeline.ts index 86d5ff19c..db2dc1a0e 100644 --- a/packages/viewer/src/modules/pipeline/Pipeline.ts +++ b/packages/viewer/src/modules/pipeline/Pipeline.ts @@ -1,31 +1,40 @@ -import { Camera, Plane, Scene, Texture, Vector2, WebGLRenderer } from 'three' +import { Camera, Plane, Scene, Vector2, WebGLRenderer } from 'three' import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js' import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js' import Batcher from '../batching/Batcher' +import SpeckleRenderer from '../SpeckleRenderer' import { ApplySAOPass } from './ApplySAOPass' +import { CopyOutputPass } from './CopyOutputPass' +import { DepthPass } from './DepthPass' +import { NormalsPass } from './NormalsPass' import { DefaultSpeckleDynamicSAOPassParams, - NormalsType, - SpeckleDynamicSAOPass, + DynamicSAOPass, SpeckleDynamicSAOPassParams -} from './SpeckleDynamicSAOPass' -import { SpeckleStaticAOGeneratePass } from './SpeckleStaticAOGeneratePass' +} from './DynamicSAOPass' +// import { SpecklePass } from './SpecklePass' +// import { SpeckleStaticAOGeneratePass } from './SpeckleStaticAOGeneratePass' -enum RenderType { - NORMAL, - ACCUMULATION -} -export interface SpecklePass { - get displayName(): string - get outputTexture(): Texture +export enum PipelineOutputType { + DEPTH_RGBA = 0, + DEPTH = 1, + COLOR = 2, + GEOMETRY_NORMALS = 3, + RECONSTRUCTED_NORMALS = 4, + DYNAMIC_AO = 5, + DYNAMIC_AO_BLURED = 6, + PROGRESSIVE_AO = 7, + FINAL = 8 } export interface PipelineOptions { + pipelineOutput: PipelineOutputType dynamicAoEnabled: boolean dynamicAoParams: SpeckleDynamicSAOPassParams } export const DefaultPipelineOptions: PipelineOptions = { + pipelineOutput: PipelineOutputType.FINAL, dynamicAoEnabled: true, dynamicAoParams: DefaultSpeckleDynamicSAOPassParams // saoScaleOffset: 0, @@ -43,23 +52,79 @@ export class Pipeline { private _pipelineOptions: PipelineOptions = Object.assign({}, DefaultPipelineOptions) private composer: EffectComposer = null + private depthPass: DepthPass = null + private normalsPass: NormalsPass = null private renderPass: RenderPass = null - private dynamicAoPass: SpeckleDynamicSAOPass = null + private dynamicAoPass: DynamicSAOPass = null private applySaoPass: ApplySAOPass = null - private staticAOGenerationPass: SpeckleStaticAOGeneratePass = null + private copyOutputPass: CopyOutputPass = null private drawingSize: Vector2 = new Vector2() - private _renderType: RenderType = RenderType.NORMAL - private accumulationFrame = 0 - private readonly NUM_ACCUMULATION_FRAMES = 16 - private enableProgressive = true public set pipelineOptions(options: Partial) { Object.assign(this._pipelineOptions, options) + this.pipelineOutput = options.pipelineOutput + this.dynamicAoPass.setParams(options.dynamicAoParams) } - private set renderType(value: RenderType) { - this._renderType = value + public set pipelineOutput(outputType: PipelineOutputType) { + switch (outputType) { + case PipelineOutputType.DEPTH_RGBA: + this.dynamicAoPass.enabled = false + this.renderPass.enabled = false + this.applySaoPass.enabled = false + this.normalsPass.enabled = false + this.depthPass.enabled = true + this.copyOutputPass.enabled = true + this.copyOutputPass.setTexture('tDiffuse', this.depthPass.outputTexture) + this.copyOutputPass.setOutputType(PipelineOutputType.DEPTH_RGBA) + break + + case PipelineOutputType.DEPTH: + this.dynamicAoPass.enabled = false + this.renderPass.enabled = false + this.applySaoPass.enabled = false + this.depthPass.enabled = true + this.normalsPass.enabled = false + this.copyOutputPass.enabled = true + this.copyOutputPass.setTexture('tDiffuse', this.depthPass.outputTexture) + this.copyOutputPass.setOutputType(PipelineOutputType.DEPTH) + break + + case PipelineOutputType.COLOR: + this.depthPass.enabled = false + this.dynamicAoPass.enabled = false + this.applySaoPass.enabled = false + this.copyOutputPass.enabled = false + this.normalsPass.enabled = false + this.renderPass.enabled = true + break + + case PipelineOutputType.GEOMETRY_NORMALS: + this.depthPass.enabled = false + this.dynamicAoPass.enabled = false + this.applySaoPass.enabled = false + this.renderPass.enabled = false + this.normalsPass.enabled = true + this.copyOutputPass.enabled = true + this.copyOutputPass.setTexture('tDiffuse', this.normalsPass.outputTexture) + this.copyOutputPass.setOutputType(PipelineOutputType.GEOMETRY_NORMALS) + break + + case PipelineOutputType.RECONSTRUCTED_NORMALS: + this.depthPass.enabled = true + this.dynamicAoPass.enabled = true + this.applySaoPass.enabled = false + this.renderPass.enabled = false + this.normalsPass.enabled = false + this.copyOutputPass.enabled = true + this.copyOutputPass.setTexture('tDiffuse', this.dynamicAoPass.outputTexture) + this.copyOutputPass.setOutputType(PipelineOutputType.GEOMETRY_NORMALS) + this.dynamicAoPass.outputReconstructedNormals = true + break + default: + break + } } public constructor(renderer: WebGLRenderer, batcher: Batcher) { @@ -71,59 +136,64 @@ export class Pipeline { } public configure(scene: Scene, camera: Camera) { - this.dynamicAoPass = new SpeckleDynamicSAOPass( - scene, - camera, - this._batcher, - false, - NormalsType.IMPROVED - ) - this.staticAOGenerationPass = new SpeckleStaticAOGeneratePass(this._batcher) - this.staticAOGenerationPass.depthTexture = - this.dynamicAoPass.depthRenderTarget.texture - this.composer.addPass(this.dynamicAoPass) + this.depthPass = new DepthPass() + this.normalsPass = new NormalsPass() + this.normalsPass.enabled = false + this.dynamicAoPass = new DynamicSAOPass() this.renderPass = new RenderPass(scene, camera) this.renderPass.renderToScreen = true - // this.renderPass.enabled = false - this.composer.addPass(this.renderPass) - this.composer.addPass(this.staticAOGenerationPass) this.applySaoPass = new ApplySAOPass() - this.applySaoPass.setAoTexture(this.dynamicAoPass.saoRenderTarget.texture) this.applySaoPass.renderToScreen = true + this.copyOutputPass = new CopyOutputPass() + this.copyOutputPass.renderToScreen = true + this.copyOutputPass.enabled = false + this.composer.addPass(this.depthPass) + this.composer.addPass(this.normalsPass) + this.composer.addPass(this.dynamicAoPass) + this.composer.addPass(this.renderPass) this.composer.addPass(this.applySaoPass) + this.composer.addPass(this.copyOutputPass) + + this.dynamicAoPass.setTexture('tDepth', this.depthPass.outputTexture) + this.applySaoPass.setTexture('tDiffuse', this.dynamicAoPass.outputTexture) + + let restoreVisibility + this.depthPass.onBeforeRender = () => { + restoreVisibility = this._batcher.saveVisiblity() + const opaque = this._batcher.getOpaque() + this._batcher.applyVisibility(opaque) + } + this.depthPass.onAfterRender = () => { + this._batcher.applyVisibility(restoreVisibility) + } + + this.normalsPass.onBeforeRender = () => { + restoreVisibility = this._batcher.saveVisiblity() + const opaque = this._batcher.getOpaque() + this._batcher.applyVisibility(opaque) + } + this.normalsPass.onAfterRender = () => { + this._batcher.applyVisibility(restoreVisibility) + } } public updateClippingPlanes(planes: Plane[]) { - this.dynamicAoPass.depthMaterial.clippingPlanes = planes - this.dynamicAoPass.normalMaterial.clippingPlanes = planes + this.depthPass.setClippingPlanes(planes) } - public render(scene: Scene, camera: Camera): boolean { + public update(renderer: SpeckleRenderer) { + this.depthPass.update(renderer.scene, renderer.camera) + this.dynamicAoPass.update(renderer.scene, renderer.camera) + this.normalsPass.update(renderer.scene, renderer.camera) + } + + public render(): boolean { this._renderer.getDrawingBufferSize(this.drawingSize) if (this.drawingSize.length() === 0) return - if (this._renderType === RenderType.NORMAL) { - this._renderer.clear(true) - this.applySaoPass.setAoTexture(this.dynamicAoPass.saoRenderTarget.texture) - this.renderPass.scene = scene - this.renderPass.camera = camera - this.dynamicAoPass.scene = scene - this.dynamicAoPass.camera = camera - this.composer.render() - return true - } else if (this.enableProgressive) { - this._renderer.clear(true) - this.applySaoPass.setAoTexture(this.staticAOGenerationPass.outputTexture.texture) - this.renderPass.scene = scene - this.renderPass.camera = camera - this.dynamicAoPass.scene = scene - this.dynamicAoPass.camera = camera - this.staticAOGenerationPass.update(camera, this.accumulationFrame) - this.composer.render() - this.accumulationFrame++ - console.warn('rendering stationary frame => ', this.accumulationFrame) - return this.accumulationFrame < this.NUM_ACCUMULATION_FRAMES ? true : false - } + this._renderer.clear(true) + this.composer.render() + return true } public resize(width: number, height: number) { @@ -131,15 +201,15 @@ export class Pipeline { } public onStationaryBegin() { - this.renderType = RenderType.ACCUMULATION - this.staticAOGenerationPass.enabled = true - this.accumulationFrame = 0 + // this.renderType = RenderType.ACCUMULATION + // this.staticAOGenerationPass.enabled = true + // this.accumulationFrame = 0 console.warn('Starting stationary') } public onStationaryEnd() { - this.renderType = RenderType.NORMAL - this.staticAOGenerationPass.enabled = false + // this.renderType = RenderType.NORMAL + // this.staticAOGenerationPass.enabled = false console.warn('Ending stationary') } } diff --git a/packages/viewer/src/modules/pipeline/SpecklePass.ts b/packages/viewer/src/modules/pipeline/SpecklePass.ts new file mode 100644 index 000000000..0451fcb50 --- /dev/null +++ b/packages/viewer/src/modules/pipeline/SpecklePass.ts @@ -0,0 +1,17 @@ +import { Camera, Plane, Scene, Texture } from 'three' + +export type InputColorTextureUniform = 'tDiffuse' +export type InputDepthTextureUniform = 'tDepth' + +export interface SpecklePass { + onBeforeRender?: () => void + onAferRender?: () => void + + get displayName(): string + get outputTexture(): Texture + + update?(scene: Scene, camera: Camera) + setTexture?(uName: string, texture: Texture) + setParams?(params: unknown) + setClippingPlanes?(planes: Plane[]) +}