上一節咱們提到了如何在一張畫布上畫一個簡單幾何圖形,經過建立畫布,獲取WebGLRendering上下文,建立一個簡單的着色器,而後將一些頂點數據綁定到gl的Buffer中,最後經過綁定buffer數據,提供buffer中頂點數據的狀況,執行渲染繪製方法,將數據結果從buffer中刷新到幀緩存中。整個流程十分清晰明瞭,但是經過對比原來OpenGL中的整個流程,咱們會發現其中還缺乏了一些很重要的處理步驟,雖然咱們建立了屬於本身的着色器,可並無對頂點數據進行相似於頂點處理管線中的模型視圖變換、透視投影變換等操做,僅僅只是在屏幕上實現了一個靜態的幾何圖形。因此這一篇就讓咱們經過從新構造一個可以替代頂點處理管線的着色器,並利用該着色器對咱們的幾何圖形進行相應的變換。html
在上一節中提到了GLSL的着色器中有四種變量:頂點屬性attribute/一致變量uniform/易變變量varying/常量constweb
- Uniform 全局變量,能夠出如今頂點着色器、片元着色器中。通常對於全部頂點來講,能夠共用共享的屬性就能夠用這種類型的變量定義。數組
- Attribute 頂點着色器專用的屬性,從外部【通常從JavaScript中動態地獲取】接收輸入數據,主要用來表示逐頂點的信息。一般在GLSL中內置了一下關於頂點屬性:緩存
attribute vec4 gl_Color //頂點顏色
attribute vec4 gl_SecondaryColor //輔助頂點顏色
attribute vec3 gl_Normal //頂點法線
attribute vec4 gl_Position //頂點物體空間座標
- Varying 如上一篇提到的,varying做爲一個信使,將頂點着色器中的參數傳遞到片元着色器中,值得一提的是,Varying變量的數據類型只能被限制在: float、vec二、vec三、vec四、mat二、mat3以及mat4。其緣由是由於,通常從頂點着色器傳遞過來的頂點數據每每會通過光柵器後再傳遞給片元着色器。在光柵化過程當中,各個像素的值每每是根據頂點像素值進行內插的,若是頂點值是一些文本字符型的數據類型,則沒法完成內插。less
- Const常量,必須是常量,設定後不可修改。函數
衆所周知,屏幕是由一個個像素格組成,而咱們在屏幕上畫出來的幾何圖形也是由像素組成的。咱們能夠把幾何圖形變換的問題轉換成爲對像素圖形變換的問題,也就是說,對幾何圖形中的全部像素進行操做,便可完成幾何圖形變換。學習
那麼如何對像素進行幾何圖形變換呢?通常來講,咱們會把屏幕當作一個座標系統,屏幕上的幾何圖形處於這個座標系統內,每一個像素可視爲屏幕座標系中的一個點。這樣一來,咱們處理像素的問題,又轉換成了處理座標系中點位置的問題,從圖形à像素à點,咱們不斷地分解抽象一個複雜的問題,並最終將其轉換成爲了方便求解的情景,這是咱們處理問題的一個基本思路。webgl
現實世界到數學世界的映射spa
既然把問題轉移到了座標系裏,咱們就能夠將之前學到過的數學來求解這個問題。咱們以二維座標系爲例,咱們須要將一個點A(x , y)平移(a , b)到另外一點A’,很簡單,A’的座標爲(x+a, y+b),同理,將組成一個幾何圖形的若干個點所有進行平移操做,則這個幾何圖形也就完成一次平移。那若是我想對一個圖形進行旋轉呢?放大縮小呢?這些變換的操做彷彿沒有平移那麼直觀,那麼咱們須要藉助一些外力來解決這些問題。.net
齊次座標:此處須要引入在OpenGL基礎裏提到過的齊次座標,增長座標的維度,這樣可讓咱們更方便的在低維空間中表示高維度的要素。這裏有一個關於齊次座標很好的解釋:http://blog.csdn.net/janestar/article/details/44244849,這篇博客用齊次座標證實了在平面空間裏出現滅點的緣由,頗有趣。因此在一個平面直角座標系中,咱們能夠用齊次座標(x, y, 1)來表示一個點,這樣一來,咱們忽然發現好像經過矩陣的點乘就能夠到達實現點的繞軸旋轉以及縮放圖形的想法了!
咱們但願可以用齊次座標(x, y, 1)表明一個點,而後用每個點的齊次座標乘上一個3*3的變換矩陣,從而獲得一個變換後的齊次座標(x’, y’, 1),此時的x’和y’就是處理後的座標,這是處理座標仿射變換的一個基本思路。
平移矩陣PY 旋轉矩陣XZ 縮放矩陣SF
經過點(x, y, 1)·變換矩陣PY/XZ/SF可獲得:
( x+tx, y+ty, 1) ( x·cosα + y·sinα,y·cosα - x·sinα )① ( x·sx, y·sy, 1)
① 關於旋轉的數學證實,可參照如下證實:(主要經過三角函數的和差公式在單位圓中的關係進行證實)
假設平面直角座標系O中,有一個半徑爲r的單位圓,點A爲圓上一點(x, y),OA連線與X軸所成夾角爲α,先將OA繞原點O逆時針旋轉β°,求A點旋轉至B點後的座標值:
∵|OA| = x / cos α = y / sinα; |OB| = x' / cos(α+β) = y' / sin(α+β)
r = |OA| =|OB|
∴ x' = cos(α+β)·r,y' = sin(α+β)·r
經過三角函數的和差公式可得:
x' = r·(cosα·cosβ - sinα·sinβ) = x·cosβ - y·sinβ
y' = r·(sinα·cosβ + sinβ·cosα) = y·cosβ + x·sinβ
注意此處爲逆時針旋轉,β值小於0,因此此處獲得的結果與上述公式有符號上的差別
經過上述簡單的介紹,已經大概可以瞭解到如何在平面直角座標系中的幾何圖形進行仿射變換,但在現實操做中,咱們每每會對一個圖形進行多種變換操做,那該如何實現呢?
很簡單,咱們能夠經過假設存在一個綜合變換矩陣Z = F( PY·XZ·SF ),顯而易見的是經過( x, y ,1 )·Z能夠獲得將點:1)先x, y縮放sx, sy倍;2)再旋轉α弧度;3)最後x, y分別平移tx, ty個單位。不知大家發現沒有,這兒有一個有趣的現象,咱們明明是按照平移PY,旋轉XZ以及縮放SF的順序來做爲行矩陣相乘的輸入,但是在後面的過程文字說明中,我是按照逆向進行解釋的。這是爲何呢?由於矩陣採用的是一個二維數組進行存儲,而webGL在向着色器傳入矩陣的過程當中,是按列傳入着色器的,即着色器按每列的數據進行處理。
通常來講,兩個矩陣相乘,能夠用以下方法表示:
multiply: function(a, b) { var a00 = a[0 * 3 + 0]; var a01 = a[0 * 3 + 1]; var a02 = a[0 * 3 + 2]; var a10 = a[1 * 3 + 0]; var a11 = a[1 * 3 + 1]; var a12 = a[1 * 3 + 2]; var a20 = a[2 * 3 + 0]; var a21 = a[2 * 3 + 1]; var a22 = a[2 * 3 + 2]; var b00 = b[0 * 3 + 0]; var b01 = b[0 * 3 + 1]; var b02 = b[0 * 3 + 2]; var b10 = b[1 * 3 + 0]; var b11 = b[1 * 3 + 1]; var b12 = b[1 * 3 + 2]; var b20 = b[2 * 3 + 0]; var b21 = b[2 * 3 + 1]; var b22 = b[2 * 3 + 2]; return [ b00 * a00 + b01 * a10 + b02 * a20, b00 * a01 + b01 * a11 + b02 * a21, b00 * a02 + b01 * a12 + b02 * a22, b10 * a00 + b11 * a10 + b12 * a20, b10 * a01 + b11 * a11 + b12 * a21, b10 * a02 + b11 * a12 + b12 * a22, b20 * a00 + b21 * a10 + b22 * a20, b20 * a01 + b21 * a11 + b22 * a21, b20 * a02 + b21 * a12 + b22 * a22, ]; }
經過上述代碼能夠發現:輸入參數爲a, b,但最終相乘是以b · a來實現的。在通常常見的WebGL API中,都是如此設計的:雖然從調用API的過程來看,輸入順序爲:平移、旋轉、縮放,可在API的具體實現中,則是按照縮放、旋轉、平移的順序來進行代碼實現的。說實話,其實我也沒太弄明白爲何要這樣作,總感受設計的比較反人類;有的資料建議能夠從空間變換的角度來考慮這個問題,大意是:能夠把操做對象假想成爲空間而非空間中的幾何體,隨後的平移、旋轉、縮放能夠視爲對空間所進行的操做,空間中的幾何體不變的前提下,空間發生變換,幾何體的位置座標也就被動地發生了變化(由於幾何體的位置徹底依賴於空間座標系提供的座標值表達,失去了座標系,幾何體的位置信息就丟失了)。具體可見:https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-2d-matrices.html;
<!-- 2D vertex shader --> <!-- attribute:同理由應用程序提供,一眼用於頂點着色器中 --> <!-- uniform:全局變量,由外部傳遞到着色器,只能讀,不能修改 --> <!-- varying:易變變量,做爲頂點着色器和片元着色器之間的通訊 --> <script id="2d-vertex-shader" type="x-shader/x-vertex"> attribute vec2 a_position; uniform vec2 u_resolution; //矩陣 uniform mat3 u_matrix; void main() { // 2D-Vertex-shader: gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1); } function draw(){ .....
//操做順序:縮放、旋轉、平移: //translationMatrix:平移矩陣; //rotationMatrix:旋轉矩陣; //scaleMatrix:縮放矩陣 var matrix = m3.multiply(projectionMatrix, translationMatrix); matrix = m3.multiply(matrix, rotationMatrix); matrix = m3.multiply(matrix, scaleMatrix);
gl.uniformMatrix4fv(matrixLocation, false, matrix);
//Draw
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
//發出繪製命令
var primitiveType = gl.TRIANGLES;
var offset = 0;
var count = 16*6;
gl.drawArrays(primitiveType, offset, count);
}
PS:不一樣的變換順序,如:平移--旋轉--縮放和縮放--旋轉--平移,這兩個操做過程會獲得徹底不同的兩種效果,若是先平移再縮放的話,平移的距離會由於縮放的關係而發生伸長或縮短,徹底背離了操做者的本來意圖。在通常變換過程當中,每每建議先縮放再平移。
本篇主要簡述了再WebGL中,平面直角座標系的幾何圖形仿射變換與矩形計算的關聯實現,下一步會繼續拓展到三維座標系中,並增長相機、投影以及紋理等內容。
推薦一個很完整的入門教程:https://webglfundamentals.org/webgl/lessons/zh_cn/webgl-fundamentals.html,這是到目前爲止,我參照最多的一個資料,特別是學習思路上。