How to set texel invisible depending on a texture from another object

Hello everyone,

Introduction

I wan’t to be able to create a sort of fog of war for my app using shaders. I feel that I might be close, but I cant figure out how to make it work… (maybe I don’t have the right approach all together lol, I hope not)

Here is the context:

  • I have a plane that has a canvas texture. The user will be able to draw in black on this plane.
    We call it the “board”.

  • I have objects that slide on this plane.
    We call them “tokens”.

What I want:

  • I want to set invisibe the tokens once they are over a part of the board that is black.

  • If the token is half in the black part, half in the non-black part, only the part of the token over the black part will be invisible.

What I implemented so far

Typescript part:

// Create canvas element and color a half of it black, the other half  transparent
this.FowCanvas = document.createElement('canvas');
this.fowContext = this.FowCanvas.getContext('2d');
this.fowContext.fillStyle = 'rgb(0, 0, 0)';
this.fowContext.fillRect(0, 0, this.FOWCanvas.width/2, this.FOWCanvas.height);


// Create board

const texture = new THREE.CanvasTexture(this.FowCanvas);

const material = new THREE.MeshBasicMaterial({
  map: texture,
  transparent: true
});

const board= new THREE.Mesh(
  new THREE.PlaneBufferGeometry(canvas.width / WORLD_UNIT, canvas.height / WORLD_UNIT),
  material
);

board.receiveShadow = false;
board.rotation.x = - Math.PI * 0.5;
board.position.y = BOARD_POSITION_Y; // this is like 0.05 for pixel fighting purposes

// Applying shader to my object's materials
token.traverse((object) => {
  const material = this.board.material as THREE.MeshBasicMaterial;
  if (object instanceof THREE.Mesh) {
    if (object.material instanceof Array) {
      for (let i = 0; i < object.material.length; i ++) {
        this.applyFOWShader(object.material[i], material.map);
      }
    } else {
      this.applyFOWShader(object.material, material.map);
    }
});

applyFOWShader(material, texture) {
  material.transparent = true;
  material.onBeforeCompile = (shader) => {
    fowShader(texture, shader);
  };
} 

Now, here is the actual shader

const fowShader = (texture, shader) => {
  shader.uniforms.uTexture = texture;

  // Vertex Shader overwrite before Main() for uniforms and varyings declaration
  shader.vertexShader = shader.vertexShader.replace(
    '#include <common>',
    `
      #include <common>

      varying vec4 vFragPos;
      `
  );

  // Vertex Shader overwrite inside Main() for calculations
  shader.vertexShader = shader.vertexShader.replace(
    '#include <begin_vertex>',
    `
      #include <begin_vertex>
      vec4 vFragPos = modelMatrix * vec4(position, 1.0);
    `
  );

  // Fragment Shader before Main() for uniforms and varyings declaration
  shader.fragmentShader = shader.fragmentShader.replace(
    '#include <common>',
    `
      #include <common>
      uniform sampler2D uTexture;
      varying vec4 vFragPos;
      
      `
  );

  // Fragment Shader inside Main() for calculations
  shader.fragmentShader = shader.fragmentShader.replace(
    '#include <output_fragment>',
    `
      #ifdef OPAQUE
      diffuseColor.a = 1.0;
      #endif
      
      // https://github.com/mrdoob/three.js/pull/22425
      #ifdef USE_TRANSMISSION
      diffuseColor.a *= transmissionAlpha + 0.1;
      #endif

      vec4 texColor = texture2D(uTexture, vFragPos.xy);

    if (texColor.a != 0.0) {
      diffuseColor.a = 0.0;
    }

    gl_FragColor = vec4( outgoingLight, diffuseColor.a );
    `
  );
};

export default fowShader;

So there you go.

TLDR would be this

vec4 texColor = texture2D(uTexture, vFragPos.xy);

      if (texColor.r == 0.0 && texColor.g == 0.0 && texColor.b == 0.0) {
        diffuseColor.a = 0.0;
      }

      gl_FragColor = vec4( outgoingLight, diffuseColor.a );

I try to get the texColor of my boardTexture at the position of my token.
If this color is not transparent, then I want to have the color of my token transparent 100%.

Some images to show the status


Note: the battlemap that you see next to the black can be ignored, it is the actual board with a battlemap and is not used in this context.

Note2: You can see that the canvas texture is well updated and visible on the scene. I think it is being sent to the shader without issue. So maybe the problem is in the shader itself? idk…

So there it is. I am quite new to threejs and shaders, so maybe all this does not make sense at all and maybe a better approach could be used…
Right now I am hopeless!

Thank you for reading this! :slight_smile: