WebGL implements transparent objects (alpha blending)

Table of Contents

alpha mix

How to implement alpha blending

1. Turn on the mixing function:

2. Specify the mixing function

Mixed function

gl.blendFunc() function specification

Constants that can be assigned to src_factor and dst_factor

Calculation formula of mixed color

additive mixing

Translucent triangles (LookAtBlendedTriangles.js)

Example effect

Sample code

Translucent three-dimensional objects (BlendedCube.js)

Example effect

Sample code

Transparent and opaque objects coexist

5 Steps to Achieve Hidden Surface Elimination and Alpha Blending Simultaneously

1. Turn on the hidden surface elimination function.

2. Draw all opaque objects (α is 1.0).

3. Lock the write operation of the depth buffer used for hidden surface elimination to make it read-only.

4. Draw all translucent objects (α less than 1.0), note that they should be sorted by depth, and then drawn from back to front.

5. Release the depth buffer and make it readable and writable.

gl.depthMask() function specification


α Mix

The alpha component of a color (the A in RGBA) controls the transparency of the color. If the alpha component of an object’s color has a value of 0.5, the object is translucent and other objects behind the object can be seen through it. If the alpha component of an object’s color has a value of 0, then it is completely transparent and we will not be able to see it at all. In the example program in this article, as the alpha component decreases, the entire drawing area will gradually become white, because by default, alpha blending not only affects the drawn objects, but also the background color. In the end, the white you see is actually It is a blank web page after .

How to implement alpha mixing

There are two steps to follow to turn on alpha blending.

1. Turn on the mixing function:

gl.enable(gl.BLEND)

2. Specify the mixing function

gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)

Mixed function

Let’s study the role of gl.blendFunc() function. When performing alpha mixing, WebGL actually uses two colors, namely the source color and the destination color. The former is the color “to be mixed in” and the latter is the “to be mixed in”. The color that goes in”. For example, if we first draw a triangle, and then draw another triangle on top of this triangle, then when drawing the latter triangle (pixels in the area that overlaps with the previous triangle), a blending operation is involved. , the color of the latter needs to be “mixed” into the former, the color of the latter is the source color, and the color of the former is the target color.

gl.blendFunc() function specification

Constant that can be assigned to src_factor and dst_factor

WebGL removes gl.CONSTANT_COLOR, gl.ONE_MINUS_CONSTANT_COLOR, gl.CONSTANT_ALPHA, gl.ONE_MINUS_CONSTANT_ALPHA from OpenGL

In the above table, (Rs, Gs, Bs, As) and (Rd, Gd, Bd, Ad) represent the respective components of the source color and the target color.

The sample programs in this article will all use

gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

Calculation formula of mixed color

Thus,if the source color is semi-transparent green (0.0, 1.0, 0.0, 0.4) and the destination color is normal (fully opaque) yellow (1.0, 1.0, 0.0, 1.0), then src_factor is the alpha of the source color The component is 0.4, and the dst_factor is (1- 0.4) = 0.6. The calculated color after mixing is (0.6, 1.0, 0.0)as shown below.

Additive Mixing

You can try specifying the src_factor and dst_factor parameters as other constants. For example, there is a commonly used blending mode – Additive blending (additive blending), as shown below. Additive blending will make the mixed area brighter, and is usually used to achieve explosive lighting effects, or task items in the game that need to attract the player’s attention.

gl.BlendFunc(gl.SRC_ALHPA, gl.ONE)

Translucent triangle (LookAtBlendedTriangles.js)

Take a look at the sample program LookAtBlendedTriangles. The running effect draws three triangles as shown in the figure and allows the use of arrow keys to control the position of the viewpoint. This example changes the alpha value of these three triangles from 1.0 to 0.4. The figure below shows the effect of this example running. As you can see, the triangle has become translucent and you can see through the triangle in front of it to the triangle behind it. Use the arrow keys to move the viewpoint, and you can see that the translucency effect always exists. ,

Sample Effect

Sample Code

The code for LookAtBlendedTriangles.js is shown below. We enable the blending function (line 26), specify the blending function (line 27), and modify the alpha component value of the triangle color in the initVertexBuffer() function (lines 40-50), the gl.vertexAttribPointer() function The size and stride parameters have been modified accordingly.

