??? 想來編程也有一段時間,什么都很明白就是對于坐標變換不是很理解,總是在關鍵的時候迷亂不已,胡亂的寫一些變換代碼,得到的結果當然讓自己云里霧里。仔細的看了一下好幾本書關于3D變換的篇章,總結了一下,希望對大家有幫助。末了聲明以下,可能我說得也有錯誤的地方,敬請局內人明鑒指正,我只是一個在校學生沒有實際的工作經驗。懇請大家提出寶貴的意見,打造一個Matrix Bible,讓更多的初學者不要走彎路。謝謝大家。
??? 矩陣變換是個相當重要的要點,難度應該僅次于數據結構部分。倒不是因為本身掌握知識對能力的要求有多么高,而是因為從來沒有人說明白過在實際情況中如何應用。
??? 在現代游戲的制作過程中,肯定是先由美工制作好要用到的模型,比如人物車輛地形等等,我們稱之為基本模型。而諸如3dsmax maya等等建模工具產生的二進制文件是Application特有的格式,所以一般需要導出,各大論壇上無數人曾經提問過如何載入3ds模型。成熟的3D引擎都有自己的一套Util工具用來把模型導出為引擎特有的數據格式,比如Doom3引擎開源論壇上就提供3dsmax maya等使用的導出插件,用來輸出為MD5格式的模型文件。其中會用到一種叫做Data Chunk的概念,不再多說。
??? 當美工制作模型的時候,肯定以建模工具提供的那個坐標系為基本坐標系進行建模。模型的頂點都是相對于各自基本的坐標系,我們稱之為Local坐標系統或者Object坐標系。
??? 美工把這些數據交給程序員。程序員需要在場景中安放這些模型,比如在地圖上放置建筑車輛人物等等??墒浅绦騿T面對的這些模型的坐標,數字可能是一樣的,因為都是相對于Local坐標系統,就這樣一股腦的載入,肯定都是在“世界”的中心位置進行繪制,根本不可能分開。于是我們需要對各個物體,也就是各個獨立的坐標系統進行Transform(包括Translate Rotate Scale操作)。
??? 這里我們以GL為概念。當我們輸入glutSolidCube(4)的時候,它會產生這樣的數據:glVertex3f(2,2,2),glVertex3f(-2,-2,-2)等等,也就是長度為4的一個立方體。注意我們畫的這個立方體的位置,肯定是出現在“世界”的中心位置。如果我們希望它移動到其他的位置呢?只能先glTranslatef(),再glutSolidCube。這個glTranslatef作用在MODELVIEW_MATRIX上,具體的形式請到OpenGL Wiki上看,那里連載了RedBook。
??? 比如我們輸入glTranslatef(1.0,0,0);glutSolidCube(4),其實它產生的“真正頂點”是,(3,2,2),(-1,-2,-2),統統向x方向移動了一個位置。如果你在自己的范例程序里看不到是因為perspective中的far near planes沒有設置好。這樣我們就仿佛實現了平移以及旋轉等等操作,注意,是仿佛。
??? 我們把變換頂點的矩陣一般稱為MV(Model View)矩陣,把和在一起一步到位的矩陣稱為MVP矩陣,在GLSL中就有gl_ModelViewMatirx和gl_ModelViewProjectionMatrix這兩個Uniform Matirx。我們輸入一個頂點,希望把它放到這個世界的正確位置上,就需要乘以適合它自己的MV,因為不一樣的模型當然需要不一樣的世界位置。矩陣乘法就可以完成這項神奇的工作。可是向量的概念則很大不同。
??? 一個正方體,只要它在我們的映像中從頭到尾都是方方正正的立在場景中,它的向量,無論朝上朝下都應該是相同的,比如(1,0,0)左邊的面,只要我們不旋轉這個正方體,它在Local坐標系還是變換后也應該是(1,0,0),這個時候我們用哪個矩陣呢?用MV顯然不同,就需要用MV的Inverse Transpose。在線性代數中,求一個矩陣的Inverse然后Transpose,與先求Transpose再求Inverse,這兩邊是完全相等的。在GLSL中,其實gl_Normal*gl_NormalMatrix等同于gl_Normal*gl_ModelViewInverseTransposeMatrix。REDBOOK是這樣說的:
???????????????????????? In other words, normal vectors are transformed by the inverse transpose of the transformation that transforms points.
??? 為什么有這樣的變化呢?我們用V(x,y,z,w)代表頂點,P(a,b,c,d)代表一個平面。相應的平面方程可以寫作,PV = 0,也就是ax + by + cz = 0,有個向量垂直于頂點所在的那個面。
??? 這是個萬用公式么?還早呢?如果我們要把這個模型的位置改變掉,我們就一定需要把頂點乘以ModelView矩陣,為了方便我就用M代表MV。這里寫作:
?????????????????? PMV = ?
??? 可是這個式子右邊等于什么呢?我也不知道。為了這個式子依舊讓右邊等于0,符合基本的幾何代數式,我們需要再給左邊乘以M-1,就是M的逆矩陣。
??????????????????P M-1 M V = 0
??? 有一個向量垂直于這個平面。于是引入n一個我們真正意義上的面向量,和平面內的任意一個向量都應該是正交的。那個任意向量如何獲得呢?最簡單的就是,那個頂點和原點構成的向量 —— 因為在我們最初的式子里面,默認這個平面就是通過原點的。我們想讓等式依舊成立,式子變成:
?????????????????N T V = 0
??? 注意上式的T。如果單純的N矩陣乘以V,得到的結果還是一個向量而不是數字,所以需要一個Transpose變換。綜合后,式子變換為:
???????????????? N T? M-1 M V = 0
???????????????? (如果我沒有理解錯的話,V應該隱含著用了2次)
??? 好的,我們開始用矩陣運算法則去分解上述的式子。MV不變,剩下的也就是(M-1)T N,也就是需要ModelView矩陣的Inverse Transpose矩陣乘以向量。
??? 只要這些明白了,高級變換也就沒有什么難得了。
Use Case
??? 古老的bump mapping
??? 在BumpMapping里面有個很重要的過程,就是把光源位置轉換到以每個頂點處的向量為Z軸的空間中去,求向量的方法我不多說,為什么這樣做也沒有必要講,最關鍵的就是可能很多人不明白為什么要乘以以N B T為元素的矩陣。
??? 其實這里很多書籍要么沒有解釋,要么一筆帶過。我來嘗試的解釋通透,可能有錯誤,希望大家指正。
??? GL的Matrix是Column-Major的形勢。我們以N B T為元素的矩陣為例。
??????? Nx Bx Tx
??????? Ny By Ty
??????? Nz Bz Tz
??? 這里隱含的意思是:把頂點變換到以N B T為3個坐標軸的空間中,無論這個坐標系是不是和世界坐標系統“傾斜”的。
??? 再次寫成4x4的形式:
????????Nx Bx Tx 0
??????? Ny?By?Ty 0
????????Nz Bz Tz? 0
????????0??? 0?? 0?? 1
??? 注意第四列的連續三個0。代表的意義是:每個頂點變換到以這個N B T為坐標軸的坐標系后,需要Translate到的位置。因為在Bump Mapping中我們不需要對頂點的位置變換,所以隱含著寫成3x3的Matrix就足夠了。
??? 這里的這個3x3矩陣,其實就是對于每個頂點來說的MV矩陣,轉換的就是那個LightPosition??墒沁@里又有一個問題,如果轉換的不止一個LightPosition,還有Normal怎么辦?因為這是個“斜”的坐標系,原來的(1,0,0)可不是變換后的(1,0,0)。記起來了么?Inverse Transpose!我們只要把原來的向量乘以這個以NBT為正交坐標軸向量的MV的Inverse Transpose,就可以得到正確的結果了。(我說得對么?)其實,因為這個矩陣3個向量都已經normalized,它的Inverse = Transpose,所以這個NBT矩陣的IT矩陣就是它自己!
??? 微軟DirectX SDK Oct里面有個Shadow Mapping的Sample,代碼中有一部分詳細的說明了這個過程。它需要變換光源的向量,如果當我們把光源綁定到車上,就需要更改矩陣中w行的元素的。有興趣的朋友可以看看。
??? gl_LightPosition提供的光源參數是針對Eye Space,也就是所有的頂點已經經過MV變換的空間中的那個點。如果你有自己的光源安排一定要在空間中互相轉換,頭疼。GLSL用Uniform3f自己指定變換后空間中光源位置,比較方便,適合完成以場景為單位的光照計算。
??????? 不再新潮的Shadow Mapping
?Shadow mapping,包括后來的Variance SM,PCF等,有個關鍵的步驟,就是把場景轉換到以光源為攝像機的空間LightCamera中,獲得深度。這里,場景中所有的頂點需要變換到以LightCamera的NBT為坐標軸的坐標系中,向量的正確變換則需要乘以NBT的Inverse Transpose矩陣。(我推測的,希望大家指正)。接下來的事情么,在Shader中愛做什么做什么。
??? 那么如何傳入所需要的矩陣呢?其實相當簡單。功夫厲害的,直接把數組通過glUniform4fmatrix(),或者cgSetParameter傳入。功夫弱一些的,老老實實的gl_MatrixModel(GL_MODELVIEW);glLoadIndentity();glMultMatrix();//乘以需要的矩陣到單位矩陣上,然后后再傳入Shader。
??? 有的時候我們需要自己獨立求逆矩陣,如何辦到呢?這可不是紙上的線性代數考試可以用初等變換計算。
????????
??? 矩陣的變換和逆變換就那么3種,Translate,Rotate,Scale。
??? 我們知道MV =? T * R。(T R代表為了實現Translate和Rotate相應的矩陣)
??? 則Inv(MV) = Inv(T) * Inv(R)
????????
??? 也就是說,假設MV是
??????? ( R R R P)
????? ? ( R R R P)
??????? ( R R R P)
????? ? ( 0001)
??? 則它所代表的?R T矩陣就是
?????? ( R R R 0)
?????? (R R R 0)
?????? ( R R R 0)
?????? (?0001)
??? 和
????? ( 100 P)
????? ( 010 P)
????? ( 001 P)
???? ? ( 0001)
??? 計算相應子矩陣的逆矩陣,Inv(T)就是
???????(?100 -P)
?????? ( 010 -P)
???????( 001 -P)
???????( 000? 1)
??? 逆旋轉矩陣可能復雜一些,不過依舊可以計算出來,也就是它的Transpose。然后乘一下,逆矩陣就出來了。
??? 目前我所想到的關鍵就這么多,更多的懇請大家添加,謝謝。
解析 NVIDIA中的HW Shadow Mapping Demo
?????? 既然是SM的DEMO,在LightSpace和CameraSpace之間進行變換肯定是少不了的。當然,也應用到了多通道的思想。
????quad.new_list(GL_COMPILE);
????glPushMatrix();
????glRotatef(-90,?1,?0,?0);
????glScalef(4,4,4);
????glBegin(GL_QUADS);
????glNormal3f(0,?0,?1);
????glVertex2f(-1,?-1);
????glVertex2f(-1,??1);
????glVertex2f(?1,??1);
????glVertex2f(?1,?-1);
????glEnd();
????glPopMatrix();
????quad.end_list();
????wirecube.new_list(GL_COMPILE);
????glutWireCube(2);?
????wirecube.end_list();
????
????geometry.new_list(GL_COMPILE);
????glPushMatrix();
????glTranslatef(0,?.4f,?0);
????glutSolidTeapot(.5f);
????glPopMatrix();
????geometry.end_list();
???
??? 首選我們新建了3個顯示列表,可以看出,quad的意義是,處在世界平面的x z平面的尺寸為4x4的一個平面(先畫xy平面內的點,不過又旋轉了90度)。geometry么,就是那個著名的nurbs茶壺,我們想象為在世界平面y向上的0.4f處。注意每次繪制前都會調用glPushMatrix把MV矩陣推入Stack,這個步驟相當重要,因為我們還不知道前面的坐標系,究竟在哪里,不過后面我們又看到了如何解決這個問題。
void?render_scene(glut_simple_mouse_interactor?&?view)
{
????glColor3f(1,1,1);
????glPushMatrix();
????view.apply_inverse_transform();
????glPushMatrix();
????object.apply_transform();
????render_quad();
????glEnable(GL_LIGHTING);
????geometry.call_list();
????glDisable(GL_LIGHTING);
????glPopMatrix();
????glPopMatrix();
}
??? 通篇代碼閱讀完畢,發現這個函數最重要。參數view,我的理解是,它是View變換矩陣,也就是儲存了3個正交單位向量,有可能包括眼睛的位置(注意是有可能),無論這個眼睛是攝像機,還是光源。
??? 不過這個view.apply_inverse_transform(),它究竟代表了哪些操作呢?讓我們在nvidia自己寫的glh文件里面探尋一下吧。
????void?apply_transform()
????{
??????translator.apply_transform();
??????trackball.apply_transform();
????}
????void?apply_inverse_transform()
????{
??????trackball.apply_inverse_transform();
??????translator.apply_inverse_transform();
????}
??? 如果要調用apply_transform()進行坐標變換,那么是先位移,再旋轉。如果要返回到最初的坐標系,那么就應該是先旋轉回來,再位移回去。知道為什么么?
???
??? 我們默認的位移其實應該是相對于World Coordinate,也就是說,我們意義上的向xyz方向移動幾個單位其實是在那個最初的平面世界中的,而不是應該在攝像機空間中的位移 —— 因為最初世界坐標系里面的三個正交方向向量其實也已經旋轉過了,也就是說,如果我們先旋轉再位移,得到的軌跡相對于我們腦海中的世界坐標系是一條斜直線 —— 雖然說它對于攝像機坐標系來說是坐標軸直線。
??? 如果用線性代數的性質也很好解釋,本來正確的transform順序(原因在上面)就是I*T*R,如果要回到I,就必須I*T*R*R-1*T-1 = I。OpenGL的matrix操作是右結合的。
??? 這里的
view.apply_inverse_transform()就好理解了。不管我渲染什么,我總是要先把坐標系放回到世界坐標系中的原點處,保存好當前矩陣,然后再調用顯示列表。不過我們又發現那個render_quad(),好,我們再把它揪出來。
void
?render_quad()
{
????glActiveTextureARB(GL_TEXTURE0_ARB);
????obj_linear_texgen();
????texgen(
true
);
????glMatrixMode(GL_TEXTURE);
????glLoadIdentity();
????glScalef(
4
,
4
,
1
);
????glMatrixMode(GL_MODELVIEW);
????glDisable(GL_LIGHTING);
????decal.bind();
????decal.enable();
????quad.call_list();
????decal.disable();
????glEnable(GL_LIGHTING);
????texgen(
false
);
????glMatrixMode(GL_TEXTURE);
????glLoadIdentity();
????glMatrixMode(GL_MODELVIEW);
}
??? 激活第一個紋理單元,自動生成紋理,調整紋理矩陣,準備好紋理,繪制桌面。這里繪制的是,以光源為視點的場景,應該是這個樣子,全面的內容解析看注釋。
void
?render_scene_from_light_view()
{
??? //放置燈光
????glPushMatrix();
????? glLoadIdentity();
????? glLightfv(GL_LIGHT0,?GL_POSITION,?
&
?vec4f(
0
,
0
,
0
,
1
)[
0
]);
????glPopMatrix();
??? //為什么這里光源是(0,0,0)呢?gl的光源坐標是在object coordinates中,也就是它要被I矩陣轉換,結果依舊是EyeSpace中的(0,0,0)
????
//
?spot?image
????glActiveTextureARB(GL_TEXTURE1_ARB);
??? glPushMatrix();
????? eye_linear_texgen();????
????? texgen(
true
);
????glPopMatrix();
????glMatrixMode(GL_TEXTURE);
????glLoadIdentity();
????glTranslatef(.5f,?.5f,?.5f);
????glScalef(.5f,?.5f,?.5f);
????gluPerspective(lightshaper.fovy,?
1
,?lightshaper.zNear,?lightshaper.zFar);
??? //這里生成的是一個生成紋理坐標的矩陣,它的形式是I*T*S*P,提供給處于以光源為原點的場景坐標使用。
??? glMatrixMode(GL_MODELVIEW);
??? light_image.bind();
????light_image.enable();
????glTexEnvi(GL_TEXTURE_ENV,?GL_TEXTURE_ENV_MODE,?GL_MODULATE);
????glActiveTextureARB(GL_TEXTURE0_ARB);
????lightshaper.apply();
????
if
(display_funcs[current_display_func]?
==
?render_scene_from_light_view)
????????largest_square_power_of_two_viewport();
????render_scene(spotlight);//讓思路回到上面的那個函數,仔細體會
????glActiveTextureARB(GL_TEXTURE1_ARB);
????light_image.disable();
????glActiveTextureARB(GL_TEXTURE0_ARB);
}
再把這個函數貼出來,請自己仔細推敲變換過程。
void
?render_scene_from_camera_view()
{
????
//
?place?light
????glPushMatrix();
????glLoadIdentity();
????camera.apply_inverse_transform();
????spotlight.apply_transform();
????glLightfv(GL_LIGHT0,?GL_POSITION,?
&
?vec4f(
0
,
0
,
0
,
1
)[
0
]);
????glPopMatrix();
????
//
?spot?image
????glActiveTextureARB(GL_TEXTURE1_ARB);
????glPushMatrix();
????camera.apply_inverse_transform();
????eye_linear_texgen();????
????texgen(
true
);
????glPopMatrix();
????glMatrixMode(GL_TEXTURE);
????glLoadIdentity();
????glTranslatef(.5f,?.5f,?.5f);
????glScalef(.5f,?.5f,?.5f);
????gluPerspective(lightshaper.fovy,?
1
,?lightshaper.zNear,?lightshaper.zFar);
????spotlight.apply_inverse_transform();
????glMatrixMode(GL_MODELVIEW);
????light_image.bind();
????light_image.enable();
????glTexEnvi(GL_TEXTURE_ENV,?GL_TEXTURE_ENV_MODE,?GL_MODULATE);
????glActiveTextureARB(GL_TEXTURE0_ARB);
????reshaper.apply();
????render_scene(camera);
????glActiveTextureARB(GL_TEXTURE1_ARB);
????light_image.disable();
????glActiveTextureARB(GL_TEXTURE0_ARB);
????render_light_frustum();
}
看哪,天梯!
? 說了這么多的東西,貼了這么多代碼,我們究竟應該把握住哪些東西呢?
??? 1、計算出自己需要的View變換矩陣,從此告別gluLookAt或者D3DXMatrixLookAtLH
??? 首先選擇Eye所在世界中的位置,比如說在
(4,4,4)處。選擇目光所看的點,比如原點
O(0,0,0),或者一個方向向量
D(-4,-4,-4)。
??? 選擇一個世界坐標系中Up向量,在GL中就是
UpTmp(0,1,0)。
??? 得到一個新向量
C = cross(D,UpTmp)。注意是D叉乘UpTmp。
??? 仍掉那個UpTmp。
U(Up)= cross(C,D)。
???
??? 完成了大半工作了!讓我們繼續。
???
D.normalize();C.normalize();D.normalize();把向量縮放為單位長度。
??? 構造這個矩陣。你可以理解為一個定義在原點的旋轉矩陣:
??? matrix4f v( c[0],c[1],c[2],0,
??? ??? ??? ??? u[0],u[1],u[2],0,
??? ??? ??? ??? -d[0],-d[1],-d[2],0,
??? ??? ??? ??? 0,0,0,1
??? ??? );??? 再次引用Eye的位置(4,4,4),構造一個translate矩陣:
??? matrix4f t(1,0,0,-4,
??? ??? ??? ??? 0,1,0,-4,
??? ??? ??? ??? 0,0,1,-4,
??? ??? ??? ??? 0,0,0,1
??? ??? );//注意是負的,因為這是用center - eyepos得到的??? 有了這兩個矩陣,一切就都好辦了。我們就可以得到一個View Transform的完整矩陣:
???
matrix4f ViewTransformMatrix = v.mult_right(t);注意是右乘,它的效果等同于:
??? glMatrixMode(GL_MODELVIEW);
??? glLoadIndentity();
??? glMultMatrixf(v);//這里只是比喻一下
??? glTranslatef(-4,-4,-4);
??? 有了這個變換矩陣后,我們還需要它的逆矩陣。
???
matrix4f ViewTransformInverseMatrix = ViewTransformMatrix.inverse();???
??? 接下來把數據放到2個數組中去。
????for(?i?=?0;i<4;i++?)
????????for(?j=0;j<4;j++){
????????????ViewTransformMatrixArray[i*4+j]?=ViewTransformMatrix.element(j,i);
??????????? ViewTransformInverseMatrixArray[i*4+j]?=ViewTransformInverseMatrix.element(j,i);
????????}
???
??? 注意,OpenGL的矩陣是Colunm - Major的順序,所以載入數組的時候需要把i j位置替換下。
static?void?
display(void)
{
????glClear(GL_COLOR_BUFFER_BIT?|?GL_DEPTH_BUFFER_BIT);
????
????//gluLookAt(4,4,4,0,0,0,0,1,0);
????glMatrixMode(GL_MODELVIEW);
????glLoadIdentity();
????glMultMatrixf(ViewTransformMatrixArray);
????glMultMatrixf(ViewTransformInverseMatrixArray);
????glMultMatrixf(LightViewTransformMatrix);//我生成了2套矩陣,分別用于Eye和Camera
/*
????glMatrixMode(GL_MODELVIEW);
????glLoadIdentity();
????gluLookAt(4,4,4,0,0,0,0,1,0);
*/
????glPushMatrix();
????glPointSize(4.0f);
????????glBegin(GL_LINES);
????????????glColor3f(0,1.0,0);
????????????glVertex3f(0,0,0);
????????????glVertex3f(1,0,0);
????????????glVertex3f(0,0,0);
????????????glVertex3f(0,0,1);
????????????glVertex3f(0,0,0);
????????????glVertex3f(0,1,0);
????????glEnd();
????glPopMatrix();
????glPushMatrix();
????????glTranslatef(0,ypos,0);
????????glutSolidSphere(0.5,32,32);
????glPopMatrix();
????glutSwapBuffers();
}
???
??? 是不是覺得我多此一舉?為什么要乘來乘去的,不就是回到單位矩陣么?事實上我曾經調試了很多次,通過比較輸出gluLookAt(4,4,4,0,0,0,0,1,0)生成的矩陣和自己生成的矩陣是否相同,結果正確的變換到了LightView空間。
對光源位置的轉換
這個問題討論已久,仿佛久久沒有標準,總是有初學者不斷提問,而我們回答的也總是一個子集,治標不治本。
在上文中,我們已經生成了用于轉換Object Space Coordinates的2個MV矩陣以及相應的逆矩陣。我們先從固定管線的Phone光照模型的GL入手,看看如何正確的轉換光源。我們先看看gl manual怎么定義那個GL_POSITION的。
The?params?parameter?contains?four?integer?or?floating-point?values?that?specify?the?position?of?the?light?in?homogeneous?object?coordinates.?Both?integer?and?floating-point?values?are?mapped?directly.?Neither?integer?nor?floating-point?values?are?clamped.?
The?position?is?transformed?by?the?modelview?matrix?when?glLight?is?called?(just?as?if?it?were?a?point),?and?it?is?stored?in?eye?coordinates.?If?the?w?component?of?the?position?is?0.0,?the?light?is?treated?as?a?directional?source.?Diffuse?and?specular?lighting?calculations?take?the?lights?direction,?but?not?its?actual?position,?into?account,?and?attenuation?is?disabled.?Otherwise,?diffuse?and?specular?lighting?calculations?are?based?on?the?actual?location?of?the?light?in?eye?coordinates,?and?attenuation?is?enabled.?The?default?position?is?(0,0,1,0);?thus,?the?default?light?source?is?directional,?parallel?to,?and?in?the?direction?of?the?–z?axis.

