• 【视觉高级篇】21 # 如何添加相机,用透视原理对物体进行投影?


    说明

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

    如何理解相机和视图矩阵?

    用一个三维坐标(Position)和一个三维向量方向(LookAt Target)来表示 WebGL 的三维世界的一个相机。要绘制以相机为观察者的图形,需要用一个变换,将世界坐标转换为相机坐标。这个变换的矩阵就是视图矩阵(ViewMatrix)

    怎么计算视图矩阵?

    1. 先计算相机的模型矩阵
    2. 然后对矩阵使用 lookAt 函数,得到的矩阵就是视图矩阵的逆矩阵。
    3. 最后再对这个逆矩阵求一次逆,就可以得到视图矩阵。

    用代码的方式表示:

    function updateCamera(eye, target = [0, 0, 0]) {
    	const [x, y, z] = eye;
    	// 设置相机初始位置矩阵 m
    	const m = new Mat4(
    		1, 0,0, 0,
    		0, 1, 0, 0,
    		0, 0, 1, 0,
    		x, y, z, 1,
    	);
    	const up = [0, 1, 0];
    	m.lookAt(eye, target, up).inverse();
    	renderer.uniforms.viewMatrix = m;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    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 { Mat4 } from './common/lib/math/Mat4.js';
                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 mat4 viewMatrix;
                    uniform mat3 normalMatrix;
                    
                    const vec3 lightPosition = vec3(1, 0, 0);
    
                    void main() {
                        gl_PointSize = 1.0;
                        vColor = color;
                        vec4 pos =  viewMatrix * modelMatrix * vec4(a_vertexPosition, 1.0);
                        vec4 lp = viewMatrix * vec4(lightPosition, 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 updateCamera(eye, target = [0, 0, 0]) {
                    const [x, y, z] = eye;
                    // 设置相机初始位置矩阵 m
                    const m = new Mat4(
                        1, 0,0, 0,
                        0, 1, 0, 0,
                        0, 0, 1, 0,
                        x, y, z, 1,
                    );
                    const up = [0, 1, 0];
                    m.lookAt(eye, target, up).inverse();
                    renderer.uniforms.viewMatrix = m;
                }
    
                // 设置相机位置
                updateCamera([0.5, 0, 0.5]);
    
                renderer.setMeshData([
                    {
                        positions: geometry.positions,
                        attributes: {
                            color: geometry.color,
                            normal: geometry.normal
                        },
                        cells: geometry.cells,
                    },
                ]);
    
                renderer.uniforms.modelMatrix = new Mat4(
                    1, 0, 0, 0,
                    0, 1, 0, 0,
                    0, 0, 1, 0,
                    0, 0, 0, 1,
                );
    
                function update() {
                    const modelViewMatrix = multiply([], renderer.uniforms.viewMatrix, renderer.uniforms.modelMatrix);
                    
                    renderer.uniforms.modelViewMatrix = modelViewMatrix;
                    renderer.uniforms.normalMatrix = normalFromMat4([], modelViewMatrix);
                    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

    在这里插入图片描述

    剪裁空间和投影对 3D 图像的影响

    WebGL 的默认坐标范围是从 -1 到 1 的。只有当图像的 x、y、z 的值在 -1 到 1 区间内才会被显示在画布上,而在其他位置上的图像都会被剪裁掉。

    给下面图形分别给 x、y、z 轴增加 0.5 的平移

    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);
                        vec4 lp = vec4(lightPosition, 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.5, 1.0, 30,
                    [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;
                }
    
                renderer.setMeshData([
                    {
                        positions: geometry.positions,
                        attributes: {
                            color: geometry.color,
                            normal: geometry.normal
                        },
                        cells: geometry.cells,
                    },
                ]);
    
                const rotationX = 0.5;
                const rotationY = 0.5;
                const rotationZ = 0;
    
                function update() {
                    const modelMatrix = fromRotation(rotationX, rotationY, rotationZ);
                    modelMatrix[12] = 0.5; // 给 x 轴增加 0.5 的平移
                    modelMatrix[13] = 0.5; // 给 y 轴增加 0.5 的平移
                    modelMatrix[14] = 0.5; // 给 z 轴增加 0.5 的平移
                    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

    在这里插入图片描述

    为了让图形在剪裁空间中正确显示,我们不能只反转 z 轴,还需要将图像从三维空间中投影到剪裁坐标内。

    正投影

    正投影是将物体投影到一个长方体的空间(又称为视景体),并且无论相机与物体距离多远,投影的大小都不变。正投影又叫做平行投影

    在这里插入图片描述

    下面 ortho 是计算正投影的函数,它的参数是视景体 x、y、z 三个方向的坐标范围,它的返回值就是投影矩阵

    // 计算正投影矩阵
    function ortho(out, left, right, bottom, top, near, far) {
    	let lr = 1 / (left - right);
    	let bt = 1 / (bottom - top);
    	let nf = 1 / (near - far);
    	out[0] = -2 * lr;
    	out[1] = 0;
    	out[2] = 0;
    	out[3] = 0;
    	out[4] = 0;
    	out[5] = -2 * bt;
    	out[6] = 0;
    	out[7] = 0;
    	out[8] = 0;
    	out[9] = 0;
    	out[10] = 2 * nf;
    	out[11] = 0;
    	out[12] = (left + right) * lr;
    	out[13] = (top + bottom) * bt;
    	out[14] = (far + near) * nf;
    	out[15] = 1;
    	return out;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    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 { Mat4 } from './common/lib/math/Mat4.js';
                import { multiply, ortho } 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 mat4 viewMatrix;
                    uniform mat3 normalMatrix;
                    
                    const vec3 lightPosition = vec3(1, 0, 0);
    
                    void main() {
                        gl_PointSize = 1.0;
                        vColor = color;
                        vec4 pos =  viewMatrix * modelMatrix * vec4(a_vertexPosition, 1.0);
                        vec4 lp = viewMatrix * vec4(lightPosition, 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)
                );
    
                function projection(left, right, bottom, top, near, far) {
                    return ortho([], left, right, bottom, top, near, far);
                }
    
                // 让视景体三个方向的范围都是 (-1, 1)
                const projectionMatrix = projection(-1, 1, -1, 1, -1, 1);
                renderer.uniforms.projectionMatrix = projectionMatrix;
    
                renderer.uniforms.lightColor = [218/255, 165/255, 32/255, 0.6];// goldenrod rgb(218, 165, 32)
    
                function updateCamera(eye, target = [0, 0, 0]) {
                    const [x, y, z] = eye;
                    // 设置相机初始位置矩阵 m
                    const m = new Mat4(
                        1, 0,0, 0,
                        0, 1, 0, 0,
                        0, 0, 1, 0,
                        x, y, z, 1,
                    );
                    const up = [0, 1, 0];
                    m.lookAt(eye, target, up).inverse();
                    renderer.uniforms.viewMatrix = m;
                }
    
                // 设置相机位置
                updateCamera([0.5, 0, 0.5]);
    
                renderer.setMeshData([
                    {
                        positions: geometry.positions,
                        attributes: {
                            color: geometry.color,
                            normal: geometry.normal
                        },
                        cells: geometry.cells,
                    },
                ]);
    
                renderer.uniforms.modelMatrix = new Mat4(
                    1, 0, 0, 0,
                    0, 1, 0, 0,
                    0, 0, 1, 0,
                    0, 0, 0, 1,
                );
    
                function update() {
                    const modelViewMatrix = multiply([], renderer.uniforms.viewMatrix, renderer.uniforms.modelMatrix);
                    
                    renderer.uniforms.modelViewMatrix = modelViewMatrix;
                    renderer.uniforms.normalMatrix = normalFromMat4([], modelViewMatrix);
                    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

    在这里插入图片描述

    透视投影

    透视投影离相机近的物体大,离相机远的物体小。与正投影不同,正投影的视景体是一个长方体,而透视投影的视景体是一个棱台。

    在这里插入图片描述
    下面 perspective 是计算透视投影的函数,它的参数有近景平面 near、远景平面 far、视角 fovy 和宽高比率 aspect,返回值也是投影矩阵。

    // 计算透视投影矩阵
    function perspective(out, fovy, aspect, near, far) {
    	let f = 1.0 / Math.tan(fovy / 2);
    	let nf = 1 / (near - far);
    	out[0] = f / aspect;
    	out[1] = 0;
    	out[2] = 0;
    	out[3] = 0;
    	out[4] = 0;
    	out[5] = f;
    	out[6] = 0;
    	out[7] = 0;
    	out[8] = 0;
    	out[9] = 0;
    	out[10] = (far + near) * nf;
    	out[11] = -1;
    	out[12] = 0;
    	out[13] = 0;
    	out[14] = 2 * far * near * nf;
    	out[15] = 0;
    	return out;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    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 { Mat4 } from './common/lib/math/Mat4.js';
                import { multiply, perspective } 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 mat4 viewMatrix;
                    uniform mat3 normalMatrix;
                    
                    const vec3 lightPosition = vec3(1, 0, 0);
    
                    void main() {
                        gl_PointSize = 1.0;
                        vColor = color;
                        vec4 pos =  viewMatrix * modelMatrix * vec4(a_vertexPosition, 1.0);
                        vec4 lp = viewMatrix * vec4(lightPosition, 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)
                );
    
                function projection(near = 0.1, far = 100, fov = 45, aspect = 1) {
                    return perspective([], fov * Math.PI / 180, aspect, near, far);
                }
    
                const projectionMatrix = projection();
                renderer.uniforms.projectionMatrix = projectionMatrix;
    
                renderer.uniforms.lightColor = [218/255, 165/255, 32/255, 0.6];// goldenrod rgb(218, 165, 32)
    
                function updateCamera(eye, target = [0, 0, 0]) {
                    const [x, y, z] = eye;
                    // 设置相机初始位置矩阵 m
                    const m = new Mat4(
                        1, 0,0, 0,
                        0, 1, 0, 0,
                        0, 0, 1, 0,
                        x, y, z, 1,
                    );
                    const up = [0, 1, 0];
                    m.lookAt(eye, target, up).inverse();
                    renderer.uniforms.viewMatrix = m;
                }
    
                // 设置相机位置
                updateCamera([1.5, 0, 1.5]);
    
                renderer.setMeshData([
                    {
                        positions: geometry.positions,
                        attributes: {
                            color: geometry.color,
                            normal: geometry.normal
                        },
                        cells: geometry.cells,
                    },
                ]);
    
                renderer.uniforms.modelMatrix = new Mat4(
                    1, 0, 0, 0,
                    0, 1, 0, 0,
                    0, 0, 1, 0,
                    0, 0, 0, 1,
                );
    
                function update() {
                    const modelViewMatrix = multiply([], renderer.uniforms.viewMatrix, renderer.uniforms.modelMatrix);
                    
                    renderer.uniforms.modelViewMatrix = modelViewMatrix;
                    renderer.uniforms.normalMatrix = normalFromMat4([], modelViewMatrix);
                    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

    在这里插入图片描述

    在透视投影下,距离观察者(相机)近的部分大,距离它远的部分小,这更符合真实世界中我们看到的效果。

    3D 绘图标准模型

    3D 绘图的标准模型也就是3D 绘制几何体的基本数学模型,标准模型一共有四个矩阵,它们分别是:投影矩阵、视图矩阵(ViewMatrix)、模型矩阵(ModelMatrix)、法向量矩阵(NormalMatrix)

    • 前三个矩阵用来计算最终显示的几何体的顶点位置
    • 第四个法向量矩阵用来实现光照等效果

    比较成熟的图形库,如 ThreeJSBabylonJSOGL:轻量级的图形库基本上都是采用这个标准模型来进行 3D 绘图的。

    如何使用 OGL 绘制基本的几何体

    OGL:https://github.com/oframe/ogl

    OGL 是一个小型、高效的 WebGL 库,目标是那些喜欢最小抽象层并有兴趣创建自己的着色器的开发人员。这个 API 是用 es6 模块编写的,没有任何依赖,与 ThreeJS 有很多相似之处,但是它与 WebGL 紧密耦合,而且功能少得多。在其设计中,该库做了必要的最低抽象,因此开发人员仍然可以轻松地将其与原生 WebGL 命令一起使用。保持较低的抽象层次有助于使库更易于理解和扩展,也使它作为 WebGL 学习资源更实用。

    OGL 库绘制几何体分成 7 个步骤:

    在这里插入图片描述
    下面我们参考这个 demo 来实操一下:https://oframe.github.io/ogl/examples/?src=base-primitives.html

    在这里插入图片描述

    demo 的源码

    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>使用 OGL 绘制基本的几何体title>
            <style>
                canvas {
                    border: 1px dashed rgb(250, 128, 114);
                }
            style>
        head>
        <body>
            <canvas width="512" height="512">canvas>
            <script type="module">
                import { Renderer, Camera, Transform, Plane, Sphere, Box, Cylinder, Torus, Program, Mesh } from './common/lib/ogl/index.mjs';
    
                // 1、创建 Renderer 对象
                const canvas = document.querySelector('canvas');
                const renderer = new Renderer({
                    canvas,
                    width: 512,
                    height: 512,
                    dpr: 2
                });
    
                const gl = renderer.gl;
                gl.clearColor(1, 1, 1, 1);
    
                // 2、通过 new Camera 来创建相机(默认创建出的是透视投影相机)
                const camera = new Camera(gl, {
                    fov: 35 // 视角设置为 35 度
                });
                // 位置设置为 (0, 1, 7)
                camera.position.set(0, 1, 7);
                // 朝向为 (0, 0, 0)
                camera.lookAt([0, 0, 0]);
    
                // 3、创建场景
                const scene = new Transform(); // OGL 使用树形渲染的方式,需要使用 Transform 元素,它可以添加子元素和设置几何变换
    
                // 4、创建几何体对象
                const planeGeometry = new Plane(gl); // 平面
                const sphereGeometry = new Sphere(gl); // 球体
                const cubeGeometry = new Box(gl); // 立方体
                const cylinderGeometry = new Cylinder(gl); // 圆柱
                const torusGeometry = new Torus(gl); // 环面
    
                // 5、创建 WebGL 程序
                const vertex = `
                    attribute vec3 position;
                    attribute vec3 normal;
                    uniform mat4 modelViewMatrix;
                    uniform mat4 projectionMatrix;
                    uniform mat3 normalMatrix;
                    varying vec3 vNormal;
                    void main() {
                        vNormal = normalize(normalMatrix * normal);
                        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                    }
                `;
    
                const fragment = `
                    precision highp float;
    
                    varying vec3 vNormal;
                    void main() {
                        vec3 normal = normalize(vNormal);
                        float lighting = dot(normal, normalize(vec3(-0.3, 0.8, 0.6)));
                        gl_FragColor.rgb = vec3(0.98, 0.50, 0.44) + lighting * 0.1;
                        gl_FragColor.a = 1.0;
                    }
                `;
    
                const program = new Program(gl, {
                    vertex,
                    fragment,
                    cullFace: null // 加上能使平面是双面的,不然旋转的时候会有一段空白
                });
    
                // 6、构建网格(Mesh)元素:设置不同的位置,然后将它们添加到场景 scene 中去
                // 平面
                const plane = new Mesh(gl, {geometry: planeGeometry, program});
                plane.position.set(0, 1.3, 0);
                plane.setParent(scene);
                // 球体
                const sphere = new Mesh(gl, {geometry: sphereGeometry, program});
                sphere.position.set(0, 0, 0);
                sphere.setParent(scene);
                // 立方体
                const cube = new Mesh(gl, {geometry: cubeGeometry, program});
                cube.position.set(0, -1.3, 0);
                cube.setParent(scene);
                // 圆柱
                const cylinder = new Mesh(gl, {geometry: cylinderGeometry, program});
                cylinder.position.set(-1.3, 0, 0);
                cylinder.setParent(scene);
                // 环面
                const torus = new Mesh(gl, {geometry: torusGeometry, program});
                torus.position.set(1.3, 0, 0);
                torus.setParent(scene);
    
                // 7、完成渲染
                requestAnimationFrame(update);
                function update() {
                    requestAnimationFrame(update);
                    
                    plane.rotation.x -= 0.02;
                    sphere.rotation.y -= 0.03;
                    cube.rotation.y -= 0.04;
                    cylinder.rotation.z -= 0.02;
                    torus.rotation.y -= 0.02;
    
                    renderer.render({scene, camera});
                }
            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

    在这里插入图片描述

    下面我们优化一下,让圆看起来更圆,然后这个几个图形的颜色渲染的不一样。

    圆可以通过加大参数 widthSegments 处理

    在这里插入图片描述

    颜色问题我们可以通过 Program 传不同颜色到 fragment 里去。

    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>使用 OGL 绘制基本的几何体2title>
            <style>
                canvas {
                    border: 1px dashed rgb(250, 128, 114);
                }
            style>
        head>
        <body>
            <canvas width="512" height="512">canvas>
            <script type="module">
                import { Renderer, Camera, Transform, Plane, Sphere, Box, Cylinder, Torus, Program, Mesh } from './common/lib/ogl/index.mjs';
    
                import { Vec3 } from "./common/lib/math/vec3.js";
    
                // 1、创建 Renderer 对象
                const canvas = document.querySelector('canvas');
                const renderer = new Renderer({
                    canvas,
                    width: 512,
                    height: 512,
                    dpr: 2
                });
    
                const gl = renderer.gl;
                gl.clearColor(1, 1, 1, 1);
    
                // 2、通过 new Camera 来创建相机(默认创建出的是透视投影相机)
                const camera = new Camera(gl, {
                    fov: 35 // 视角设置为 35 度
                });
                // 位置设置为 (0, 1, 7)
                camera.position.set(0, 1, 7);
                // 朝向为 (0, 0, 0)
                camera.lookAt([0, 0, 0]);
    
                // 3、创建场景
                const scene = new Transform(); // OGL 使用树形渲染的方式,需要使用 Transform 元素,它可以添加子元素和设置几何变换
    
                // 4、创建几何体对象
                const planeGeometry = new Plane(gl); // 平面
                const sphereGeometry = new Sphere(gl, {
                    widthSegments: 400
                }); // 球体
                const cubeGeometry = new Box(gl); // 立方体
                const cylinderGeometry = new Cylinder(gl); // 圆柱
                const torusGeometry = new Torus(gl); // 环面
    
                // 5、创建 WebGL 程序
                const vertex = `
                    attribute vec3 position;
                    attribute vec3 normal;
                    uniform mat4 modelViewMatrix;
                    uniform mat4 projectionMatrix;
                    uniform mat3 normalMatrix;
                    varying vec3 vNormal;
                    void main() {
                        vNormal = normalize(normalMatrix * normal);
                        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                    }
                `;
    
                const fragment = `
                    precision highp float;
    
                    varying vec3 vNormal;
                    uniform vec3 uColor;
    
                    void main() {
                        vec3 normal = normalize(vNormal);
                        float lighting = dot(normal, normalize(vec3(-0.3, 0.8, 0.6)));
                        gl_FragColor.rgb = uColor + lighting * 0.1;
                        gl_FragColor.a = 1.0;
                    }
                `;
    
                function createdProgram(r, g, b) {
                    return new Program(gl, {
                        vertex,
                        fragment,
                        uniforms:{
                            uColor:{
                                value: new Vec3(r, g, b)
                            }
                        },
                        cullFace: null // 加上能使平面是双面的,不然旋转的时候会有一段空白
                    })
                }
    
                // 6、构建网格(Mesh)元素:设置不同的位置,然后将它们添加到场景 scene 中去
                // 平面
                const plane = new Mesh(gl, {
                    geometry: planeGeometry,
                    program: createdProgram(250/255, 128/255, 114/255) // salmon rgb(250, 128, 114)
                });
                plane.position.set(0, 1.3, 0);
                plane.setParent(scene);
                // 球体
                const sphere = new Mesh(gl, {
                    geometry: sphereGeometry,
                    program: createdProgram(218/255, 165/255, 32/255) // goldenrod rgb(218, 165, 32)
                });
                sphere.position.set(0, 0, 0);
                sphere.setParent(scene);
                // 立方体
                const cube = new Mesh(gl, {
                    geometry: cubeGeometry,
                    program: createdProgram(46/255, 139/255, 87/255) // seagreen rgb(46, 139, 87)
                });
                cube.position.set(0, -1.3, 0);
                cube.setParent(scene);
                // 圆柱
                const cylinder = new Mesh(gl, {
                    geometry: cylinderGeometry,
                    program: createdProgram(135/255, 206/255, 235/255) // skyblue rgb(135, 206, 235)
                });
                cylinder.position.set(-1.3, 0, 0);
                cylinder.setParent(scene);
                // 环面
                const torus = new Mesh(gl, {
                    geometry: torusGeometry,
                    program: createdProgram(106/255, 90/255, 205/255) // slateblue rgb(106, 90, 205)
                });
                torus.position.set(1.3, 0, 0);
                torus.setParent(scene);
    
                // 7、完成渲染
                requestAnimationFrame(update);
                function update() {
                    requestAnimationFrame(update);
                    
                    plane.rotation.x -= 0.02;
                    sphere.rotation.y -= 0.03;
                    cube.rotation.y -= 0.04;
                    cylinder.rotation.z -= 0.02;
                    torus.rotation.y -= 0.02;
    
                    renderer.render({scene, camera});
                }
            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

    在这里插入图片描述

  • 相关阅读:
    助力水泥基建裂痕自动化巡检,基于yolov5融合ASPP开发构建多尺度融合目标检测识别系统
    Unity中的动画系统
    【K8S系列】深入解析k8s 网络插件—Antrea
    Maven版本管理
    Hive简介及安装配置
    Uniapp零基础开发学习笔记(2) - 简单格式检查和事件响应
    http 403
    git 本地多个账号错乱问题解决
    突出最强算法模型——回归算法 !!
    【排序】桶排序(c++)
  • 原文地址:https://blog.csdn.net/kaimo313/article/details/126834405