var VSHADER_SOURCE =
  'attribute vec4 a_Position;\\
' +
  'attribute vec4 a_Color;\\
' +
  'uniform mat4 u_ViewMatrix;\\
' +
  'uniform mat4 u_ProjMatrix;\\
' +
  'varying vec4 v_Color;\\
' +
  'void main() {\\
' +
  ' gl_Position = u_ProjMatrix * u_ViewMatrix * a_Position;\\
' +
  ' v_Color = a_Color;\\
' +
  '}\\
';
var FSHADER_SOURCE =
  '#ifdef GL_ES\\
' +
  'precision mediump float;\\
' +
  '#endif\\
' +
  'varying vec4 v_Color;\\
' +
  'void main() {\\
' +
  ' gl_FragColor = v_Color;\\
' +
  '}\\
';

function main() {
  var canvas = document.getElementById('webgl');
  var gl = getWebGLContext(canvas);
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) return
  var n = initVertexBuffers(gl);
  gl.clearColor(0, 0, 0, 1);
  gl.enable (gl.BLEND); // Enable blending function
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); //Specify blending function
  var u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');
  var u_ProjMatrix = gl.getUniformLocation(gl.program, 'u_ProjMatrix');
  var viewMatrix = new Matrix4();
  window.onkeydown = function(ev){ keydown(ev, gl, n, u_ViewMatrix, viewMatrix); };
  var projMatrix = new Matrix4();
  projMatrix.setOrtho(-1, 1, -1, 1, 0, 2);
  gl.uniformMatrix4fv(u_ProjMatrix, false, projMatrix.elements);
  // Draw
  draw(gl, n, u_ViewMatrix, viewMatrix);
}

function initVertexBuffers(gl) {
  var verticesColors = new Float32Array([
    //Vertex coordinates and color data (color transparency is 0.4)
    0.0, 0.5, -0.4, 0.4, 1.0, 0.4, 0.4, // The back green one
   -0.5, -0.5, -0.4, 0.4, 1.0, 0.4, 0.4,
    0.5, -0.5, -0.4, 1.0, 0.4, 0.4, 0.4,
    0.5, 0.4, -0.2, 1.0, 0.4, 0.4, 0.4, // The middle yerrow one
   -0.5, 0.4, -0.2, 1.0, 1.0, 0.4, 0.4,
    0.0, -0.6, -0.2, 1.0, 1.0, 0.4, 0.4,
    0.0, 0.5, 0.0, 0.4, 0.4, 1.0, 0.4, // The front blue one
   -0.5, -0.5, 0.0, 0.4, 0.4, 1.0, 0.4,
    0.5, -0.5, 0.0, 1.0, 0.4, 0.4, 0.4,
  ]);
  var n = 9;
  var vertexColorbuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorbuffer);
  gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);
  var FSIZE = verticesColors.BYTES_PER_ELEMENT;
  var a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, FSIZE * 7, 0);
  gl.enableVertexAttribArray(a_Position);
  var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
  gl.vertexAttribPointer(a_Color, 4, gl.FLOAT, false, FSIZE * 7, FSIZE * 3);
  gl.enableVertexAttribArray(a_Color);
  gl.bindBuffer(gl.ARRAY_BUFFER, null);
  return n;
}

function keydown(ev, gl, n, u_ViewMatrix, viewMatrix) {
    if(ev.keyCode == 39) { //Right arrow key pressed
      g_EyeX + = 0.01;
    } else
    if (ev.keyCode == 37) { // Arrow key pressed
      g_EyeX -= 0.01;
    } else return;
    draw(gl, n, u_ViewMatrix, viewMatrix);
}

// eye position
var g_EyeX = 0.20, g_EyeY = 0.25, g_EyeZ = 0.25;
function draw(gl, n, u_ViewMatrix, viewMatrix) {
  viewMatrix.setLookAt(g_EyeX, g_EyeY, g_EyeZ, 0, 0, 0, 0, 1, 0);
  gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  gl.drawArrays(gl.TRIANGLES, 0, n);
}

Translucent three-dimensional object (BlendedCube.js)

