Web GL enters the three-dimensional world

Time:2022-5-25
  • Three dimensional objects are composed of triangles, so we need to draw each triangle of the object one by one as before, and finally draw the whole three-dimensional object, but there is a significant difference between three-dimensional and two-dimensional. When drawing two-dimensional objects, we only need to consider X and Y information, but when drawing three-dimensional objects, we also need to consider their depth information
Viewpoint and line of sight

The position of the observation point is called the viewpoint, and the ray starting from the viewpoint along the observation direction is called the line of sight

Point of view, point of view

In order to determine the state of the observer, two pieces of information need to be obtained: viewpoint: that is, the position of the observer, and observation target point, that is, the point where the observation target is located, which can be used to determine the line of sight In addition, if we want to draw the observed scenery on the screen, we also need up direction With these three items, we can determine the state of the observer

Web GL enters the three-dimensional world

Viewpoint, line of sight, observation point
  • Viewpoint: the position of the observer in three-dimensional space and the starting point of the line of sight
  • Observation target point: the point where the observed target is located. The line of sight starts from the viewpoint, passes through the target point and continues to extend,
  • Up direction: the upward direction in the image finally drawn on the screen
    In webgl, we can create a view matrix with the above three vectors, and then pass the matrix to the shader The view matrix can represent the observer’s state, including the observer’s viewpoint, observation target point and up direction information In three View matrix encapsulated in JS Setlookat method
Matrix4.setLookAt(eyeX,eyeY,eyeZ,atX,atY,atZ,upX,upY,upZ)
//Eyex, eyez, eyez stands for viewpoint
//ATX, ATY and ATZ represent observation points
//Upx, upy and upz represent the up direction

In webgl, the default state of the observer should be

  • The viewpoint is at the coordinate origin by default
  • The line of sight is the negative direction of Z, the observation point is (0,0, – 1), and the upward direction is the negative half axis of Y, i.e. (0,1,0)
    The example code is as follows
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body onload="main()">
        <canvas width="400" height="400"></canvas>
    </body>
    <script type="x-shader/x-vertex">
        attribute vec4 a_Position;
        attribute vec4 a_Color;
        uniform mat4 u_ViewMatrix;
        varying  vec4 v_Color;
        void main () {
            gl_Position = u_ViewMatrix*a_Position;
            v_Color = a_Color;
        }
    </script>
    <script type="x-shader/x-fragment">
        //Set the precision of floating-point numbers globally. Other types have default precision types. Floating-point numbers need to be set separately
        precision mediump float;
        varying  vec4 v_Color;
        void main () {
            gl_FragColor = v_Color;
        }
    </script>
    <script src="./jsm/util.js"></script>
    <script src="tool/cuon-matrix.js"></script>
    <script>
        function main() {
            const canvas = document.getElementById('webgl')
            const gl = canvas.getContext('webgl')
            const vertextShader = document.getElementById('vertextShader').innerText
            const fragmentShader = document.getElementById('fragmentShader').innerText
            if (!initShaders(gl, vertextShader, fragmentShader)) return
            if (!gl) return
            const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
            const a_Color = gl.getAttribLocation(gl.program, 'a_Color')
            const u_ViewMatrix = gl.getUniformLocation(gl.program, 'u_ViewMatrix')
            if (a_Position < 0) return
            //Create buffer
            let n = initVertexBuffers(gl, a_Position, a_Color)
            //Write data to buffer
            let viewMatrix = new Matrix4()
            viewMatrix.setLookAt(
                0.20, 0.25, 0.25, // viewpoint
                0.0, 0.0, 0.0, // observation point
                0, 1, 0, // up
            )
            gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix.elements)
            gl.drawArrays(gl.TRIANGLES, 0, n)
        }
        function initVertexBuffers(gl, a_Position, a_Color) {
            let verticesColors = new Float32Array([
                0.0, 0.5, -0.4, 1.0, 0.0, 0.0,
                -0.5, - 0.5, - 0.4, 1.0, 0.0, 0.0, // red triangle
                0.5, -0.5, -0.4, 0.1, 0.0, 0.0,

                0.5, 0.4, -0.2, 0.0, 1.0, 0.0,
                -0.5, 0.4, - 0.2, 0.0, 1.0, 0.0, // Green Triangle
                0.0, -0.6, -0.2, 0.0, 1.0, 0.0,

                0.0, 0.5, 0.0, 0.0, 0.0, 1.0,
                -0.5, - 0.5, 0.0, 0.0, 0.0, 1.0, // blue
                0.5, -0.5, 0.0, 0.0, 0.0, 1.0,
            ])
            var n = 9
            var vertexColorbuffer = gl.createBuffer()
            gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorbuffer)
            gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW)
            let fsize = verticesColors.BYTES_PER_ELEMENT;
            gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, fsize * 6, 0)
            gl.enableVertexAttribArray(a_Position)
            gl.clearColor(0.0, 0.0, 0.0, 1.0)
            gl.clear(gl.COLOR_BUFFER_BIT)
            gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, fsize * 6, fsize * 3)
            gl.enableVertexAttribArray(a_Color)
            return n
        }
    </script>
