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

news2024/11/15 3:57:30

大家好,我是前端西瓜哥。之前绘制的图形都是在 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);

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

完整代码

贴一下完整代码:

/** @type {HTMLCanvasElement} */
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。

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

应用视图矩阵后:

结尾

我是前端西瓜哥,欢迎关注我,学习更多 WebGL 知识。

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/446805.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微服务下网关聚合Swagger文档、starter统一配置Swagger

一、starter实现统一配置微服务文档 把Swagger配置中的公共部分抽取出来Swagger与SpringBoot整合中,可能会由于版本问题出现各种问题 1、制作starter 参考: 【SpringBoot】自定义启动器 Starter【保姆级教程】用starter实现Oauth2中资源服务的统一配置用…

中级软件设计师备考---数据库系统1

目录 数据库模式数据库的设计过程E-R模型关系代数与元组演算 数据库模式 三级模式、两级映射 定义: 三级模式:外模式、概念模式和内模式;两级映射:外模式-概念模式映射、概念模式-内模式映射 外模式是用户看到的数据库的部分 概…

Linux的常见指令 -掌握

前言 为什么要学命令行? windows/苹果图形界面,是商业化的产物,也就是使用必须简单小白,才能有人用,so what?严格意义上讲,我们必须要学一下Linux命令行。因为企业后端有大量的服务器&#xff…

Web3.0:重新定义互联网的未来

💗wei_shuo的个人主页 💫wei_shuo的学习社区 🌐Hello World ! Web3.0:重新定义互联网的未来 Web3.0是指下一代互联网,也称为“分布式互联网”。相比于Web1.0和Web2.0,Web3.0具有更强的去中心化、…

如何快速查找下载外文文献,哪个文献下载网站好用

​​如何高效获取到自己需要的外文文献,最好的办法就是去文献来源数据库中查找,你需要的文献来源数据库有可能是Elsevier(sciencedirect)、也可能是Wiley Online Library、也有可能是IEEE等等,外文数据库机构太多了。这…

微信跨平台方案Donut快速上手

一、Donut简介 Donut 是微信开发出的多端框架,用于支持使用小程序原生语法开发移动应用的框架,开发者可以一次编码,就可以编译出小程序和 Android 以及 iOS 应用,实现多端开发。能够帮助企业有效降低多端应用开发的技术门槛和研发…

html画布绘制图形

一.题目要求 使用canvas标签完成直角三角形、矩形及圆形的绘制. 二.相关知识点 1.认识<canvas> 标签翻译&#xff1a;画布。 <canvas> 标签定义图形&#xff0c;比如图表和其他图像&#xff0c;您必须使用脚本来绘制图形。 <canvas> 标签只是图形容器&am…

[学习笔记] [机器学习] 2. Seaborn及练习案例

视频链接 数据集下载地址&#xff1a;https://download.csdn.net/download/weixin_44878336/87711308 目录 1. 绘制统计图形1.1 可视化数据的分布1.2 绘制单变量分布1.3 绘制双变量分布1.4 绘制成对的双变量分布 2. 用分类数据绘图2.1 类别散点图2.1.1 sns.stripplot()2.1.2 sn…

Hi3861 硬件 i2c 驱动 oled

一、前言 最近想用 3861 做个有意思的东西&#xff0c;记录一下开发过程。今天使用 3861 的硬件 i2c 驱动 oled。 硬件平台&#xff1a;Bearpi-Nano 软件SDK&#xff1a;润和sdk 二、搬一个 OLED 轮子 我之前写过一篇基于 stm32cubemx 快速使用 iic 接口 oled 的过程&#xff0…

一文搞懂C#实时调试时,程序数据库文件.pdb(符号文件)的作用。延伸搞懂Debug/Release、AnyCPU(首选32位)/x86/x64/ARM的区别

一、准备工作 MS引用&#xff1a;在 Visual Studio 调试器&#xff08;C#、C、Visual Basic、F#&#xff09;中指定符号 (.pdb) 和源文件 MS引用&#xff1a;为 C#、ASP.NET 或 Visual Basic 项目 &#xff08;.NET Framework&#xff09; 生成符号文件 MS引用&#xff1a;用…

