文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

一起学 WebGL:感受三维世界之视图矩阵

2024-11-30 14:37

关注

大家好,我是前端西瓜哥。之前绘制的图形都是在 XY 轴所在的平面上,这次我们来加入一点深度信息 z,带你走入三维的世界。

视图矩阵

对于一个立方体来说,我们从它的正前方看,不管距离它多远,也只能看到一个二维的正方形。因此我们需要引入 视图矩阵(view matrix)。它的作用就像是一个在特定位置的摄像头。

视图矩阵需要三个信息:

  1. 视点位置;
  2. 观察点位置;
  3. 上方向;

就好比我们站在某个位置看一个模型,眼睛的位置就是观察点,目光落在的点就是视点。我们站着看,上方向 就是朝上(y 正轴方向),躺着看就是水平方向,倒立着看就是朝下(y 负半轴方向)。

实际上我们并没有一个真正的视口,我们的世界坐标的正中心永远是原点,z 负半轴指向观察者。

但我们可以利用相对运动的原理,给图形做一个相反的操作,比如我往右边走 1 个单位去看模型,其实等价于我不懂,模型向左移动 1 个单位,它们的效果是一样的。

视图矩阵的算法实现如下:

function createViewMatrix(eyeX, eyeY, eyeZ, atX, atY, atZ, upX, upY, upZ) {
  const normalize = (v) => {
    const length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
    return [v[0] / length, v[1] / length, v[2] / length];
  };
  const subtract = (v1, v2) => {
    return [v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]];
  };
  const cross = (v1, v2) => {
    return [
      v1[1] * v2[2] - v1[2] * v2[1],
      v1[2] * v2[0] - v1[0] * v2[2],
      v1[0] * v2[1] - v1[1] * v2[0]
    ];
  };

  const zAxis = normalize(subtract([eyeX, eyeY, eyeZ], [atX, atY, atZ]));
  const xAxis = normalize(cross([upX, upY, upZ], zAxis));
  const yAxis = normalize(cross(zAxis, xAxis));

  return new Float32Array([
    xAxis[0],
    yAxis[0],
    zAxis[0],
    0,
    xAxis[1],
    yAxis[1],
    zAxis[1],
    0,
    xAxis[2],
    yAxis[2],
    zAxis[2],
    0,
    -(xAxis[0] * eyeX + xAxis[1] * eyeY + xAxis[2] * eyeZ),
    -(yAxis[0] * eyeX + yAxis[1] * eyeY + yAxis[2] * eyeZ),
    -(zAxis[0] * eyeX + zAxis[1] * eyeY + zAxis[2] * eyeZ),
    1
  ]);
}

视图坐标的实现细节不讲,不重要。(顺带一提,上面的算法由 Github Copilot 生成)

通过这个方法计算出矩阵,传入到顶点着色器的矩阵变量中,和顶点位置计算即可。

const viewMatrix = createViewMatrix(0.2, 0.25, 0.25, 0, 0, 0, 0, 1, 0);
const u_ViewMatrix = gl.getUniformLocation(gl.program, "u_ViewMatrix");
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix);

其他的创建缓冲区的逻辑就不讲了,之前的文章都讲过了。

完整代码

贴一下完整代码:


const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl");

const vertexShaderSrc = `
attribute vec4 a_Position;
attribute vec4 a_Color;
uniform mat4 u_ViewMatrix;
varying vec4 v_Color;
void main() {
 gl_Position = u_ViewMatrix * a_Position;
 v_Color = a_Color;
}
`;

const fragmentShaderSrc = `
precision mediump float;
varying vec4 v_Color;
void main() {
  gl_FragColor = v_Color;
}
`;


// 创建顶点渲染器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSrc);
gl.compileShader(vertexShader);
// 创建片元渲染器
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSrc);
gl.compileShader(fragmentShader);
// 程序对象
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
gl.program = program;

