Working always-on-top stencil outlines

This commit is contained in:
AlexandruPopovici
2023-01-25 22:10:41 +02:00
parent c2a137135b
commit e35bf79829
10 changed files with 433 additions and 52 deletions
@@ -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)
+27 -21
View File
@@ -159,6 +159,33 @@ export default class Batcher {
return visibilityRanges
}
public getStencil(): 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).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) {
@@ -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,
@@ -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
@@ -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 <logdepthbuf_vertex>
#include <clipping_planes_vertex>
#include <worldpos_vertex>
@@ -0,0 +1,53 @@
export const speckleDisplaceFrag = /* glsl */ `
uniform vec3 diffuse;
uniform float opacity;
#ifndef FLAT_SHADED
varying vec3 vNormal;
#endif
#include <common>
#include <dithering_pars_fragment>
#include <color_pars_fragment>
#include <uv_pars_fragment>
#include <uv2_pars_fragment>
#include <map_pars_fragment>
#include <alphamap_pars_fragment>
#include <alphatest_pars_fragment>
#include <aomap_pars_fragment>
#include <lightmap_pars_fragment>
#include <envmap_common_pars_fragment>
#include <envmap_pars_fragment>
#include <cube_uv_reflection_fragment>
#include <fog_pars_fragment>
#include <specularmap_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <clipping_planes_pars_fragment>
void main() {
#include <clipping_planes_fragment>
vec4 diffuseColor = vec4( diffuse, opacity );
#include <logdepthbuf_fragment>
#include <map_fragment>
#include <color_fragment>
#include <alphamap_fragment>
#include <alphatest_fragment>
#include <specularmap_fragment>
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 <aomap_fragment>
reflectedLight.indirectDiffuse *= diffuseColor.rgb;
vec3 outgoingLight = reflectedLight.indirectDiffuse;
#include <envmap_fragment>
#include <output_fragment>
#include <tonemapping_fragment>
#include <encodings_fragment>
#include <fog_fragment>
#include <premultiplied_alpha_fragment>
#include <dithering_fragment>
}
`
@@ -0,0 +1,111 @@
export const speckleDisplaceVert = /* glsl */ `
#include <common>
#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 <uv_pars_vertex>
#include <uv2_pars_vertex>
#include <envmap_pars_vertex>
#include <color_pars_vertex>
#include <fog_pars_vertex>
#include <morphtarget_pars_vertex>
#include <skinning_pars_vertex>
#include <logdepthbuf_pars_vertex>
#include <clipping_planes_pars_vertex>
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 <uv_vertex>
#include <uv2_vertex>
#include <color_vertex>
#include <morphcolor_vertex>
#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )
#include <beginnormal_vertex>
#include <morphnormal_vertex>
#include <skinbase_vertex>
#include <skinnormal_vertex>
#include <defaultnormal_vertex>
#endif
#include <begin_vertex>
#include <morphtarget_vertex>
#include <skinning_vertex>
// #include <project_vertex> 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 <logdepthbuf_vertex>
#include <clipping_planes_vertex>
#include <worldpos_vertex>
#include <envmap_vertex>
#include <fog_vertex>
}
`
@@ -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)
@@ -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()
}
}
@@ -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()
}
}