javascriptcsscanvasthree.jsdat.gui

How to make Canvas animation Background tranparent using three.js/dat.gui


I want to make this canvas as a background animation. But the three.js render the background to black. I want to make it transparent. I tried to change it by css background-color:transparent; I looked for other similar issues in stackoverflow but nothing helped. How can I do it? These are my html,css and js. Thanks in advance. Here is the codepen link. My HTML:

<canvas id="webgl-canvas"></canvas>
<!-- vertexShader -->
<script id="js-vertex-shader" type="x-shader/x-vertex">
attribute vec3 position;
void main() {
  gl_Position = vec4(position, 1.0);
}
</script>

<!-- fragmentShader -->
<script id="js-fragment-shader" type="x-shader/x-fragment">
precision highp float;
uniform vec2 resolution;
uniform float time;
uniform float xScale;
uniform float yScale;
uniform float distortion;

void main() {
  vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
  
  float d = length(p) * distortion;
  
  float rx = p.x * (1.0 + d);
  float gx = p.x;
  float bx = p.x * (1.0 - d);

  float r = 0.05 / abs(p.y + sin((rx + time) * xScale) * yScale);
  float g = 0.05 / abs(p.y + sin((gx + time) * xScale) * yScale);
  float b = 0.05 / abs(p.y + sin((bx + time) * xScale) * yScale);
  
  gl_FragColor = vec4(r, g, b, 1.0);
}
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r124/three.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script>

My CSS:

#webgl-canvas{
  width: 20% !important;
  height:10% !important;
  background: null !important;
}

My JS:

class Stage {
  constructor() {
    this.renderParam = {
      clearColor: 0xffffff,
      width: window.innerWidth,
      height: window.innerHeight
    };

    this.cameraParam = {
      left: -1,
      right: 1,
      top: 1,
      bottom: 1,
      near: 0,
      far: -1
    };

    this.scene = null;
    this.camera = null;
    this.renderer = null;
    this.geometry = null;
    this.material = null;
    this.mesh = null;

    this.isInitialized = false;
  }

  init() {
    this._setScene();
    this._setRender();
    this._setCamera();

    this.isInitialized = true;
  }

  _setScene() {
    this.scene = new THREE.Scene();
  }

  _setRender() {
    this.renderer = new THREE.WebGLRenderer({
      canvas: document.getElementById("webgl-canvas")
    });
    this.renderer.setPixelRatio(window.devicePixelRatio);
    this.renderer.setClearColor(new THREE.Color(this.renderParam.clearColor));
    this.renderer.setSize(this.renderParam.width, this.renderParam.height);
  }

  _setCamera() {
    if (!this.isInitialized) {
      this.camera = new THREE.OrthographicCamera(
        this.cameraParam.left,
        this.cameraParam.right,
        this.cameraParam.top,
        this.cameraParam.bottom,
        this.cameraParam.near,
        this.cameraParam.far
      );
    }
    
    const windowWidth = window.innerWidth;
    const windowHeight = window.innerHeight;

    this.camera.aspect = windowWidth / windowHeight;

    this.camera.updateProjectionMatrix();
    this.renderer.setSize(windowWidth, windowHeight);
  }

  _render() {
    this.renderer.render(this.scene, this.camera);
  }

  onResize() {
    this._setCamera();
  }

  onRaf() {
    this._render();
  }
}

class Mesh {
  constructor(stage) {
    this.canvas = document.getElementById("webgl-canvas");
    this.canvasWidth = this.canvas.width;
    this.canvasHeight = this.canvas.height;

    this.uniforms = {
      resolution: { type: "v2", value: [ this.canvasWidth, this.canvasHeight ] },
      time: { type: "f", value: 0.0 },
      xScale: { type: "f", value: 1.0 },
      yScale: { type: "f", value: 0.5 },
      distortion: { type: "f", value: 0.050 }
    };

    this.stage = stage;

    this.mesh = null;
    
    this.xScale = 1.0;
    this.yScale = 0.5;
    this.distortion = 0.050;
  }

  init() {
    this._setMesh();
    // this._setGui();
  }

  _setMesh() {
    const position = [
      -1.0, -1.0, 0.0,
       1.0, -1.0, 0.0,
      -1.0,  1.0, 0.0,
       1.0, -1.0, 0.0,
      -1.0,  1.0, 0.0,
       1.0,  1.0, 0.0
    ];

    const positions = new THREE.BufferAttribute(new Float32Array(position), 3);

    const geometry = new THREE.BufferGeometry();
    geometry.setAttribute("position", positions);

    const material = new THREE.RawShaderMaterial({
      vertexShader: document.getElementById("js-vertex-shader").textContent,
      fragmentShader: document.getElementById("js-fragment-shader").textContent,
      uniforms: this.uniforms,
      side: THREE.DoubleSide
    });

    this.mesh = new THREE.Mesh(geometry, material);

    this.stage.scene.add(this.mesh);
  }
  
