import { ClearPass, DepthCopyPass, DepthMaskMaterial, DepthTestStrategy, Pass, RenderPass, Selection, ShaderPass } from 'postprocessing' import { BasicDepthPacking, Color, DepthTexture, LessEqualDepth, MeshBasicMaterial, RedFormat, RGBADepthPacking, Uniform, UnsignedIntType, WebGLRenderTarget, type Camera, type DepthPackingStrategies, type Scene, type Texture, type TextureDataType, type WebGLRenderer } from 'three' import { resolveIncludes } from '@takram/three-geospatial' import { depth } from '@takram/three-geospatial/shaders' import fragmentShader from './shaders/lightingMask.frag?raw' export class LightingMaskPass extends Pass { private readonly renderPass: RenderPass private readonly depthTexture: DepthTexture private readonly renderTarget: WebGLRenderTarget private readonly depthCopyPass0: DepthCopyPass private readonly depthCopyPass1: DepthCopyPass private readonly clearPass: ClearPass private readonly depthMaskMaterial: DepthMaskMaterial private readonly depthMaskPass: ShaderPass readonly selection = new Selection() constructor(scene: Scene, camera: Camera) { super('LightingMaskPass') this.needsSwap = false this.needsDepthTexture = true this.renderPass = new RenderPass(scene, camera, new MeshBasicMaterial()) this.renderPass.ignoreBackground = true this.renderPass.skipShadowMapUpdate = true this.renderPass.selection = this.selection // We need a separate depth buffer. See the discussion below. this.depthTexture = new DepthTexture(1, 1, UnsignedIntType) this.renderTarget = new WebGLRenderTarget(1, 1, { format: RedFormat, depthTexture: this.depthTexture }) this.depthCopyPass0 = new DepthCopyPass({ depthPacking: RGBADepthPacking }) this.depthCopyPass1 = new DepthCopyPass({ depthPacking: RGBADepthPacking }) this.clearPass = new ClearPass(true, false, false) this.clearPass.overrideClearColor = new Color(0xffffff) this.clearPass.overrideClearAlpha = 1 const depthMaskMaterial = new DepthMaskMaterial() depthMaskMaterial.fragmentShader = resolveIncludes(fragmentShader, { core: { depth } }) depthMaskMaterial.uniforms.inverted = new Uniform(false) depthMaskMaterial.copyCameraSettings(camera) depthMaskMaterial.depthBuffer0 = this.depthCopyPass0.texture depthMaskMaterial.depthPacking0 = RGBADepthPacking depthMaskMaterial.depthBuffer1 = this.depthCopyPass1.texture depthMaskMaterial.depthPacking1 = RGBADepthPacking depthMaskMaterial.depthMode = LessEqualDepth depthMaskMaterial.maxDepthStrategy = DepthTestStrategy.DISCARD_MAX_DEPTH this.depthMaskMaterial = depthMaskMaterial this.depthMaskPass = new ShaderPass(depthMaskMaterial) } // eslint-disable-next-line accessor-pairs override set mainScene(value: Scene) { this.renderPass.mainScene = value } // eslint-disable-next-line accessor-pairs override set mainCamera(value: Camera) { this.renderPass.mainCamera = value this.depthMaskMaterial.copyCameraSettings(value) } override initialize( renderer: WebGLRenderer, alpha: boolean, frameBufferType: TextureDataType ): void { this.renderPass.initialize(renderer, alpha, frameBufferType) this.clearPass.initialize(renderer, alpha, frameBufferType) this.depthMaskPass.initialize(renderer, alpha, frameBufferType) } override setDepthTexture( depthTexture: Texture, depthPacking: DepthPackingStrategies = BasicDepthPacking ): void { this.depthCopyPass0.setDepthTexture(depthTexture, depthPacking) this.depthCopyPass1.setDepthTexture(this.depthTexture, depthPacking) } override render( renderer: WebGLRenderer, inputBuffer: WebGLRenderTarget | null, outputBuffer: WebGLRenderTarget | null, deltaTime?: number, stencilTest?: boolean ): void { const autoClear = renderer.autoClear renderer.autoClear = false // We cannot precisely compare the depth buffer and a texture rendered with // MeshDepthMaterial. Store the current depth and the selection depth with // the same packing and create a mask. this.depthCopyPass0.render(renderer, null, null) this.renderPass.render(renderer, this.renderTarget, null) this.depthCopyPass1.render(renderer, null, null) this.clearPass.render(renderer, this.renderTarget, null) this.depthMaskPass.render(renderer, null, this.renderTarget) renderer.autoClear = autoClear } override setSize(width: number, height: number): void { this.renderTarget.setSize(width, height) this.depthCopyPass0.setSize(width, height) this.depthCopyPass1.setSize(width, height) } get texture(): Texture { return this.renderTarget.texture } get selectionLayer(): number { return this.selection.layer } set selectionLayer(value: number) { this.selection.layer = value } get inverted(): boolean { return this.depthMaskMaterial.uniforms.inverted.value } set inverted(value: boolean) { this.depthMaskMaterial.uniforms.inverted.value = value } }