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
- 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