diff --git a/packages/viewer/src/modules/SpeckleRenderer.ts b/packages/viewer/src/modules/SpeckleRenderer.ts index 46248616b..0eb0fe261 100644 --- a/packages/viewer/src/modules/SpeckleRenderer.ts +++ b/packages/viewer/src/modules/SpeckleRenderer.ts @@ -189,7 +189,8 @@ export default class SpeckleRenderer { this._renderer = new WebGLRenderer({ antialias: true, alpha: true, - preserveDrawingBuffer: true + preserveDrawingBuffer: true, + stencil: true }) this._renderer.setClearColor(0xffffff, 0) this._renderer.setPixelRatio(window.devicePixelRatio) diff --git a/packages/viewer/src/modules/batching/Batcher.ts b/packages/viewer/src/modules/batching/Batcher.ts index 24dd650cb..c5466ca16 100644 --- a/packages/viewer/src/modules/batching/Batcher.ts +++ b/packages/viewer/src/modules/batching/Batcher.ts @@ -159,6 +159,33 @@ export default class Batcher { return visibilityRanges } + public getStencil(): 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).stencilWrite === true) + visibilityRanges[k] = AllBatchUpdateRange + } else { + const stencilGroup = batchMesh.geometry.groups.find((value) => { + return batchMesh.material[value.materialIndex].stencilWrite === true + }) + if (stencilGroup) { + visibilityRanges[k] = { + offset: stencilGroup.start, + count: stencilGroup.count + } + } + } + } + return visibilityRanges + } + public getOpaque() { const visibilityRanges = {} for (const k in this.batches) { @@ -190,27 +217,6 @@ export default class Batcher { 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) { diff --git a/packages/viewer/src/modules/materials/Materials.ts b/packages/viewer/src/modules/materials/Materials.ts index 4edca8fed..8db7ca00b 100644 --- a/packages/viewer/src/modules/materials/Materials.ts +++ b/packages/viewer/src/modules/materials/Materials.ts @@ -1,9 +1,11 @@ import { + AlwaysStencilFunc, Color, DoubleSide, FrontSide, Material, MathUtils, + ReplaceStencilOp, Texture, Vector2 } from 'three' @@ -19,7 +21,6 @@ import defaultGradient from '../../assets/gradient.png' import { Assets } from '../Assets' import { getConversionFactor } from '../converter/Units' import SpeckleGhostMaterial from './SpeckleGhostMaterial' -import SpeckleBasicMaterial from './SpeckleBasicMaterial' export interface MaterialOptions { rampIndex?: number @@ -98,32 +99,25 @@ export default class Materials { } private async createDefaultMeshMaterials() { - // this.meshHighlightMaterial = new SpeckleStandardMaterial( - // { - // color: 0x047efb, - // emissive: 0x0, - // roughness: 1, - // metalness: 0, - // side: DoubleSide - // }, - // ['USE_RTE'] - // ) - // this.meshHighlightMaterial.clipShadows = true - this.meshHighlightMaterial = new SpeckleBasicMaterial( + this.meshHighlightMaterial = new SpeckleStandardMaterial( { color: 0x047efb, + emissive: 0x0, + roughness: 1, + metalness: 0, side: DoubleSide }, ['USE_RTE'] ) this.meshHighlightMaterial.clipShadows = true - // this.meshHighlightMaterial.depthWrite = false - // this.meshHighlightMaterial.depthTest = false - // this.meshHighlightMaterial.transparent = true - // this.meshHighlightMaterial.blendSrc = OneFactor - // this.meshHighlightMaterial.blendDst = ZeroFactor - // this.meshHighlightMaterial.blendSrcAlpha = OneFactor - // this.meshHighlightMaterial.blendDstAlpha = ZeroFactor + this.meshHighlightMaterial.stencilWrite = true + this.meshHighlightMaterial.stencilWriteMask = 0xff + this.meshHighlightMaterial.stencilRef = 0x00 + this.meshHighlightMaterial.stencilFunc = AlwaysStencilFunc + this.meshHighlightMaterial.stencilZFail = ReplaceStencilOp + this.meshHighlightMaterial.stencilZPass = ReplaceStencilOp + this.meshHighlightMaterial.stencilFail = ReplaceStencilOp + this.meshTransparentHighlightMaterial = new SpeckleStandardMaterial( { color: 0x047efb, diff --git a/packages/viewer/src/modules/materials/SpeckleDisplaceMaterial.ts b/packages/viewer/src/modules/materials/SpeckleDisplaceMaterial.ts new file mode 100644 index 000000000..544df605b --- /dev/null +++ b/packages/viewer/src/modules/materials/SpeckleDisplaceMaterial.ts @@ -0,0 +1,63 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable camelcase */ +import { speckleDisplaceVert } from './shaders/speckle-displace.vert' +import { speckleDisplaceFrag } from './shaders/speckle-displace-frag' +import { UniformsUtils, ShaderLib, Vector3, Vector2 } from 'three' +import SpeckleBasicMaterial from './SpeckleBasicMaterial' + +class SpeckleDisplaceMaterial extends SpeckleBasicMaterial { + constructor(parameters, defines = []) { + super(parameters) + + this.userData.uViewer_high = { + value: new Vector3() + } + this.userData.uViewer_low = { + value: new Vector3() + } + this.userData.size = { + value: new Vector2() + } + this.userData.displacement = { + value: 0 + } + ;(this as any).vertProgram = speckleDisplaceVert + ;(this as any).fragProgram = speckleDisplaceFrag + ;(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 + }, + size: { + value: this.userData.size.value + }, + displacement: { + value: this.userData.displacement.value + } + } + ]) + + this.onBeforeCompile = function (shader) { + shader.uniforms.uViewer_high = this.userData.uViewer_high + shader.uniforms.uViewer_low = this.userData.uViewer_low + shader.uniforms.size = this.userData.size + shader.uniforms.displacement = this.userData.displacement + shader.vertexShader = this.vertProgram + shader.fragmentShader = this.fragProgram + } + + if (defines) { + this.defines = {} + } + for (let k = 0; k < defines.length; k++) { + this.defines[defines[k]] = ' ' + } + } +} + +export default SpeckleDisplaceMaterial diff --git a/packages/viewer/src/modules/materials/shaders/speckle-basic-vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-basic-vert.ts index f2952cb99..121370bf9 100644 --- a/packages/viewer/src/modules/materials/shaders/speckle-basic-vert.ts +++ b/packages/viewer/src/modules/materials/shaders/speckle-basic-vert.ts @@ -94,13 +94,6 @@ void main() { mvPosition = modelViewMatrix * mvPosition; gl_Position = projectionMatrix * mvPosition; - - // Transform normal vector from object space to clip space. - vec3 normalHCS = mat3(projectionMatrix) * normalMatrix * normal; - - // Move vertex along normal vector in clip space. - float width = 5.; - gl_Position.xy += normalize(normalHCS.xy) / vec2(2034., 1896.) * gl_Position.w * width * 2.; #include #include #include diff --git a/packages/viewer/src/modules/materials/shaders/speckle-displace-frag.ts b/packages/viewer/src/modules/materials/shaders/speckle-displace-frag.ts new file mode 100644 index 000000000..e0c7a426d --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-displace-frag.ts @@ -0,0 +1,53 @@ +export const speckleDisplaceFrag = /* glsl */ ` +uniform vec3 diffuse; +uniform float opacity; +#ifndef FLAT_SHADED + varying vec3 vNormal; +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +void main() { + #include + vec4 diffuseColor = vec4( diffuse, opacity ); + #include + #include + #include + #include + #include + #include + ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) ); + // accumulation (baked indirect lighting only) + #ifdef USE_LIGHTMAP + vec4 lightMapTexel = texture2D( lightMap, vUv2 ); + reflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI; + #else + reflectedLight.indirectDiffuse += vec3( 1.0 ); + #endif + // modulation + #include + reflectedLight.indirectDiffuse *= diffuseColor.rgb; + vec3 outgoingLight = reflectedLight.indirectDiffuse; + #include + #include + #include + #include + #include + #include + #include +} +` diff --git a/packages/viewer/src/modules/materials/shaders/speckle-displace.vert.ts b/packages/viewer/src/modules/materials/shaders/speckle-displace.vert.ts new file mode 100644 index 000000000..eeaea675b --- /dev/null +++ b/packages/viewer/src/modules/materials/shaders/speckle-displace.vert.ts @@ -0,0 +1,111 @@ +export const speckleDisplaceVert = /* glsl */ ` +#include +#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 +uniform vec2 size; +uniform float displacement; +#include +#include +#include +#include +#include +#include +#include +#include +#include + +vec4 computeRelativePositionSeparate(in vec3 position_low, in vec3 position_high, in vec3 relativeTo_low, in vec3 relativeTo_high){ + /* + Vector calculation for the high and low differences works on everything + *BESIDES* Apple Silicon (or whatever they call it) GPUs + + It would seem that when this code gets compiled, vector types get a lower precision(?) + which completely brakes the 2 float -> double reconstructio. Doing it separately for each + vector component using floats works fine. + */ + vec3 highDifference; + vec3 lowDifference; + float t1 = position_low.x - relativeTo_low.x; + float e = t1 - position_low.x; + float t2 = ((-relativeTo_low.x - e) + (position_low.x - (t1 - e))) + position_high.x - relativeTo_high.x; + highDifference.x = t1 + t2; + lowDifference.x = t2 - (highDifference.x - t1); + + t1 = position_low.y - relativeTo_low.y; + e = t1 - position_low.y; + t2 = ((-relativeTo_low.y - e) + (position_low.y - (t1 - e))) + position_high.y - relativeTo_high.y; + highDifference.y = t1 + t2; + lowDifference.y = t2 - (highDifference.y - t1); + + t1 = position_low.z - relativeTo_low.z; + e = t1 - position_low.z; + t2 = ((-relativeTo_low.z - e) + (position_low.z - (t1 - e))) + position_high.z - relativeTo_high.z; + highDifference.z = t1 + t2; + lowDifference.z = t2 - (highDifference.z - t1); + + vec3 position = highDifference.xyz + lowDifference.xyz; + return vec4(position, 1.); +} + +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 + #if defined ( USE_ENVMAP ) || defined ( USE_SKINNING ) + #include + #include + #include + #include + #include + #endif + #include + #include + #include + // #include COMMENTED CHUNK + #ifdef USE_RTE + vec4 mvPosition = computeRelativePositionSeparate(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; + + // Transform normal vector from object space to clip space. + vec3 normalHCS = mat3(projectionMatrix) * normalMatrix * normal; + + // Move vertex along normal vector in clip space. + gl_Position.xy += normalize(normalHCS.xy) / size * gl_Position.w * displacement * 2.; + #include + #include + #include + #include + #include +} +` diff --git a/packages/viewer/src/modules/pipeline/Pipeline.ts b/packages/viewer/src/modules/pipeline/Pipeline.ts index 72eda86fb..ca75be921 100644 --- a/packages/viewer/src/modules/pipeline/Pipeline.ts +++ b/packages/viewer/src/modules/pipeline/Pipeline.ts @@ -23,6 +23,8 @@ import { } from './StaticAOPass' import { SpecklePass } from './SpecklePass' import { ColorPass } from './ColorPass' +import { StencilPass } from './StencilPass' +import { StencilMaskPass } from './StencilMaskPass' export enum RenderType { NORMAL, @@ -71,7 +73,9 @@ export class Pipeline { private depthPass: DepthPass = null private normalsPass: NormalsPass = null + private stencilPass: StencilPass = null private renderPass: ColorPass = null + private stencilMaskPass: StencilMaskPass = null private dynamicAoPass: DynamicSAOPass = null private applySaoPass: ApplySAOPass = null private copyOutputPass: CopyOutputPass = null @@ -224,7 +228,9 @@ export class Pipeline { this.depthPass = new DepthPass() this.normalsPass = new NormalsPass() this.dynamicAoPass = new DynamicSAOPass() + this.stencilPass = new StencilPass() this.renderPass = new ColorPass() + this.stencilMaskPass = new StencilMaskPass() this.applySaoPass = new ApplySAOPass() this.staticAoPass = new StaticAOPass() @@ -233,11 +239,13 @@ export class Pipeline { this.depthPass.setLayers([ObjectLayers.STREAM_CONTENT]) this.normalsPass.setLayers([ObjectLayers.STREAM_CONTENT]) + this.stencilPass.setLayers([ObjectLayers.STREAM_CONTENT]) this.renderPass.setLayers([ ObjectLayers.PROPS, ObjectLayers.STREAM_CONTENT, ObjectLayers.SHADOWCATCHER ]) + this.stencilMaskPass.setLayers([ObjectLayers.STREAM_CONTENT]) let restoreVisibility this.depthPass.onBeforeRender = () => { @@ -258,6 +266,24 @@ export class Pipeline { this._batcher.applyVisibility(restoreVisibility) } + this.stencilPass.onBeforeRender = () => { + restoreVisibility = this._batcher.saveVisiblity() + const stencil = this._batcher.getStencil() + this._batcher.applyVisibility(stencil) + } + this.stencilPass.onAfterRender = () => { + this._batcher.applyVisibility(restoreVisibility) + } + + this.stencilMaskPass.onBeforeRender = () => { + restoreVisibility = this._batcher.saveVisiblity() + const stencil = this._batcher.getStencil() + this._batcher.applyVisibility(stencil) + } + this.stencilMaskPass.onAfterRender = () => { + this._batcher.applyVisibility(restoreVisibility) + } + this.setPipeline(this.getDefaultPipeline()) } @@ -287,7 +313,9 @@ export class Pipeline { pipeline.push(this.normalsPass) pipeline.push(this.dynamicAoPass) pipeline.push(this.staticAoPass) + pipeline.push(this.stencilPass) pipeline.push(this.renderPass) + pipeline.push(this.stencilMaskPass) pipeline.push(this.applySaoPass) this.needsProgressive = true @@ -316,7 +344,9 @@ export class Pipeline { } public update(renderer: SpeckleRenderer) { + this.stencilPass.update(renderer.scene, renderer.camera) this.renderPass.update(renderer.scene, renderer.camera) + this.stencilMaskPass.update(renderer.scene, renderer.camera) this.depthPass.update(renderer.scene, renderer.camera) this.dynamicAoPass.update(renderer.scene, renderer.camera) this.normalsPass.update(renderer.scene, renderer.camera) diff --git a/packages/viewer/src/modules/pipeline/StencilMaskPass.ts b/packages/viewer/src/modules/pipeline/StencilMaskPass.ts new file mode 100644 index 000000000..92c282885 --- /dev/null +++ b/packages/viewer/src/modules/pipeline/StencilMaskPass.ts @@ -0,0 +1,101 @@ +import { + Camera, + Color, + DoubleSide, + EqualStencilFunc, + Material, + Scene, + Texture, + Vector2 +} from 'three' +import SpeckleDisplaceMaterial from '../materials/SpeckleDisplaceMaterial' +import { BaseSpecklePass, SpecklePass } from './SpecklePass' + +export class StencilMaskPass extends BaseSpecklePass implements SpecklePass { + private camera: Camera + private scene: Scene + private overrideMaterial: Material = null + private _oldClearColor: Color = new Color() + private clearColor: Color = null + private clearAlpha = 0 + private clearDepth = true + private drawBufferSize: Vector2 = new Vector2() + + public onBeforeRender: () => void = null + public onAfterRender: () => void = null + + public constructor() { + super() + this.overrideMaterial = new SpeckleDisplaceMaterial({ color: 0x04a5fb }, [ + 'USE_RTE' + ]) + this.overrideMaterial.userData.displacement.value = 2 + this.overrideMaterial.colorWrite = true + this.overrideMaterial.depthWrite = false + this.overrideMaterial.stencilWrite = true + this.overrideMaterial.stencilFunc = EqualStencilFunc + this.overrideMaterial.stencilRef = 0xff + this.overrideMaterial.side = DoubleSide + } + public get displayName(): string { + return 'STENCIL' + } + public get outputTexture(): Texture { + return null + } + + public update(scene: Scene, camera: Camera) { + this.camera = camera + this.scene = scene + } + + render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */) { + if (this.onBeforeRender) this.onBeforeRender() + const oldAutoClear = renderer.autoClear + renderer.autoClear = false + + let oldClearAlpha, oldOverrideMaterial + + if (this.overrideMaterial !== undefined) { + oldOverrideMaterial = this.scene.overrideMaterial + + this.scene.overrideMaterial = this.overrideMaterial + } + + if (this.clearColor) { + renderer.getClearColor(this._oldClearColor) + oldClearAlpha = renderer.getClearAlpha() + + renderer.setClearColor(this.clearColor, this.clearAlpha) + } + + if (this.clearDepth) { + renderer.clearDepth() + } + + this.applyLayers(this.camera) + + renderer.setRenderTarget(this.renderToScreen ? null : readBuffer) + + // TODO: Avoid using autoClear properties, see https://github.com/mrdoob/three.js/pull/15571#issuecomment-465669600 + if (this.clear) + renderer.clear( + renderer.autoClearColor, + renderer.autoClearDepth, + renderer.autoClearStencil + ) + renderer.getDrawingBufferSize(this.drawBufferSize) + this.overrideMaterial.userData.size.value.copy(this.drawBufferSize) + renderer.render(this.scene, this.camera) + + if (this.clearColor) { + renderer.setClearColor(this._oldClearColor, oldClearAlpha) + } + + if (this.overrideMaterial !== undefined) { + this.scene.overrideMaterial = oldOverrideMaterial + } + renderer.autoClear = oldAutoClear + if (this.onAfterRender) this.onAfterRender() + } +} diff --git a/packages/viewer/src/modules/pipeline/StencilPass.ts b/packages/viewer/src/modules/pipeline/StencilPass.ts index 91fa47b0b..003db9eca 100644 --- a/packages/viewer/src/modules/pipeline/StencilPass.ts +++ b/packages/viewer/src/modules/pipeline/StencilPass.ts @@ -1,4 +1,15 @@ -import { Camera, Color, Material, Scene, Texture } from 'three' +import { + AlwaysStencilFunc, + Camera, + Color, + DoubleSide, + Material, + ReplaceStencilOp, + Scene, + Texture, + Vector2 +} from 'three' +import SpeckleDisplaceMaterial from '../materials/SpeckleDisplaceMaterial' import { BaseSpecklePass, SpecklePass } from './SpecklePass' export class StencilPass extends BaseSpecklePass implements SpecklePass { @@ -9,12 +20,27 @@ export class StencilPass extends BaseSpecklePass implements SpecklePass { private clearColor: Color = null private clearAlpha = 0 private clearDepth = true + private drawBufferSize: Vector2 = new Vector2() public onBeforeRender: () => void = null public onAfterRender: () => void = null public constructor() { super() + this.overrideMaterial = new SpeckleDisplaceMaterial({ color: 0xff0000 }, [ + 'USE_RTE' + ]) + this.overrideMaterial.userData.displacement.value = 2 + this.overrideMaterial.colorWrite = false + this.overrideMaterial.depthWrite = false + this.overrideMaterial.stencilWrite = true + this.overrideMaterial.stencilFunc = AlwaysStencilFunc + this.overrideMaterial.stencilWriteMask = 0xff + this.overrideMaterial.stencilRef = 0xff + this.overrideMaterial.stencilZFail = ReplaceStencilOp + this.overrideMaterial.stencilZPass = ReplaceStencilOp + this.overrideMaterial.stencilFail = ReplaceStencilOp + this.overrideMaterial.side = DoubleSide } public get displayName(): string { return 'STENCIL' @@ -29,7 +55,7 @@ export class StencilPass extends BaseSpecklePass implements SpecklePass { } render(renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */) { - this.onBeforeRender() + if (this.onBeforeRender) this.onBeforeRender() const oldAutoClear = renderer.autoClear renderer.autoClear = false @@ -63,6 +89,9 @@ export class StencilPass extends BaseSpecklePass implements SpecklePass { renderer.autoClearDepth, renderer.autoClearStencil ) + renderer.getDrawingBufferSize(this.drawBufferSize) + this.overrideMaterial.userData.size.value.copy(this.drawBufferSize) + renderer.clear(false, false, true) renderer.render(this.scene, this.camera) if (this.clearColor) { @@ -73,6 +102,6 @@ export class StencilPass extends BaseSpecklePass implements SpecklePass { this.scene.overrideMaterial = oldOverrideMaterial } renderer.autoClear = oldAutoClear - this.onAfterRender() + if (this.onAfterRender) this.onAfterRender() } }