iT邦幫忙

2021 iThome 鐵人賽

DAY 13
1
Modern Web

如何在網頁中繪製 3D 場景?從 WebGL 的基礎開始說起系列 第 13

視角 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

完整程式碼可以在這邊找到:


上一篇
Perspective 3D 成像
下一篇
使相機看著目標
系列文
如何在網頁中繪製 3D 場景?從 WebGL 的基礎開始說起30

尚未有邦友留言

立即登入留言