preface

In order to better simulate 3D real scene, three concepts, namely Projection matrix, View matrix and Model matrix, are introduced. The final Vertex positions are obtained by using perspective matrix * View matrix * Model matrix * vertices in Vertex Shader.

Projection matrix

The main purpose of projection matrix is to project vertices from 3D space to 2D space. There are mainly two projection methods, Perspective and Orthogonal. The difference between them mainly lies in whether the distance of the vertex on the Z-axis will affect the position of its projection on the XY plane. 1) and (1, 2, 10) correspond to the same point in 2D space under orthogonal projection, but not the same point under perspective projection. The application of orthogonal is generally 2D UI rendering, while perspective is applied to 3D object rendering. Through perspective projection, the real world visual effect can be formed. These two matrices are described in detail below

1. Orthogonal projection matrix

Example complete project code, in chapter2, section3-0 directory

The projection matrix is a 4×4 matrix. The orthogonal projection matrix is generally used to convert pixel-based coordinates into OpenGL coordinates. For example, for an 800 x 600 window, the three points (400,200), (300,300), and (500,300) are used to draw a triangle. As previously thought, you need to manually convert the coordinates into a range of -1 to 1 and pass them to OpenGL, but if you use the orthogonal projection matrix, you can directly use the vertex based on the pixel coordinate system. Now we use glMatrix to construct an orthogonal projection matrix and modify the Shader to use this matrix

First change the vertex data

