@@ -96,6 +96,17 @@ export class EffectComposer {
9696
9797 this . depthTexture = null ;
9898
99+ /**
100+ * A render target that holds a stable copy of the scene depth. Scene depth is copied into here
101+ * to avoid feedback loops and undefined behavior that can happen when the same depth attachment is
102+ * used on both the input and output buffers.
103+ *
104+ * @type {WebGLRenderTarget }
105+ * @private
106+ */
107+
108+ this . _stableDepthTarget = null ;
109+
99110 /**
100111 * The passes.
101112 *
@@ -278,13 +289,14 @@ export class EffectComposer {
278289 /**
279290 * Creates a depth texture attachment that will be provided to all passes.
280291 *
281- * Note: When a shader reads from a depth texture and writes to a render target that uses the same depth texture
282- * attachment, the depth information will be lost. This happens even if `depthWrite` is disabled.
292+ * To prevent errors or incorrect behavior when the same depth buffer is attached to the input and output buffers,
293+ * a separate stable depth target is created alongside the ping-pong buffers. All passes receive the stable target's
294+ * depth texture, which is never used as a render output and therefore cannot create a feedback loop. The stable
295+ * texture is populated each frame via blitFramebuffer immediately before the first buffer swap.
283296 *
284297 * @private
285- * @return {DepthTexture } The depth texture.
298+ * @return {DepthTexture } The stable depth texture distributed to passes .
286299 */
287-
288300 createDepthTexture ( ) {
289301
290302 const depthTexture = this . depthTexture = new DepthTexture ( ) ;
@@ -304,7 +316,56 @@ export class EffectComposer {
304316
305317 }
306318
307- return depthTexture ;
319+ const stableDepthTexture = new DepthTexture ( ) ;
320+ stableDepthTexture . format = depthTexture . format ;
321+ stableDepthTexture . type = depthTexture . type ;
322+ stableDepthTexture . name = "EffectComposer.StableDepth" ;
323+
324+ this . _stableDepthTarget = new WebGLRenderTarget ( this . inputBuffer . width , this . inputBuffer . height , {
325+ depthBuffer : true ,
326+ stencilBuffer : this . inputBuffer . stencilBuffer ,
327+ depthTexture : stableDepthTexture
328+ } ) ;
329+
330+ return stableDepthTexture ;
331+
332+ }
333+
334+ /**
335+ * Copies the depth buffer from the src render target into the stable depth target.
336+ *
337+ * @private
338+ * @param {WebGLRenderTarget } srcTarget - The render target whose depth buffer should be copied.
339+ */
340+
341+ blitDepthBuffer ( srcTarget ) {
342+
343+ const renderer = this . renderer ;
344+ const gl = renderer . getContext ( ) ;
345+
346+ renderer . setRenderTarget ( this . _stableDepthTarget ) ;
347+
348+ const props = renderer . properties ;
349+ // eslint-disable-next-line no-underscore-dangle
350+ const srcFBO = props . get ( srcTarget ) . __webglFramebuffer ;
351+ // eslint-disable-next-line no-underscore-dangle
352+ const dstFBO = props . get ( this . _stableDepthTarget ) . __webglFramebuffer ;
353+
354+ const blitMask = srcTarget . stencilBuffer
355+ ? ( gl . DEPTH_BUFFER_BIT | gl . STENCIL_BUFFER_BIT )
356+ : gl . DEPTH_BUFFER_BIT ;
357+
358+ gl . bindFramebuffer ( gl . READ_FRAMEBUFFER , srcFBO ) ;
359+ gl . bindFramebuffer ( gl . DRAW_FRAMEBUFFER , dstFBO ) ;
360+ gl . blitFramebuffer (
361+ 0 , 0 , srcTarget . width , srcTarget . height ,
362+ 0 , 0 , this . _stableDepthTarget . width , this . _stableDepthTarget . height ,
363+ blitMask , gl . NEAREST
364+ ) ;
365+ gl . bindFramebuffer ( gl . READ_FRAMEBUFFER , null ) ;
366+ gl . bindFramebuffer ( gl . DRAW_FRAMEBUFFER , null ) ;
367+
368+ renderer . setRenderTarget ( null ) ;
308369
309370 }
310371
@@ -325,6 +386,13 @@ export class EffectComposer {
325386 this . inputBuffer . depthTexture = null ;
326387 this . inputBuffer . dispose ( ) ;
327388
389+ if ( this . _stableDepthTarget !== null ) {
390+
391+ this . _stableDepthTarget . dispose ( ) ;
392+ this . _stableDepthTarget = null ;
393+
394+ }
395+
328396 for ( const pass of this . passes ) {
329397
330398 pass . setDepthTexture ( null ) ;
@@ -478,7 +546,7 @@ export class EffectComposer {
478546
479547 } else {
480548
481- pass . setDepthTexture ( this . depthTexture ) ;
549+ pass . setDepthTexture ( this . _stableDepthTarget . depthTexture ) ;
482550
483551 }
484552
@@ -509,7 +577,11 @@ export class EffectComposer {
509577
510578 if ( ! depthTextureRequired ) {
511579
512- if ( pass . getDepthTexture ( ) === this . depthTexture ) {
580+ const composerDepth = this . _stableDepthTarget !== null
581+ ? this . _stableDepthTarget . depthTexture
582+ : this . depthTexture ;
583+
584+ if ( pass . getDepthTexture ( ) === composerDepth ) {
513585
514586 pass . setDepthTexture ( null ) ;
515587
@@ -581,6 +653,7 @@ export class EffectComposer {
581653 let outputBuffer = this . outputBuffer ;
582654
583655 let stencilTest = false ;
656+ let depthBlitted = false ;
584657 let context , stencil , buffer ;
585658
586659 if ( deltaTime === undefined ) {
@@ -594,6 +667,14 @@ export class EffectComposer {
594667
595668 if ( pass . enabled ) {
596669
670+ // Copy the depth buffer to the stable depth target just before the first pass that swaps buffers
671+ if ( ! depthBlitted && this . _stableDepthTarget !== null && pass . needsSwap ) {
672+
673+ this . blitDepthBuffer ( this . inputBuffer ) ;
674+ depthBlitted = true ;
675+
676+ }
677+
597678 pass . render ( renderer , inputBuffer , outputBuffer , deltaTime , stencilTest ) ;
598679
599680 if ( pass . needsSwap ) {
@@ -665,6 +746,12 @@ export class EffectComposer {
665746 this . inputBuffer . setSize ( drawingBufferSize . width , drawingBufferSize . height ) ;
666747 this . outputBuffer . setSize ( drawingBufferSize . width , drawingBufferSize . height ) ;
667748
749+ if ( this . _stableDepthTarget !== null ) {
750+
751+ this . _stableDepthTarget . setSize ( drawingBufferSize . width , drawingBufferSize . height ) ;
752+
753+ }
754+
668755 for ( const pass of this . passes ) {
669756
670757 pass . setSize ( drawingBufferSize . width , drawingBufferSize . height ) ;
0 commit comments