Search code examples
javascriptwebglwebgl2

WebGL Alpha blending between scene elements and background


I'm trying to figure out alpha blending in WebGL and almost there but need some insight.

I've read this question WebGL: How to correctly blend alpha channel png and a few articles on the topic like this one David Guan: Alpha Blending and WebGL which talk about the blend modes, premultiplying alpha and fragment shaders but can't quite figure this out.

Here's what I've got so far:

enter image description here

The squares blend onto the background but not onto each other.

Also the top edge of the red box has a dark border.

enter image description here

Anyone have any ideas?

function drawHtml5() {
  var html5 = document.getElementById("html5Canvas").getContext("2d");
  html5.fillStyle = "rgba(255,0,0,0.5)";
  html5.fillRect(50,20,200,75);
  html5.fillStyle = "rgba(0,255,0,0.5)";
  html5.fillRect(100,50,175,75);
  html5.fillStyle = "rgba(0,0,255,0.5)";
  html5.fillRect(30,75,175,70);
}

function drawWebGL() {
  var gl = document.getElementById("canvas").getContext("webgl", {
    premultipliedAlpha: false,
  });
  gl.enable(gl.BLEND);
  gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

  var vertices = [
    -0.5,0.5,0.0,
    -0.5,-0.5,0.0,
    0.5,-0.5,0.0,
    0.5,0.5,0.0 
  ];

  indices = [3,2,1,3,1,0];

  // Create an empty buffer object to store vertex buffer
  var vertex_buffer = gl.createBuffer();

  // Bind appropriate array buffer to it
  gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);

  // Pass the vertex data to the buffer
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

  // Unbind the buffer
  gl.bindBuffer(gl.ARRAY_BUFFER, null);

  // Create an empty buffer object to store Index buffer
  var Index_Buffer = gl.createBuffer();

  // Bind appropriate array buffer to it
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, Index_Buffer);

  // Pass the vertex data to the buffer
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);

  // Unbind the buffer
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

  /*====================== Shaders =======================*/

  // Vertex shader source code
  var vertCode =
      'attribute vec3 coordinates;' +
      'uniform vec4 translation;'+
      'void main(void) {' +
      ' gl_Position = vec4(coordinates, 1) + translation;' +      
      '}';

  // Create a vertex shader object
  var vertShader = gl.createShader(gl.VERTEX_SHADER);

  // Attach vertex shader source code
  gl.shaderSource(vertShader, vertCode);

  // Compile the vertex shader
  gl.compileShader(vertShader);

  // Fragment shader source code
  var fragCode =
      'precision mediump float;' +
      'uniform vec4 u_fragColor;' + 
      'void main(void) {' +
      ' gl_FragColor = u_fragColor;' +
      //' gl_FragColor.rgb *= u_fragColor.a;' +
      '}';

  // Create fragment shader object 
  var fragShader = gl.createShader(gl.FRAGMENT_SHADER);

  // Attach fragment shader source code
  gl.shaderSource(fragShader, fragCode);

  // Compile the fragmentt shader
  gl.compileShader(fragShader);

  // Create a shader program object to
  // store the combined shader program
  var shaderProgram = gl.createProgram();

  // Attach a vertex shader
  gl.attachShader(shaderProgram, vertShader);

  // Attach a fragment shader
  gl.attachShader(shaderProgram, fragShader);

  // Link both the programs
  gl.linkProgram(shaderProgram);

  // Use the combined shader program object
  gl.useProgram(shaderProgram);

  /* ======= Associating shaders to buffer objects =======*/

  // Bind vertex buffer object
  gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);

  // Bind index buffer object
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, Index_Buffer); 

  // Get the attribute location
  var coord = gl.getAttribLocation(shaderProgram, "coordinates");

  // Point an attribute to the currently bound VBO
  gl.vertexAttribPointer(coord, 3, gl.FLOAT, false, 0, 0);

  // Enable the attribute
  gl.enableVertexAttribArray(coord);

  /*============= Drawing the Quad ================*/

  // Clear the canvas
  gl.clearColor(0.0, 0.0, 0.0, 0.0);

  // Enable the depth test
  gl.enable(gl.DEPTH_TEST);

  // Clear the color buffer bit
  gl.clear(gl.COLOR_BUFFER_BIT);

  // Set the view port
  gl.viewport(0,0,canvas.width,canvas.height);

    // Draw red box
  // 
    var u_FragColor = gl.getUniformLocation(shaderProgram, 'u_fragColor'); 
  gl.uniform4f(u_FragColor, 1,0,0,0.5);
  
    var translation = gl.getUniformLocation(shaderProgram, 'translation');
  gl.uniform4f(translation, 0, 0, 0, 0);
         
  gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT,0);
  
  // Draw green box
  // 
    var u_FragColor = gl.getUniformLocation(shaderProgram, 'u_fragColor'); 
  gl.uniform4f(u_FragColor, 0,1,0,0.5);
  
    var translation = gl.getUniformLocation(shaderProgram, 'translation');
  gl.uniform4f(translation, 0.2, -0.2, 0, 0);
  
  gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT,0);
  
  // Draw Blue box
  // 
    var u_FragColor = gl.getUniformLocation(shaderProgram, 'u_fragColor'); 
  gl.uniform4f(u_FragColor, 0,0,1,0.5);
  
    var translation = gl.getUniformLocation(shaderProgram, 'translation');
  gl.uniform4f(translation, -0.2, -0.4, 0, 0);
         
  gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT,0);

}