function prepareVertexData() { // Prepare Vertex Data let vertices = [ 400, 200, 0, 300, 300, 0, 500, 300, 0, ]; VertexData = gl.createbuffer (); // create space for vertexData on the GPU. Gl.bindbuffer (gl.array_buffer, vertexData); Gl.bufferdata (gl.array_buffer, new Float32Array(vertices), gl.static_draw); }Copy the code

Use glMatrix to create the orthogonal projection matrix

let projection:mat4 = mat4.create(); Ortho (projection, 0, 800, 600, 0, -100,100); mat4. Ortho (projection, 0, 800, 600, 0, -100,100);Copy the code

The parameters of ortho method respectively represent the left, right, bottom and top pixel coordinates of the pixel coordinate system, as well as the near and far distances from the viewpoint on the z axis, where the viewpoint is set to 0,0,0, and these constraints constitute a box, as shown in the figure below

The vertices inside this box will be mapped to OpenGL’s coordinate space by the projection matrix, and notice that the nearest and farthest points here are z-near and -far, so if near and far are 0 to 100, then only the vertices whose z-axes are -100 to 0 will be projected. The figure below shows the frame space of OpenGL

Finally, the matrix is transferred and applied by modifying Vertex Shader

let vertexShaderCode = "attribute vec4 position;" + "uniform mat4 projection;" + "void main() {" + "gl_Position = projection * position;" + "} ";Copy the code
let uniformLoc = gl.getUniformLocation(program, "projection");
gl.uniformMatrix4fv(uniformLoc, false, projection);
Copy the code

2. Perspective projection matrix

Example complete project code, in chapter2, section3-1 directory

The purpose of perspective projection matrix is to simulate the visual effects of the real world. How to create a perspective projection matrix

let projection:mat4 = mat4.create(); Mat4. Projection (projection, 60/180 * math.pi, 800/600,0.001,1000);Copy the code

There are four parameters in total, namely, FOvy, Aspect, near, and far. Fovy represents the Angle of view, and the unit is radian. Aspect is the aspect ratio of the canvas. The difference here is that near and far have to be greater than 0. Here is the bounding box corresponding to the perspective matrix

The enclosing box here is a trapezoidal cone, and the internal vertices are converted to the OpenGL coordinate system space by perspective projection matrix. As can be seen from the figure above, the larger the perspective fovy is, the more vertices can be projected to the OpenGL coordinate system space, which means the larger the field of view. The aspect ratio of the plane near the point of view and the plane away from the point of view are both aspects.

Adjust the vertex coordinates so that the triangle is in view

Let are = [0, 0.5, 3, 0.5, 0.5, 3, 0.5, 0.5, 3,];Copy the code

Here the z-axis uses -3, between -0.001 and -1000, and you can try changing the z-axis to see what difference it makes.

The view matrix

Example complete project code, in chapter2, section3-2 directory

The view matrix simulates a real world camera. It works by converting vertices from the world coordinate system to the view coordinate system. It can be easily understood by the following diagram, assuming that there is an observation point and an object being observed in a one-dimensional coordinate system

The function of the view matrix is to transform the coordinates of the observed object into the coordinate system with the observation point as the origin.

In the projection matrix, we set the viewpoint at point (0,0,0), so we need to convert the vertex coordinates to the coordinate system where the viewpoint is point (0,0,0) through the view matrix before handing the vertex to the projection matrix for processing. So let’s go back to the 3d coordinate system, and let’s say that the transformation matrix of the virtual camera is the CameraMatrix, and what we want to do is figure out what the transformation matrix of the vertices should be when the camera transformation matrix is the identity matrix

Let's say that the vertex transformation matrix is ModelMatrix let's say that the vertex is in a coordinate system where the camera transformation matrix is the identity matrix and the transformation matrix is ModelMatrix_camera then we have the following equation ModelMatrix_camera = inverse(CameraMatrix) * ModelMatrix Inverse represents the inverse of the CameraMatrixCopy the code

The view matrix is actually the inverse of the virtual camera transformation matrix.

Next, practice the view matrix with glMatrix. First, create the view matrix

let view:mat4 = mat4.create(); Mat4. LookAt (view, [0, 1], [0, 0], [0, 0]).Copy the code

LookAt creates the view matrix directly and does not need to invert it. The second parameter of the method, eye, represents the position of the virtual camera, the third parameter, Center, represents the coordinate of the camera pointing forward, and the fourth parameter, Up, represents the direction of the camera facing up. Once you have the view matrix, you need to pass it to the Vertex Shader and multiply it by the projection matrix

let vertexShaderCode = "attribute vec4 position;" + "uniform mat4 projection;" + "uniform mat4 view;" + "void main() {" + "gl_Position = projection * view * position;" + "} ";Copy the code

Note that the multiplication order is made by projection * view * position. View * position converts the vertex coordinates to the coordinate system of view 0,0,0 and is projected by projection into OpenGL coordinate system. Since matrix multiplication follows the associative law, it can be interpreted as (projection * (view * position)).

The model of matrix

The model matrix is easily understood as the transformation matrix of the vertices themselves, including translation, scaling, and rotation, and can be supported by simple modifications to the Vertex Shader

let vertexShaderCode = "attribute vec4 position;" + "uniform mat4 projection;" + "uniform mat4 view;" + "uniform mat4 model;" + "void main() {" + "gl_Position = projection * view * model * position;" + "} ";Copy the code

Vertices are first transformed by model matrix and then by projection * View to OpenGL coordinate system. In general, each individual model will have a model matrix and all models will share the view and projection matrix. In this way, real world object and camera movements are simulated.

Model browser

Example complete project code, in chapter2, section3-3 directory

The following is a model browser example to consolidate the knowledge of the three matrices, in order to better show the effect of the example, you need to upgrade the rendered objects from triangles to cubes

Render the cube

A cube has 6 faces. Divide each face into 2 triangles, which is 12 triangles. Assuming the cube’s length, width and height are all 1, you can get its vertex data

Let are = [/ / on the X axis plane 0.5, 0.5, 0.5, 1, 0, 0, 0.5, 0.5, 0.5, 1, 0, 0, 0.5, 0.5, 0.5, 1, 0, 0, 0.5, 0.5, 0.5, 1, 0, 0, 0.5, 0.5, 0.5, 1, 0, 0, 0.5, 0.5, 0.5, 1, 0, 0, 0.5, 0.5, 0.5, 1, 0, 0, 0.5, 0.5, 0.5, 1, 0, 0, 0.5, 0.5, 0.5, 1, 0, 0, 0.5, 0.5, 0.5, 1, 0, 0, 0.5, 0.5, 0.5, 1, 0, 0, 0.5, 0.5, 0.5, 1, 0, 0, / / on the Y axis plane - 0.5, 0.5, 0.5, 0, 1, 0, 0.5, 0.5, 0.5, 0, 1, 0, 0.5, 0.5, 0.5, 0, 1, 0, 0.5, 0.5, 0.5, 0, 1, 0, 0.5, 0.5, 0.5, 0, 1, 0, 0.5, 0.5, 0.5, 0, 1, 0, 0.5, 0.5, 0.5, 0, 1, 0, 0.5, 0.5, 0.5, 0, 1, 0, 0.5, 0.5, 0.5, 0, 1, 0, 0.5, 0.5, 0.5, 0. 1, 0, 0.5, 0.5, 0.5, 0, 1, 0, 0.5, 0.5, 0.5, 0, 1, 0, / / Z axis on the plane - 0.5, 0.5, 0.5, 0, 0, 1, 0.5, 0.5, 0.5, 0, 0, 1, 0.5, 0.5, 0.5, 0, 0, 1, 0.5, 0.5, 0.5, 0, 0, 1, 0.5, 0.5, 0.5, 0, 0, 1, 0.5, 0.5, 0.5, 0, 0, 1, 0.5, 0.5, 0.5, 0. 0, 1, 0.5, 0.5, 0.5, 0, 0, 1, 0.5, 0.5, 0.5, 0, 0, 1, 0.5, 0.5, 0.5, 0, 0, 1, 0.5, 0.5, 0.5, 0, 0, 1, 0.5, 0.5, -0.5, 0, 0, 1,];Copy the code

The first three floats of each vertex data are positions and the last three are colors. Setting a different color for each face is a better way to see the perspective change. To render the cube’s faces in the correct Z-order, you need to turn on the depth test function

gl.enable(gl.DEPTH_TEST);
Copy the code

This step is mentioned in the WebGL drawing process, which removes the rasterized blocked pixels so that the faces look the right order on the z-axis.

Modify the Vertex Shader

Add projection, View, and Model to Vertex Shader

let vertexShaderCode = "attribute vec4 position;" + "attribute vec3 color;" + "varying vec3 frag_color;" + "uniform mat4 projection;" + "uniform mat4 view;" + "uniform mat4 model;" + "void main() {" + "frag_color = color;" + "gl_Position = projection * view * model * position;" + "} ";Copy the code

Set the projection matrix

To achieve real world perspective, select a perspective projection matrix

let projection:mat4 = mat4.create(); Mat4. Projection (projection, 60/180 * math.pi, canvas.width/Canvas.height,0.001,1000);Copy the code

Set the perspective projection matrix to the Projection of the Vertex Shader

let uniformLoc = gl.getUniformLocation(program, "projection");
gl.uniformMatrix4fv(uniformLoc, false, projection);
Copy the code

Control view matrix

This example mainly controls the view matrix to change the perspective, so as to achieve the purpose of viewing 3D objects from all angles. The view matrix is mainly controlled by the following three variables

var cameraAngleAroundY = 0;
var cameraAngleAroundX = 0;
var cameraDistanceFromTarget = 3;
Copy the code

The virtual camera moves the cameraDistanceFromTarget from 0,0,0 along the positive z axis, then rotates the cameraAngleAroundX radian around the X axis and finally rotates the cameraAngleAroundY radian around the Y axis. To get the position of the virtual camera, hold the 3D object at 0,0,0 with the camera facing 0,0,0, and get the view matrix

Function updateViewMatrix() {let xRot = mat4.create(); mat4.identity(xRot); mat4.rotateX(xRot, xRot, cameraAngleAroundX); let yRot = mat4.create(); mat4.identity(yRot); mat4.rotateY(yRot, yRot, cameraAngleAroundY); let translate = mat4.create(); mat4.identity(translate); mat4.translate(translate, translate, [0, 0, cameraDistanceFromTarget]); let finalMatrix = mat4.create(); mat4.multiply(finalMatrix, yRot, xRot); mat4.multiply(finalMatrix, finalMatrix, translate); let pos = vec4.create(); vec4.set(pos, 0, 0, 0, 1); vec4.transformMat4(pos, pos, finalMatrix); Let xRotPlus = mat4.create(); let xRotPlus = mat4.create(); let xRotPlus = mat4.create(); mat4.identity(xRotPlus); Mat4. rotateX(xRotPlus, xRotPlus, CameraAngLearoundx-1/180.0 * math.pi); let finalMatrixPlus = mat4.create(); mat4.multiply(finalMatrixPlus, yRot, xRotPlus); mat4.multiply(finalMatrixPlus, finalMatrixPlus, translate); let posPlus = vec4.create(); vec4.set(posPlus, 0, 0, 0, 1); vec4.transformMat4(posPlus, posPlus, finalMatrixPlus); let cameraUp: vec3 = vec3.create(); vec3.set(cameraUp, posPlus[0] - pos[0], posPlus[1] - pos[1], posPlus[2] - pos[2]); vec3.normalize(cameraUp, cameraUp); mat4.lookAt(viewMatrix, [pos[0],pos[1],pos[2]], [0, 0, 0], cameraUp); }Copy the code

By multiplying the two rotation matrices and translation matrices, the position of the virtual camera can be calculated. However, with the rotation of the virtual camera, we hope that its up direction should always be tangent to the circle around the X-axis, so we use the point rotated 1 degree more along the X-axis and the position of the virtual camera to find the up vector.

Set the view projection matrix to the Vertex Shader’s view

uniformLoc = gl.getUniformLocation(program, "view");
gl.uniformMatrix4fv(uniformLoc, false, viewMatrix);
Copy the code

Setting the model matrix

There is no need to control the transformation of the model through the model matrix, so set an identity matrix

let model:mat4 = mat4.create();
mat4.identity(model);
uniformLoc = gl.getUniformLocation(program, "model");
gl.uniformMatrix4fv(uniformLoc, false, model);
Copy the code

Use the mouse to control the view matrix related variables

Control the cameraAngleAroundY and cameraAngleAroundX variables by mouse x, y change, control the cameraDistanceFromTarget variable by mouse wheel, achieve the effect of mouse control virtual camera change

var lastX:number = 0, lastY: number = 0, isMouseDown = false;

window.onmousedown = (evt: MouseEvent) => {
    lastX = evt.x;
    lastY = evt.y;
    isMouseDown = true;
}

window.onmousemove = (evt: MouseEvent) => {
    if (isMouseDown) {
        let xdelta = evt.x - lastX;
        let ydelta = evt.y - lastY;
        lastX = evt.x;
        lastY = evt.y;
        cameraAngleAroundX -= ydelta / 100;
        cameraAngleAroundY -= xdelta / 100;
    }
}

window.onmouseup = () => {
    isMouseDown = false;
}

window.onmousewheel = (evt: any) => {
    let wheelDelta = evt["wheelDelta"];
    cameraDistanceFromTarget -= wheelDelta / 30.0;
    cameraDistanceFromTarget = Math.min(Math.max(1, cameraDistanceFromTarget), 10);
}
Copy the code

conclusion

This paper mainly introduces three important matrix concepts, through these three kinds of matrix, we can better simulate the real 3D world. The perspective projection matrix provides a visual effect, the view matrix simulates a real world camera, and the model matrix gives each object its own characteristics of motion.

practice

  1. For the 3D browser example, add back and forth left and right motion mode. Press w, S to move the camera back and forth, and press A, D to move the camera left and right, keeping the camera always facing the negative z axis