WebGL model view projection matrix

WebGL Perspective Projection_Hawthorn Tree’s Blog – One problem with the PerspectiveView code in the CSDN blog is that we use a long section of boring code to define all vertex and color data. There are only 6 triangles in the example. We can still manage this data manually, but if the number of triangles increases further, it will be a real mess. Fortunately, there is a more efficient way to solve this problem.

If you look carefully at the picture below, you will find that the size, position, and color of the left and right sets of triangles are all corresponding. If there are three such triangles at the dotted line mark, then translate them 0.75 units in the positive direction of the X-axis to get the triangle on the right, and translate them 0.75 units in the negative direction of the X-axis to get the triangle on the left.

Taking advantage of this, we only need to follow the steps below to obtain the effect of WebGL perspective projection_ hawthorn tree’s blog-CSDN blog:

1. Prepare the vertex data of three triangles at the dotted line, that is, along the Z axis.

2. Draw these triangles by translating them 0.75 units in the positive direction of the X-axis (based on the original position).

3. Draw these triangles by translating them 0.75 units in the negative direction of the X-axis (based on the original position).

The example program PerspectiveView_mvp attempts to do just that.

The PerspectiveView program uses the projection matrix to define the visual space and the view matrix to define the observer. The PerspectiveView_mvp program also adds a model matrix to transform the triangle.

Matrix derivation

We need to review matrix transformations. Please look at the previously written WebGL view matrix, model view matrix_Hawthorn tree’s blog-CSDN blog program LookAtRotatedTriangles, which allows the observer to observe the rotated triangle from a custom position. Equation 1 describes the transformation process of triangle vertices.

<View Matrix>×<Model Matrix>×<Vertex Coordinates>

The later LookAtTriangles_ViewVolume program (which fixed a bug where one corner of the triangle was cut off) uses Equation 2 to calculate the final vertex coordinates, where the projection matrix may be an orthographic or perspective projection matrix .

<Projection matrix>×<View matrix>×<Vertex coordinates>

Equation 3 can be deduced from the above two equations:

<Projection matrix>×<View matrix>×<Model matrix>×<Vertex coordinates>

Equation 3 indicates that in WebGL, you can use three matrices: projection matrix, view matrix, and model matrix to calculate the final vertex coordinates (that is, the coordinates of the vertex in the canonical cube).

If the projection matrix is an identity matrix, then Equation 3 is exactly the same as Equation 1; similarly, if the model matrix is an identity matrix, then Equation 3 is exactly the same as Equation 2. The identity matrix is like 1 in multiplication. It is multiplied by any matrix, or any matrix is multiplied by it, and the result is still this matrix (any parameters required for the projection matrix are not set in the program, and the default parameters are calculated. The matrix can be understood as 1).

Model view projection matrix sample code (PerspectiveView_mvp)

var VSHADER_SOURCE =
  'attribute vec4 a_Position;\
' +
  'attribute vec4 a_Color;\
' +
  'uniform mat4 u_ModelMatrix;\
' +
  'uniform mat4 u_ViewMatrix;\
' +
  'uniform mat4 u_ProjMatrix;\
' +
  'varying vec4 v_Color;\
' +
  'void main() {\
' +
  ' gl_Position = u_ProjMatrix * u_ViewMatrix * u_ModelMatrix * 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
  //Set vertex coordinates and color (blue triangle in front)
  var n = initVertexBuffers(gl);
  gl.clearColor(0, 0, 0, 1);
  // Get the storage locations of u_ModelMatrix, u_ViewMatrix and u_ProjectMatrix
  var u_ModelMatrix = gl.getUniformLocation(gl.program, 'u_ModelMatrix');
  var u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix');
  var u_ProjMatrix = gl.getUniformLocation(gl.program, 'u_ProjMatrix');

  var modelMatrix = new Matrix4(); // model matrix
  var viewMatrix = new Matrix4(); // View matrix
  var projMatrix = new Matrix4(); // Projection matrix

  // Calculate the view matrix and projection matrix
  modelMatrix.setTranslate(0.75, 0, 0); // Translate 0.75 units along the positive x-axis
  viewMatrix.setLookAt(0, 0, 5, 0, 0, -100, 0, 1, 0); // Viewpoint, observation point, positive direction
  projMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100); //Viewing angle, near section aspect ratio, near, far
  // Pass the model, view and projection matrix to unified variables respectively
  gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
  gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements);
  gl.uniformMatrix4fv(u_ProjMatrix, false, projMatrix.elements);

  gl.clear(gl.COLOR_BUFFER_BIT);
  gl.drawArrays(gl.TRIANGLES, 0, n); // Draw the right triangle

  // Prepare model matrix for another pair of triangles
  modelMatrix.setTranslate(-0.75, 0, 0); // Translate 0.75 units along the negative x-axis
  // Only modify the model matrix
  gl.uniformMatrix4fv(u_ModelMatrix, false, modelMatrix.elements);
  gl.drawArrays(gl.TRIANGLES, 0, n); // Draw the left triangle
}

function initVertexBuffers(gl) {
  var verticesColors = new Float32Array([
    //Vertex coordinates and color
     0.0, 1.0, -4.0, 0.4, 1.0, 0.4, // Green behind
    -0.5, -1.0, -4.0, 0.4, 1.0, 0.4,
     0.5, -1.0, -4.0, 1.0, 0.4, 0.4,
     0.0, 1.0, -2.0, 1.0, 1.0, 0.4, // middle yellow
    -0.5, -1.0, -2.0, 1.0, 1.0, 0.4,
     0.5, -1.0, -2.0, 1.0, 0.4, 0.4,
     0.0, 1.0, 0.0, 0.4, 0.4, 1.0, // front blue
    -0.5, -1.0, 0.0, 0.4, 0.4, 1.0,
     0.5, -1.0, 0.0, 1.0, 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 * 6, 0);
  gl.enableVertexAttribArray(a_Position);
  var a_Color = gl.getAttribLocation(gl.program, 'a_Color');
  gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, FSIZE * 6, FSIZE * 3);
  gl.enableVertexAttribArray(a_Color);
  return n;
}

Detailed code explanation

Line 9 of the vertex shader implements Equation 3

The main() function calls the initVertexBuffers() function (line 27) to define the triangle vertex data to be passed to the buffer object (line 58). We only defined 3 triangles, the centers of which are all on the Z axis. In PerspectiveView.js, we define a total of 6 triangles on both sides of the Z axis. As mentioned before, this is because these 3 triangles will be used in conjunction with translation transformations.

Next, we obtain the storage address of the u_ModelMatrix variable in the vertex shader (line 30), then create a new model matrix modelMatrix object (line 34), and calculate it based on the parameters (line 39). At this time, the model matrix will translate the triangle 0.75 units in the positive direction of the X-axis.

Except for calculating the model matrix (line 39), the process of calculating the view matrix and projection matrix is the same as in PerspectiveView.js. The model matrix is passed to u_ModelMatrix (line 43) and drawn, and three triangles on the right side of the Z axis are drawn (line 48).

The triangle on the left is drawn in a similar way: first recalculate the model matrix (line 51) so that it translates the original triangle 0.75 units in the negative direction of the X-axis. After calculating the new model matrix, pass it to the shader, and then call gl.drawArrays() to draw, and the triangle on the left is drawn. The view matrix and projection matrix do not need to change, and there is no need to worry about them.

As you can see, the program draws two sets of shapes (6 triangles) using only one set of data (vertices and color information of 3 triangles). Although this reduces the number of vertices, it increases the number of calls to gl.drawArrays(). Which method is more efficient, or how to balance the two, depends on the program itself and the implementation of WebGL.