想來編程也有一段時間,什么都很明白就是對于坐標變換不是很理解,總是在關鍵的時候迷亂不已,胡亂的寫一些變換代碼,得到的結果當然讓自己云里霧里。仔細的看了一下好幾本書關于3D變換的篇章,總結了一下,希望對大家有幫助。末了聲明以下,可能我說得也有錯誤的地方,敬請局內人明鑒指正,我只是一個在校學生沒有實際的工作經驗。懇請大家提出寶貴的意見,打造一個Matrix Bible,讓更多的初學者不要走彎路。謝謝大家。
矩陣變換是個相當重要的要點,難度應該僅次于數據結構部分。倒不是因為本身掌握知識對能力的要求有多么高,而是因為從來沒有人說明白過在實際情況中如何應用。
在現代游戲的制作過程中,肯定是先由美工制作好要用到的模型,比如人物車輛地形等等,我們稱之為基本模型。而諸如3dsmax maya等等建模工具產生的二進制文件是Application特有的格式,所以一般需要導出,各大論壇上無數人曾經提問過如何載入3ds模型。成熟的3D引擎都有自己的一套Util工具用來把模型導出為引擎特有的數據格式,比如Doom3引擎開源論壇上就提供3dsmax maya等使用的導出插件,用來輸出為MD5格式的模型文件。其中會用到一種叫做Data Chunk的概念,不再多說。
當美工制作模型的時候,肯定以建模工具提供的那個坐標系為基本坐標系進行建模。模型的頂點都是相對于各自基本的坐標系,我們稱之為Local坐標系統或者Object坐標系。
美工把這些數據交給程序員。程序員需要在場景中安放這些模型,比如在地圖上放置建筑車輛人物等等。可是程序員面對的這些模型的坐標,數字可能是一樣的,因為都是相對于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中包含了大量成熟的基礎代碼,在不侵犯原作者權益的情況下應該合理的采用,省下諸多開發調試時間。我引用的HEADER文件和代碼。
|