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

88 Views Asked by At

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();
})();
1

There are 1 best solutions below

3
obscure On BEST ANSWER

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>