</html>

In fact, according to the custom observer state, drawing the scene seen by the observer is equivalent to translating, rotating and zooming with 3D objects using the default observer state

Visual range

Although we can place a 3D object anywhere in 3D space, web GL can only draw it when it is within its visual range In fact, not drawing objects outside the visual range is a means to reduce program overhead In addition to the limitations of horizontal and vertical range, webgl also limits the visual depth of the observer, that is, how far he can see. All these limitations, including horizontal perspective, vertical perspective and visual depth, define the visual space

visual space

There are two visual spaces

  • Cuboid visual space, also known as boxed space, is generated by orthographic projection
  • Pyramid visual space, generated by perspective projection
    Under perspective projection, the generated 3D scene looks more deep and natural. In contrast, orthophoto projection allows users to easily compare the size of objects in the scene, because the size of objects has nothing to do with their location
Working principle of box visual space of Orthophoto projection

The shape of the box visual space is shown in the figure below The visual space is determined by the front and back rectangular surfaces, which are called near clipping surface and far clipping surface respectively The first four vertices are (right, top, – near), (- left, top, – near) (left, – bottom, – near) (right, – bottom, – near) and the last four vertices are (right, top, far) (- left, top, far) (left, – bottom, far) (right, – bottom, far) What is displayed on canvas is the projection of the visible space object on the near cut plane If the width ratio of the clipping surface is different from that of the canvas, the picture will be compressed according to the width ratio of the canvas, and the object will be deformed

Web GL enters the three-dimensional world

image.png
Define box visual space

THREE. JS provides matrix4 The setortho method can be used to set the projection matrix

parameter Parameter description
left,right,bottom,top Specify the left and right boundaries of the near cut surface
bottom,top Upper and lower boundary of near cutting surface
near,far Specify the location of the near and far cut faces, that is, the near and far boundaries of the visual space
  • Note: the object always looks at far from near. If the value of near is greater than far, it means looking at the object from the opposite side
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body onload="main()">
        <canvas width="400" height="400"></canvas>
    </body>
    <script type="x-shader/x-vertex">
        attribute vec4 a_Position;
        attribute vec4 a_Color;
        uniform mat4 u_ProjMatrix;
        varying  vec4 v_Color;
        void main () {
            gl_Position = u_ProjMatrix*a_Position;
            v_Color = a_Color;
        }
    </script>
    <script type="x-shader/x-fragment">
        //Set the precision of floating-point numbers globally. Other types have default precision types. Floating-point numbers need to be set separately
        precision mediump float;
        varying  vec4 v_Color;
        void main () {
            gl_FragColor = v_Color;
        }
    </script>
    <script src="./jsm/util.js"></script>
    <script src="tool/cuon-matrix.js"></script>
    <script>
        let g_near =0.0,g_far = 0.5;
        function main() {
            const canvas = document.getElementById('webgl')
            const gl = canvas.getContext('webgl')
            const vertextShader = document.getElementById('vertextShader').innerText
            const fragmentShader = document.getElementById('fragmentShader').innerText
            if (!initShaders(gl, vertextShader, fragmentShader)) return
            if (!gl) return
            const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
            const a_Color = gl.getAttribLocation(gl.program, 'a_Color')
            const u_ProjMatrix = gl.getUniformLocation(gl.program, 'u_ProjMatrix')
            if (a_Position < 0) return
            let projMatrix = new Matrix4()
            //Create buffer
            let n = initVertexBuffers(gl, a_Position, a_Color);
            draw(gl,n,u_ProjMatrix,projMatrix);
            document.onkeydown = function(ev) {
                keyDown(ev, gl, n, u_ProjMatrix, projMatrix)
            }
        }
        function keyDown(ev, gl, n, u_ViewMatrix, viewMatrix) {
            //Push button
            console.log(ev.keyCode)
            switch(ev.keyCode) {
                case 39: {
                    g_near +=0.1;break
                };
                case 37: g_near -= 0.01;break;
                case 38: g_far +=0.01;break;
                case 40: g_far -=0.01;break;
            }
            draw(gl,n, u_ViewMatrix, viewMatrix)
        }
        function draw(gl,n, u_ViewMatrix, viewMatrix) {
            // debugger
            
            viewMatrix.setOrtho(-1,1,-1,1,g_near,g_far)
            console.log( g_near,g_far)
            // 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.drawArrays(gl.TRIANGLES, 0, n)
        }
        
        function initVertexBuffers(gl, a_Position, a_Color) {
            let verticesColors = new Float32Array([
                0.0, 0.5, -0.4, 1.0, 0.0, 0.0,
                -0.5, - 0.5, - 0.4, 1.0, 0.0, 0.0, // red triangle
                0.5, -0.5, -0.4, 0.1, 0.0, 0.0,

                0.5, 0.4, -0.2, 0.0, 1.0, 0.0,
                -0.5, 0.4, - 0.2, 0.0, 1.0, 0.0, // Green Triangle
                0.0, -0.6, -0.2, 0.0, 1.0, 0.0,

                0.0, 0.5, 0.0, 0.0, 0.0, 1.0,
                -0.5, - 0.5, 0.0, 0.0, 0.0, 1.0, // blue
                0.5, -0.5, 0.0, 0.0, 0.0, 1.0,
            ])
            var n = 9
            var vertexColorbuffer = gl.createBuffer()
            gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorbuffer)
            gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW)
            let fsize = verticesColors.BYTES_PER_ELEMENT;
            gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, fsize * 6, 0)
            gl.enableVertexAttribArray(a_Position)
            gl.clearColor(0.0, 0.0, 0.0, 1.0)
            gl.clear(gl.COLOR_BUFFER_BIT)
            gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, fsize * 6, fsize * 3)
            gl.enableVertexAttribArray(a_Color)

            return n
        }
    </script>
