OpenGL中的矩陣變換(上)
這裡有一句,我認為,最能夠消解頭腦中的雲霧的話:OpenGL中所有的變換,都是在變換座標系。
你還好嗎?你還能看見你眼前書桌上那個蘋果嗎?你把它向右邊移動10釐米看看?移好了嗎?想一想,假如你眼前就是OpenGL的一個渲染視窗的話,蘋果是不是往x軸正方向移動了10單位(假設單位:釐米)?恩,你現在所感受到的真實告訴你:蘋果確實不再在原位置,而是向右移了10釐米到了一個新的位置。我想說的是,當你完全邁進3D圖形學殿堂後,請不要再那麼輕易相信的眼睛所感受到的真實——它是真實,但不是真實的全部。在你剛才一瞬間想象出的OpenGL渲染窗口裡,蘋果沒有移動,它一直在那裡,一直....而移動的是整個空間,整個世界,包括書桌,包括你,包括你的眼睛!
在OpenGL的那個世界中,最初存在著幾個重疊的空間。有模型空間,有世界空間,檢視空間,螢幕空間等。每個空間實質就是一個座標系(統)——座標系統當然就有座標軸。在最初的這個時刻,也許整個OpenGL世界也就只有這一套座標軸了:恩,一套。世界永遠只有一個,但是依附於這個世界的空間則平行地同時存在——實體(例如座標軸,蘋果)唯一,而實體的表示(各個空間中對應的座標軸,蘋果)則多樣。我是這麼理解的,OpenGL世界。
最初的OpenGL世界只有一套看不見的座標軸,然後,使用者說:蘋果!於是世上就有了蘋果。蘋果輕輕地出現在對映著這個世界的各個空間中——就在那個萬物之源的座標原點上。原點與蘋果的“中心”對應(當然“中心”不一定指蘋果中間,它由上帝...不,使用者在製造這個蘋果時決定。確切地說,我們在畫物件(或者直接叫:模型)時給予這些東西的各個glVertex3頂點(座標x,y,z),它們的存在必然以位置(0,0,0)為標尺——這個位置就是此物件的“中心”)。就在蘋果出現的此瞬間,模型空間(Model-Coordination,也稱Local-Corrdination)安靜了,此刻的整個世界的一切如同螢幕截圖般被儲存起來,儲存在模型空間——永遠以當前這個“中心”為原點,以當前這些座標(x,y,z)為蘋果各頂點的"模型座標系座標"。
- //1.使用者創世第1天,說:蘋果! 世上便有了蘋果---蘋果在模型空間(local 空間)被永恆描述
- DrawApple() //{glBegin();glVertex3f(A);glVertex3f(B)....glEnd()}
//1.使用者創世第1天,說:蘋果! 世上便有了蘋果---蘋果在模型空間(local 空間)被永恆描述DrawApple() //{glBegin();glVertex3f(A);glVertex3f(B)....glEnd()}
當然,蘋果出現在模型空間座標系統原點,必然也就出現在其他空間座標系統的原點,此時各空間的座標系是重合的,只不過模型空間被固定了,其他空間還沒而已。蘋果怎麼移動到別處呢?恩,程式設計的時候可以這麼問,但當你正在瞭解OpenGL世界的時候(如現在),這個問法很不靠譜。你看上面的程式碼(glVertex3f(A); glVertex3f(B)....)這裡點A,B...不是給定了嗎?就算是變數,在“畫點”的時刻變數的值也是給定的呀。是的,變的不該是蘋果,而是座標系,準確地理解,是世界空間(World-Coordination)的那個座標系要變,在原先與模型空間座標系重合的基礎上變。這個變的過程,叫模型變換(Model-Translation)。具體來說就是用一個表示“轉動/移動/縮放”的矩陣左乘蘋果的各個頂點座標,得出的結果(是一些不同的座標)作為蘋果對應頂點在世界空間座標系中的位置座標。誒?蘋果變了嗎?可以這麼說,蘋果的“座標”還是原來那些A.B...但對應於世界座標系的“位置”變化了。
這裡確實是很容易迷糊,默唸吧:OpenGL中所有的變換,都是在變換座標系。不是蘋果右移了10釐米,而是世界座標系左移了10釐米!假設蘋果中心最初恰就是出現在模型空間並與模型座標系原點重合,然後考慮以此為初狀態的世界空間,在使用者做了模型變換(向蘋果中心點所在之座標(0,0,0),左乘一個表示右移10釐米的模型變換矩陣)後,這個座標由(0,0,0)變成(10,0,0),相當於座標系原來的虛擬座標(10,0,0)變成現在的(0,0,0)。看,座標系(由無限的虛擬座標構成)左移10釐米!我想說,這才是模型變換(Model-Translation)的真正所為。與之前一樣,當一切變換完成,結果被截圖存入世界空間。世界空間的動盪結束,從此定型。
- //2.使用者創世第2天,說:蘋果右移! 於是世界座標系便左移了,結果存入世界空間
- glMatrixMode(GL_MODEL);//表示接下來要作“模型轉換”
- glTranslatef(10,0,0);//相當於一個平移矩陣
- //1.使用者創世第1天,說:蘋果! 世上便有了蘋果---蘋果在模型空間(local 空間)被永恆描述
- DrawApple() //{glBegin();glVertex3f(A);glVertex3f(B)....glEnd()}
//2.使用者創世第2天,說:蘋果右移! 於是世界座標系便左移了,結果存入世界空間glMatrixMode(GL_MODEL);//表示接下來要作“模型轉換”glTranslatef(10,0,0);//相當於一個平移矩陣//1.使用者創世第1天,說:蘋果! 世上便有了蘋果---蘋果在模型空間(local 空間)被永恆描述 DrawApple() //{glBegin();glVertex3f(A);glVertex3f(B)....glEnd()}
接下來發生的事情跟之前一樣,只是從模型空間---(模型變換)---》世界空間的關係,改成世界空間---(檢視變換 View Translation)---》檢視空間的關係而已。請好好再模擬一次:一模一樣的過程,這次的移動所需要的左乘矩陣(左乘剛才儲存的模型轉換結果嘛)由相機(眼睛,視線)的設定提供,結果存入檢視空間而已。
- //3.使用者創世第3天,說:我是主角! 於是他擁有了第一人稱視覺,結果存檢視空間
- glMatrixMode(GL_VIEW);//表示接下來要作“檢視轉換”
- gluLookat(eye,look,up)//也相當於一個平移矩陣
- //2.使用者創世第2天,說:蘋果右移! 於是世界座標系便左移了,結果存入世界空間
- glMatrixMode(GL_MODEL);//表示接下來要作“模型轉換”
- glTranslatef(10,0,0);//相當於一個平移矩陣
- //1.使用者創世第1天,說:蘋果! 世上便有了蘋果---蘋果在模型空間(local 空間)被永恆描述
- DrawApple() //{glBegin();glVertex3f(A);glVertex3f(B)....glEnd()}
//3.使用者創世第3天,說:我是主角! 於是他擁有了第一人稱視覺,結果存檢視空間glMatrixMode(GL_VIEW);//表示接下來要作“檢視轉換” gluLookat(eye,look,up)//也相當於一個平移矩陣 //2.使用者創世第2天,說:蘋果右移! 於是世界座標系便左移了,結果存入世界空間 glMatrixMode(GL_MODEL);//表示接下來要作“模型轉換” glTranslatef(10,0,0);//相當於一個平移矩陣 //1.使用者創世第1天,說:蘋果! 世上便有了蘋果---蘋果在模型空間(local 空間)被永恆描述 DrawApple() //{glBegin();glVertex3f(A);glVertex3f(B)....glEnd()}
噢,接下來是:檢視空間---(投影變換)---》螢幕空間。投影變換的變換手法與之前的不同,螢幕空間以檢視空間結果為基礎,先用一個平頭錐體(視景錐)把視線範圍外的空間割了(裁剪),再把投影到一個可以覆蓋渲染螢幕視窗的矩形上(具體做法是,XYZ除以隱含的齊次座標W,然後捨棄深度Z),儲存為螢幕空間——一個平面,讓使用者所能感悟到這一切不過顯示器螢幕一部分畫素的把戲。當然,最後的座標還要隱對映為渲染視窗的客戶區座標(原點在視窗左上角,Y軸下X軸右的平面座標),算是走出了OPENGL世界。
- //4.使用者創世第4天,說:到顯示屏來吧! 世界便是"平"的了.最後得螢幕空間
- glViewport(0,0,width,height);//設定那個"視景區矩形"大小
- glMatrixMode(GL_PROJECTION));//表示接下來要作“投影轉換”
- gluPerspective(fov,aspect,near,far);//視景體應用
- //3.使用者創世第3天,說:我是主角! 於是他擁有了第一人稱視覺,結果存檢視空間
- glMatrixMode(GL_VIEW);//表示接下來要作“檢視轉換”
- gluLookat(eye,look,up)//也相當於一個平移矩陣
- //2.使用者創世第2天,說:蘋果右移! 於是世界座標系便左移了,結果存入世界空間
- glMatrixMode(GL_MODEL);//表示接下來要作“模型轉換”
- glTranslatef(10,0,0);//相當於一個平移矩陣
- //1.使用者創世第1天,說:蘋果! 世上便有了蘋果---蘋果在模型空間(local 空間)被永恆描述
- DrawApple() //{glBegin();glVertex3f(A);glVertex3f(B)....glEnd()}
//4.使用者創世第4天,說:到顯示屏來吧! 世界便是"平"的了.最後得螢幕空間glViewport(0,0,width,height);//設定那個"視景區矩形"大小glMatrixMode(GL_PROJECTION));//表示接下來要作“投影轉換” gluPerspective(fov,aspect,near,far);//視景體應用//3.使用者創世第3天,說:我是主角! 於是他擁有了第一人稱視覺,結果存檢視空間 glMatrixMode(GL_VIEW);//表示接下來要作“檢視轉換” gluLookat(eye,look,up)//也相當於一個平移矩陣 //2.使用者創世第2天,說:蘋果右移! 於是世界座標系便左移了,結果存入世界空間 glMatrixMode(GL_MODEL);//表示接下來要作“模型轉換” glTranslatef(10,0,0);//相當於一個平移矩陣 //1.使用者創世第1天,說:蘋果! 世上便有了蘋果---蘋果在模型空間(local 空間)被永恆描述 DrawApple() //{glBegin();glVertex3f(A);glVertex3f(B)....glEnd()}
事實上OpenGL的glMatrixMode函式裡沒有GL_MODEL和GL_VIEW這兩種設值。上面提到過,模型變換和檢視變換其實是同一種處理,而且檢視變換隻由相機控制,有則有,無則預設。因此OpenGL直接就用GL_MODELVIEW一起轉換了。簡潔是簡潔,但是這樣一來使用者就不知道世界空間裡的座標系什麼時候改變了,也難得到物體在世界座標系下的位置座標了(事實上還是有辦法的,不過搞複雜了,見以後的博文啦)。
- glViewport(0,0,width,height);//設定那個"視景區矩形"大小
- glMatrixMode(GL_PROJECTION));//表示接下來要作“投影轉換”
- glLoadIdentity();
- gluPerspective(fov,aspect,near,far);//視景體應用
- gluLookat(eye,look,up)//控制整個視景體
- glMatrixMode(GL_MODELVIEW);//表示接下來要作“模型檢視轉換”
- glLoadIdentity();
- glTranslatef(10,0,0);//相當於一個平移矩陣
- DrawApple() //{glBegin();glVertex3f(A);glVertex3f(B)....glEnd()}
- ........
glViewport(0,0,width,height);//設定那個"視景區矩形"大小 glMatrixMode(GL_PROJECTION));//表示接下來要作“投影轉換” glLoadIdentity();gluPerspective(fov,aspect,near,far);//視景體應用 gluLookat(eye,look,up)//控制整個視景體 glMatrixMode(GL_MODELVIEW);//表示接下來要作“模型檢視轉換” glLoadIdentity(); glTranslatef(10,0,0);//相當於一個平移矩陣 DrawApple() //{glBegin();glVertex3f(A);glVertex3f(B)....glEnd()}........
此外,每次變換不是以之前儲存的那個空間的位置資訊為基礎嗎?這要用到glLoadIdentity()函式,初始化當前矩陣,為什麼?還是看下篇吧:
亂彈OpenGL中的矩陣變換(下)。
P.S.有時候發覺,有些BLOG文是行文一塌糊塗,但寫著寫著自己心裡“塌實”了。