THREE.SpriteMaterial transparency not working on Shader with Depth Texture

Hello,

Stumbled upon a stylized water shader with foam on GitHub, downloaded and made a few additions and restructuring to it.

All seems to work fine until I added a Cloud.png to a THREE.SpriteMaterial with transparency. When looking at the cloud from an angle that does not overlap the “water” shader, the transparency works, however; if the viewing angle overlaps the “water” shader, the plane behind the SpritMaterial is visible.

Here is a CodePen link, demonstrating this issue, LINK HERE.

What is causing this problem and how can it be fixed?

Thanks,
Regards.

@Mugen87 I’d appreciate any help you can give regarding this issue. Thanks.

/*------------------------------
Imports
------------------------------*/
import { OrbitControls } from "https://cdn.jsdelivr.net/npm/three@0.112.1/examples/jsm/controls/OrbitControls.js";

/*------------------------------
Initial Variables
------------------------------*/
let cloudMesh;
let water;
let camera, scene, renderer, renderTarget, depthMaterial, clock;
let canvas = document.querySelector(".webgl-canvas");

let params = {
  foamColor: 0xf0faff,
  waterColor: 0x1d38,
  threshold: 0.1,
  fogNear: 0,
  fogFar: 55,
  fogColour: 0x0
};

/*------------------------------
Run App
------------------------------*/
init();
animate();

