视角 Transform

大家好,我是西瓜,你现在看到的是 2021 iThome 铁人赛『如何在网页中绘制 3D 场景?从 WebGL 的基础开始说起』系列文章的第 13 篇文章。本系列文章从 WebGL 基本运作机制以及使用的原理开始介绍,最後建构出绘制 3D、光影效果之网页。介绍完 WebGL 运作方式与 2D transform 後,本章节讲述的是建构并 transform 渲染成 3D 物件,如果在阅读本文时觉得有什麽未知的东西被当成已知的,可能可以在前面的文章中找到相关的内容

有了 matrix4.perspective() 使用眼睛/相机的方式进行成像,反而让画面变成一片惨白,假设把 3D 物件本身的 transform 取消,也就是 worldMatrix 设定成 matrix4.identity(),那麽 3D 物件与 frustum 区域的相对关系看起来像是这样:

perspective-obj-visualized-210826210735.svg

3D 物件不在 frustum 中,因此什麽都看不到,我们当然可以直接把 3D 物件做 translate 移动到 frustum 中,但是在现实生活中,如果架设好了一个场景,放了很多物件,而相机的位置不对,这时候我们会移动的是相机,而不是整个场景,这就是接下来要做的事情

viewMatrix 与视角 transform

先前在制作作用在 a_position 上的 u_matrix 之前,会先产生两个矩阵相乘:viewMatrixworldMatrix,绘制多个物件时 viewMatrix 为同一颗相机/画面下的所有物件共用,worldMatrix 则为物件本身的 transform,会因为不同物件而异;接下来要加入的视角 transform 想当然尔为同一颗相机/画面下的所有物件共用,因此 viewMatrix 除了 clip space 的 transform 之外,也要开始包含视角相关的 transform,成为名符其实的 viewMatrix

虽然笔者才刚说我们不应该移动整个场景来符合相机位置(就放在 viewMatrix 这部份的抽象来说确实像是移动相机本身),但是视角 transform 本身能做的事情就是移动场景,所有的 a_position / 物件都会经过视角 transform:

clip space <= perspective <= 视角 transform <= worldMatrix transform <= a_position
              ^...viewMatrix transform...^

假设我们想要把相机放在这个位置:

camera-transform

把移动相机的 transform 叫做 cameraMatrix ,因为视角 transform 只能移动整个场景,所以视角 transform 可以当成『反向做 cameraMatrix』,对整个场景做反向的 cameraMatrix,在定义 cameraMatrix 这件事情上,就真的抽象成移动相机了;反向这件事可以靠反矩阵(inverse matrix) 来做到,为什麽的部份笔者只好再推荐一次 3Blue1Brown 的 Youtube 影片 -- 反矩阵、行空间与零空间,看了精美的动画之後,希望大家就能理解为什麽反矩阵在几何上等於把某个 transform 反向的做

以实际作用在 a_position 向量上的视角 transform(等於 inverse(cameraMatrix))来说,看起来像是这样:

inverse-camera-transform

最後当然得在 lib/matrix.js 中加入 matrix4.inverse(m) 的实做,不过,有做过三阶反矩阵运算就会知道计算量不小,更何况我们需要的是 4x4 四阶,写成程序码的公式行数实在不少,笔者就不直接放在文章中了,有需要可以在下方完整程序码中找到

实做视角 transform 到 viewMatrix

要实做上图红色箭头的 cameraMatrix,看起来用 matrix4.translate() 就足够,因此在主程序 render() 内定义 viewMatrix 之前加上:

const cameraMatrix = matrix4.translate(250, 0, 400);

并且像是上面说的,使用 matrix4.inverse(cameraMatrix) 加入 viewMatrix:

const viewMatrix = matrix4.multiply(
  matrix4.perspective(state.fieldOfView, gl.canvas.width / gl.canvas.height, 0.1, 2000),
  matrix4.inverse(cameraMatrix),
);

存档看看结果:

back-of-3d-model

这个 P 上下颠倒了,而且现在现在看到的不是正面,在建构模型时,除了其背面往 +z 长之外,我们使用 2D 时惯用的 y 轴正向为萤幕下方方向,这些与 3D 中使用的惯例都是相反的,同时也可以看本篇第一张图中标示的『萤幕上方方向』想像看到的画面;如果要重新定位 P 形状的 3D 模型实在是太累,因此笔者选择修改 rotationX 的预设值转 210 度过去:

 // async function setup() {
 // ...
   return {
     gl,
     program, attributes, uniforms,
     buffers, modelBufferArrays,
     state: {
       fieldOfView: 45 * Math.PI / 180,
       translate: [150, 100, 0],
-      rotate: [degToRad(30), degToRad(30), degToRad(0)],
+      rotate: [degToRad(210), degToRad(30), degToRad(0)],
       scale: [1, 1, 1],
     },
   }
 }

HTML 那边的预设值也得改一下,看起来就好多罗:

perspective-front-3d-p

完整程序码可以在这边找到:


<<:  前端工程师也能开发全端网页:挑战 30 天用 React 加上 Firebase 打造社群网站|Day13 文章页面

>>:  Day-12 於新电视上再次闪耀的那颗 SEGA 土星

Day11 事件修饰符(2)

上次介绍完前面两个修饰符,今天就来把它学习完吧!!! .stop .prevent .capture...

C# 入门数据类型(补充)

前面我们有简单的介绍了一下数组,后来考虑了一下,还是在这里增加一个补充说明一下。本篇除了说明数组外,...

Day 4 - 介绍Laravel Eloquent ORM

前一篇介绍了如何运用 Laravel 框架设计模式规划大型专案,当中有提到Model,今天就来介绍这...

[Matplotlib] tight_layout()

With tight_layout() import numpy as np import mat...

简报版-第六章-谈谈手机APP下载安全吗?

其实原本最初规画想要做Index方式的纪录,然後多增加一些没写到的面向 不过,总是计画赶不上变化 ...