drawHtml5();
drawWebGL();
div {
  background-color: purple;
  background-image: linear-gradient(45deg, rgba(255, 255, 255, 1) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 1) 50%, rgba(255, 255, 255, 1) 75%, transparent 75%, transparent);
  background-size: 50px 50px;
  min-height: 600px;
  padding: 50px;
}

canvas {  
  border: 3px solid red;
  height: 300px;
  width: 400px;
}

p {
  color: red;
  font-family: "Arial";
  font-weight: "Bold";
  font-size: 2em;
  background: white;  
}
<div>
  <p>
    WebGL Canvas
  </p>
  <canvas id="canvas"></canvas>
  <p>
    HTML5 Canvas (Expected output)
  </p>
  <canvas id="html5Canvas"></canvas>
</div>

Link to JS Fiddle

Check out the JS Fiddle below if you want a live playground

https://jsfiddle.net/scichart/wr6Lny4x/


Solution

  • I saw your post on LinkedIn. Posting my answer here as well for awareness.

    Heres a link to a jsfiddle containing all the changes: https://jsfiddle.net/uom3ckqy/1/

    The actual alpha blending is disabled in the fragment shader on line 78. Try uncommenting it, so that the fragment shader now is:

    // Fragment shader source code
      var fragCode =
          'precision mediump float;' +
          'uniform vec4 u_fragColor;' + 
          'void main(void) {' +
          ' gl_FragColor = u_fragColor;' +
          ' gl_FragColor.rgb *= u_fragColor.a;' +
          '}';
    

    You can now enable the premultipliedAlpha argument, by setting it to true, leaving the gl variable as:

    var gl = document.getElementById("canvas").getContext("webgl", {
        premultipliedAlpha: true,
      });
    

    I tried it with your jsfiddle, and it now looks like this: enter image description here

    I also noticed that the drawn quads by WebGL are a bit blurry. I am no expert on the field, but it seems like you need to set the dimensions of the canvas both in the HTML element's attributes and in either CSS or JS. These answers go into more detail: WebGL: Everything is blurry despite using same code and Canvas width and height in HTML5

    I changed the canvas to include the width and height attributes. Setting these to be the same as in the CSS, The canvas containing the WebGL elements is now:

      <canvas id="canvas" height='300' width='400'></canvas>
    

    The canvas now looks as expected:

    correct webgl