diff --git a/packages/viewer-sandbox/src/Sandbox.ts b/packages/viewer-sandbox/src/Sandbox.ts index 9322f4652..4d84de649 100644 --- a/packages/viewer-sandbox/src/Sandbox.ts +++ b/packages/viewer-sandbox/src/Sandbox.ts @@ -16,7 +16,7 @@ export default class Sandbox { private viewsFolder!: FolderApi private streams: { [url: string]: Array } = {} private properties: PropertyInfo[] - private selectionList: SelectionEvent[] = null + private selectionList: SelectionEvent[] public static urlParams = { url: 'https://latest.speckle.dev/streams/c43ac05d04/commits/ec724cfbeb' @@ -30,6 +30,23 @@ export default class Sandbox { tonemapping: 4 //'ACESFilmicToneMapping' } + public static postParams = { + saoEnabled: true, + saoParams: { + saoBias: 0.15, + saoIntensity: 1.25, + saoScale: 434, + saoKernelRadius: 10, + saoMinResolution: 0, + saoBlur: true, + saoBlurRadius: 4, + saoBlurStdDev: 4, + saoBlurDepthCutoff: 0.0007 + }, + saoScaleOffset: 0, + saoNormalsRendering: 2 + } + public static lightParams: SunLightConfiguration = { enabled: true, castShadow: true, @@ -38,7 +55,7 @@ export default class Sandbox { elevation: 1.33, azimuth: 0.75, radius: 0, - indirectLightIntensity: 1.85 + indirectLightIntensity: 1.2 } public static filterParams = { @@ -172,7 +189,7 @@ export default class Sandbox { }) toggleSectionBox.on('click', () => { this.viewer.setSectionBoxFromObjects( - this.selectionList.map((val) => val.userData.id) as string[] + this.selectionList.map((val) => val.hits[0].object.id) as string[] ) this.viewer.toggleSectionBox() }) @@ -189,7 +206,7 @@ export default class Sandbox { }) zoomExtents.on('click', () => { this.viewer.zoom( - this.selectionList.map((val) => val.userData.id) as string[], + this.selectionList.map((val) => val.hits[0].object.id) as string[], undefined, true ) @@ -321,6 +338,116 @@ 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 + }) + .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, '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, '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, 'saoBlurStdDev', { + // min: 0, + // max: 150 + // }) + // .on('change', () => { + // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + // this.viewer.requestRender() + // }) + + // postFolder + // .addInput(Sandbox.postParams.saoParams, 'saoBlurDepthCutoff', { + // min: 0, + // max: 10 + // }) + // .on('change', () => { + // this.viewer.getRenderer().pipelineOptions = Sandbox.postParams + // this.viewer.requestRender() + // }) const lightsFolder = this.tabs.pages[1].addFolder({ title: 'Lights', diff --git a/packages/viewer-sandbox/src/main.ts b/packages/viewer-sandbox/src/main.ts index b2355a70b..2384b49f2 100644 --- a/packages/viewer-sandbox/src/main.ts +++ b/packages/viewer-sandbox/src/main.ts @@ -82,14 +82,14 @@ sandbox.makeFilteringUI() await sandbox.loadUrl( // 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8?c=%5B-7.66134,10.82932,6.41935,-0.07739,-13.88552,1.8697,0,1%5D' // Revit sample house (good for bim-like stuff with many display meshes) - 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8' + // 'https://speckle.xyz/streams/da9e320dad/commits/5388ef24b8' // 'Super' heavy revit shit // 'https://speckle.xyz/streams/e6f9156405/commits/0694d53bb5' // Same sample revit house, local to dim's computer // 'http://localhost:3000/streams/6960695d7b/commits/da0a2343fa' // 'http://100.66.180.109:3000/streams/6960695d7b/commits/417526751d' // IFC building (good for a tree based structure) - // 'https://latest.speckle.dev/streams/92b620fb17/commits/2ebd336223' + 'https://latest.speckle.dev/streams/92b620fb17/commits/2ebd336223' // IFC story, a subtree of the above // 'https://latest.speckle.dev/streams/92b620fb17/objects/8247bbc53865b0e0cb5ee4e252e66216' // Small scale lines @@ -99,4 +99,14 @@ await sandbox.loadUrl( // 'https://latest.speckle.dev/streams/92b620fb17/commits/af6098915b?c=%5B0.02144,-0.0377,0.05554,0.00566,0.00236,0,0,1%5D' // AutoCAD // 'https://latest.speckle.dev/streams/3ed8357f29/commits/d10f2af1ce' + //Blizzard world + // 'https://latest.speckle.dev/streams/0c6ad366c4/commits/aa1c393aec' + //Car + // 'https://latest.speckle.dev/streams/17d2e25a97/commits/6b6cf3d43e' + // Jonathon's + // 'https://latest.speckle.dev/streams/501258ee5f/commits/f885570011' + // Alex's cube + // 'https://latest.speckle.dev/streams/46e3e0e1ec/commits/a6392c19d6?c=%5B6.85874,2.9754,0.79022,0,0,0,0,1%5D' + // Groups of groups + // 'https://speckle.xyz/streams/1ce562e99a/commits/6fa28a5a0f' ) diff --git a/packages/viewer/src/modules/SpeckleRenderer.ts b/packages/viewer/src/modules/SpeckleRenderer.ts index 9b3ee5b0b..7b1d581e8 100644 --- a/packages/viewer/src/modules/SpeckleRenderer.ts +++ b/packages/viewer/src/modules/SpeckleRenderer.ts @@ -45,6 +45,7 @@ import { SunLightConfiguration, ViewerEvent } from '../IViewer' +import { DefaultPipelineOptions, Pipeline, PipelineOptions } from './pipeline/Pipeline' export default class SpeckleRenderer { private readonly SHOW_HELPERS = false @@ -59,6 +60,7 @@ export default class SpeckleRenderer { private sunConfiguration: SunLightConfiguration = DefaultLightConfiguration public viewer: Viewer // TEMPORARY private filterBatchRecording: string[] + private pipeline: Pipeline public get renderer(): WebGLRenderer { return this._renderer @@ -106,6 +108,10 @@ export default class SpeckleRenderer { return this.sun } + public set pipelineOptions(value: PipelineOptions) { + this.pipeline.pipelineOptions = value + } + public constructor(viewer: Viewer /** TEMPORARY */) { this.scene = new Scene() this.rootGroup = new Group() @@ -137,6 +143,10 @@ export default class SpeckleRenderer { this._renderer.setSize(container.offsetWidth, container.offsetHeight) 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.pipelineOptions = DefaultPipelineOptions + this.input = new Input(this._renderer.domElement, InputOptionsDefault) this.input.on(ViewerEvent.ObjectClicked, this.onObjectClick.bind(this)) this.input.on('object-clicked-debug', this.onObjectClickDebug.bind(this)) @@ -232,11 +242,46 @@ export default class SpeckleRenderer { } } } + + const v = new Vector3() + const box = this.sceneBox + const camPos = new Vector3().copy( + this.viewer.cameraHandler.activeCam.camera.position + ) + let d = 0 + v.set(box.min.x, box.min.y, box.min.z) // 000 + d = Math.max(camPos.distanceTo(v), d) + v.set(box.min.x, box.min.y, box.max.z) // 001 + d = Math.max(camPos.distanceTo(v), d) + v.set(box.min.x, box.max.y, box.min.z) // 010 + d = Math.max(camPos.distanceTo(v), d) + v.set(box.min.x, box.max.y, box.max.z) // 011 + d = Math.max(camPos.distanceTo(v), d) + v.set(box.max.x, box.min.y, box.min.z) // 100 + d = Math.max(camPos.distanceTo(v), d) + v.set(box.max.x, box.min.y, box.max.z) // 101 + d = Math.max(camPos.distanceTo(v), d) + v.set(box.max.x, box.max.y, box.min.z) // 110 + d = Math.max(camPos.distanceTo(v), d) + v.set(box.max.x, box.max.y, box.max.z) // 111 + d = Math.max(camPos.distanceTo(v), d) + this.viewer.cameraHandler.camera.far = d + this.viewer.cameraHandler.activeCam.camera.far = d + this.viewer.cameraHandler.activeCam.camera.updateProjectionMatrix() + this.viewer.cameraHandler.camera.updateProjectionMatrix() + this.pipeline.pipelineOptions = { saoParams: { saoScale: d } } + // console.log(d) } public render(camera: Camera) { this.batcher.render(this.renderer) - this.renderer.render(this.scene, camera) + this.pipeline.render(this.scene, camera) + // this.renderer.render(this.scene, camera) + } + + public resize(width: number, height: number) { + this.renderer.setSize(width, height) + this.pipeline.resize(width, height) } public addRenderTree(subtreeId: string) { @@ -331,6 +376,7 @@ export default class SpeckleRenderer { } } }) + this.pipeline.updateClippingPlanes(planes) this.renderer.shadowMap.needsUpdate = true } @@ -427,7 +473,7 @@ export default class SpeckleRenderer { public setSunLightConfiguration(config: SunLightConfiguration) { Object.assign(this.sunConfiguration, config) - if (config.indirectLightIntensity) { + if (config.indirectLightIntensity !== undefined) { this.indirectIBLIntensity = config.indirectLightIntensity } this.updateDirectLights() @@ -515,7 +561,6 @@ export default class SpeckleRenderer { } }) } as SelectionEvent - this.viewer.emit(ViewerEvent.ObjectClicked, selectionInfo) } @@ -631,7 +676,7 @@ export default class SpeckleRenderer { this.viewer.cameraHandler.controls.minDistance = distance / 100 this.viewer.cameraHandler.controls.maxDistance = distance * 100 - this.viewer.cameraHandler.camera.near = distance / 100 + this.viewer.cameraHandler.camera.near = Math.max(distance / 100, 0.1) this.viewer.cameraHandler.camera.far = distance * 100 this.viewer.cameraHandler.camera.updateProjectionMatrix() diff --git a/packages/viewer/src/modules/Viewer.ts b/packages/viewer/src/modules/Viewer.ts index f8a5f2aec..9fbbd82a2 100644 --- a/packages/viewer/src/modules/Viewer.ts +++ b/packages/viewer/src/modules/Viewer.ts @@ -82,6 +82,8 @@ export class Viewer extends EventEmitter implements IViewer { this.clock = new THREE.Clock() this.inProgressOperations = 0 + this.cameraHandler = new CameraHandler(this) + this.speckleRenderer = new SpeckleRenderer(this) this.speckleRenderer.create(this.container) window.addEventListener('resize', this.resize.bind(this), false) @@ -92,7 +94,6 @@ export class Viewer extends EventEmitter implements IViewer { // eslint-disable-next-line @typescript-eslint/no-explicit-any ;(window as any)._V = this // For debugging! - this.cameraHandler = new CameraHandler(this) this.sectionBox = new SectionBox(this) this.sectionBox.off() this.sectionBox.controls.addEventListener('change', () => { @@ -134,10 +135,7 @@ export class Viewer extends EventEmitter implements IViewer { } public resize() { - this.speckleRenderer.renderer.setSize( - this.container.offsetWidth, - this.container.offsetHeight - ) + this.speckleRenderer.resize(this.container.offsetWidth, this.container.offsetHeight) this.needsRender = true } @@ -157,7 +155,7 @@ export class Viewer extends EventEmitter implements IViewer { private render() { if (this.needsRender) { this.speckleRenderer.render(this.cameraHandler.activeCam.camera) - this._needsRender = false + // this._needsRender = false } } diff --git a/packages/viewer/src/modules/batching/Batch.ts b/packages/viewer/src/modules/batching/Batch.ts index 250660ec0..8577be8a3 100644 --- a/packages/viewer/src/modules/batching/Batch.ts +++ b/packages/viewer/src/modules/batching/Batch.ts @@ -20,6 +20,7 @@ export interface Batch { getCount(): number setBatchMaterial(material: Material): void setVisibleRange(...range: BatchUpdateRange[]) + getVisibleRange(): BatchUpdateRange setDrawRanges(...ranges: BatchUpdateRange[]) autoFillDrawRanges() resetDrawRanges() @@ -41,3 +42,8 @@ export const HideAllBatchUpdateRange = { offset: 0, count: 0 } as BatchUpdateRange + +export const AllBatchUpdateRange = { + offset: 0, + count: Infinity +} as BatchUpdateRange diff --git a/packages/viewer/src/modules/batching/Batcher.ts b/packages/viewer/src/modules/batching/Batcher.ts index 3c9848788..79ab95f60 100644 --- a/packages/viewer/src/modules/batching/Batcher.ts +++ b/packages/viewer/src/modules/batching/Batcher.ts @@ -5,10 +5,16 @@ import { WorldTree } from '../tree/WorldTree' import LineBatch from './LineBatch' import Materials from '../materials/Materials' import { NodeRenderView } from '../tree/NodeRenderView' -import { Batch, BatchUpdateRange, GeometryType, HideAllBatchUpdateRange } from './Batch' +import { + AllBatchUpdateRange, + Batch, + BatchUpdateRange, + GeometryType, + HideAllBatchUpdateRange +} from './Batch' import PointBatch from './PointBatch' // import { FilterMaterialType } from '../FilteringManager' -import { Material, WebGLRenderer } from 'three' +import { Material, Mesh, WebGLRenderer } from 'three' import { FilterMaterial, FilterMaterialType } from '../filtering/FilteringManager' export default class Batcher { @@ -96,6 +102,114 @@ export default class Batcher { } } + public saveVisiblity(): Record { + const visibilityRanges = {} + for (const k in this.batches) { + const batch: Batch = this.batches[k] + // if (batch.geometryType !== GeometryType.MESH) continue + visibilityRanges[k] = batch.getVisibleRange() + } + return visibilityRanges + } + + public applyVisibility(ranges: Record) { + for (const k in this.batches) { + const batch: Batch = this.batches[k] + // if (batch.geometryType !== GeometryType.MESH) continue + const range = ranges[k] + if (!range) { + batch.setVisibleRange(HideAllBatchUpdateRange) + } else { + batch.setVisibleRange(range) + } + } + } + + public getTransparent(): Record { + const visibilityRanges = {} + for (const k in this.batches) { + const batch: Batch = this.batches[k] + if (batch.geometryType !== GeometryType.MESH) { + visibilityRanges[k] = HideAllBatchUpdateRange + continue + } + const batchMesh: Mesh = batch.renderObject as Mesh + if (batchMesh.geometry.groups.length === 0) { + if ((batchMesh.material as Material).transparent === true) + visibilityRanges[k] = AllBatchUpdateRange + } else { + const transparentGroup = batchMesh.geometry.groups.find((value) => { + return batchMesh.material[value.materialIndex].visible === true + }) + const hiddenGroup = batchMesh.geometry.groups.find((value) => { + return batchMesh.material[value.materialIndex].visible === false + }) + if (transparentGroup) { + visibilityRanges[k] = { + offset: transparentGroup.start, + count: + hiddenGroup !== undefined + ? hiddenGroup.start + : batch.getCount() - transparentGroup.start + } + } + } + } + return visibilityRanges + } + + public getOpaque() { + const visibilityRanges = {} + for (const k in this.batches) { + const batch: Batch = this.batches[k] + if (batch.geometryType !== GeometryType.MESH) { + visibilityRanges[k] = HideAllBatchUpdateRange + continue + } + const batchMesh: Mesh = batch.renderObject as Mesh + if (batchMesh.geometry.groups.length === 0) { + if ((batchMesh.material as Material).transparent === false) + visibilityRanges[k] = AllBatchUpdateRange + } else { + const transparentOrHiddenGroup = batchMesh.geometry.groups.find((value) => { + return ( + batchMesh.material[value.materialIndex].transparent === true || + batchMesh.material[value.materialIndex].visible === false + ) + }) + visibilityRanges[k] = { + offset: 0, + count: + transparentOrHiddenGroup !== undefined + ? transparentOrHiddenGroup.start + : batch.getCount() + } + } + } + return visibilityRanges + } + + public enableTransparent(value: boolean) { + for (const k in this.batches) { + const batch: Batch = this.batches[k] + if (batch.geometryType !== GeometryType.MESH) continue + const batchMesh: Mesh = batch.renderObject as Mesh + if (batchMesh.geometry.groups.length === 0) { + batchMesh.visible = (batchMesh.material as Material).transparent + ? value + : batchMesh.visible + } else { + const transparentGroup = batchMesh.geometry.groups.find((value) => { + return batchMesh.material[value.materialIndex].transparent === true + }) + batch.setVisibleRange({ + offset: 0, + count: transparentGroup.start + }) + } + } + } + public purgeBatches(subtreeId: string) { for (const k in this.batches) { if (this.batches[k].subtreeId === subtreeId) { @@ -129,20 +243,6 @@ export default class Batcher { filterMaterial: FilterMaterial, uniqueRvsOnly = true ): string[] { - // const rvs = [] - // ids.forEach((val: string) => { - // rvs.push(WorldTree.getRenderTree().getRenderViewForNodeId(val)) - // /** The batcher should take the explicit IDs it's given and roll with them - // * It shouldn;t try to expand the list of render views on it's own - // */ - // // const views = WorldTree.getRenderTree().getRenderViewsForNodeId(val) - // // for (let k = 0; k < views.length; k++) { - // // if (rvs.includes(views[k])) return - // // } - // // rvs = rvs.concat(views) - // }) - // console.log(ids) - // console.log(rvs) let renderViews = rvs if (uniqueRvsOnly) renderViews = [...Array.from(new Set(rvs.map((value) => value)))] const batchIds = [...Array.from(new Set(renderViews.map((value) => value.batchId)))] @@ -175,12 +275,6 @@ export default class Batcher { if (!value) return this.batches[value].autoFillDrawRanges() }) - // let groupCount = 0 - // for (const k in this.batches) { - // const gLength = (this.batches[k].renderObject as Mesh).geometry.groups.length - // groupCount += gLength === 0 ? 1 : gLength - // } - // console.warn(groupCount) } /** Conveniece method. This should also work as a filtering action @@ -248,98 +342,4 @@ export default class Batcher { } } } - - /** KEEPING THESE FOR REFERENCE FOR NOW */ - /* - public selectRenderViews(renderViews: NodeRenderView[]) { - this.resetBatchesDrawRanges() - const batchIds = [...Array.from(new Set(renderViews.map((value) => value.batchId)))] - console.warn('<<<< BATCHES >>>>>>') - for (let i = 0; i < batchIds.length; i++) { - const batch = this.batches[batchIds[i]] - const views = renderViews - .filter((value) => value.batchId === batchIds[i]) - .map((rv: NodeRenderView) => { - return { - offset: rv.batchStart, - count: rv.batchCount, - material: this.materials.getHighlightMaterial(rv) - } - }) - // console.warn(views) - batch.setDrawRanges(true, ...views) - } - } - - public selectRenderView(renderView: NodeRenderView) { - this.resetBatchesDrawRanges() - const batch = this.batches[renderView.batchId] - batch.setDrawRanges( - false, - { - offset: 0, - count: renderView.batchStart, - material: batch.batchMaterial - } as BatchUpdateRange, - { - offset: renderView.batchStart, - count: renderView.batchCount, - material: this.materials.getHighlightMaterial(renderView) - } as BatchUpdateRange, - { - offset: renderView.batchEnd, - count: Infinity, - material: batch.batchMaterial - } as BatchUpdateRange - ) - } - - public isolateRenderView(renderView: NodeRenderView) { - this.resetBatchesDrawRanges() - - for (const k in this.batches) { - if (k === renderView.batchId) { - const batch = this.batches[renderView.batchId] - batch.setVisibleRange({ - offset: renderView.batchStart, - count: renderView.batchCount, - material: batch.batchMaterial - } as BatchUpdateRange) - batch.setDrawRanges(false, { - offset: renderView.batchStart, - count: renderView.batchCount, - material: batch.batchMaterial - } as BatchUpdateRange) - } else { - this.batches[k].setVisibleRange(HideAllBatchUpdateRange) - } - } - } - - public isolateRenderViews(renderViews: NodeRenderView[]) { - this.resetBatchesDrawRanges() - const batchIds = [...Array.from(new Set(renderViews.map((value) => value.batchId)))] - // console.warn('<<<< BATCHES >>>>>>') - for (const k in this.batches) { - if (!batchIds.includes(k)) { - this.batches[k].setVisibleRange(HideAllBatchUpdateRange) - } - } - for (let i = 0; i < batchIds.length; i++) { - const batch = this.batches[batchIds[i]] - const views = renderViews - .filter((value) => value.batchId === batchIds[i]) - .map((rv: NodeRenderView) => { - return { - offset: rv.batchStart, - count: rv.batchCount, - material: batch.batchMaterial - } - }) - // console.warn(views) - batch.setDrawRanges(false, ...views) - batch.setVisibleRange(...views) - } - } - */ } diff --git a/packages/viewer/src/modules/batching/LineBatch.ts b/packages/viewer/src/modules/batching/LineBatch.ts index 94bf38b30..cfcd57692 100644 --- a/packages/viewer/src/modules/batching/LineBatch.ts +++ b/packages/viewer/src/modules/batching/LineBatch.ts @@ -15,7 +15,13 @@ import { Geometry } from '../converter/Geometry' import SpeckleLineMaterial from '../materials/SpeckleLineMaterial' import { NodeRenderView } from '../tree/NodeRenderView' import { Viewer } from '../Viewer' -import { Batch, BatchUpdateRange, GeometryType } from './Batch' +import { + AllBatchUpdateRange, + Batch, + BatchUpdateRange, + GeometryType, + HideAllBatchUpdateRange +} from './Batch' export default class LineBatch implements Batch { public id: string @@ -58,6 +64,24 @@ export default class LineBatch implements Batch { } public setVisibleRange(...ranges: BatchUpdateRange[]) { + if ( + ranges.length === 1 && + ranges[0].offset === HideAllBatchUpdateRange.offset && + ranges[0].count === HideAllBatchUpdateRange.count + ) { + this.mesh.visible = false + return + } + + if ( + ranges.length === 1 && + ranges[0].offset === AllBatchUpdateRange.offset && + ranges[0].count === AllBatchUpdateRange.count + ) { + this.mesh.visible = true + return + } + this.mesh.visible = true const data = this.colorBuffer.array as number[] for (let k = 0; k < data.length; k += 4) { data[k + 3] = 0 @@ -78,6 +102,11 @@ export default class LineBatch implements Batch { this.geometry.attributes['instanceColorEnd'].needsUpdate = true } + public getVisibleRange() { + return AllBatchUpdateRange + // TO DO if required + } + public setDrawRanges(...ranges: BatchUpdateRange[]) { const data = this.colorBuffer.array as number[] @@ -118,6 +147,7 @@ export default class LineBatch implements Batch { material: this.batchMaterial }) this.mesh.material = this.batchMaterial + this.mesh.visible = true } public buildBatch() { diff --git a/packages/viewer/src/modules/batching/MeshBatch.ts b/packages/viewer/src/modules/batching/MeshBatch.ts index d1eff75c2..2c352f297 100644 --- a/packages/viewer/src/modules/batching/MeshBatch.ts +++ b/packages/viewer/src/modules/batching/MeshBatch.ts @@ -14,7 +14,13 @@ import SpeckleStandardColoredMaterial from '../materials/SpeckleStandardColoredM import SpeckleMesh from '../objects/SpeckleMesh' import { NodeRenderView } from '../tree/NodeRenderView' import { Viewer } from '../Viewer' -import { Batch, BatchUpdateRange, GeometryType, HideAllBatchUpdateRange } from './Batch' +import { + AllBatchUpdateRange, + Batch, + BatchUpdateRange, + GeometryType, + HideAllBatchUpdateRange +} from './Batch' export default class MeshBatch implements Batch { public id: string @@ -64,6 +70,16 @@ export default class MeshBatch implements Batch { this.mesh.visible = false return } + if ( + ranges.length === 1 && + ranges[0].offset === AllBatchUpdateRange.offset && + ranges[0].count === AllBatchUpdateRange.count + ) { + this.geometry.setDrawRange(0, this.getCount()) + this.mesh.visible = true + return + } + let minOffset = Infinity let maxOffset = 0 ranges.forEach((range) => { @@ -77,6 +93,14 @@ export default class MeshBatch implements Batch { ) } + public getVisibleRange(): BatchUpdateRange { + if (this.geometry.groups.length === 0) return AllBatchUpdateRange + return { + offset: this.geometry.drawRange.start, + count: this.geometry.drawRange.count + } + } + public setDrawRanges(...ranges: BatchUpdateRange[]) { const materials = ranges.map((val) => val.material) const uniqueMaterials = [...Array.from(new Set(materials.map((value) => value)))] @@ -195,7 +219,7 @@ export default class MeshBatch implements Batch { b.materialIndex ] const visibleOrder = +materialB.visible - +materialA.visible - const transparentOrder = +materialB.transparent - +materialA.transparent + const transparentOrder = +materialA.transparent - +materialB.transparent if (visibleOrder !== 0) return visibleOrder return transparentOrder }) @@ -207,6 +231,19 @@ export default class MeshBatch implements Batch { return previousValue }, materialOrder) + // if (materialOrder.length > 1) { + // for (let m = 0; m < materialOrder.length; m++) { + // if (!this.mesh.material[materialOrder[m]].visible) + // console.log( + // `Batch ${this.id} material: Hidden -> ${!this.mesh.material[ + // materialOrder[m] + // ].visible}, Transparent -> ${ + // this.mesh.material[materialOrder[m]].transparent + // }` + // ) + // } + // } + const grouped = [] for (let k = 0; k < materialOrder.length; k++) { grouped.push( diff --git a/packages/viewer/src/modules/batching/PointBatch.ts b/packages/viewer/src/modules/batching/PointBatch.ts index 002e36459..3ad67f4f2 100644 --- a/packages/viewer/src/modules/batching/PointBatch.ts +++ b/packages/viewer/src/modules/batching/PointBatch.ts @@ -9,7 +9,13 @@ import { import { Geometry } from '../converter/Geometry' import { NodeRenderView } from '../tree/NodeRenderView' import { Viewer } from '../Viewer' -import { Batch, BatchUpdateRange, GeometryType, HideAllBatchUpdateRange } from './Batch' +import { + AllBatchUpdateRange, + Batch, + BatchUpdateRange, + GeometryType, + HideAllBatchUpdateRange +} from './Batch' export default class PointBatch implements Batch { public id: string @@ -67,6 +73,11 @@ export default class PointBatch implements Batch { maxOffset - minOffset + ranges.find((val) => val.offset === maxOffset).count ) } + + public getVisibleRange() { + return AllBatchUpdateRange + } + /** * This is the first version for multi draw ranges with automatic fill support * In the near future, we'll re-sort the index buffer so we minimize draw calls to diff --git a/packages/viewer/src/modules/context/CameraHanlder.js b/packages/viewer/src/modules/context/CameraHanlder.js index 5985a653e..40729c57f 100644 --- a/packages/viewer/src/modules/context/CameraHanlder.js +++ b/packages/viewer/src/modules/context/CameraHanlder.js @@ -30,10 +30,7 @@ export default class CameraHandler { this.orthoCamera.updateProjectionMatrix() CameraControls.install({ THREE }) - this.controls = new CameraControls( - this.camera, - this.viewer.speckleRenderer.renderer.domElement - ) + this.controls = new CameraControls(this.camera, this.viewer.container) this.controls.maxPolarAngle = Math.PI / 2 this.setupWASDControls() diff --git a/packages/viewer/src/modules/materials/SpeckleDepthMaterial.ts b/packages/viewer/src/modules/materials/SpeckleDepthMaterial.ts index f1b1f011f..3a905f119 100644 --- a/packages/viewer/src/modules/materials/SpeckleDepthMaterial.ts +++ b/packages/viewer/src/modules/materials/SpeckleDepthMaterial.ts @@ -89,6 +89,9 @@ class SpeckleDepthMaterial extends MeshDepthMaterial { return this } + /** Another note here, this will NOT get called by three when rendering shadowmaps. We update the uniforms manually + * inside SpeckleRenderer for shadowmaps + */ onBeforeRender(_this, scene, camera, geometry, object, group) { SpeckleDepthMaterial.matBuff.copy(camera.matrixWorldInverse) SpeckleDepthMaterial.matBuff.elements[12] = 0 @@ -111,6 +114,7 @@ class SpeckleDepthMaterial extends MeshDepthMaterial { this.userData.uViewer_low.value.copy(SpeckleDepthMaterial.vecBuff1) this.userData.uViewer_high.value.copy(SpeckleDepthMaterial.vecBuff2) + this.userData.rteModelViewMatrix.value.copy(object.modelViewMatrix) this.needsUpdate = true } diff --git a/packages/viewer/src/modules/materials/SpeckleNormalMaterial.ts b/packages/viewer/src/modules/materials/SpeckleNormalMaterial.ts new file mode 100644 index 000000000..d54193b17 --- /dev/null +++ b/packages/viewer/src/modules/materials/SpeckleNormalMaterial.ts @@ -0,0 +1,95 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable camelcase */ +import { speckleNormalVert } from './shaders/speckle-normal-vert' +import { speckleNormalFrag } from './shaders/speckle-normal-frag' +import { UniformsUtils, ShaderLib, Vector3, MeshNormalMaterial } from 'three' +import { Matrix4 } from 'three' +import { Geometry } from '../converter/Geometry' + +class SpeckleNormalMaterial extends MeshNormalMaterial { + protected static readonly matBuff: Matrix4 = new Matrix4() + protected static readonly vecBuff0: Vector3 = new Vector3() + protected static readonly vecBuff1: Vector3 = new Vector3() + protected static readonly vecBuff2: Vector3 = new Vector3() + + constructor(parameters, defines = []) { + super(parameters) + + this.userData.uViewer_high = { + value: new Vector3() + } + this.userData.uViewer_low = { + value: new Vector3() + } + ;(this as any).vertProgram = speckleNormalVert + ;(this as any).fragProgram = speckleNormalFrag + ;(this as any).uniforms = UniformsUtils.merge([ + ShaderLib.standard.uniforms, + { + uViewer_high: { + value: this.userData.uViewer_high.value + }, + uViewer_low: { + value: this.userData.uViewer_low.value + } + } + ]) + + this.onBeforeCompile = function (shader) { + shader.uniforms.uViewer_high = this.userData.uViewer_high + shader.uniforms.uViewer_low = this.userData.uViewer_low + shader.vertexShader = this.vertProgram + shader.fragmentShader = this.fragProgram + } + + if (defines) { + this.defines = {} + } + for (let k = 0; k < defines.length; k++) { + this.defines[defines[k]] = ' ' + } + } + + copy(source) { + super.copy(source) + this.userData = {} + this.userData.uViewer_high = { + value: new Vector3() + } + this.userData.uViewer_low = { + value: new Vector3() + } + this.defines['USE_RTE'] = ' ' + + return this + } + + onBeforeRender(_this, scene, camera, geometry, object, group) { + SpeckleNormalMaterial.matBuff.copy(camera.matrixWorldInverse) + SpeckleNormalMaterial.matBuff.elements[12] = 0 + SpeckleNormalMaterial.matBuff.elements[13] = 0 + SpeckleNormalMaterial.matBuff.elements[14] = 0 + SpeckleNormalMaterial.matBuff.multiply(object.matrixWorld) + object.modelViewMatrix.copy(SpeckleNormalMaterial.matBuff) + + SpeckleNormalMaterial.vecBuff0.set( + camera.matrixWorld.elements[12], + camera.matrixWorld.elements[13], + camera.matrixWorld.elements[14] + ) + + Geometry.DoubleToHighLowVector( + SpeckleNormalMaterial.vecBuff0, + SpeckleNormalMaterial.vecBuff1, + SpeckleNormalMaterial.vecBuff2 + ) + + this.userData.uViewer_low.value.copy(SpeckleNormalMaterial.vecBuff1) + this.userData.uViewer_high.value.copy(SpeckleNormalMaterial.vecBuff2) + + this.needsUpdate = true + } +} + +export default SpeckleNormalMaterial diff --git a/packages/viewer/src/modules/materials/shaders/speckle-normal-frag.ts b/packages/viewer/src/modules/materials/shaders/speckle-normal-frag.ts new file mode 100644 index 000000000..b42c4df73 --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-normal-frag.ts @@ -0,0 +1,24 @@ +export const speckleNormalFrag = /* glsl */ ` +#define NORMAL +uniform float opacity; +#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP ) + varying vec3 vViewPosition; +#endif +#include +#include +#include +#include +#include +#include +#include +void main() { + #include + #include + #include + #include + gl_FragColor = vec4( packNormalToRGB( normal ), opacity ); + #ifdef OPAQUE + gl_FragColor.a = 1.0; + #endif +} +` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-normal-vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-normal-vert.ts new file mode 100644 index 000000000..d54bd0091 --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-normal-vert.ts @@ -0,0 +1,68 @@ +export const speckleNormalVert = /* glsl */ ` +#define NORMAL +#ifdef USE_RTE + // The high component is stored as the default 'position' attribute buffer + attribute vec3 position_low; + uniform vec3 uViewer_high; + uniform vec3 uViewer_low; +#endif +#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP ) + varying vec3 vViewPosition; +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +vec4 computeRelativePosition(in vec3 position_low, in vec3 position_high, in vec3 relativeTo_low, in vec3 relativeTo_high){ + /* + Source https://github.com/virtualglobebook/OpenGlobe/blob/master/Source/Examples/Chapter05/Jitter/GPURelativeToEyeDSFUN90/Shaders/VS.glsl + Note here, we're storing the high part of the position encoding inside three's default 'position' attribute buffer so we avoid redundancy + */ + vec3 t1 = position_low.xyz - relativeTo_low; + vec3 e = t1 - position_low.xyz; + vec3 t2 = ((-relativeTo_low - e) + (position_low.xyz - (t1 - e))) + position_high.xyz - relativeTo_high; + vec3 highDifference = t1 + t2; + vec3 lowDifference = t2 - (highDifference - t1); + vec3 position = highDifference.xyz + lowDifference.xyz; + return vec4(position, 1.); +} + +void main() { + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + //#include // EDITED CHUNK + #ifdef USE_RTE + vec4 mvPosition = computeRelativePosition(position_low.xyz, position.xyz, uViewer_low, uViewer_high); + #else + vec4 mvPosition = vec4( transformed, 1.0 ); + #endif + + #ifdef USE_INSTANCING + + mvPosition = instanceMatrix * mvPosition; + + #endif + mvPosition = modelViewMatrix * mvPosition; + + gl_Position = projectionMatrix * mvPosition; + #include + #include +#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP ) + vViewPosition = - mvPosition.xyz; +#endif +} +` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-sao-frag.ts b/packages/viewer/src/modules/materials/shaders/speckle-sao-frag.ts new file mode 100644 index 000000000..b7c283856 --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-sao-frag.ts @@ -0,0 +1,216 @@ +export const speckleSaoFrag = /* glsl */ ` + #include + varying vec2 vUv; + #if DIFFUSE_TEXTURE == 1 + uniform sampler2D tDiffuse; + #endif + uniform sampler2D tDepth; + #if NORMAL_TEXTURE == 1 + uniform sampler2D tNormal; + #endif + uniform float cameraNear; + uniform float cameraFar; + uniform mat4 cameraProjectionMatrix; + uniform mat4 cameraInverseProjectionMatrix; + uniform float scale; + uniform float intensity; + uniform float bias; + uniform float kernelRadius; + uniform float minResolution; + uniform vec2 size; + uniform float randomSeed; + // RGBA depth + #include + vec4 getDefaultColor( const in vec2 screenPosition ) { + #if DIFFUSE_TEXTURE == 1 + return texture2D( tDiffuse, vUv ); + #else + return vec4( 1.0 ); + #endif + } + float getDepth( const in vec2 screenPosition ) { + #if DEPTH_PACKING == 1 + return unpackRGBAToDepth( texture2D( tDepth, screenPosition ) ); + #else + return texture2D( tDepth, screenPosition ).x; + #endif + } + float getViewZ( const in float depth ) { + #if PERSPECTIVE_CAMERA == 1 + return perspectiveDepthToViewZ( depth, cameraNear, cameraFar ); + #else + return orthographicDepthToViewZ( depth, cameraNear, cameraFar ); + #endif + } + vec3 getViewPosition( const in vec2 screenPosition, const in float depth, const in float viewZ ) { + float clipW = cameraProjectionMatrix[2][3] * viewZ + cameraProjectionMatrix[3][3]; + vec4 clipPosition = vec4( ( vec3( screenPosition, depth ) - 0.5 ) * 2.0, 1.0 ); + clipPosition *= clipW; // unprojection. + return ( cameraInverseProjectionMatrix * clipPosition ).xyz; + } + + //https://wickedengine.net/2019/09/22/improved-normal-reconstruction-from-depth/ + vec3 viewNormalImproved(in vec2 uv, in vec3 origin) + { + highp vec2 dd = abs(vec2(1./size.x, 1./size.y)); + highp vec2 ddx = vec2(dd.x, 0.); + highp vec2 ddy = vec2(0., dd.y); + + float sampleDepth = getDepth( uv - ddy ); + float sampleViewZ = getViewZ( sampleDepth ); + highp vec3 top = getViewPosition( uv - ddy, sampleDepth, sampleViewZ ); + + sampleDepth = getDepth( uv + ddy ); + sampleViewZ = getViewZ( sampleDepth ); + highp vec3 bottom = getViewPosition( uv + ddy, sampleDepth, sampleViewZ ); + + highp vec3 center = origin; + + sampleDepth = getDepth( uv - ddx ); + sampleViewZ = getViewZ( sampleDepth ); + highp vec3 left = getViewPosition( uv - ddx, sampleDepth, sampleViewZ ); + + sampleDepth = getDepth( uv + ddx ); + sampleViewZ = getViewZ( sampleDepth ); + highp vec3 right = getViewPosition( uv + ddx, sampleDepth, sampleViewZ ); + + // get the difference between the current and each offset position + vec3 l = center - left; + vec3 r = right - center; + vec3 d = center - top; + vec3 u = bottom - center; + + // pick horizontal and vertical diff with the smallest z difference + vec3 hDeriv = abs(l.z) < abs(r.z) ? l : r; + vec3 vDeriv = abs(d.z) < abs(u.z) ? d : u; + + // get view space normal from the cross product of the two smallest offsets + vec3 viewNormal = normalize(cross(hDeriv, vDeriv)); + + return viewNormal; + } + + vec3 viewNormalAccurate(in vec2 uv, in vec3 origin, in float centerDepth) { + highp vec2 dd = abs(vec2(1./size.x, 1./size.y)); + highp vec2 ddx = vec2(dd.x, 0.); + highp vec2 ddy = vec2(0., dd.y); + + float sampleDepth = getDepth( uv - ddy ); + float sampleViewZ = getViewZ( sampleDepth ); + highp vec3 top = getViewPosition( uv - ddy, sampleDepth, sampleViewZ ); + + sampleDepth = getDepth( uv + ddy ); + sampleViewZ = getViewZ( sampleDepth ); + highp vec3 bottom = getViewPosition( uv + ddy, sampleDepth, sampleViewZ ); + + highp vec3 center = origin; + + sampleDepth = getDepth( uv - ddx ); + sampleViewZ = getViewZ( sampleDepth ); + highp vec3 left = getViewPosition( uv - ddx, sampleDepth, sampleViewZ ); + + sampleDepth = getDepth( uv + ddx ); + sampleViewZ = getViewZ( sampleDepth ); + highp vec3 right = getViewPosition( uv + ddx, sampleDepth, sampleViewZ ); + + // get the difference between the current and each offset position + vec3 l = center - left; + vec3 r = right - center; + vec3 d = center - top; + vec3 u = bottom - center; + + // get depth values at 1 & 2 pixels offsets from current along the horizontal axis + vec4 H = vec4( + getDepth(uv - ddx), + getDepth(uv + ddx), + getDepth(uv - 2. * ddx), + getDepth(uv + 2. * ddx) + ); + + // get depth values at 1 & 2 pixels offsets from current along the vertical axis + vec4 V = vec4( + getDepth(uv - ddy), + getDepth(uv + ddy), + getDepth(uv - 2. * ddy), + getDepth(uv + 2. * ddy) + ); + + // current pixel's depth difference from slope of offset depth samples + // differs from original article because we're using non-linear depth values + // see article's comments + vec2 he = abs((2. * H.xy - H.zw) - centerDepth); + vec2 ve = abs((2. * V.xy - V.zw) - centerDepth); + + // pick horizontal and vertical diff with the smallest depth difference from slopes + vec3 hDeriv = he.x < he.y ? l : r; + vec3 vDeriv = ve.x < ve.y ? d : u; + + // get view space normal from the cross product of the best derivatives + vec3 viewNormal = normalize(cross(hDeriv, vDeriv)); + + return viewNormal; + + } + + vec3 getViewNormal( const in vec3 viewPosition, const in vec2 screenPosition, in float centerDepth ) { + #if NORMAL_TEXTURE == 1 + return unpackRGBToNormal( texture2D( tNormal, screenPosition ).xyz ); + #elif IMPROVED_NORMAL_RECONSTRUCTION == 1 + return viewNormalImproved(screenPosition, viewPosition); + #elif ACCURATE_NORMAL_RECONSTRUCTION == 1 + return viewNormalAccurate(screenPosition, viewPosition, centerDepth); + #else + return normalize( cross( dFdx( viewPosition ), dFdy( viewPosition ) ) ); + #endif + } + + float scaleDividedByCameraFar; + float minResolutionMultipliedByCameraFar; + float getOcclusion( const in vec3 centerViewPosition, const in vec3 centerViewNormal, const in vec3 sampleViewPosition ) { + vec3 viewDelta = sampleViewPosition - centerViewPosition; + float viewDistance = length( viewDelta ); + float scaledScreenDistance = scaleDividedByCameraFar * viewDistance; + return max(0.0, (dot(centerViewNormal, viewDelta) - minResolutionMultipliedByCameraFar) / scaledScreenDistance - bias) / (1.0 + pow2( scaledScreenDistance ) ); + } + // moving costly divides into consts + const float ANGLE_STEP = PI2 * float( NUM_RINGS ) / float( NUM_SAMPLES ); + const float INV_NUM_SAMPLES = 1.0 / float( NUM_SAMPLES ); + float getAmbientOcclusion( const in vec3 centerViewPosition, in float centerDepth ) { + // precompute some variables require in getOcclusion. + scaleDividedByCameraFar = scale / cameraFar; + minResolutionMultipliedByCameraFar = minResolution * cameraFar; + vec3 centerViewNormal = getViewNormal( centerViewPosition, vUv, centerDepth ); + // jsfiddle that shows sample pattern: https://jsfiddle.net/a16ff1p7/ + float angle = rand( vUv + randomSeed ) * PI2; + vec2 radius = vec2( kernelRadius * INV_NUM_SAMPLES ) / size; + vec2 radiusStep = radius; + float occlusionSum = 0.0; + float weightSum = 0.0; + for( int i = 0; i < NUM_SAMPLES; i ++ ) { + vec2 sampleUv = vUv + vec2( cos( angle ), sin( angle ) ) * radius; + radius += radiusStep; + angle += ANGLE_STEP; + float sampleDepth = getDepth( sampleUv ); + if( sampleDepth >= ( 1.0 - EPSILON ) ) { + continue; + } + float sampleViewZ = getViewZ( sampleDepth ); + vec3 sampleViewPosition = getViewPosition( sampleUv, sampleDepth, sampleViewZ ); + occlusionSum += getOcclusion( centerViewPosition, centerViewNormal, sampleViewPosition ); + weightSum += 1.0; + } + if( weightSum == 0.0 ) discard; + return occlusionSum * ( intensity / weightSum ); + } + void main() { + float centerDepth = getDepth( vUv ); + if( centerDepth >= ( 1.0 - EPSILON ) ) { + discard; + } + float centerViewZ = getViewZ( centerDepth ); + vec3 viewPosition = getViewPosition( vUv, centerDepth, centerViewZ ); + float ambientOcclusion = getAmbientOcclusion( viewPosition, centerDepth ); + gl_FragColor = getDefaultColor( vUv ); + gl_FragColor.xyz *= 1.0 - ambientOcclusion; + // gl_FragColor.xyz = depth_cross(vUv, viewPosition) * 0.5 + 0.5; + }` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-sao-vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-sao-vert.ts new file mode 100644 index 000000000..d85755936 --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-sao-vert.ts @@ -0,0 +1,6 @@ +export const speckleSaoVert = /* glsl */ ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); + }` diff --git a/packages/viewer/src/modules/pipeline/ApplySAOPass.ts b/packages/viewer/src/modules/pipeline/ApplySAOPass.ts new file mode 100644 index 000000000..cbddfdc58 --- /dev/null +++ b/packages/viewer/src/modules/pipeline/ApplySAOPass.ts @@ -0,0 +1,48 @@ +import { + AddEquation, + CustomBlending, + DstAlphaFactor, + DstColorFactor, + NoBlending, + ShaderMaterial, + Texture, + UniformsUtils, + ZeroFactor +} from 'three' +import { FullScreenQuad, Pass } from 'three/examples/jsm/postprocessing/Pass' +import { CopyShader } from 'three/examples/jsm/shaders/CopyShader.js' + +export class ApplySAOPass extends Pass { + private fsQuad: FullScreenQuad + private materialCopy: ShaderMaterial + + constructor(srcSao: Texture) { + super() + this.materialCopy = new ShaderMaterial({ + uniforms: UniformsUtils.clone(CopyShader.uniforms), + vertexShader: CopyShader.vertexShader, + fragmentShader: CopyShader.fragmentShader, + blending: NoBlending + }) + this.materialCopy.transparent = true + this.materialCopy.depthTest = false + this.materialCopy.depthWrite = false + this.materialCopy.blending = CustomBlending + this.materialCopy.blendSrc = DstColorFactor + this.materialCopy.blendDst = ZeroFactor + this.materialCopy.blendEquation = AddEquation + this.materialCopy.blendSrcAlpha = DstAlphaFactor + this.materialCopy.blendDstAlpha = ZeroFactor + this.materialCopy.blendEquationAlpha = AddEquation + this.materialCopy.uniforms['tDiffuse'].value = srcSao + this.materialCopy.needsUpdate = true + this.fsQuad = new FullScreenQuad(this.materialCopy) + } + + render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive*/) { + writeBuffer + readBuffer + renderer.setRenderTarget(null) + this.fsQuad.render(renderer) + } +} diff --git a/packages/viewer/src/modules/pipeline/Pipeline.ts b/packages/viewer/src/modules/pipeline/Pipeline.ts new file mode 100644 index 000000000..e6991a1b4 --- /dev/null +++ b/packages/viewer/src/modules/pipeline/Pipeline.ts @@ -0,0 +1,97 @@ +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 { SAOPassParams } from 'three/examples/jsm/postprocessing/SAOPass.js' +import Batcher from '../batching/Batcher' +import { ApplySAOPass } from './ApplySAOPass' +import { NormalsType, SpeckleSAOPass } from './SpeckleSAOPass' + +export interface PipelineOptions { + saoEnabled?: boolean + saoParams?: Partial + saoScaleOffset?: number + saoNormalsRendering?: NormalsType +} + +export const DefaultPipelineOptions: PipelineOptions = { + saoEnabled: true, + saoParams: { + saoBias: 0.15, + saoIntensity: 1.25, + saoScale: 434, + saoKernelRadius: 10, + saoMinResolution: 0, + saoBlur: true, + saoBlurRadius: 4, + saoBlurStdDev: 4, + saoBlurDepthCutoff: 0.0007 + }, + saoScaleOffset: 0, + saoNormalsRendering: NormalsType.ACCURATE +} + +export class Pipeline { + private _renderer: WebGLRenderer = null + private _batcher: Batcher = null + private _pipelineOptions: PipelineOptions = {} + private composer: EffectComposer = null + private renderPass: RenderPass = null + private saoPass: SpeckleSAOPass = null + private applySaoPass: ApplySAOPass = null + private drawingSize: Vector2 = new Vector2() + + public set pipelineOptions(options: PipelineOptions) { + Object.assign(this._pipelineOptions, options) + if (this.saoPass) { + this.applySaoPass.enabled = this._pipelineOptions.saoEnabled + Object.assign(this.saoPass.params, this._pipelineOptions.saoParams) + this.saoPass.params.saoScale += this._pipelineOptions.saoScaleOffset + this.saoPass.normalsRendering = this._pipelineOptions.saoNormalsRendering + } + } + + public constructor(renderer: WebGLRenderer, batcher: Batcher) { + this._renderer = renderer + this._batcher = batcher + this.composer = new EffectComposer(renderer) + this.composer.readBuffer = null + this.composer.writeBuffer = null + } + + public configure(scene: Scene, camera: Camera) { + this.saoPass = new SpeckleSAOPass( + scene, + camera, + this._batcher, + false, + NormalsType.IMPROVED + ) + this.composer.addPass(this.saoPass) + this.renderPass = new RenderPass(scene, camera) + this.renderPass.renderToScreen = true + this.composer.addPass(this.renderPass) + this.applySaoPass = new ApplySAOPass(this.saoPass.saoRenderTarget.texture) + this.applySaoPass.renderToScreen = true + this.composer.addPass(this.applySaoPass) + } + + public updateClippingPlanes(planes: Plane[]) { + this.saoPass.depthMaterial.clippingPlanes = planes + this.saoPass.normalMaterial.clippingPlanes = planes + } + + public render(scene: Scene, camera: Camera) { + this._renderer.getDrawingBufferSize(this.drawingSize) + if (this.drawingSize.length() === 0) return + + this.renderPass.scene = scene + this.renderPass.camera = camera + this.saoPass.scene = scene + this.saoPass.camera = camera + this.composer.render() + } + + public resize(width: number, height: number) { + this.composer.setSize(width, height) + } +} diff --git a/packages/viewer/src/modules/pipeline/SpeckleSAOPass.ts b/packages/viewer/src/modules/pipeline/SpeckleSAOPass.ts new file mode 100644 index 000000000..94866ecc0 --- /dev/null +++ b/packages/viewer/src/modules/pipeline/SpeckleSAOPass.ts @@ -0,0 +1,306 @@ +import { + Camera, + DoubleSide, + NoBlending, + OrthographicCamera, + PerspectiveCamera, + RGBADepthPacking, + Scene, + ShaderMaterial, + UniformsUtils, + Vector2 +} from 'three' +import { FullScreenQuad } from 'three/examples/jsm/postprocessing/Pass' +import { SAOPass } from 'three/examples/jsm/postprocessing/SAOPass.js' +import { BlurShaderUtils } from 'three/examples/jsm/shaders/DepthLimitedBlurShader.js' +import { speckleSaoFrag } from '../materials/shaders/speckle-sao-frag' +import { speckleSaoVert } from '../materials/shaders/speckle-sao-vert' +import { SAOShader } from 'three/examples/jsm/shaders/SAOShader.js' +import Batcher from '../batching/Batcher' +import SpeckleDepthMaterial from '../materials/SpeckleDepthMaterial' +import SpeckleNormalMaterial from '../materials/SpeckleNormalMaterial' + +export enum NormalsType { + DEFAULT = 0, + IMPROVED = 1, + ACCURATE = 2 +} + +/** + * SAO implementation inspired from bhouston previous SAO work + */ + +export class SpeckleSAOPass extends SAOPass { + private _oldClearColor + private prevStdDev + private prevNumSamples + private batcher: Batcher = null + private normalsType: NormalsType = NormalsType.IMPROVED + + public set normalsRendering(type: NormalsType) { + this.normalsType = type + this.saoMaterial.defines['NORMAL_TEXTURE'] = + this.normalsType === NormalsType.DEFAULT ? 1 : 0 + this.saoMaterial.defines['IMPROVED_NORMAL_RECONSTRUCTION'] = + this.normalsType === NormalsType.IMPROVED ? 1 : 0 + this.saoMaterial.defines['ACCURATE_NORMAL_RECONSTRUCTION'] = + this.normalsType === NormalsType.ACCURATE ? 1 : 0 + this.saoMaterial.needsUpdate = true + } + + constructor( + scene: Scene, + camera: Camera, + batcher: Batcher, + useDepthTexture = false, + normalsType: NormalsType, + resolution = new Vector2(256, 256) + ) { + super(scene, camera, useDepthTexture, true, resolution) + + this.batcher = batcher + + /** 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.depthRenderTarget.depthBuffer = true + this.depthRenderTarget.stencilBuffer = true + this.normalRenderTarget.depthBuffer = true + this.normalRenderTarget.stencilBuffer = true + + this.depthMaterial = new SpeckleDepthMaterial( + { + depthPacking: RGBADepthPacking + }, + ['USE_RTE', 'ALPHATEST_REJECTION'] + ) + this.depthMaterial.blending = NoBlending + this.depthMaterial.side = DoubleSide + + this.normalMaterial = new SpeckleNormalMaterial({}, ['USE_RTE']) + this.normalMaterial.blending = NoBlending + this.normalMaterial.side = DoubleSide + + this.saoMaterial = new ShaderMaterial({ + defines: { + NUM_SAMPLES: 7, + NUM_RINGS: 4, + NORMAL_TEXTURE: 0, + DIFFUSE_TEXTURE: 0, + DEPTH_PACKING: 1, + PERSPECTIVE_CAMERA: 1 + }, + fragmentShader: speckleSaoFrag, + vertexShader: speckleSaoVert, + uniforms: UniformsUtils.clone(SAOShader.uniforms) + }) + this.normalsRendering = normalsType + this.saoMaterial.extensions.derivatives = true + this.saoMaterial.defines['DEPTH_PACKING'] = this.supportsDepthTextureExtension + ? 0 + : 1 + this.saoMaterial.defines['PERSPECTIVE_CAMERA'] = (this.camera as PerspectiveCamera) + .isPerspectiveCamera + ? 1 + : 0 + this.saoMaterial.uniforms['tDepth'].value = this.supportsDepthTextureExtension + ? this.beautyRenderTarget.depthTexture + : this.depthRenderTarget.texture + this.saoMaterial.uniforms['tNormal'].value = this.normalRenderTarget.texture + this.saoMaterial.uniforms['size'].value.set(this.resolution.x, this.resolution.y) + this.saoMaterial.uniforms['cameraInverseProjectionMatrix'].value.copy( + this.camera.projectionMatrixInverse + ) + this.saoMaterial.uniforms['cameraProjectionMatrix'].value = + this.camera.projectionMatrix + this.saoMaterial.blending = NoBlending + } + + public render(renderer, writeBuffer, readBuffer) { + writeBuffer + readBuffer + if (this.params.output === 1) { + return + } + + renderer.getClearColor(this._oldClearColor) + this.oldClearAlpha = renderer.getClearAlpha() + renderer.autoClear = false + + renderer.setRenderTarget(this.depthRenderTarget) + renderer.clear() + + this.saoMaterial.uniforms['bias'].value = this.params.saoBias + this.saoMaterial.uniforms['intensity'].value = this.params.saoIntensity + this.saoMaterial.uniforms['scale'].value = this.params.saoScale + this.saoMaterial.uniforms['kernelRadius'].value = this.params.saoKernelRadius + this.saoMaterial.uniforms['minResolution'].value = this.params.saoMinResolution + this.saoMaterial.uniforms['cameraNear'].value = ( + this.camera as PerspectiveCamera | OrthographicCamera + ).near + this.saoMaterial.uniforms['cameraFar'].value = ( + this.camera as PerspectiveCamera | OrthographicCamera + ).far + this.saoMaterial.uniforms['cameraInverseProjectionMatrix'].value.copy( + this.camera.projectionMatrixInverse + ) + this.saoMaterial.uniforms['cameraProjectionMatrix'].value = + this.camera.projectionMatrix + // this.saoMaterial.uniforms['randomSeed'].value = Math.random(); + + const depthCutoff = + this.params.saoBlurDepthCutoff * + ((this.camera as PerspectiveCamera | OrthographicCamera).far - + (this.camera as PerspectiveCamera | OrthographicCamera).near) + this.vBlurMaterial.uniforms['depthCutoff'].value = depthCutoff + this.hBlurMaterial.uniforms['depthCutoff'].value = depthCutoff + + this.vBlurMaterial.uniforms['cameraNear'].value = ( + this.camera as PerspectiveCamera | OrthographicCamera + ).near + this.vBlurMaterial.uniforms['cameraFar'].value = ( + this.camera as PerspectiveCamera | OrthographicCamera + ).far + this.hBlurMaterial.uniforms['cameraNear'].value = ( + this.camera as PerspectiveCamera | OrthographicCamera + ).near + this.hBlurMaterial.uniforms['cameraFar'].value = ( + this.camera as PerspectiveCamera | OrthographicCamera + ).far + + this.params.saoBlurRadius = Math.floor(this.params.saoBlurRadius) + if ( + this.prevStdDev !== this.params.saoBlurStdDev || + this.prevNumSamples !== this.params.saoBlurRadius + ) { + BlurShaderUtils.configure( + this.vBlurMaterial, + this.params.saoBlurRadius, + this.params.saoBlurStdDev, + new Vector2(0, 1) + ) + BlurShaderUtils.configure( + this.hBlurMaterial, + this.params.saoBlurRadius, + this.params.saoBlurStdDev, + new Vector2(1, 0) + ) + this.prevStdDev = this.params.saoBlurStdDev + this.prevNumSamples = this.params.saoBlurRadius + } + + const restoreVisibility = this.batcher.saveVisiblity() + const opaque = this.batcher.getOpaque() + this.batcher.applyVisibility(opaque) + // Re-render scene if depth texture extension is not supported + if (!this.supportsDepthTextureExtension) { + // Clear rule : far clipping plane in both RGBA and Basic encoding + this.renderOverride( + renderer, + this.depthMaterial, + this.depthRenderTarget, + 0x000000, + 1.0 + ) + } + + if (this.normalsType === NormalsType.DEFAULT) { + if (this.supportsNormalTexture) { + // Clear rule : default normal is facing the camera + this.renderOverride( + renderer, + this.normalMaterial, + this.normalRenderTarget, + 0x7777ff, + 1.0 + ) + } + } + this.batcher.applyVisibility(restoreVisibility) + + // Rendering SAO texture + this.renderPass(renderer, this.saoMaterial, this.saoRenderTarget, 0xffffff, 1.0) + + // Blurring SAO texture + if (this.params.saoBlur) { + this.renderPass( + renderer, + this.vBlurMaterial, + this.blurIntermediateRenderTarget, + 0xffffff, + 1.0 + ) + this.renderPass(renderer, this.hBlurMaterial, this.saoRenderTarget, 0xffffff, 1.0) + } + } + + public renderPass( + renderer, + passMaterial, + renderTarget, + clearColor = undefined, + clearAlpha = undefined + ) { + // save original state + renderer.getClearColor(this.originalClearColor) + const originalClearAlpha = renderer.getClearAlpha() + const originalAutoClear = renderer.autoClear + + renderer.setRenderTarget(renderTarget) + + // setup pass state + renderer.autoClear = false + if (clearColor !== undefined && clearColor !== null) { + renderer.setClearColor(clearColor) + renderer.setClearAlpha(clearAlpha || 0.0) + renderer.clear() + } + + ;(this.fsQuad as FullScreenQuad).material = passMaterial + ;(this.fsQuad as FullScreenQuad).render(renderer) + + // restore original state + renderer.autoClear = originalAutoClear + renderer.setClearColor(this.originalClearColor) + renderer.setClearAlpha(originalClearAlpha) + } + + public renderOverride( + renderer, + overrideMaterial, + renderTarget, + clearColor, + clearAlpha + ) { + renderer.getClearColor(this.originalClearColor) + const originalClearAlpha = renderer.getClearAlpha() + const originalAutoClear = renderer.autoClear + + renderer.setRenderTarget(renderTarget) + renderer.autoClear = false + + clearColor = overrideMaterial.clearColor || clearColor + clearAlpha = overrideMaterial.clearAlpha || clearAlpha + if (clearColor !== undefined && clearColor !== null) { + renderer.setClearColor(clearColor) + renderer.setClearAlpha(clearAlpha || 0.0) + renderer.clear() + } + + const shadowmapEnabled = renderer.shadowMap.enabled + const shadowmapNeedsUpdate = renderer.shadowMap.needsUpdate + this.scene.overrideMaterial = overrideMaterial + 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.originalClearColor) + renderer.setClearAlpha(originalClearAlpha) + } +}