Skip to content

TSL: Unused If() branch sampling a texture breaks the texture's direct usage in output #33295

@dgreenheck

Description

@dgreenheck

Description

When a depth texture is sampled directly in the TSL output AND also sampled inside an If() branch gated by a runtime uniform, the direct output sample breaks when the If() branch is not taken (uniform = 0).

The unused If() branch should have no effect on the output — the depth texture is used unconditionally in the return value. However, the presence of the disabled branch causes the depth texture to read as a constant/incorrect value.

Removing the If() block entirely restores correct behavior.

Reproduction Steps

  1. Create a render target with a DepthTexture
  2. Render a scene to the render target
  3. In a fullscreen quad's TSL colorNode, sample the depth texture directly in the output
  4. Add an If() branch gated by a uniform(0.0) that also samples the same depth texture
  5. Observe the depth texture output is broken despite being used unconditionally
  6. Either remove the If() block or set the uniform to 1.0 — rendering is correct again

Code

import * as THREE from "three/webgpu";
import { Fn, texture, screenUV, vec4, float, uniform, If, step } from "three/tsl";

async function init() {
  const renderer = new THREE.WebGPURenderer({ antialias: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  document.body.appendChild(renderer.domElement);
  await renderer.init();

  const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 100);
  camera.position.z = 3;

  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x222222);
  scene.add(new THREE.Mesh(new THREE.SphereGeometry(), new THREE.MeshBasicMaterial()));

  const rt = new THREE.RenderTarget(window.innerWidth, window.innerHeight);
  rt.depthTexture = new THREE.DepthTexture(window.innerWidth, window.innerHeight);

  const featureEnabled = uniform(0.0);

  const colorNode = Fn(() => {
    const depth = texture(rt.depthTexture, screenUV);
    const extra = float(0.0).toVar();
    // BUG: This unused If() block breaks the depth sample in the return
    If(featureEnabled.greaterThan(0.5), () => {
      extra.assign(depth.r);
    });
    return vec4(step(float(1.0).sub(depth.r), float(0.0)), 0, 0, 1.0);
  })();

  const quadMat = new THREE.MeshBasicNodeMaterial();
  quadMat.colorNode = colorNode;

  const quadScene = new THREE.Scene();
  quadScene.add(new THREE.Mesh(new THREE.PlaneGeometry(2, 2), quadMat));
  const quadCam = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);

  renderer.setAnimationLoop(() => {
    renderer.setRenderTarget(rt);
    renderer.render(scene, camera);
    renderer.setRenderTarget(null);
    renderer.render(quadScene, quadCam);
  });
}

init();

Live Example

https://jsfiddle.net/w8v0Lfx2/3/

Version

r183.2

Device

Desktop

Browser

Chrome

OS

MacOS

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugTSLThree.js Shading Language

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions