• 【视觉高级篇】20 # 如何用WebGL绘制3D物体?


    说明

    【跟月影学可视化】学习笔记。

    如何用 WebGL 绘制三维立方体

    我们知道立方体有8个顶点,6个面,在 WebGL 中,需要用 12 个三角形来绘制它。把每个面的顶点分开,需要 24 个顶点。

    在这里插入图片描述
    绘制 3D 图形与绘制 2D 图形有一点不一样,必须要开启深度检测和启用深度缓冲区。

    在 WebGL 中,可以通过 gl.enable(gl.DEPTH_TEST),来开启深度检测。

    在清空画布的时候,也要用 gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT),来同时清空颜色缓冲区和深度缓冲区。

    DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>如何用 WebGL 绘制三维立方体title>
            <style>
                canvas {
                    border: 1px dashed salmon;
                }
            style>
        head>
        <body>
            <canvas width="512" height="512">canvas>
            <script src="./common/lib/gl-renderer.js">script>
            <script type="module">
                const vertex = `
                    attribute vec3 a_vertexPosition;
                    attribute vec4 color;
    
                    varying vec4 vColor;
    
                    void main() {
                        gl_PointSize = 1.0;
                        vColor = color;
                        gl_Position = vec4(a_vertexPosition, 1);
                    }
                `;
    
                const fragment = `
                    #ifdef GL_ES
                    precision highp float;
                    #endif
    
                    varying vec4 vColor;
    
                    void main() {
                        gl_FragColor = vColor;
                    }
                `;
    
                const canvas = document.querySelector("canvas");
                // 开启深度检测
                const renderer = new GlRenderer(canvas, {
                    depth: true
                });
                const program = renderer.compileSync(fragment, vertex);
                renderer.useProgram(program);
    
                // 用来生成立方体 6 个面的 24 个顶点,以及 12 个三角形的索引
                function cube(size = 1.0, colors = [[1, 0, 0, 1]]) {
                    const h = 0.5 * size;
                    // 立方体的顶点
                    const vertices = [
                        [-h, -h, -h],
                        [-h, h, -h],
                        [h, h, -h],
                        [h, -h, -h],
                        [-h, -h, h],
                        [-h, h, h],
                        [h, h, h],
                        [h, -h, h],
                    ];
    
                    const positions = [];
                    const color = [];
                    const cells = [];
    
                    let colorIdx = 0;
                    let cellsIdx = 0;
                    const colorLen = colors.length;
    
                    function quad(a, b, c, d) {
                        [a, b, c, d].forEach((i) => {
                            positions.push(vertices[i]);
                            color.push(colors[colorIdx % colorLen]);
                        });
                        cells.push(
                            [0, 1, 2].map(i => i + cellsIdx),
                            [0, 2, 3].map(i => i + cellsIdx),
                        );
                        colorIdx++;
                        cellsIdx += 4;
                    }
                    // 立方体的六个面
                    quad(1, 0, 3, 2);
                    quad(4, 5, 6, 7);
                    quad(2, 3, 7, 6);
                    quad(5, 4, 0, 1);
                    quad(3, 0, 4, 7);
                    quad(6, 5, 1, 2);
    
                    return { positions, color, cells };
                }
    
                const geometry = cube(1.0, [
                    [250/255, 128/255, 114/255, 1], // salmon rgb(250 128 114)
                    [218/255, 165/255, 32/255, 1],// goldenrod rgb(218, 165, 32)
                    [46/255, 139/255, 87/255, 1], // seagreen rgb(46 139 87)
                    [255/255, 192/255, 203/255, 1], // pink rgb(255, 192, 203)
                    [135/255, 206/255, 235/255, 1],// skyblue rgb(135, 206, 235)
                    [106/255, 90/255, 205/255, 1], // slateblue rgb(106, 90, 205)
                ]);
    
                renderer.setMeshData([
                    {
                        positions: geometry.positions,
                        attributes: {
                            color: geometry.color,
                        },
                        cells: geometry.cells,
                    },
                ]);
                renderer.render();
            script>
        body>
    html>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119

    在这里插入图片描述

    投影矩阵:变换 WebGL 坐标系

    上面朝向我们的面应该是 goldenrod 颜色, WebGL 默认的剪裁坐标的 z 轴方向,的确是朝内的。WebGL 坐标系就是一个左手系而不是右手系。下面我们需要将 WebGL 的坐标系从左手系转换为右手系。

    实际上就是将 z 轴坐标方向反转,对应的齐次矩阵如下:

    [
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, -1, 0,
      0, 0, 0, 1
    ]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>投影矩阵:变换 WebGL 坐标系title>
            <style>
                canvas {
                    border: 1px dashed rgb(250, 128, 114);
                }
            style>
        head>
        <body>
            <canvas width="512" height="512">canvas>
            <script src="./common/lib/gl-renderer.js">script>
            <script type="module">
                const vertex = `
                    attribute vec3 a_vertexPosition;
                    attribute vec4 color;
    
                    varying vec4 vColor;
                    uniform mat4 projectionMatrix;
    
                    void main() {
                        gl_PointSize = 1.0;
                        vColor = color;
                        gl_Position = projectionMatrix * vec4(a_vertexPosition, 1);
                    }
                `;
    
                const fragment = `
                    #ifdef GL_ES
                    precision highp float;
                    #endif
    
                    varying vec4 vColor;
    
                    void main() {
                        gl_FragColor = vColor;
                    }
                `;
    
                const canvas = document.querySelector("canvas");
                // 开启深度检测
                const renderer = new GlRenderer(canvas, {
                    depth: true
                });
                const program = renderer.compileSync(fragment, vertex);
                renderer.useProgram(program);
    
                // 用来生成立方体 6 个面的 24 个顶点,以及 12 个三角形的索引
                function cube(size = 1.0, colors = [[1, 0, 0, 1]]) {
                    const h = 0.5 * size;
                    // 立方体的顶点
                    const vertices = [
                        [-h, -h, -h],
                        [-h, h, -h],
                        [h, h, -h],
                        [h, -h, -h],
                        [-h, -h, h],
                        [-h, h, h],
                        [h, h, h],
                        [h, -h, h],
                    ];
    
                    const positions = [];
                    const color = [];
                    const cells = [];
    
                    let colorIdx = 0;
                    let cellsIdx = 0;
                    const colorLen = colors.length;
    
                    function quad(a, b, c, d) {
                        [a, b, c, d].forEach((i) => {
                            positions.push(vertices[i]);
                            color.push(colors[colorIdx % colorLen]);
                        });
                        cells.push(
                            [0, 1, 2].map(i => i + cellsIdx),
                            [0, 2, 3].map(i => i + cellsIdx),
                        );
                        colorIdx++;
                        cellsIdx += 4;
                    }
                    // 立方体的六个面
                    quad(1, 0, 3, 2);
                    quad(4, 5, 6, 7);
                    quad(2, 3, 7, 6);
                    quad(5, 4, 0, 1);
                    quad(3, 0, 4, 7);
                    quad(6, 5, 1, 2);
    
                    return { positions, color, cells };
                }
    
                const geometry = cube(1.0, [
                    [250/255, 128/255, 114/255, 1], // salmon rgb(250 128 114)
                    [218/255, 165/255, 32/255, 1],// goldenrod rgb(218, 165, 32)
                    [46/255, 139/255, 87/255, 1], // seagreen rgb(46 139 87)
                    [255/255, 192/255, 203/255, 1], // pink rgb(255, 192, 203)
                    [135/255, 206/255, 235/255, 1],// skyblue rgb(135, 206, 235)
                    [106/255, 90/255, 205/255, 1], // slateblue rgb(106, 90, 205)
                ]);
    
                // 将 z 轴坐标方向反转,对应的齐次矩阵如下,转换坐标的齐次矩阵,又被称为投影矩阵(ProjectionMatrix)
                renderer.uniforms.projectionMatrix = [
                    1, 0, 0, 0,
                    0, 1, 0, 0,
                    0, 0, -1, 0,
                    0, 0, 0, 1,
                ];
    
                renderer.setMeshData([
                    {
                        positions: geometry.positions,
                        attributes: {
                            color: geometry.color,
                        },
                        cells: geometry.cells,
                    },
                ]);
                renderer.render();
            script>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127

    在这里插入图片描述

    模型矩阵:让立方体旋转起来

    用立方体沿 x、y、z 轴的旋转来生成模型矩阵。以 x、y、z 三个方向的旋转得到三个齐次矩阵,然后将它们相乘,就能得到最终的模型矩阵。

    DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>模型矩阵:让立方体旋转起来title>
            <style>
                canvas {
                    border: 1px dashed rgb(250, 128, 114);
                }
            style>
        head>
        <body>
            <canvas width="512" height="512">canvas>
            <script src="./common/lib/gl-renderer.js">script>
            <script type="module">
                import { multiply } from './common/lib/math/functions/Mat4Func.js';
    
                const vertex = `
                    attribute vec3 a_vertexPosition;
                    attribute vec4 color;
    
                    varying vec4 vColor;
                    uniform mat4 projectionMatrix;
                    uniform mat4 modelMatrix;
                    
                    void main() {
                        gl_PointSize = 1.0;
                        vColor = color;
                        gl_Position = projectionMatrix * modelMatrix * vec4(a_vertexPosition, 1);
                    }
                `;
    
                const fragment = `
                    #ifdef GL_ES
                    precision highp float;
                    #endif
    
                    varying vec4 vColor;
    
                    void main() {
                        gl_FragColor = vColor;
                    }
                `;
    
                const canvas = document.querySelector("canvas");
                // 开启深度检测
                const renderer = new GlRenderer(canvas, {
                    depth: true
                });
                const program = renderer.compileSync(fragment, vertex);
                renderer.useProgram(program);
    
                // 用来生成立方体 6 个面的 24 个顶点,以及 12 个三角形的索引
                function cube(size = 1.0, colors = [[1, 0, 0, 1]]) {
                    const h = 0.5 * size;
                    // 立方体的顶点
                    const vertices = [
                        [-h, -h, -h],
                        [-h, h, -h],
                        [h, h, -h],
                        [h, -h, -h],
                        [-h, -h, h],
                        [-h, h, h],
                        [h, h, h],
                        [h, -h, h],
                    ];
    
                    const positions = [];
                    const color = [];
                    const cells = [];
    
                    let colorIdx = 0;
                    let cellsIdx = 0;
                    const colorLen = colors.length;
    
                    function quad(a, b, c, d) {
                        [a, b, c, d].forEach((i) => {
                            positions.push(vertices[i]);
                            color.push(colors[colorIdx % colorLen]);
                        });
                        cells.push(
                            [0, 1, 2].map(i => i + cellsIdx),
                            [0, 2, 3].map(i => i + cellsIdx),
                        );
                        colorIdx++;
                        cellsIdx += 4;
                    }
                    // 立方体的六个面
                    quad(1, 0, 3, 2);
                    quad(4, 5, 6, 7);
                    quad(2, 3, 7, 6);
                    quad(5, 4, 0, 1);
                    quad(3, 0, 4, 7);
                    quad(6, 5, 1, 2);
    
                    return { positions, color, cells };
                }
    
                const geometry = cube(1.0, [
                    [250/255, 128/255, 114/255, 1], // salmon rgb(250 128 114)
                    [218/255, 165/255, 32/255, 1],// goldenrod rgb(218, 165, 32)
                    [46/255, 139/255, 87/255, 1], // seagreen rgb(46 139 87)
                    [255/255, 192/255, 203/255, 1], // pink rgb(255, 192, 203)
                    [135/255, 206/255, 235/255, 1],// skyblue rgb(135, 206, 235)
                    [106/255, 90/255, 205/255, 1], // slateblue rgb(106, 90, 205)
                ]);
    
                // 将 z 轴坐标方向反转,对应的齐次矩阵如下,转换坐标的齐次矩阵,又被称为投影矩阵(ProjectionMatrix)
                renderer.uniforms.projectionMatrix = [
                    1, 0, 0, 0,
                    0, 1, 0, 0,
                    0, 0, -1, 0,
                    0, 0, 0, 1,
                ];
    
                function fromRotation(rotationX, rotationY, rotationZ) {
                    let c = Math.cos(rotationX);
                    let s = Math.sin(rotationX);
                    const rx = [
                        1, 0, 0, 0,
                        0, c, s, 0,
                        0, -s, c, 0,
                        0, 0, 0, 1,
                    ];
    
                    c = Math.cos(rotationY);
                    s = Math.sin(rotationY);
                    const ry = [
                        c, 0, s, 0,
                        0, 1, 0, 0,
                        -s, 0, c, 0,
                        0, 0, 0, 1,
                    ];
    
                    c = Math.cos(rotationZ);
                    s = Math.sin(rotationZ);
                    const rz = [
                        c, s, 0, 0,
                        -s, c, 0, 0,
                        0, 0, 1, 0,
                        0, 0, 0, 1,
                    ];
    
                    const ret = [];
                    multiply(ret, rx, ry);
                    multiply(ret, ret, rz);
    
                    return ret;
                }
    
                renderer.setMeshData([
                    {
                        positions: geometry.positions,
                        attributes: {
                            color: geometry.color,
                        },
                        cells: geometry.cells,
                    },
                ]);
    
                let rotationX = 0;
                let rotationY = 0;
                let rotationZ = 0;
                function update() {
                    rotationX += 0.003;
                    rotationY += 0.005;
                    rotationZ += 0.007;
                    renderer.uniforms.modelMatrix = fromRotation(rotationX, rotationY, rotationZ);
                    requestAnimationFrame(update);
                }
                update();
    
                renderer.render();
            script>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178

    在这里插入图片描述

    如何用 WebGL 绘制圆柱体

    圆柱体的两个底面都是圆,可以用割圆的方式对圆进行简单的三角剖分,然后把圆柱的侧面用上下两个圆上的顶点进行三角剖分。

    在这里插入图片描述

    DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>如何用 WebGL 绘制圆柱体title>
            <style>
                canvas {
                    border: 1px dashed rgb(250, 128, 114);
                }
            style>
        head>
        <body>
            <canvas width="512" height="512">canvas>
            <script src="./common/lib/gl-renderer.js">script>
            <script type="module">
                import { multiply } from './common/lib/math/functions/Mat4Func.js';
                
                const vertex = `
                    attribute vec3 a_vertexPosition;
                    attribute vec4 color;
    
                    varying vec4 vColor;
                    uniform mat4 projectionMatrix;
                    uniform mat4 modelMatrix;
                    
                    void main() {
                        gl_PointSize = 1.0;
                        vColor = color;
                        gl_Position = projectionMatrix * modelMatrix * vec4(a_vertexPosition, 1.0);
                    }
                `;
    
                const fragment = `
                    #ifdef GL_ES
                    precision highp float;
                    #endif
    
                    varying vec4 vColor;
    
                    void main() {
                        gl_FragColor = vColor;
                    }
                `;
    
                const canvas = document.querySelector("canvas");
                // 开启深度检测
                const renderer = new GlRenderer(canvas, {
                    depth: true
                });
                const program = renderer.compileSync(fragment, vertex);
                renderer.useProgram(program);
    
                function cylinder(radius = 1.0, height = 1.0, segments = 30, colorCap = [0, 0, 1, 1], colorSide = [1, 0, 0, 1]) {
                    const positions = [];
                    const cells = [];
                    const color = [];
                    const cap = [[0, 0]];
                    const h = 0.5 * height;
    
                    // 顶和底的圆
                    for(let i = 0; i <= segments; i++) {
                        const theta = Math.PI * 2 * i / segments;
                        const p = [radius * Math.cos(theta), radius * Math.sin(theta)];
                        cap.push(p);
                    }
    
                    positions.push(...cap.map(([x, y]) => [x, y, -h]));
                    for(let i = 1; i < cap.length - 1; i++) {
                        cells.push([0, i, i + 1]);
                    }
                    cells.push([0, cap.length - 1, 1]);
    
                    let offset = positions.length;
                    positions.push(...cap.map(([x, y]) => [x, y, h]));
                    for(let i = 1; i < cap.length - 1; i++) {
                        cells.push([offset, offset + i, offset + i + 1]);
                    }
                    cells.push([offset, offset + cap.length - 1, offset + 1]);
    
                    color.push(...positions.map(() => colorCap));
    
                    // 侧面
                    offset = positions.length;
                    for(let i = 1; i < cap.length; i++) {
                        const a = [...cap[i], h];
                        const b = [...cap[i], -h];
                        const nextIdx = i < cap.length - 1 ? i + 1 : 1;
                        const c = [...cap[nextIdx], -h];
                        const d = [...cap[nextIdx], h];
    
                        positions.push(a, b, c, d);
                        color.push(colorSide, colorSide, colorSide, colorSide);
                        cells.push([offset, offset + 1, offset + 2], [offset, offset + 2, offset + 3]);
                        offset += 4;
                    }
    
                    return { positions, cells, color };
                }
    
                const geometry = cylinder(0.2, 1.0, 400,
                    [250/255, 128/255, 114/255, 1], // salmon rgb(250 128 114)
                    [46/255, 139/255, 87/255, 1], // seagreen rgb(46 139 87)
                );
    
                // 将 z 轴坐标方向反转,对应的齐次矩阵如下,转换坐标的齐次矩阵,又被称为投影矩阵(ProjectionMatrix)
                renderer.uniforms.projectionMatrix = [
                    1, 0, 0, 0,
                    0, 1, 0, 0,
                    0, 0, -1, 0,
                    0, 0, 0, 1,
                ];
    
                function fromRotation(rotationX, rotationY, rotationZ) {
                    let c = Math.cos(rotationX);
                    let s = Math.sin(rotationX);
                    const rx = [
                        1, 0, 0, 0,
                        0, c, s, 0,
                        0, -s, c, 0,
                        0, 0, 0, 1,
                    ];
    
                    c = Math.cos(rotationY);
                    s = Math.sin(rotationY);
                    const ry = [
                        c, 0, s, 0,
                        0, 1, 0, 0,
                        -s, 0, c, 0,
                        0, 0, 0, 1,
                    ];
    
                    c = Math.cos(rotationZ);
                    s = Math.sin(rotationZ);
                    const rz = [
                        c, s, 0, 0,
                        -s, c, 0, 0,
                        0, 0, 1, 0,
                        0, 0, 0, 1,
                    ];
    
                    const ret = [];
                    multiply(ret, rx, ry);
                    multiply(ret, ret, rz);
    
                    return ret;
                }
    
                renderer.setMeshData([
                    {
                        positions: geometry.positions,
                        attributes: {
                            color: geometry.color,
                        },
                        cells: geometry.cells,
                    },
                ]);
    
                let rotationX = 0;
                let rotationY = 0;
                let rotationZ = 0;
                function update() {
                    rotationX += 0.003;
                    rotationY += 0.005;
                    rotationZ += 0.007;
                    renderer.uniforms.modelMatrix = fromRotation(rotationX, rotationY, rotationZ);
                    requestAnimationFrame(update);
                }
                update();
    
                renderer.render();
            script>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175

    在这里插入图片描述

    上面是个六棱柱,我们可以修改 cylinder 函数里的 segments 参数,比如:400,就可以得到近似的圆柱体了

    在这里插入图片描述

    使用法向量和法向量矩阵来实现点光源光照效果

    对于圆柱体来说,底面和顶面法线分别是 (0, 0, -1)(0, 0, 1),侧面的法向量可以通过三角网格来计算。

    因为几何体是由三角网格构成的,而法线是垂直于三角网格的线,如果要计算法线,我们可以借助三角形的顶点,使用向量的叉积定理来求。假设在一个平面内,有向量 a 和 b,n 是它们的法向量,那我们可以得到公式:n = a X b

    在这里插入图片描述

    在片元着色器中,拿到的是变换后的顶点坐标,需要对法向量也进行变换,可以通过一个矩阵来实现,这个矩阵叫做法向量矩阵(NormalMatrix)

    在顶点着色器中,计算位于(1,0,0)坐标处的点光源与几何体法线的夹角余弦。根据物体漫反射模型,光照强度等于光线与法向量夹角的余弦,就能在片元着色器叠加光照。

    在这里插入图片描述

    DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8" />
            <meta http-equiv="X-UA-Compatible" content="IE=edge" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <title>使用法向量和法向量矩阵来实现点光源光照效果title>
            <style>
                canvas {
                    border: 1px dashed rgb(250, 128, 114);
                }
            style>
        head>
        <body>
            <canvas width="512" height="512">canvas>
            <script src="./common/lib/gl-renderer.js">script>
            <script type="module">
                import { multiply } from './common/lib/math/functions/Mat4Func.js';
                import { cross, subtract, normalize } from './common/lib/math/functions/Vec3Func.js';
                import { normalFromMat4 } from './common/lib/math/functions/Mat3Func.js';
    
                const vertex = `
                    attribute vec3 a_vertexPosition;
                    attribute vec4 color;
                    attribute vec3 normal;
    
                    varying vec4 vColor;
                    varying float vCos;
                    uniform mat4 projectionMatrix;
                    uniform mat4 modelMatrix;
                    uniform mat3 normalMatrix;
                    
                    const vec3 lightPosition = vec3(1, 0, 0);
    
                    void main() {
                        gl_PointSize = 1.0;
                        vColor = color;
                        vec4 pos =  modelMatrix * vec4(a_vertexPosition, 1.0);
                        vec3 invLight = lightPosition - pos.xyz;
                        vec3 norm = normalize(normalMatrix * normal);
                        vCos = max(dot(normalize(invLight), norm), 0.0);
                        gl_Position = projectionMatrix * pos;
                    }
                `;
    
                const fragment = `
                    #ifdef GL_ES
                    precision highp float;
                    #endif
    
                    uniform vec4 lightColor;
                    varying vec4 vColor;
                    varying float vCos;
    
                    void main() {
                        gl_FragColor.rgb = vColor.rgb + vCos * lightColor.a * lightColor.rgb;
                        gl_FragColor.a = vColor.a;
                    }
                `;
    
                const canvas = document.querySelector("canvas");
                // 开启深度检测
                const renderer = new GlRenderer(canvas, {
                    depth: true
                });
                const program = renderer.compileSync(fragment, vertex);
                renderer.useProgram(program);
    
                function cylinder(radius = 1.0, height = 1.0, segments = 30, colorCap = [0, 0, 1, 1], colorSide = [1, 0, 0, 1]) {
                    const positions = [];
                    const cells = [];
                    const color = [];
                    const cap = [[0, 0]];
                    const h = 0.5 * height;
                    const normal = [];
    
                    // 顶和底的圆
                    for(let i = 0; i <= segments; i++) {
                        const theta = Math.PI * 2 * i / segments;
                        const p = [radius * Math.cos(theta), radius * Math.sin(theta)];
                        cap.push(p);
                    }
    
                    positions.push(...cap.map(([x, y]) => [x, y, -h]));
                    normal.push(...cap.map(() => [0, 0, -1]));
    
                    for(let i = 1; i < cap.length - 1; i++) {
                        cells.push([0, i, i + 1]);
                    }
                    cells.push([0, cap.length - 1, 1]);
    
                    let offset = positions.length;
                    positions.push(...cap.map(([x, y]) => [x, y, h]));
                    normal.push(...cap.map(() => [0, 0, 1]));
    
                    for(let i = 1; i < cap.length - 1; i++) {
                        cells.push([offset, offset + i, offset + i + 1]);
                    }
                    cells.push([offset, offset + cap.length - 1, offset + 1]);
    
                    color.push(...positions.map(() => colorCap));
    
                    const tmp1 = [];
                    const tmp2 = [];
                    // 侧面,这里需要求出侧面的法向量
                    offset = positions.length;
                    for(let i = 1; i < cap.length; i++) {
                        const a = [...cap[i], h];
                        const b = [...cap[i], -h];
                        const nextIdx = i < cap.length - 1 ? i + 1 : 1;
                        const c = [...cap[nextIdx], -h];
                        const d = [...cap[nextIdx], h];
    
                        positions.push(a, b, c, d);
    
                        const norm = [];
                        cross(norm, subtract(tmp1, b, a), subtract(tmp2, c, a));
                        normalize(norm, norm);
                        normal.push(norm, norm, norm, norm); // abcd四个点共面,它们的法向量相同
    
                        color.push(colorSide, colorSide, colorSide, colorSide);
                        cells.push([offset, offset + 1, offset + 2], [offset, offset + 2, offset + 3]);
                        offset += 4;
                    }
    
                    return { positions, cells, color, normal };
                }
    
                const geometry = cylinder(0.2, 1.0, 400,
                    [250/255, 128/255, 114/255, 1], // salmon rgb(250 128 114)
                    [46/255, 139/255, 87/255, 1], // seagreen rgb(46 139 87)
                );
    
                // 将 z 轴坐标方向反转,对应的齐次矩阵如下,转换坐标的齐次矩阵,又被称为投影矩阵(ProjectionMatrix)
                renderer.uniforms.projectionMatrix = [
                    1, 0, 0, 0,
                    0, 1, 0, 0,
                    0, 0, -1, 0,
                    0, 0, 0, 1,
                ];
    			
                renderer.uniforms.lightColor = [218/255, 165/255, 32/255, 0.6];// goldenrod rgb(218, 165, 32)
    
                function fromRotation(rotationX, rotationY, rotationZ) {
                    let c = Math.cos(rotationX);
                    let s = Math.sin(rotationX);
                    const rx = [
                        1, 0, 0, 0,
                        0, c, s, 0,
                        0, -s, c, 0,
                        0, 0, 0, 1,
                    ];
    
                    c = Math.cos(rotationY);
                    s = Math.sin(rotationY);
                    const ry = [
                        c, 0, s, 0,
                        0, 1, 0, 0,
                        -s, 0, c, 0,
                        0, 0, 0, 1,
                    ];
    
                    c = Math.cos(rotationZ);
                    s = Math.sin(rotationZ);
                    const rz = [
                        c, s, 0, 0,
                        -s, c, 0, 0,
                        0, 0, 1, 0,
                        0, 0, 0, 1,
                    ];
    
                    const ret = [];
                    multiply(ret, rx, ry);
                    multiply(ret, ret, rz);
    
                    return ret;
                }
    
                console.log(geometry);
    
                renderer.setMeshData([
                    {
                        positions: geometry.positions,
                        attributes: {
                            color: geometry.color,
                            normal: geometry.normal
                        },
                        cells: geometry.cells,
                    },
                ]);
    
                let rotationX = 0;
                let rotationY = 0;
                let rotationZ = 0;
                function update() {
                    rotationX += 0.003;
                    rotationY += 0.005;
                    rotationZ += 0.007;
                    const modelMatrix = fromRotation(rotationX, rotationY, rotationZ);
                    renderer.uniforms.modelMatrix = modelMatrix;
                    renderer.uniforms.normalMatrix = normalFromMat4([], modelMatrix);
                    requestAnimationFrame(update);
                }
                update();
    
                renderer.render();
            script>
        body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209

    在这里插入图片描述

  • 相关阅读:
    Nuxt - 每个页面单独设置 SEO 相关标签及网页标题、图标等(页面配置 head)
    信息化发展47
    ThreadX内核源码分析(SMP) - 核间通信(arm)
    matlab绘图函数plot和fplot的区别
    WPF —— Calendar日历控件详解
    SCHNOKA施努卡:电池模组回收铣削多功能一体机
    计蒜客:C10 第四部分:深度优先搜索基础 引爆炸弹
    用沃创云CRM系统获取客户的方式是什么?
    ⭐每天一道leetcode:21.合并两个有序链表(简单;双指针)
    智慧公厕:细致入微的城市贴心服务与便捷方便的生活配套
  • 原文地址:https://blog.csdn.net/kaimo313/article/details/126834378