At this point, we will use the alpha blending function to achieve a translucent effect on a typical three-dimensional object – a cube. The sample program BlendedCube draws a cube from 0 to 1 in WebGL_ hawthorn tree’s blog – ColoredCube in the CSDN blog, and adds the use of alpha blending to the code The two steps are as shown in the figure below.

Sample Effect

Run the program, and you will find that the expected effect in the picture below (right) does not appear. The actual effect is as shown in the picture below (left), and WebGL draws a cube from 0 to 1_ hawthorn tree’s blog-CSDN blog’s < strong>ColoredCubeThere is no difference.

This is because the program turns on the hidden surface elimination function (line 27). We know that alpha blending occurs during the process of drawing fragments, and when the hidden surface elimination function is turned on, the hidden fragments will not be drawn, so the blending process will not occur, and there will be no translucent effect. In fact, you only need to comment out this line of code that enables hidden surface elimination.

Sample Code

var VSHADER_SOURCE =
  'attribute vec4 a_Position;\\
' +
  'attribute vec4 a_Color;\\
' +
  'uniform mat4 u_MvpMatrix;\\
' +
  'varying vec4 v_Color;\\
' +
  'void main() {\\
' +
  ' gl_Position = u_MvpMatrix * a_Position;\\
' +
  ' v_Color = a_Color;\\
' +
  '}\\
';
var FSHADER_SOURCE =
  '#ifdef GL_ES\\
' +
  'precision mediump float;\\
' +
  '#endif\\
' +
  'varying vec4 v_Color;\\
' +
  'void main() {\\
' +
  ' gl_FragColor = v_Color;\\
' +
  '}\\
';

function main() {
  var canvas = document.getElementById('webgl');
  var gl = getWebGLContext(canvas);
  if (!initShaders(gl, VSHADER_SOURCE, FSHADER_SOURCE)) return
  var n = initVertexBuffers(gl);
  
  // Set clear background color and enable depth test
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  // gl.enable(gl.DEPTH_TEST);
  // Enable alpha blending
  gl.enable (gl.BLEND);
  //Set the blending function
  gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

  var u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix');
  var mvpMatrix = new Matrix4();
  mvpMatrix.setPerspective(30, 1, 1, 100);
  mvpMatrix.lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0);
  gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0);
}

function initVertexBuffers(gl) {
  // v6----- v5
  // /| /|
  // v1------v0|
  // | | | |
  // | |v7---|-|v4
  // |/ |/
  // v2------v3
  var vertices = new Float32Array([ // Vertex coordinates
     1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0,-1.0, 1.0, 1.0,-1.0, 1.0, // v0-v1-v2-v3 front
     1.0, 1.0, 1.0, 1.0,-1.0, 1.0, 1.0,-1.0,-1.0, 1.0, 1.0,-1.0, // v0-v3-v4-v5 right
     1.0, 1.0, 1.0, 1.0, 1.0,-1.0, -1.0, 1.0,-1.0, -1.0, 1.0, 1.0, // v0-v5-v6-v1 up
    -1.0, 1.0, 1.0, -1.0, 1.0,-1.0, -1.0,-1.0,-1.0, -1.0,-1.0, 1.0, // v1-v6-v7-v2 left
    -1.0,-1.0,-1.0, 1.0,-1.0,-1.0, 1.0,-1.0, 1.0, -1.0,-1.0, 1.0, // v7-v4-v3-v2 down
     1.0,-1.0,-1.0, -1.0,-1.0,-1.0, -1.0, 1.0,-1.0, 1.0, 1.0,-1.0 // v4-v7-v6-v5 back
  ]);
  var colors = new Float32Array([ // Colors
      0.5, 0.5, 1.0, 0.4, 0.5, 0.5, 1.0, 0.4, 0.5, 0.5, 1.0, 0.4, 0.5, 0.5, 1.0, 0.4, // v0-v1-v2-v3 front(blue)
      0.5, 1.0, 0.5, 0.4, 0.5, 1.0, 0.5, 0.4, 0.5, 1.0, 0.5, 0.4, 0.5, 1.0, 0.5, 0.4, // v0-v3-v4-v5 right(green)
      1.0, 0.5, 0.5, 0.4, 1.0, 0.5, 0.5, 0.4, 1.0, 0.5, 0.5, 0.4, 1.0, 0.5, 0.5, 0.4, // v0-v5-v6-v1 up(red)
      1.0, 1.0, 0.5, 0.4, 1.0, 1.0, 0.5, 0.4, 1.0, 1.0, 0.5, 0.4, 1.0, 1.0, 0.5, 0.4, // v1-v6-v7-v2 left
      1.0, 1.0, 1.0, 0.4, 1.0, 1.0, 1.0, 0.4, 1.0, 1.0, 1.0, 0.4, 1.0, 1.0, 1.0, 0.4, // v7-v4-v3-v2 down
      0.5, 1.0, 1.0, 0.4, 0.5, 1.0, 1.0, 0.4, 0.5, 1.0, 1.0, 0.4, 0.5, 1.0, 1.0, 0.4 // v4-v7-v6-v5 back
  ]);
  var indices = new Uint8Array([ // Indices of the vertices
     0, 1, 2, 0, 2, 3, // front
     4, 5, 6, 4, 6, 7, // right
     8, 9,10, 8,10,11, // up
    12,13,14, 12,14,15, // left
    16,17,18, 16,18,19, // down
    20,21,22, 20,22,23 // back
  ]);
  var indexBuffer = gl.createBuffer();
  if (!initArrayBuffer(gl, vertices, 3, gl.FLOAT, 'a_Position')) return -1;
  if (!initArrayBuffer(gl, colors, 4, gl.FLOAT, 'a_Color')) return -1;
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
  return indices.length;
}