</html>

As can be seen from the above example, when the face near is at the origin, the specific of far can see the completed three triangles behind the third triangle. Far doesn’t understand. When near moves between the first triangle and the second triangle, only the second and third triangles can be seen This is because the first triangle is not in the visible range. Similarly, if near moves between the second and third triangles, the first and second triangles will not be visible. If near moves to the third triangle, they will not be visible

perspective projection

The visual space of perspective projection is shown in the figure below Perspective projection also has viewpoint, line of sight, near cut plane and far cut plane In this way, objects in visual space will be displayed, and objects outside visual space will not be displayed

Web GL enters the three-dimensional world

Perspective projection visual space

In three The setperspective method is defined in JS matrix to set the projection matrix

parameter Parameter description Parameter location
fov Specify vertical viewing angle 0
aspect Specifies the aspect ratio of the near cut face 1
near,far Distance between near cutting surface and far cutting surface 2,3
Correctly handle the context of objects

By default, webgl will draw graphics in the order of buffer, and the graphics drawn later will overwrite the graphics drawn earlier Because it’s efficient If the objects in the scene do not change, the observer’s state is unique There’s no problem with this, but if you want to keep moving your point of view and look at objects from different angles, you can’t decide the order of objects in advance In order to solve this problem, web GL provides the function of hidden surface elimination This function will help us eliminate those occluded surfaces. We can draw the scene safely without worrying about the order of objects in the buffer To enable the hidden surface elimination function, you need to follow the following steps

  • Turn on hidden surface elimination
    gl.enable(gl.DEPTH_TEST)
  • Clear the depth buffer before drawing
    gl.clear(gl.DEPTH_BUFFER_BIT)
    Use GL The clear () method clears the depth buffer Depth buffer is an intermediate object, which is used to help webgl eliminate hidden faces Webgl draws geometric figures in the color punch, and displays the color buffer on canvas after drawing If you want to eliminate the hidden surface, you must know the depth information of each geometry, and the depth buffer is used to store the depth information. Because the depth direction is usually the z-axis direction, we also call it Z buffer
Deep conflict

Hidden surface elimination is a complex and powerful feature of Web GL. In most cases, it can complete the task well However, problems arise when the two surfaces of a geometry or object are close This phenomenon is called deep conflict In order to solve this problem, web GL provides a mechanism called polygon offset to solve this problem This mechanism will automatically add an offset to the Z value The value of this offset is determined by the angle of the object surface relative to the observer Starting this mechanism requires the following two steps

  • Start polygon offset
    gl.enable(gl.POLY_OFFSET_FILL)
  • Specify the parameters for calculating the offset before drawing
    gl.polygonOffset(1.0,1.0)

Cube

As mentioned earlier, we draw a cube by drawing two triangles on each face of the cube, that is, a cube with 36 vertices But in fact, the cube has only 8 vertices, but 36 vertices are used, which increases the complexity of the code Now provides a more comprehensive way to draw cubes Firstly, the cube is divided into six faces. Each face is composed of two triangles, which is related to the two triangles in China As shown in the figure below