  _diffuse() {
    
  }
  
  _render() {
    this.uniforms.time.value += 0.01;
  }

  _setGui() {
    const parameter = {
      xScale: this.xScale,
      yScale: this.yScale,
      distortion: this.distortion
    }
    const gui = new dat.GUI();
    gui.add(parameter, "xScale", 0.00, 5.00, 0.01).onChange((value) => {
      this.mesh.material.uniforms.xScale.value = value;
    });
    gui.add(parameter, "yScale", 0.00, 1.00, 0.01).onChange((value) => {
      this.mesh.material.uniforms.yScale.value = value;
    });
    gui.add(parameter, "distortion", 0.001, 0.100, 0.001).onChange((value) => {
      this.mesh.material.uniforms.distortion.value = value;
    });
  }

  onRaf() {
    this._render();
  }
}

(() => {
  const stage = new Stage();

  stage.init();

  const mesh = new Mesh(stage);

  mesh.init();

  window.addEventListener("resize", () => {
    stage.onResize();
  });
  
  window.addEventListener("load", () => {
    setTimeout(() => {
      mesh._diffuse();
    }, 1000);
  });

  const _raf = () => {
    window.requestAnimationFrame(() => {
      stage.onRaf();
      mesh.onRaf();

      _raf();
    });
  };

  _raf();
})();