图解网络(三)——IP

文章目录 前言一、IP地址与MAC地址的作用二、IP基础2.1 基础2.2 IP 地址的分类 三、ping的工作原理3.1 ICMP 协议3.2 TCP发数据和ping的区别3.2.1 如果用的是TCP的方式去发送消息3.2.1 如果用的是ping 四、断网了&#xff0c;还能 ping 通 127.0.0.1 吗&#xff1f;4.1 什么是1…

编译和链接

目录 1. 程序的翻译环境和执行环境 2. 详解编译链接 2.1 翻译环境 2.2 编译本身也分为几个阶段&#xff1a; 2.2.1汇编过程的简略图 2.3讲解汇编过程的具体过程和要点 2.4运行环境 1. 程序的翻译环境和执行环境 在ANSIC的任何一种实现中&#xff0c;存在两个不同的环境。…

C++基础入门(一)

前言 欢迎进入C世界&#xff01;这是一种令人兴奋的语言&#xff0c;它在C语言的基础上添加上了对面向对象编程和泛型编程的支持&#xff0c;在20世纪90年代便是最重要的编程语言之一&#xff0c;并在21世纪仍保持强劲势头。C继承了C语言的高效、简洁、快速和可移植性的传统。C…

【计算机视觉 | 图像分割】Segment Anything论文讲解

文章目录 一、前言二、论文出发点三、创新思路四、方法4.1 Segment Anything Task4.2 Segment Anything Model4.3 Segment Anything Data Engine4.4 Segment Anything Dataset 五、结果 一、前言 论文&#xff1a;https://arxiv.org/pdf/2304.02643.pdf 项目&#xff1a;https…

数据结构(一)—— 数组

文章目录 一、数组基础二、题1. 704 二分查找2. 27 移除元素3. 977 有序数组的平方4. 209 长度最小的子数组 一、数组基础 数组是存放在连续内存空间上的相同类型数据的集合&#xff0c;也就是说数组内存空间的地址是连续的。 因为数组的在内存空间的地址是连续的&#xff0c;…

COIG:首个大规模、可商用的中文开源指令数据!

文 | ZenMoore ChatGPT 出现后的这几个月&#xff0c;整个学界和业界的疯狂想必大家都已经看到了。 然而&#xff0c;在背各种各样的动物还有山海经怪兽的英语单词的时候&#xff0c;其实不妨停下来想一想复现中文 ChatGPT 到底缺什么&#xff1f;缺大模型吗&#xff1f;缺工程…

C++的引用

目录 引用概念 引用的用法 做函数形参 优点一 优点二 引用做返回值 让我们更深入的了解引用与指针 语法层引用与指针完全不同的概念 站在底层的角度看指针与引用 笔记类型文章 引用概念 在语言层面上&#xff1a;引用不是定义新的变量&#xff0c;而是给已存在变量再…

大模型带来的Web复兴,会是昙花一现吗?

大家是不是对GPT、对话式AI、生成式AI之类的话题&#xff0c;已经有点审美疲劳了&#xff1f; 写这篇文章之前&#xff0c;我有点犹豫&#xff0c;究竟还要不要接着讨论GPT了。最终决定写&#xff0c;是觉得个人用户、开发者&#xff0c;以及正在紧锣密鼓训大模型的AI公司和云厂…

Go | 一分钟掌握Go | 1 - 安装Go

作者&#xff1a;Mars酱 声明&#xff1a;本文章由Mars酱原创&#xff0c;部分内容来源于网络&#xff0c;如有疑问请联系本人。 转载&#xff1a;欢迎转载&#xff0c;转载前先请联系我&#xff01; 为什么学Go 为什么要学Go&#xff1f;网上太多广告和说辞了&#xff0c;什…

“SCSA-T学习导图+”首发:路由技术之OSPF入门

本期引言&#xff1a; 路由技术是网络环境中&#xff0c;为不同的节点传输数据提供传输路径的技术&#xff0c;企业网络的拓扑一般会比较复杂&#xff0c;不同的部门或者总部和分支可能处于不同的网段中&#xff0c;此时就需要使用路由协议来连接不同的网段&#xff0c;实现数…