意思是,我們指定的坐標是Object Space空間的坐標,然后被MV轉換。W是作為齊次縮放系數使用的,0代表無限遠好象太陽光束。
我們上面已經提到光源的位置在(-2,4,2)。這里我們寫成無限遠的(-2,4,2,0)。為了測試起見,我的顯示函數寫成了切換視點的模式。
static?void?
display(void)


{
????glClear(GL_COLOR_BUFFER_BIT?|?GL_DEPTH_BUFFER_BIT);

????switch(InWhichSpace)
{
????????case?0:
????????????glMatrixMode(GL_MODELVIEW);
????????????glLoadIdentity();
????????????glMultMatrixf(ViewTransformMatrix);
????????????glLightfv(GL_LIGHT0,?GL_POSITION,?&?vec4f(-2,4,2,0)[0]);
??????????break;
????????case?1:
????????????glMatrixMode(GL_MODELVIEW);
????????????glLoadIdentity();
????????????glMultMatrixf(LightViewTransformMatrix);
????????????glLightfv(GL_LIGHT0,?GL_POSITION,?&?vec4f(-2,4,2,0)[0]);

????????????break;
????????}
????glPushMatrix();
????glPointSize(12.0f);
????????glScalef(4,4,4);
????????glBegin(GL_LINES);
????????????
????????????glColor3f(1,1,1);
????????????
????????????glVertex3f(0,0,0);
????????????glVertex3f(-2,4,2);
????????????
????????????glColor3f(1,0,0);
????????????glVertex3f(0,0,0);
????????????glVertex3f(1,0,0);
????????????
????????????glColor3f(0,1,0);
????????????glVertex3f(0,0,0);
????????????glVertex3f(0,0,1);

????????????glColor3f(0,0,1);
????????????glVertex3f(0,0,0);
????????????glVertex3f(0,1,0);
????????glEnd();
????glPopMatrix();
????glPushMatrix();
????????glTranslatef(0,zviewpos,0);
????????glutSolidSphere(0.5,32,32);
????glPopMatrix();


????glutSwapBuffers();
}
注意看switch開關。如果我切換到Camera,我將看到這樣。

