這次沒有帶來遊戲啦,本來還是打算用一個小遊戲來介紹陰影,但是發現陰影這塊想完完整整介紹一次太大了,涉及到很多,再加上業務時間的緊張,所以就暫時放棄了遊戲,先好好介紹一遍webgl中的Matrix。
這篇文章算是webgl的基礎知識,因為如果想不走馬觀花的說陰影的話,需要打牢一定的基礎,文章中我盡力把知識點講的更易懂。內容偏向剛上手webgl的同學,至少知道著色器是什麼,webgl中drawElements這樣的API會使用~
文章的標題是Matrix is magic,矩陣對於3D世界來說確實是魔法一般的存在,說到webgl中的矩陣,PMatrix/VMatrix/MMatrix這三個大家相信不會陌生,那就正文let’s go~
1/ 矩陣的來源
剛剛有說到PMatrix/VMatrix/MMatrix這三個詞,他們中的Matrix就是矩陣的意思,矩陣是幹什麼的?用來改變頂點位置資訊的,先牢記這句話,然後我們先從canvas2D入手相信一下我們有一個100*100的canvas畫布,然後畫一個矩形
1 2 3 |
<canvas width="100" height="100"></canvas> ctx.rect(40, 40, 20, 20); ctx.fill(); |
程式碼很簡單,在畫布中間畫了一個矩形
現在我們希望將圓向左移動10px
1 2 |
ctx.rect(30, 40, 20, 20); ctx.fill(); |
結果如下:
原始碼地址:https://vorshen.github.io/3Dmaze/1.html
結果展示:
改變rect方法第一個引數就可以了,很簡單,因為rect()對應的就是一個矩形,是一個物件,canvas2D是物件級別的畫布操作,而webgl不是,webgl是片元級別的操作,我們操作的是頂點
用webgl如何畫一個矩形?地址如下,可以直接檢視原始碼
原始碼地址:https://vorshen.github.io/3Dmaze/2.html
結果展示:
這裡我們可以看到position這個陣列,這裡面存的就是矩形4個點的頂點資訊,我們可以通過操作改變其中點的值來改變位置(頁面原始碼也可以看到實現),但是捫心自問這樣不累嗎?有沒有可以一次性改變某個物體所有頂點的方式呢?
有,那就是矩陣,magic is coming
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
上面這個是一個單位矩陣(矩陣最基礎的知識這裡就不說了),我們用這個乘一個頂點(2,1,0)來看看
並沒有什麼變化啊!那我們換一個矩陣來看
1 0 0 1
0 1 0 0
0 0 1 0
0 0 0 1
如果你再多用幾個頂點試一下就會發現,無論我們用哪個頂點,都會得到這樣的一個x座標+1這樣一個結果
來,回憶一下我們之前的目的,現在是不是有了一種一次性改變頂點位置的方式呢?
2/ 矩陣規律介紹
剛剛我們改變了矩陣16個值中的一個,就使得矩陣有改變頂點的能力,我們能否總結一下矩陣各個值的規律呢?當然是可以的,如下圖
然後是經典的圍繞各個軸的旋轉矩陣(記憶的時候注意圍繞y軸旋轉時,幾個三角函式的符號……)
還有剪下(skew)效果的變換矩陣,這裡用個x軸的例子來體現
這裡都是某一種單一效果的變化矩陣,可以相乘配合使用的,很簡單。我們這裡重點來找一下規律,似乎所有的操作都是圍繞著紅框這一塊來的
其實也比較好理解,因為矩陣這裡每一行對應了個座標
那麼問題來了,最下面那行幹啥用的?
一個頂點,座標(x,y,z),這個是在笛卡爾座標系中的表示,在3D世界中我們會將其轉換為齊次座標系,也就是變成了(x,y,z,w),這樣的形式(之前那麼多圖中w=1)
矩陣的最後一行也就代表著齊次座標,那麼齊次座標有啥作用?很多書上都會說齊次座標可以區分一個座標是點還是向量,點的話齊次項是1,向量的話齊次項是0(所以之前圖中w=1)
對於webgl中的Matrix來說齊次項有什麼用處呢?或者說這個第四行改變了有什麼好處呢?一句話概括(敲黑板,劃重點)
它可以讓物體有透視的效果
舉個例子,大名鼎鼎的透視矩陣,如圖
在第四行的第三列就有值,而不像之前的是0;還有一個細節就是第四行的第四列是0,而不是之前的1
寫到這裡的時候我糾結了,要不要詳細的把正視和透視投影矩陣推導寫一下,但是考慮到篇幅,實在是不好放在這裡了,否則這篇文章要太長了,因為後面還有內容
大部分3D程式開發者可能不是很關注透視矩陣(PMatrix),只是知道有這一回事,用上這個矩陣可以近大遠小,然後程式碼上也就是glMatrix.setPerspective(……)一下就行了
所以決定後面單獨再寫一篇,專門說下正視透視矩陣的推導、矩陣的優化這些知識
這裡就暫且打住,我們先只考慮紅框部分的矩陣所帶來的變化
3/ webgl的座標系
我們前面bb了那麼多,可以總結一下就是“矩陣是用來改變頂點座標位置的!”,可以這麼理解對吧(不理解的再回去看下第二節裡面的各種圖)
那再看下文章開頭說的PMatrix/VMatrix/MMatrix三個,這三個貨都是矩陣啊,都是來改變頂點位置座標的,再加上矩陣也是可以結合的啊,為什麼這三個貨要分開呢?
首先,這三個貨分開說是為了方便理解,因為它們各司其職
MMatrix ---> 模型矩陣(用於物體在世界中變化)
VMatrix ---> 檢視矩陣(用於世界中攝像機的變化)
PMatrix ---> 透視矩陣
模型矩陣和檢視矩陣具體的原理和關係我之前那篇射擊小遊戲文章裡有說過,它們的改變的一般就是仿射變換,也就是平移、旋轉之類的變化
這裡稍微回憶一下原理,具體細節就不再說了
這兩貨一個是先旋轉,後平移(MMatrix),另一個是先平移,後旋轉(VMatrix)
但就這個小區別,讓人感覺一個是物體本身在變化,一個是攝像機在變化
好啦,重點說下PMatrix。這裡不是來推匯出它如何有透視效果的,這裡是講它除了透視的另一大隱藏的功能
說到這裡,先打一個斷點,然後我們思考另一個問題
canvas2D中和webgl中畫布的區別
它們在DOM中的寬高都是通過設定canvas標籤上width和height屬性來設定的,這很一致。但webgl中我們的座標空間是-1 ~ 1
(width=800,height=600中canvas2D中,矩形左頂點居中時,rect方法的前兩個引數)
(width=800,height=600中webgl中,矩形左頂點居中時,左頂點的座標)
我們會發現x座標小於-1或者大於1的的話就不會展示了(y同理),x和y很好理解,因為螢幕是2D的,畫布是2D的,2D就只有x,y,也就是我們直觀上所看到的東西
那z座標靠什麼來看到呢?
對比
首先至少有兩個物體,它們的z座標不同,這個z座標會決定它們在螢幕上顯示的位置(或者說覆蓋)的情景,讓我們試試看
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 |
var aPo = [ -0.2, -0.2, -0.5, 0.2, -0.2, -0.5, 0.2, 0.2, -0.5, -0.2, 0.2, -0.5 ]; var aIndex = [0, 1, 2, 0, 2, 3]; webgl.bindBuffer(webgl.ARRAY_BUFFER, webgl.createBuffer()); webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(aPo), webgl.STATIC_DRAW); webgl.vertexAttribPointer(aPosition, 3, webgl.FLOAT, false, 0, 0); webgl.vertexAttrib3f(aColor, 1, 0, 0); webgl.bindBuffer(webgl.ELEMENT_ARRAY_BUFFER, webgl.createBuffer()); webgl.bufferData(webgl.ELEMENT_ARRAY_BUFFER, new Uint16Array(aIndex), webgl.STATIC_DRAW); // 先畫一個z軸是-0.5的矩形,顏色是紅色 webgl.drawElements(webgl.TRIANGLES, 6, webgl.UNSIGNED_SHORT, 0); aPo = [ 0, -0.4, -0.8, 0.4, -0.4, -0.8, 0.4, 0, -0.8, 0, 0, -0.8 ]; webgl.bindBuffer(webgl.ARRAY_BUFFER, webgl.createBuffer()); webgl.bufferData(webgl.ARRAY_BUFFER, new Float32Array(aPo), webgl.STATIC_DRAW); webgl.vertexAttribPointer(aPosition, 3, webgl.FLOAT, false, 0, 0); webgl.vertexAttrib3f(aColor, 0, 1, 0); // 再畫一個z軸是-0.8的矩形,顏色是綠色 webgl.drawElements(webgl.TRIANGLES, 6, webgl.UNSIGNED_SHORT, 0); |
注意開啟深度測試,否則就沒戲啦
(不開啟深度測試,計算機會無視頂點的z座標資訊,只關注drawElements(drawArrays)方法的呼叫順序,最後畫的一定是最上一層)
程式碼中A矩形(紅色)的z值為-0.5, B矩形(綠色)的z值為-0.8,最終畫布上誰會覆蓋誰呢?
如果我問的是x=0.5和x=0.8之間,誰在左,誰在右,我相信每個人都確定知道,因為這太熟了,螢幕就是2D的,畫布座標x軸就是右大左小就是這樣的嘛
那我們更深層的考慮下為什麼x和y的位置沒人懷疑,因為“左手座標系”和“右手座標系”中x,y軸是一樣的,如圖所示
而左手座標系和右手座標系中的z軸正方向不同,一個是螢幕向內,一個是螢幕向外,所以可以認為
如果左手座標系下,B矩形(z=-0.8)小於A矩形(z=-0.5),那麼理應覆蓋了A矩形,右手座標系的話恰恰相反
事實勝於雄辯,我們所以執行一下程式碼
檢視結果:https://vorshen.github.io/3Dmaze/3.html
可以看到B矩形是覆蓋了A矩形的,也就意味著webgl是左手座標系
excuse me???所有文章說webgl都是右手座標系啊,為什麼這裡居然是左手座標系?
答案就是webgl中所說的右手座標系,其實是一種規範,是希望開發者一起遵循的規範,但是webgl本身,是不在乎物體是左手座標系還是右手座標系的
可事實在眼前,webgl左手座標系的證據大家也看到了,這是為什麼?剛剛說的有點籠統,不應該是“webgl是左手座標系”,而應該說“webgl的裁剪空間是按照左手座標系來的”
裁剪空間詞如其名,就是用來把超過座標空間的東西切割掉(-1 ~ 1),其中裁剪空間的z座標就是按照左手座標系來的
程式碼中我們有操作這個裁剪空間嗎?有!回到斷點的位置!
就是PMatrix它除了達成透視效果的另一個能力!
其實無論是PMatrix(透視投影矩陣)還是OMatrix(正視投影矩陣),它們都會操作裁剪空間,其中有一步就是將左手座標系給轉換為右手座標系
怎麼轉化的,來,我們用這個單位矩陣試一下
1 0 0 0
0 1 0 0
0 0 -1 0
0 0 0 1
只需要我們將z軸反轉,就可以得到將裁剪空間由左手座標系轉變為右手座標系了。用之前的矩形A和矩形B再試一次看看
地址:https://vorshen.github.io/3Dmaze/4.html
果然如此!
這樣我們就瞭解到了webgl世界中幾個最為關鍵的Matrix了
4/ 結語
至於具體的PMatrix和OMatrix是怎麼來的,Matrix能否進行一些優化,我們下次再說~
有疑問和建議的歡迎留言一起討論~