// prettier-ignore
const verticesColors = new Float32Array([
  // 下方的红色三角形
  0, 0.2, -0.2, 1, 0, 0,  // 位置和颜色信息
  -0.2, -0.2, -0.2, 1, 0, 0,  
  0.2, -0.2, -0.2, 1, 0, 0,  
  // 上方的黄色三角形
  0, 0.2, 0, 1, 1, 0,  // 点 1 的位置和颜色信息
  -0.2, -0.2, 0, 1, 1, 0,  // 点 2
  0.2, -0.2, 0, 1, 1, 0,  // 点 3
]);
// 每个数组元素的字节数
const SIZE = verticesColors.BYTES_PER_ELEMENT;

// 创建缓存对象
const vertexColorBuffer = gl.createBuffer();
// 绑定缓存对象到上下文
gl.bindBuffer(gl.ARRAY_BUFFER, vertexColorBuffer);
// 向缓存区写入数据
gl.bufferData(gl.ARRAY_BUFFER, verticesColors, gl.STATIC_DRAW);

// 获取 a_Position 变量地址
const a_Position = gl.getAttribLocation(gl.program, "a_Position");
gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, SIZE * 6, 0);
gl.enableVertexAttribArray(a_Position);

const a_Color = gl.getAttribLocation(gl.program, "a_Color");
gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, SIZE * 6, SIZE * 3);
gl.enableVertexAttribArray(a_Color);


// prettier-ignore
// 取消下面一行注释,并注释下下一行代码,可观察没有使用视图矩阵的原始效果
// const viewMatrix = new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0,0,0,1]);
const viewMatrix = createViewMatrix(0.2, 0.25, 0.25, 0, 0, 0, 0, 1, 0);
const u_ViewMatrix = gl.getUniformLocation(gl.program, "u_ViewMatrix");
gl.uniformMatrix4fv(u_ViewMatrix, false, viewMatrix);


// 清空画布,并指定颜色
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// 绘制三角形
gl.drawArrays(gl.TRIANGLES, 0, 6);

function createViewMatrix(eyeX, eyeY, eyeZ, atX, atY, atZ, upX, upY, upZ) {
  const normalize = (v) => {
    const length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
    return [v[0] / length, v[1] / length, v[2] / length];
  };
  const subtract = (v1, v2) => {
    return [v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]];
  };
  const cross = (v1, v2) => {
    return [
      v1[1] * v2[2] - v1[2] * v2[1],
      v1[2] * v2[0] - v1[0] * v2[2],
      v1[0] * v2[1] - v1[1] * v2[0]
    ];
  };

  const zAxis = normalize(subtract([eyeX, eyeY, eyeZ], [atX, atY, atZ]));
  const xAxis = normalize(cross([upX, upY, upZ], zAxis));
  const yAxis = normalize(cross(zAxis, xAxis));

  return new Float32Array([
    xAxis[0],
    yAxis[0],
    zAxis[0],
    0,
    xAxis[1],
    yAxis[1],
    zAxis[1],
    0,
    xAxis[2],
    yAxis[2],
    zAxis[2],
    0,
    -(xAxis[0] * eyeX + xAxis[1] * eyeY + xAxis[2] * eyeZ),
    -(yAxis[0] * eyeX + yAxis[1] * eyeY + yAxis[2] * eyeZ),
    -(zAxis[0] * eyeX + zAxis[1] * eyeY + zAxis[2] * eyeZ),
    1
  ]);
}

demo 地址:

https://codesandbox.io/s/ijxwu2?file=/index.js。

这里我绘制了红色和黄色两个三角形,红色在更下边,z 为 -0.2,黄色在上面一点,z 为 0。

应用视图矩阵前的效果。因为两者大小相同,黄色三角形完全盖住了红色。

应用视图矩阵后:

结尾

今天简单讲了下让我们指定一个位置观察模型的方法:视图矩阵。

之前我们也讲了一个叫做模型矩阵的玩意,模型矩阵就好比一个三维软件,我们将一个模型导入到场景中,移动它的位置、缩放它的尺寸,旋转一下之类的。视图矩阵就好比通过一个摄像机的视角看到的世界。

不知道你发现没有,这里的两个三角形并没有近大远小的透视效果。此外,当我们的观察点位置非常靠右或靠左的时候,三角形会缺失部分。

关于这点,我会在下节讲解 可视空间,解答这些问题。

来源:前端西瓜哥内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