/*------------------------------
 
App
 
------------------------------*/
function init() {
  /*------------------------------
  Clock & Camera
  ------------------------------*/
  clock = new THREE.Clock();
  camera = new THREE.PerspectiveCamera(
    70,
    window.innerWidth / window.innerHeight,
    0.1,
    100
  );
  camera.position.set(0, 7, 10);

  /*------------------------------
  Scene
  ------------------------------*/
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x1e485e);

  /*--------------
  Renderer
  --------------*/
  renderer = new THREE.WebGLRenderer({ antialias: true, canvas, alpha: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  // renderer.gammaOutput = true;

  /*------------------------------
  Orbit Controls
  ------------------------------*/
  let controls = new OrbitControls(camera, renderer.domElement);
  controls.minDistance = 1;
  controls.maxDistance = 50;

  /*------------------------------
  Fog
  ------------------------------*/
  // let fog = new THREE.Fog(params.fogColour, params.fogNear, params.fogFar);
  // scene.fog = fog;

  /*------------------------------
  Lights
  ------------------------------*/
  let ambientLight = new THREE.AmbientLight(0xcccccc, 0.4);
  scene.add(ambientLight);

  let dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
  dirLight.position.set(0, 5, 5);
  scene.add(dirLight);

  /*------------------------------
  Models
  ------------------------------*/
  let boxGeometry = new THREE.BoxGeometry(10, 1, 1);
  let boxMaterial = new THREE.MeshLambertMaterial({ color: 0xea4d10 });

  /*--------------
  Box 1
  --------------*/
  let box1 = new THREE.Mesh(boxGeometry, boxMaterial);
  box1.position.z = 4.5;
  scene.add(box1);

  /*--------------
  Box 2
  --------------*/
  let box2 = new THREE.Mesh(boxGeometry, boxMaterial);
  box2.position.z = -4.5;
  scene.add(box2);

  /*--------------
  Box 3
  --------------*/
  let box3 = new THREE.Mesh(boxGeometry, boxMaterial);
  box3.position.x = -5;
  box3.rotation.y = Math.PI * 0.5;
  scene.add(box3);

  /*--------------
  Box 4
  --------------*/
  let box4 = new THREE.Mesh(boxGeometry, boxMaterial);
  box4.position.x = 5;
  box4.rotation.y = Math.PI * 0.5;
  scene.add(box4);

  /*--------------
  Box 5
  --------------*/
  let box5 = new THREE.Mesh(new THREE.BoxGeometry(), boxMaterial);
  box5.rotation.y = Math.PI * 0.1;
  box5.rotation.x = Math.PI * 0.05;
  scene.add(box5);

  /*--------------
  Box 6
  --------------*/
  let box6 = new THREE.Mesh(new THREE.BoxGeometry(), boxMaterial);
  box6.position.y = 0;
  box6.position.x = 3;
  box6.position.z = 3;
  scene.add(box6);

  /*--------------
  Box 7
  --------------*/
  let box7 = new THREE.Mesh(new THREE.BoxGeometry(), boxMaterial);
  box7.position.y = 0;
  box7.position.x = -3;
  box7.position.z = -2;
  scene.add(box7);

  /*--------------
  Cloud
  --------------*/
  let cloudSrc =
    "https://dubsy-world.fra1.cdn.digitaloceanspaces.com/Images/cl-001.png";
  let cloudTexture = new THREE.TextureLoader().load(cloudSrc);
  let cloudMaterial = new THREE.SpriteMaterial({
    map: cloudTexture,
    transparent: true,
    opacity: 1
  });

  cloudMesh = new THREE.Sprite(cloudMaterial);

  cloudMesh.scale.set(8.95, 4.66, 1.38);
  cloudMesh.position.set(0, 4.1, 0);
  cloudMesh.castShadow = true;

  scene.add(cloudMesh);

  /*------------------------------
  Depth Texture & Pixel Ratio
  ------------------------------*/
  let supportsDepthTextureExtension = !!renderer.extensions.get(
    "WEBGL_depth_texture"
  );
  let pixelRatio = renderer.getPixelRatio();

  /*------------------------------
  Render Target
  ------------------------------*/
  renderTarget = new THREE.WebGLRenderTarget(
    window.innerWidth * pixelRatio,
    window.innerHeight * pixelRatio
  );
  renderTarget.texture.minFilter = THREE.NearestFilter;
  renderTarget.texture.magFilter = THREE.NearestFilter;
  renderTarget.texture.generateMipmaps = false;
  renderTarget.stencilBuffer = false;

  /*------------------------------
  Set Depth Texture
  ------------------------------*/
  if (supportsDepthTextureExtension === true) {
    renderTarget.depthTexture = new THREE.DepthTexture();
    renderTarget.depthTexture.type = THREE.UnsignedShortType;
    renderTarget.depthTexture.minFilter = THREE.NearestFilter;
    renderTarget.depthTexture.maxFilter = THREE.NearestFilter;
  }

  /*------------------------------
  Set Depth Material
  ------------------------------*/
  depthMaterial = new THREE.MeshDepthMaterial();
  depthMaterial.depthPacking = THREE.RGBADepthPacking;
  depthMaterial.blending = THREE.NoBlending;

  /*------------------------------
  Displacemet Map
  ------------------------------*/
  let dudvMap = new THREE.TextureLoader().load(
    "https://i.imgur.com/uVQJZFn.png"
  );
  dudvMap.wrapS = dudvMap.wrapT = THREE.RepeatWrapping;

  /*------------------------------
  Water Geometry & Material
  ------------------------------*/
  let waterGeometry = new THREE.PlaneGeometry(10, 10);
  let waterMaterial = new THREE.ShaderMaterial({
    /*--------------
    Defines
    --------------*/
    defines: {
      DEPTH_PACKING: supportsDepthTextureExtension === true ? 0 : 1,
      ORTHOGRAPHIC_CAMERA: 0
    },

    /*--------------
    Uniforms
    --------------*/
    uniforms: {
      time: { value: 0 },
      threshold: { value: 0.1 },
      tDudv: { value: null },
      tDepth: { value: null },
      cameraNear: { value: 0 },
      cameraFar: { value: 0 },
      resolution: { value: new THREE.Vector2() },
      foamColor: { value: new THREE.Color() },
      waterColor: { value: new THREE.Color() },

      ...THREE.UniformsLib["fog"]
    },

    /*--------------
    Others
    --------------*/
    fog: true,
    vertexShader: vertexShader(),
    fragmentShader: fragmentShader(),
    transparent: true
    // blending: THREE.AdditiveBlending,
  });

  /*------------------------------
  Assign Unifoms Values
  ------------------------------*/
  waterMaterial.uniforms.cameraNear.value = camera.near;
  waterMaterial.uniforms.cameraFar.value = camera.far;
  waterMaterial.uniforms.resolution.value.set(
    window.innerWidth * pixelRatio,
    window.innerHeight * pixelRatio
  );
  waterMaterial.uniforms.tDudv.value = dudvMap;
  waterMaterial.uniforms.tDepth.value =
    supportsDepthTextureExtension === true
      ? renderTarget.depthTexture
      : renderTarget.texture;

  /*------------------------------
  Water Mesh
  ------------------------------*/
  water = new THREE.Mesh(waterGeometry, waterMaterial);
  water.rotation.x = -Math.PI * 0.5;
  water.scale.set(500, 500, 500);
  scene.add(water);

  /*------------------------------
  Resize
  ------------------------------*/
  window.addEventListener("resize", onWindowResize, false);
}

/*------------------------------
 
Set Window Resize
 
------------------------------*/
function onWindowResize() {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
  let pixelRatio = renderer.getPixelRatio();

  renderTarget.setSize(
    window.innerWidth * pixelRatio,
    window.innerHeight * pixelRatio
  );
  water.material.uniforms.resolution.value.set(
    window.innerWidth * pixelRatio,
    window.innerHeight * pixelRatio
  );
}

/*------------------------------
 
Update Every Frame
 
------------------------------*/
function animate() {
  requestAnimationFrame(animate);

  /*------------------------------
  Depth Pass
  ------------------------------*/
  water.visible = false; // we don't want the depth of the water
  scene.overrideMaterial = depthMaterial;

  /*------------------------------
  Render & Render Target
  ------------------------------*/
  renderer.setRenderTarget(renderTarget);
cloudMesh.visible=false;
  renderer.render(scene, camera);
  cloudMesh.visible=true;
  renderer.setRenderTarget(null);
  scene.overrideMaterial = null;
  water.visible = true;

  /*------------------------------
  Final Pass
  ------------------------------*/
  let time = clock.getElapsedTime();
  water.material.uniforms.threshold.value = params.threshold;
  water.material.uniforms.time.value = time;
  water.material.uniforms.foamColor.value.set(params.foamColor);
  water.material.uniforms.waterColor.value.set(params.waterColor);
  renderer.render(scene, camera);
}

/*------------------------------
 
Vertex Shader
 
------------------------------*/
function vertexShader() {
  return /*glsl*/ `
  
    #include <fog_pars_vertex>
    varying vec2 vUv;

    void main() {

      #include <begin_vertex>
      #include <project_vertex>
      #include <fog_vertex>

      vUv = uv;

    }
  
  `;
}

/*------------------------------
 
Fragment Shader
 
------------------------------*/
function fragmentShader() {
  return /*glsl*/ `

      #include <common>
      #include <packing>
      #include <fog_pars_fragment>

      varying vec2 vUv;
      uniform sampler2D tDepth;
      uniform sampler2D tDudv;
      uniform vec3 waterColor;
      uniform vec3 foamColor;
      uniform float cameraNear;
      uniform float cameraFar;
      uniform float time;
      uniform float threshold;
      uniform vec2 resolution;

      float getDepth( const in vec2 screenPosition ) {
      	#if DEPTH_PACKING == 1
      		return unpackRGBAToDepth( texture2D( tDepth, screenPosition ) );
      	#else
      		return texture2D( tDepth, screenPosition ).x;
      	#endif
      }

      float getViewZ( const in float depth ) {
      	#if ORTHOGRAPHIC_CAMERA == 1
      		return orthographicDepthToViewZ( depth, cameraNear, cameraFar );
      	#else
      		return perspectiveDepthToViewZ( depth, cameraNear, cameraFar );
      	#endif
      }

      void main() {

      	vec2 screenUV = gl_FragCoord.xy / resolution;

      	float fragmentLinearEyeDepth = getViewZ( gl_FragCoord.z );
      	float linearEyeDepth = getViewZ( getDepth( screenUV ) );
      	float diff = saturate( fragmentLinearEyeDepth - linearEyeDepth );

        float foamForce = 0.05;
        float thickness = 0.01;
      	float foamScale = 10.0;  // Adjust the foam scale value to control the fineness of the foam

        vec2 displacement = texture2D( tDudv, ( vUv * foamScale ) - time * 0.05 ).rg;
        displacement = ( ( displacement * 2.0 ) - 1.0 ) * 1.0;

        float waveAmount = sin((vUv.x + vUv.y) * 10.0 + time * 5.0) * foamForce;  // Adjust the parameters to control the wave effect
        displacement.x += waveAmount;
        displacement.y += waveAmount;

        diff += displacement.x;

      	gl_FragColor.rgb = mix( foamColor, waterColor, step( threshold / (0.1 / thickness), diff ) );
      	gl_FragColor.a = 1.0;

      	#include <tonemapping_fragment>
      	#include <encodings_fragment>
      	#include <fog_fragment>

      }
  
  `;
}

Thank @Chaser_Code for your responds; however “cloudMesh.visible = true” doesn’t work and the “Sprite” when logged in the “animate()” logs the “Sprite” once and then the rest are “numbers”; hence, setting the “visible = false || true” only works on the first tick and will return error subsequently.

Can u send issue screenshot?

The issue is fixed.

Your solution, setting the cloudMesh.visible = false, rendering the scene and setting it cloudMesh.visible = true worked as shown below:

/*------------------------------
Update Every Frame
------------------------------*/
function animate() {
  ...

  cloudMesh.visible = false;
  renderer.render(scene, camera);
  cloudMesh.visible = true;

  ...
}

Thank you @Chaser_Code.
Much appreciated :blush:

1 Like