Solution

  • To be able to get alpha support with three.js's WebGLRenderer at all you need to activate it's alpha buffer by setting the alpha option to true upon initialization.

    this.renderer = new THREE.WebGLRenderer({
        alpha: true,
        canvas: document.getElementById("webgl-canvas")
    });
    

    As your using a RawShaderMaterial, the source of you texture's pixel opacity is the fragment shader.

    If we take a look at it

    gl_FragColor = vec4(r, g, b, 1.0);
    

    we realize that whatever color a pixel will have, it's always opaque. That means you need to do your alpha calculations inside the fragment shader. One possible way would be using a treshold e.g. if red+green+blue is smaller than 0.5 make it completely transparent.

    float a = 1.0;
    if (r + g + b < 0.5) {
        a = 0.0;
    }
    gl_FragColor = vec4(r, g, b, a);
    

    Apart from that you also need to get rid of your custom clear color:

    this.renderer.setClearColor(new THREE.Color(this.renderParam.clearColor));
    

    and give your RawShaderMaterial the additional options transparent: true, opacity: 1

    Here's an example:

    class Stage {
        constructor() {
            this.renderParam = {
                width: window.innerWidth,
                height: window.innerHeight
            };
    
            this.cameraParam = {
                left: -1,
                right: 1,
                top: 1,
                bottom: 1,
                near: 0,
                far: -1
            };
    
            this.scene = null;
            this.camera = null;
            this.renderer = null;
            this.geometry = null;
            this.material = null;
            this.mesh = null;
    
            this.isInitialized = false;
        }
    
        init() {
            this._setScene();
            this._setRender();
            this._setCamera();
    
            this.isInitialized = true;
        }
    
        _setScene() {
            this.scene = new THREE.Scene();
        }
    
        _setRender() {
            this.renderer = new THREE.WebGLRenderer({
                alpha: true,
                canvas: document.getElementById("webgl-canvas")
            });
            this.renderer.setPixelRatio(window.devicePixelRatio);
    
            this.renderer.setSize(this.renderParam.width, this.renderParam.height);
        }
    
        _setCamera() {
            if (!this.isInitialized) {
                this.camera = new THREE.OrthographicCamera(
                    this.cameraParam.left,
                    this.cameraParam.right,
                    this.cameraParam.top,
                    this.cameraParam.bottom,
                    this.cameraParam.near,
                    this.cameraParam.far
                );
            }
    
            const windowWidth = window.innerWidth;
            const windowHeight = window.innerHeight;
    
            this.camera.aspect = windowWidth / windowHeight;
    
            this.camera.updateProjectionMatrix();
            this.renderer.setSize(windowWidth, windowHeight);
        }
    
        _render() {
            this.renderer.render(this.scene, this.camera);
        }
    
        onResize() {
            this._setCamera();
        }
    
        onRaf() {
            this._render();
        }
    }
    
    class Mesh {
        constructor(stage) {
            this.canvas = document.getElementById("webgl-canvas");
            this.canvasWidth = this.canvas.width;
            this.canvasHeight = this.canvas.height;
    
            this.uniforms = {
                resolution: {
                    type: "v2",
                    value: [this.canvasWidth, this.canvasHeight]
                },
                time: {
                    type: "f",
                    value: 0.0
                },
                xScale: {
                    type: "f",
                    value: 1.0
                },
                yScale: {
                    type: "f",
                    value: 0.5
                },
                distortion: {
                    type: "f",
                    value: 0.050
                }
            };
    
            this.stage = stage;
    
            this.mesh = null;
    
            this.xScale = 1.0;
            this.yScale = 0.5;
            this.distortion = 0.050;
        }
    
        init() {
            this._setMesh();
            // this._setGui();
        }
    
        _setMesh() {
            const position = [
                -1.0, -1.0, 0.0,
                1.0, -1.0, 0.0,
                -1.0, 1.0, 0.0,
                1.0, -1.0, 0.0,
                -1.0, 1.0, 0.0,
                1.0, 1.0, 0.0
            ];
    
            const positions = new THREE.BufferAttribute(new Float32Array(position), 3);
    
            const geometry = new THREE.BufferGeometry();
            geometry.setAttribute("position", positions);
    
            const material = new THREE.RawShaderMaterial({
                vertexShader: document.getElementById("js-vertex-shader").textContent,
                fragmentShader: document.getElementById("js-fragment-shader").textContent,
                uniforms: this.uniforms,
          transparent: true,
          opacity: 1,
                side: THREE.DoubleSide
            });
    
            this.mesh = new THREE.Mesh(geometry, material);
    
            this.stage.scene.add(this.mesh);
        }
    
        _diffuse() {
            // gsap.to(this.mesh.material.uniforms.xScale, {
            //   value: 2,
            //   duration: 0.1,
            //   ease: 'power2.inOut',
            //   repeat: -1,
            //   yoyo: true
            // });
            // gsap.to(this.mesh.material.uniforms.yScale, {
            //   value: 1,
            //   duration: 0.1,
            //   ease: 'power2.inOut',
            //   repeat: -1,
            //   yoyo: true
            // });
        }
    
        _render() {
            this.uniforms.time.value += 0.01;
        }
    
        _setGui() {
            const parameter = {
                xScale: this.xScale,
                yScale: this.yScale,
                distortion: this.distortion
            }
            const gui = new dat.GUI();
            gui.add(parameter, "xScale", 0.00, 5.00, 0.01).onChange((value) => {
                this.mesh.material.uniforms.xScale.value = value;
            });
            gui.add(parameter, "yScale", 0.00, 1.00, 0.01).onChange((value) => {
                this.mesh.material.uniforms.yScale.value = value;
            });
            gui.add(parameter, "distortion", 0.001, 0.100, 0.001).onChange((value) => {
                this.mesh.material.uniforms.distortion.value = value;
            });
        }
    
        onRaf() {
            this._render();
        }
    }
    
    (() => {
        const stage = new Stage();
    
        stage.init();
    
        const mesh = new Mesh(stage);
    
        mesh.init();
    
        window.addEventListener("resize", () => {
            stage.onResize();
        });
    
        window.addEventListener("load", () => {
            setTimeout(() => {
                mesh._diffuse();
            }, 1000);
        });
    
        const _raf = () => {
            window.requestAnimationFrame(() => {
                stage.onRaf();
                mesh.onRaf();
    
                _raf();
            });
        };
    
        _raf();
    })();
    body {
      margin: 0;
      overflow: hidden;
      background-color: red;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r124/three.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js"></script>
         <canvas id="webgl-canvas"></canvas>
    <!-- vertexShader -->
    <script id="js-vertex-shader" type="x-shader/x-vertex">
    attribute vec3 position;
    void main() {
      gl_Position = vec4(position, 1.0);
    }
    </script>
    
    <!-- fragmentShader -->
    <script id="js-fragment-shader" type="x-shader/x-fragment">
    precision highp float;
    uniform vec2 resolution;
    uniform float time;
    uniform float xScale;
    uniform float yScale;
    uniform float distortion;
    
    void main() {
      vec2 p = (gl_FragCoord.xy * 2.0 - resolution) / min(resolution.x, resolution.y);
      
      float d = length(p) * distortion;
      
      float rx = p.x * (1.0 + d);
      float gx = p.x;
      float bx = p.x * (1.0 - d);
    
      float r = 0.05 / abs(p.y + sin((rx + time) * xScale) * yScale);
      float g = 0.05 / abs(p.y + sin((gx + time) * xScale) * yScale);
      float b = 0.05 / abs(p.y + sin((bx + time) * xScale) * yScale);
      
      float a = 1.0;
      if(r+g+b<0.5)
      {
      a=0.0;
      }
    
      gl_FragColor = vec4(r, g, b, a);
    }
    </script>