function initArrayBuffer(gl, data, num, type, attribute) {
  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
  var a_attribute = gl.getAttribLocation(gl.program, attribute);
  gl.vertexAttribPointer(a_attribute, num, type, false, 0, 0);
  gl.enableVertexAttribArray(a_attribute);
  return true;
}

Transparent and opaque objects coexist

Turning off the hidden surface elimination function is just a crude solution and does not meet actual needs. When drawing a three-dimensional scene, there are often both opaque and translucent objects in the scene. If you turn off the hidden surface elimination function, the context of those opaque objects will be messed up.

5 steps to achieve hidden surface elimination and alpha blending at the same time

In fact, through a certain mechanism, hidden surface elimination and translucency effects can be achieved at the same time. We only need:

1. Turn on the hidden surface elimination function.

gl.enable(gl.DEPTH_TEST)

2. Draw all opaque objects (α is 1.0).

3. Lock the write operation of the depth buffer used for hidden surface elimination, Make it read-only.

gl.depthMask(false)

4. Draw all translucent objects (α less than 1.0) , note that they should be sorted by depth and drawn from back to front.

5. Release the depth buffer and make it readable and writable.

gl.depthMask(true)

The gl.depthMask() function is used to lock and release the depth buffer. Its specifications are as follows:

gl.depthMask() function specification

We know that the depth buffer stores the z coordinate value of each pixel (normalized between 0.0 and 1.0). Suppose there are two overlapping triangles A and B in the scene. First, when drawing triangle A, write the z value of each fragment of it into the depth buffer, and then when drawing triangle B, write the fragments in B that overlap with A and the corresponding pixels in the depth buffer. Z value comparison: If the z value in the depth buffer is small, it means triangle A is in front, then the fragment of B is discarded and will not be written to the color buffer; if the z value in the depth buffer is large , indicating that triangle B is in front, write the fragment of B into the color buffer, overwriting the previous color of A. This way, when drawing is complete, all pixels in the color buffer are the frontmost fragments, and the z-value of each pixel is stored in the depth buffer. This is the principle of hidden surface elimination. Note that all these operations are performed at the fragment level, so if two faces intersect, they will display normally.

When we follow the above steps to draw both transparent and opaque objects at the same time, after the end of steps 1 and 2, all opaque objects are correctly drawn in the color buffer and the depth buffer records each pixel The depth of (the frontmost fragment). Then proceed to steps 3, 4 and 5 to draw all semi-transparent objects. Due to the existence of the depth buffer, objects behind opaque objects, even if they are translucent, will not be displayed. This is because writing to the depth buffer is locked, so when a transparent object is drawn in front of an opaque object, the depth buffer is not updated.