Web GL enters the three-dimensional world

image.png

Web GL provides drawing objects through vertex index gl. drawElement(mode,count,type,offset)

parameter Parameter description
mode Specifies how to draw
count Specifies the number of vertices to paint
type Index data type
offset Specifies where to start drawing in the index array

The sample code for drawing a cube is as follows

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title></title>
    </head>
    <body onload="main()">
        <canvas width="400" height="400"></canvas>
    </body>
    <script type="x-shader/x-vertex">
        attribute vec4 a_Position;
        uniform mat4 u_MvpMatrix;
        attribute vec4 a_Color;
        varying  vec4 v_Color;
        void main () {
            gl_Position = u_MvpMatrix *a_Position;
            v_Color = a_Color;
        }
    </script>
    <script src="tool/cuon-matrix.js"></script>
    <script type="x-shader/x-fragment">
        precision mediump float;
        varying  vec4 v_Color;
        void main () {
            gl_FragColor = v_Color;
        }
    </script>
    <script src="./jsm/util.js"></script>
    <script src="tool/cuon-matrix.js"></script>
    <script>
        function main() {
            const canvas = document.getElementById('webgl')
            const gl = canvas.getContext('webgl')
            const vertextShader = document.getElementById('vertextShader').innerText
            const fragmentShader = document.getElementById('fragmentShader').innerText
            if (!initShaders(gl, vertextShader, fragmentShader)) return
            if (!gl) return
            const u_MvpMatrix = gl.getUniformLocation(gl.program, 'u_MvpMatrix')
            // if (a_Position < 0) return
            // debugger
            let n = initVertexBuffers(gl)
            var viewMatrix = new Matrix4()
            viewMatrix.setLookAt(3,3,7,0,0,0,0,1,0)
            var modeMatrix = new Matrix4()
            modeMatrix.setRotate(0,0,0,1)
            
            let projMatrix = new Matrix4()
            projMatrix.setPerspective(30, canvas.width/canvas.height, 1, 100)
            var modeViewMatrix = projMatrix.multiply(viewMatrix.multiply(modeMatrix))
                gl.enable(gl.DEPTH_TEST)
            // mvpMatrix.setLookAt(3,3,7,0,0,0,0,1,0)
            gl.uniformMatrix4fv(u_MvpMatrix, false, modeViewMatrix.elements)
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
            gl.drawElements(gl.TRIANGLES, n, gl.UNSIGNED_BYTE, 0)
        }
        function initVertexBuffers(gl) {
            const a_Position = gl.getAttribLocation(gl.program, 'a_Position')
            const a_Color = gl.getAttribLocation(gl.program, 'a_Color')
            var verticesColors = new Float32Array([
                1.0, 1.0, 1.0, 1.0, 0.0, 0.0, // dot color 0
                -1.0, 1.0, 1.0, 1.0, 1.0, 0.0, // dot color 1
                -1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, // 2 dots
                
                1.0, - 1.0, 1.0, 0.8, 0.9, 0.2, // 3 dots
                
                1.0, - 1.0, - 1.0, 0.3, 0.5, 0.7, // 4 dots
                
                1.0, 1.0, - 1.0, 0.4, 0.6, 0.8, // 5 dots
                
                -1.0, 1.0, - 1.0, 0.5, 0.6, 0.7, // 6 dots
                -1.0, - 1.0, - 1.0, 0.9, 0.6, 0.3 // color 7
            ])
            var indices = new Uint8Array([
                0,1,2,0,2,3,
                0,3,4,0,4,5,
                0,5,6,0,6,1,
                1,6,7,1,7,2,
                7,4,3,7,3,2,
                4,7,6,4,6,5
            ])
            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
            gl.vertexAttribPointer(a_Position,3,gl.FLOAT,false,FSize*6,0)
            gl.enableVertexAttribArray(a_Position)
            
            gl.vertexAttribPointer(a_Color,3,gl.FLOAT,false,FSize*6,FSize*3)
            gl.enableVertexAttribArray(a_Color)
            var indicesBuffer = gl.createBuffer()
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER,indicesBuffer)
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER,indices,gl.STATIC_DRAW)
            return indices.length
        }
    </script>
</html>

Recommended Today

[redis] redis’ basic principles and solutions of cache breakdown, cache penetration and cache avalanche

Article catalogue Cache penetration principle resolvent Buffer breakdown principle resolvent Cache avalanche principle resolvent Cache penetration principle The data corresponding to the key does not exist in the database. Every request for the key cannot be obtained from the cache. The request will access the database. A large number of visits to the database may […]