如果切換到光源視圖,是這樣的。

下面讓我們來看看為什么,還有注釋掉的矩陣乘法代碼。
第一個case:我們用載入ViewTransformMatrix,下面聲明LightPosition,是(-2,4,2,0),這個坐標是Object Space的坐標,在我們的想象中,就是相對于世界坐標系的位置,也就是每次我繪制一個Sphere所產生的位置。
第二個case:載入LightViewTransformMatrix,依舊傳入(-2,4,2,0),得到的結果依舊正確。
最好自己向自己復述一遍,注意一定要聯系我們上面計算矩陣的算式。
然后我們把case0代碼改一下。

????switch(InWhichSpace)
{
????????case?0:
????????????glutSetWindowTitle("From?Camera?View");
????????????glMatrixMode(GL_MODELVIEW);
????????????glLoadIdentity();
????????????glMultMatrixf(ViewTransformMatrix);

????????????glPushMatrix();
????????????//glLoadIdentity();
????????????//glMultMatrixf(ViewTransformMatrix);
????????????glMultMatrixf(LightViewTransformInverseMatrix);
????????????glLightfv(GL_LIGHT0,?GL_POSITION,?&?vec4f(0,0,0,1)[0]);
????????????glPopMatrix();
我們要好好剖析第二個PushMatrix,LoadIndentity后的那兩個連續的矩陣乘法,還有為什么光源成了(0,0,0,1)。NVIDIA的那個render_scene_from_camera也是這樣放置光源的。讓我們看看為什么。
這個V(0,0,0,1)是Object Space中的點。我們先用Mvt代表ViewTransformMatrix,再用Mlvti代表LightViewTransformInverseMatrix。寫成完整的算式應該是
Mvt(Mlvti * V)
想起來了么?矩陣乘法的結合形式,意思是,“ vertex V under transformed by Matrix Mlvt”。這里產生光源的過程如下:
Object Space中的(0,0,0,1)被Mlvti轉換到Object空間,是多少呢?(-2,4,2,1),就是光源的相對于世界的位置。其實你也可以通過vec4f new = LightViewTransformInverseMatrix.mult_matrix_vec(vec4f(0,0,0,1))自己驗證。
由于轉換到LightView空間后,產生的是,世界空間和模型空間中的(-2,4,2,1) —— GL沒有世界坐標,而且我們一般認為Object Space是和世界空間重合的。即使在D3D中,一般情況下初始化世界矩陣也都是載入單位矩陣。
?。?2,4,2,1)再乘以Mlvt,又被轉換到了 —— 其實我不知道它在哪里!相對于轉換后的CAMERA坐標系,它的位置我可以手動求出來,得到的是光柵化坐標。但是它的位置的確是正確的,效果等同于直接在glLightv中傳入(-2,4,2,1)
總結:
對于一個成熟的3D引擎來說,矩陣都是自己計算出來的,絕非調用API自己的指令。在NVIDIA SDK的DEMO中包含了大量成熟的基礎代碼,在不侵犯原作者權益的情況下應該合理的采用,省下諸多開發調試時間。
我的源程序和來自NV SDK的那個header文件也包括在一起,包含了向量計算,矩陣操作等等,對數據的處理相當實用!
這里下載
posted on 2006-12-10 13:46
周波 閱讀(2417)
評論(0) 編輯 收藏 引用 所屬分類:
Cg藝術 、
無庸技術