Merge pull request #1050 from specklesystems/alex/sao

Alex/sao
This commit is contained in:
Dimitrie Stefanescu
2022-10-03 16:07:32 +01:00
committed by GitHub
19 changed files with 1265 additions and 140 deletions
+131 -4
View File
@@ -16,7 +16,7 @@ export default class Sandbox {
private viewsFolder!: FolderApi
private streams: { [url: string]: Array<unknown> } = {}
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',
+12 -2
View File
@@ -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'
)
+49 -4
View File
@@ -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()
+4 -6
View File
@@ -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
}
}
@@ -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
+116 -116
View File
@@ -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<string, BatchUpdateRange> {
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<string, BatchUpdateRange>) {
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<string, BatchUpdateRange> {
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)
}
}
*/
}
@@ -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() {
@@ -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(
@@ -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
@@ -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()
@@ -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
}
@@ -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
@@ -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 <packing>
#include <uv_pars_fragment>
#include <normal_pars_fragment>
#include <bumpmap_pars_fragment>
#include <normalmap_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>
void main() {
#include <clipping_planes_fragment>
#include <logdepthbuf_fragment>
#include <normal_fragment_begin>
#include <normal_fragment_maps>
gl_FragColor = vec4( packNormalToRGB( normal ), opacity );
#ifdef OPAQUE
gl_FragColor.a = 1.0;
#endif
}
`
@@ -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 <common>
#include <uv_pars_vertex>
#include <displacementmap_pars_vertex>
#include <normal_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
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 <uv_vertex>
#include <beginnormal_vertex>
#include <morphnormal_vertex>
#include <skinbase_vertex>
#include <skinnormal_vertex>
#include <defaultnormal_vertex>
#include <normal_vertex>
#include <begin_vertex>
#include <morphtarget_vertex>
#include <skinning_vertex>
#include <displacementmap_vertex>
//#include <project_vertex> // 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 <logdepthbuf_vertex>
#include <clipping_planes_vertex>
#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )
vViewPosition = - mvPosition.xyz;
#endif
}
`
@@ -0,0 +1,216 @@
export const speckleSaoFrag = /* glsl */ `
#include <common>
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 <packing>
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;
}`
@@ -0,0 +1,6 @@
export const speckleSaoVert = /* glsl */ `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`
@@ -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)
}
}
@@ -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<SAOPassParams>
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)
}
}
@@ -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)
}
}