Transmission and Post Processing Mask

I’m working with materials using transmission and an image as a background. I’d like to mask the object so that the background is only visible through the object. This works fine as long as Transmission, IOR, and Thickness are not in use.

Here is the setup:

  • scene containing one plane with texture in glb format (background), and one sphere.
  • maskScene containing a copy of the sphere that will be used for the mask.

If the scene is static, things look fine, but moving the camera causes the background in the transmission object to smear over itself:

result
result-smeared

Is there a way to make this work or is Transmission incompatible with the Mask Pass?

Here is the setup for my material. Note that scene as an environment set.

//Clear Sphere

const params = {
    color: 0xffffff,
    transmission: 1,
    opacity: 1,
    metalness: 0,
    roughness: 0,
    ior: 1.5,
    thickness: 1.2,
    specularIntensity: 1,
    specularColor: 0xffffff,
    envMapIntensity: 1,
    lightIntensity: 1,
    exposure: 1
};

const geometry = new THREE.SphereGeometry( 0.25, 64, 32 );

const material = new THREE.MeshPhysicalMaterial( {
    color: params.color,
    metalness: params.metalness,
    roughness: params.roughness,
    ior: params.ior,
    envMapIntensity: params.envMapIntensity,
    transmission: params.transmission, // use material.transmission for glass materials
    specularIntensity: params.specularIntensity,
    specularColor: params.specularColor,
    opacity: params.opacity,
    //side: THREE.DoubleSide,
    //transparent: true
} );

Here is the post processing stack and render loop.

//POST PROCESSING

//EffectComposer
const parameters = {
    minFilter: THREE.LinearFilter,
    magFilter: THREE.LinearFilter,
    format: THREE.RGBFormat,
    stencilBuffer: true,
};
const renderTarget = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, parameters );
const composer = new EffectComposer( renderer, renderTarget );


//Post-processing stack
const clearPass = new ClearPass();
composer.addPass( clearPass );

const maskPass = new MaskPass( maskScene, camera );
maskPass.inverse = false;
composer.addPass( maskPass );

const renderPass = new RenderPass( scene, camera );
renderPass.clear = false;
composer.addPass( renderPass );

const clearMaskPass = new ClearMaskPass();
composer.addPass( clearMaskPass );

const gammaPass = new ShaderPass( GammaCorrectionShader );
//gammaPass.renderToScreen = true;
composer.addPass( gammaPass );

const outputPass = new ShaderPass( CopyShader );
composer.addPass( outputPass );
outputPass.renderToScreen = true


function animate() {
	resizeCanvasToDisplaySize()
    requestAnimationFrame( animate );
    controls.update();
    //console.log(camera.position);

    renderer.clear();
    composer.render();
}
animate();