Search code examples
javascriptthree.jstransparency

How to make translucent texture visible through Physical Material in THREE.js?


I would like to render a translucent PNG texture behind a see-through Physical Material. However, THREE.js doesn't render the translucent texture through the Physical Material at all.

I figured out that setting transparent: false on the material makes it visible, but that obviously makes my texture opaque, which is not what I want.

How can I make a translucent PNG texture visible through Physical Material?

It reflects correctly when the material is not transparent

It doesn't reflect when the material is transparent

$(document).ready(() => {
  const renderer = new THREE.WebGLRenderer({
    canvas: document.getElementById('canvas'),
  });

  const scene = new THREE.Scene();
  scene.background = new THREE.Color(0x0000ff);
  scene.add(new THREE.AmbientLight(0xffffff, 1));

  const texture = new THREE.TextureLoader().load('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAE4AAABOCAYAAACOqiAdAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADdSURBVHhe7dAhCgIBFADR1SgYLDatHsv7n2ItXzDILg7Y3ivT57Cu63P57jHdcpvuuU63XKZ7ztO30/TTffo3xyk/Mi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi4yLjIuMi5ZlhcxLwSYJ4qy6wAAAABJRU5ErkJggg==');

  const camera = new THREE.PerspectiveCamera(65, renderer.domElement.clientWidth / renderer.domElement.clientHeight, 0.1, 1000);

  {
    const geometry = new THREE.PlaneGeometry(4, 4);
    const material = new THREE.MeshStandardMaterial({
      color: 0xffff00,
      side: THREE.DoubleSide,
    });
    const plane = new THREE.Mesh(geometry, material);
    plane.position.z = 1;
    scene.add(plane);
  }

  {
    const geometry = new THREE.PlaneGeometry(3, 3);
    const material = new THREE.MeshStandardMaterial({
      color: 0xff0000,
      side: THREE.DoubleSide,
      map: texture,
      transparent: true,
    });
    const plane = new THREE.Mesh(geometry, material);
    scene.add(plane);

    document.getElementById('transparent').onchange = (evt) => {
      material.transparent = evt.target.checked;
      material.needsUpdate = true;
    };
  }

  {
    const geometry = new THREE.PlaneGeometry(3, 3);
    const material = new THREE.MeshPhysicalMaterial({
      side: THREE.DoubleSide,
      roughness: 0.2,
      transmission: 0.8,
      color: 0xffffff,
    });
    const plane = new THREE.Mesh(geometry, material);
    plane.position.z = -2;
    plane.position.x = 1;
    scene.add(plane);
  }

  const startTime = performance.now();

  function render() {
    const now = performance.now();
    const angle = (now - startTime) * 0.002;
    camera.position.set(Math.cos(angle) * 2, Math.sin(angle) * 2, -5);
    camera.lookAt(0, 0, 0);

    renderer.render(scene, camera);

    requestAnimationFrame(render);
  }

  requestAnimationFrame(render);
});
<script src="https://code.jquery.com/jquery-3.7.0.slim.min.js"></script>
<script src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
<script type="importmap">
  {
    "imports": {
      "three": "https://unpkg.com/[email protected]/build/three.module.js"
    }
  }
</script>
<script type="module">
  import * as THREE from 'three';
  window.THREE = THREE;
</script>
<div style="position: absolute; left: 0; top: 0; z-index: 1; color: white;">
  <input type="checkbox" id="transparent" name="transparent" checked /><label for="transparent"> Transparent</label>
</div>
<canvas id="canvas" width="350" height="150" style="position: absolute; left: 0; top: 0;" />

26/07/2023: Updated code to THREE r154. Problem still exists.


Solution

  • The "transmission" feature of THREE.MeshPhysicalMaterial is a more advanced type of physically-based transparency that requires additional rendering passes. Currently three.js (and most other realtime engines I'm aware of) cannot display alpha-blending (.transparent=true) or transmissive (.transmission>0) materials through a transmissive material. If you need multiple layers of transparency you'll need to stick with alpha blending and not transmission, provided by .transparent=true and opacity or a texture with an alpha channel.

    Related discussion: https://github.com/mrdoob/three.js/issues/22009