• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            doing5552

            記錄每日點滴,不枉人生一世

              C++博客 :: 首頁 :: 聯系 :: 聚合  :: 管理
              73 Posts :: 0 Stories :: 94 Comments :: 0 Trackbacks

            公告

            常用鏈接

            留言簿(24)

            我參與的團隊

            最新隨筆

            搜索

            •  

            積分與排名

            • 積分 - 454866
            • 排名 - 48

            最新隨筆

            最新評論

            閱讀排行榜

            評論排行榜

            前言:以前寫了第一版,有些初學者反映許多地方還不是很明白。一來是因為說得比較瑣碎,二來內容太多不容易理解。于是乎我就把原文改寫以更加的明白直觀,并且從建模、美工技術的角度開始講述矩陣、坐標變換等知識,希望可以更好的幫助初學者理解。如果有錯誤,敬請不吝賜教。如果轉載請附上我的聯系方式謝謝。

            一、符號以及命名風格約定

            Object Coordinates = LC,以模型內部的“原點”為原點的坐標系。
            Local Coordinates = OC,以每個頂點處的向量Normal為Z軸,正切Tangent向量為X(或者Y)軸,以Binormal為Y(或者X)軸的一個坐標系。
            World Coordinates = WC,應用程序自己維護的一個坐標系統,主要作用是儲存各個模型的位置,在處理的流水線中把模型正確的在假想的世界中變換。
            Camera Space  = Eye Space = ES,真正的視空間。
            ModelView矩陣 = MV,把模型從模型空間直接變換到Camera空間的矩陣。
            以此類推,ModelViewProject = MVP。
            ModelViewInverseTranspose = MVit,MV矩陣做過線性代數IT操作后的矩陣,用來把OC空間每個頂點的向量變換到ES中。
            以此類推,MVi = ModelViewInverse,MVt = ModelViewTranspose,以及MVPit = ModelViewProjectionInverseTranspose,Wiv = WorldInverseTranspose

            二、從美工說起

              讓每個藝術設計系畢業的美工都如同我們一樣精通線性代數高等數學是幾乎不可能的事情,尤其是中國四年本科的學生,會熟練使用3dsmax、maya的都很少,更別提讓他們闡述光照模型等CG基本概念了。于是我們需要首先了解美工的工作,以及他們制作出來的素材。這是一幅來自3dsmax中Perspective視圖的截圖,大家注意左下角畫白 色雙環的部分,我把3dsmax內部的坐標軸給標示了出來。

            3dsmax

             

              嗯……看似還不錯的樣子,一個簡單的室內模型。里大家可能有疑問,這有什么特殊的么?有,而且很大。如果是玩過《翡翠帝國》或者《鬼武者》就知道,這些游戲是使用3dsmax建模的,但是它的BINK動畫和游戲場景所使用的模型是一樣的,這里面就牽涉到的一個模型的導出問題。對于從3dsmax導出的模型來說,遵守我們日常數學上的坐標系(注意我沒有使用任何左右手的術語),Z向上,XY表示水平面。所以,如果你想使用3DS模型,并且和我一樣使用它的貼圖、材質,而且還想就在這個世界中進行坐標平移等等工作,請準備兩樣神兵利器 —— Deep Exploration和lib3ds,如何使用我后面會舉例說明。

            RH
             

             

               這里,如果把這個場景導出,那么,這個室內場景其實就表達了一個世界坐標系WC。因為這個坐標系是由3dsmax自己維護的。我們再從3dsmax中導出一個模型。注意左下角,坐標系我用雙圓著重了。

            Rose

             

              如果假如這個玫瑰花要被放入上面的室內場景,為了精確起見,首先在3dsmax中要把這個玫瑰花對齊到它自己的XYZ坐標原點。然后在OpenGL中,如果從頭開始繪制,我們可以這樣寫,

            glMatrixMode(GL_MODELVIEW);

            glLoadIdentity();

            gluLookAt(/*Somewhere*/,/*Somewhere*/,/*must be vec3(0,0,1)*/);

            DrawScene();

            glPushMatrix();

            glTranslatef(/*Somewhere*/);

            DrawWhiteRose();

            glPopMatrix();

              為什么繪制Scene室內場景的時候沒有使用glTranslatef做變換?

               因為我把場景當作了一個“世界”。

              為什么繪制Rose的時候使用了glTranslatef?

              因為Rose自己有一個LC,為了把它放到世界內的某個角落,需要做平移變換。

              所以說對于一個成熟的CG Coder來說,他肯定是使用導出的模型,使用它里面的燈光、場景、攝像機參數,而不是將一些參數硬編碼。當然隨著編程能力的提高,你一定也會這樣,尤其是對于大型成熟的GAME、3D軟件,都有自己內部的模型格式,比如,WarCraft3。

             

            三、看一下

              按照《Writing RenderMan Shaders Siggraph 1992 Course 21》這篇文章中的教程PPT,一切都簡化為一下幾個公式,

            XformPixar

             

              我想上面的三個式子已經解決了99%的問題。式子中的M就相當于GL中的MV矩陣,轉換后的空間就是ES。對于變換頂點,整個過程可以如下圖所示,

            GL

             

              在這里,為什么把Scale、Rotate、Translate放在一起,這是因為,只要你知道你在做什么,你就可以調整順序

              很多初學者在寫GL程序的時候總會被Rotate、Translate變換弄的頭大,其實,對于我也一樣^_^。但是我知道有些好方法可以控制復雜度,少出錯。

            whofirst

             

              先平移還是先旋轉,看了上圖估計就明白了八九分,至于要什么效果可是心隨你動。但是要注意,這個圓柱本身的LC可以一點都沒有變化,每個頂點相對于自己的原點都沒有任何變化。實時上,這種情況在實際編程中不會非常復雜。

              隨便打開一個游戲,場景大部分是不動的,相應的,它們的表面Normal也是不變的。運動的東西有,玩家角色本身,場景中的一些運動物體比如街上的汽車,夜總會門口拉客的女人。為什么我使用了兩個這兩個運動范例,因為這兩個例子牽涉到2樣東西,一樣是只變換靜態的頂點和向量,一樣是變換動態的向量。

              一輛車,開始建模,以XYZ為平面,車輪正好貼著在XY平面。如果我們以“米”為單位建立世界模型,假設汽車先在( 0,0,0 )處出現,然后直線移動到( 6,8,0 )處,那么就相當于是平移,程序我們可以這樣寫,

            glMatrixMode(GL_MODELVIEW);

            glLoadIdentity();

            gluLookAt(/*Somewhere*/,/*Somewhere*/,/*must be vec3(0,0,1)*/);

            DrawScene();

            float delta_X = Speed*TimeSlice*0.6;//X方向的位移累加量

            float delta_Y = Speed*TimeSlice*0.8;//Y方向的位移累加量

            float S_X = 0.0;

            if( S_X < 6.0 ){

            glPushMatrix();

            glTranslatef( delta_X,delta_Y,0 );

            DrawCar();

            glPopMatrix();

            S_X += delta_X;

            }else{

            glPushMatrix();

            glLoadIdentity();

            glTranslatef( 6,8,0 );

            DrawCar();

            glPopMatrix();

            }

              哈,一切都很簡單,模擬了一輛車的移動。在這里,首先讓它一步一步的移動,到了目的地后程序判斷是不是應該停下來了,如果到了目的地那么只需要在目的地繪制它就可以了。所以說對于大量物體運動的場景,CPU的負擔是很大的。我們想象一下,對于一個直線運動的物體來說,它的表面Normal會變化么?答案是,不會。但是假如車輛拐彎了,也就是牽涉到了旋轉,Normal肯定是變化了。所以對于一般情況下,向量是根本不需要變換的,如果物體僅僅是平移。

              我們已經知道,變換Normal是通過MVit矩陣。假設我們的MV只是通過glTranslatef( 6,8,0 )得到,那么變換頂點就是這個過程。假設車的中心在(0,0,0),向量為(0,0,1)指向天空。

            Eqn0

             

              變換向量就是這個樣子,

            Eqn1

             

              其實變換向量只需要MV矩陣的左上3x3的it,也就是成了這個樣子,

            Eqn2
             

             

              很明顯,對于只在WC中Translate平移變換來說,向量其實是不需要變換的。但是旋轉了,怎么辦?好辦,動手計算Wit就是了。怎么算?兩種方法。

              第一種:先清空當前的MV,然后反相變換,然后把它弄出來再Transpose一下就可以了。比如繪制車的方式是這個樣子,

            glMatrixMode(GL_MODELVIEW);

            glLoadIdentity();

            gluLookAt(/*Somewhere*/,/*Somewhere*/,/*must be vec3(0,0,1)*/);

            glPushMatrix();

            glTranslatef( 6,8,0 );

            glRotatef(45,0,0,1);

            DrawCar();

            glPopMatrix();

            那么下面的代碼得到Wit儲存在數組中

            GLdouble WCit[16];

            glMatrixMode(GL_MODELVIEW);

            glPushMatrix();

            glLoadIdentity();

            glTranslatef(-6,-8,0);

            glRotatef(-45,0,0,1);

            glGetDoublev(GL_TRANSPOSE_MODELVIEW_MATRIX_ARB,WCit);

            glPopMatrix();

              第二種方法其實更加的簡便,使用現成的庫,比如NVIDIA SDK中附帶的nv_math庫、MathGL++等。比如上面的變化,用nv_math改寫后就是這樣,

            mat4 wc(1,0,0,0,
                            0,1,0,0,
                            0,0,1,0,
                            0,0,0,1);
            vec3 t(6,8,0);
            vec3 r(0,0,1);
            wc.set_translation(t);
            wc.set_rot(nv_two_pi*30.0/360.0,r);注意這里的set_rot接受弧度
            for( int i =0 ; i<4; i++){
                cout<<wc.mat_array[4*i+0]<<'\t'<<wc.mat_array[4*i+1]<<'\t'<<wc.mat_array[4*i+2]<<'\t'<<wc.mat_array[4*i+3]<<endl;
            }

              如果是用MathGL++就是這個樣子,

            GLMatrix<double> mat;
            mat.identity();
            mat.loadTranslate(6,8,0);
            mat.applyRotateZ(30);//這里就方便一些可以直接輸入角度
            for( int i =0 ; i<4; i++){
                cout<<mat[4*i+0]<<'\t'<<mat[4*i+1]<<'\t'<<mat[4*i+2]<<'\t'<<mat[4*i+3]<<endl;
            }

              他們都會輸出下列矩陣的形式,但是注意!沒有被轉置!所以在載入GL的時候需要轉置,對于這個簡單的變換來說,也就是保證第四列是你的Translate值。

            Eqn3

              你覺得這樣的方法怎么樣?如果變換太多,自己根本不清楚的。所以,我推薦用第二種方法,手動計算絕對不會出錯,而且可以與自己的程序框架結合在一起。

            四、我好想見到她

              上面說的都是WC的事情,現在呢,讓我們結合OpenGL程序來談如何做到準確的場景變換,同時自己又不會迷糊。我上篇一文章用3dsmax做了個CornelBox然后導了出來,在Deep Exploration中觀察它,

            RHmarble

             

              哈,注意看Scene Tree,我這個里面有個FreeSpot燈光,ITVIRGEN也就是雕塑,還有CornelBox的5個平板。讓我們看一下那個雕塑的屬性,

            ITVIRGENProp

             

              哇塞,東西挺詳細,包括這個模型的位置旋轉角度都有了,下面還有一個挺復雜的變換矩陣。在這里有些朋友可能會想,“當我載入這個3DS的時候,是不是也要將屬于這個雕塑MESH的每個頂點都用這個矩陣去變換一下呢?”,哈,如果你想了這個問題說明你已經登堂入室了。我的回答是:有好心人幫我們做過了這個事情,但是是誰這么好心呢?應該是3dsmax,因為在lib3ds的中我沒有找到任何有關于矩陣變換的代碼。也就是說,從3dsmax導出的場景可以被當作是一個WC來看,所有的模型都已經各就各位,所以我們就不需要讀取mesh的頂點后再用這個矩陣去變換。

              下一個問題就是,我們把“眼睛”放在什么地方?如果是一個酷酷的動態攝像機,我們又該如何處理,最重要的是,VIEW變換是一個什么東西?

              從另外一個角度說,VIEW變換是,一個Translate和一個Rotate

              在GLU中有一個極其好用的函數叫做gluLookAt,MSDN中它的定義是,

            “The gluLookAt function creates a viewing matrix derived from an eye point, a reference point indicating the center of the scene, and an up vector. The matrix maps the reference point to the negative z-axis and the eye point to the origin, so that when you use a typical projection matrix, the center of the scene maps to the center of the viewport. Similarly, the direction described by the up vector projected onto the viewing plane is mapped to the positive y-axis so that it points upward in the viewport. The up vector must not be parallel to the line of sight from the eye to the reference point.”

              gluLookAt函數使用3個參數,Eye Position、Reference Position、Up Vector去構造了一個View矩陣。它是如何計算的呢?

              假如我們有了上面三個量,分別叫做e、r、u。我們先獲得視線的方向d=normalize(r-e),我們用D與U的叉乘得到一個量c=cross(d,u),最后重新計算一下u'=cross(c,d),然后我們就得到了一個矩陣o,讓它與一個T矩陣相乘,就得到了與gluLookAt生成的矩陣相同的VIEW矩陣,過程如下,

            Eqn4
             

             

              詳情可以去找那本《OpenGL® Distilled》,這本書的其他部分都不怎么樣,就這個地方很有價值^_^

              讓我們開始使用剛剛學到的知識了。如何讀取3DS模型是一個老生常談的故事,幾乎所有新手都會問這個問題。首先,3DS是最經典的模型,AutoCAD等工具都支持,但是它的缺點也很明顯,非文本而是二進制,處理比較麻煩。下面我將展示如何使用lib3ds去讀取一個模型文件中的所有的頂點、向量、紋理坐標、材質、貼圖,并有效的組合在一起渲染。

              首先我們定義一些基本的結構,比如這個,

            typedef struct _RenderModel
            {
                ushort* m_TriangleIndex;
                uint m_Triangles;
                float* m_VertexPtr;
                uint m_Indexs;
                float* m_NormalPtr;
                uint m_Normals;

                float* m_TexCoordPtr;
                uint m_Texels;
                float m_BoundBoxMin[3];
                float m_BoundBoxMax[3];

                float m_XformMatrix[16];

                std::string m_ModelName;
                std::string m_MatName;
            }RenderModel;

            typedef struct  _RenderMesh
            {
                uint m_Models;
                RenderModel* m_ModelPtr;
            }RenderMesh;

            typedef struct _Phong
            {
                float emission[4];
                float ambient[4];
                float diffuse[4];
                float specular[4];
                float shininess;

            }Phong;

            typedef struct _Camera
            {
                float m_Position[3];
                float m_Direction[3];
                float m_Center[3];
                float m_Up[3];
                float m_Fovy;
                float m_Near;
                float m_Far;
            }Camera;

            static map<string, Phong> MaterialMap;
            typedef pair<string,Phong> materialpair;
            static vector<RenderModel> Models;

              但是要知道,有不少成員是根本用不到的。如何處理呢?這個函數,

            static void Init3DSModel(const char* filename)
            {
                Lib3dsFile* file = 0;
                file = lib3ds_file_load(filename);
                if ( !file ){
                    printf("Error On Open File!\n");
                    system("PAUSE");
                    exit(-1);
                }
                Lib3dsMesh* mesh = 0;
                Lib3dsMaterial* mat = 0;
                typedef vector<float> scalarvec;
                typedef vector<uint> uintvec;
                vector<scalarvec> TriSoups;
                printf("INFO : Processing the 3ds file...\n");
                for( mesh=file->meshes; mesh!=0; mesh=mesh->next ){
                    scalarvec Object[3];
                    uintvec IDVector;
                    RenderModel Model;
                    Lib3dsVector *  normalL = new  Lib3dsVector[3*sizeof(Lib3dsVector)*mesh->faces];
                    lib3ds_mesh_calculate_normals(mesh,normalL);

                    for( Lib3dsDword i = 0; i < mesh->faces; i++ ){

            //取得索引
                        Lib3dsWord _0= mesh->faceL[i].points[0];
                        Lib3dsWord _1= mesh->faceL[i].points[1];
                        Lib3dsWord _2= mesh->faceL[i].points[2];

            //取得頂點

                        vec3 V0(mesh->pointL[_0].pos[0],mesh->pointL[_0].pos[1],mesh->pointL[_0].pos[2]);
                        vec3 V1(mesh->pointL[_1].pos[0],mesh->pointL[_1].pos[1],mesh->pointL[_1].pos[2]);
                        vec3 V2(mesh->pointL[_2].pos[0],mesh->pointL[_2].pos[1],mesh->pointL[_2].pos[2]);

            //取得法向量

                        Object[1].push_back( normalL[3*i+0][0] );Object[1].push_back( normalL[3*i+0][1] );Object[1].push_back( normalL[3*i+0][2] );
                        Object[1].push_back( normalL[3*i+1][0] );Object[1].push_back( normalL[3*i+1][1] );Object[1].push_back( normalL[3*i+1][2] );
                        Object[1].push_back( normalL[3*i+2][0] );Object[1].push_back( normalL[3*i+2][1] );Object[1].push_back( normalL[3*i+2][2] );

            //取得紋理坐標
                        Object[2].push_back( mesh->texelL[_0][0] );Object[2].push_back( mesh->texelL[_0][1] );
                        Object[2].push_back( mesh->texelL[_1][0] );Object[2].push_back( mesh->texelL[_1][1] );
                        Object[2].push_back( mesh->texelL[_2][0] );Object[2].push_back( mesh->texelL[_2][1] );

            //儲存起來

                        Object[0].push_back( V0.x );Object[0].push_back( V0.y );Object[0].push_back( V0.z );
                        Object[0].push_back( V1.x );Object[0].push_back( V1.y );Object[0].push_back( V1.z );
                        Object[0].push_back( V2.x );Object[0].push_back( V2.y );Object[0].push_back( V2.z );
                    }

            //分配內存

                    delete [] normalL;
                    Model.m_Triangles = mesh->faces*3;
                    Model.m_VertexPtr = new float[Object[0].size()];
                    Model.m_Normals = mesh->faces*3;
                    Model.m_NormalPtr = new float[Object[1].size()];
                    Model.m_Texels = mesh->texels*3;
                    Model.m_TexCoordPtr = new float[Object[2].size()];

            //拷貝進內存
                    copy(Object[0].begin(),Object[0].end(),Model.m_VertexPtr);
                    copy(Object[1].begin(),Object[1].end(),Model.m_NormalPtr);
                    copy(Object[2].begin(),Object[2].end(),Model.m_TexCoordPtr);
                    Model.m_MatName = mesh->faceL[0].material;
                    Model.m_ModelName = mesh->name;
                    Models.push_back(Model);
                }

            //這個函數一定要注意,這是個小小的修補程序,比如用lib3ds計算得到的Bottom的向量是向下的,我們需要翻轉一下
                for(vector<RenderModel>::iterator itr = Models.begin(); itr !=Models.end(); itr++){
                    if( itr->m_ModelName == string("Bottom01") ){
                        for( unsigned int i =2; i<itr->m_Normals*3;i+=3 )
                            itr->m_NormalPtr[i] *= -1.0f;
                    }
                }
                printf("INFO : Processing the material...\n");
                for( mat = file->materials; mat != 0; mat=mat->next ){
                    string _MatName( mat->name );
                    Phong _Mat;
                    //載入來自3ds模型文件的材質
                    for(int i = 0; i<4; i++){
                        _Mat.ambient[i] = mat->ambient[i];
                        _Mat.diffuse[i] = mat->diffuse[i];
                        _Mat.specular[i] = mat->specular[i];
                    }
                    _Mat.shininess = mat->shininess;
                    MaterialMap.insert( materialpair( _MatName , _Mat  ) );
                }
            }

              那么我們現在有了Models與MaterialMap這兩個容器,如何渲染呢?

            glEnableClientState(GL_VERTEX_ARRAY);
            glEnableClientState(GL_NORMAL_ARRAY);
            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
            for( vector<RenderModel>::iterator i=Models.begin(); i!=Models.end(); i++ ){
                map<string, Phong>::iterator matItr = MaterialMap.find( i->m_MatName );
                glMaterialfv(GL_FRONT_AND_BACK,GL_AMBIENT,&matItr->second.ambient[0]);
                glMaterialfv(GL_FRONT_AND_BACK,GL_DIFFUSE,&matItr->second.ambient[0]);
                glMaterialfv(GL_FRONT_AND_BACK,GL_SPECULAR,&matItr->second.specular[0]);
                glMaterialf(GL_FRONT_AND_BACK,GL_SHININESS,matItr->second.shininess);
                glVertexPointer(3,GL_FLOAT,0,i->m_VertexPtr);
                glNormalPointer(GL_FLOAT,0,i->m_NormalPtr);
                glTexCoordPointer(2,GL_FLOAT,0,i->m_TexCoordPtr);
                glDrawArrays(GL_TRIANGLES,0,i->m_Triangles);
            }
            glDisableClientState(GL_TEXTURE_COORD_ARRAY);
            glDisableClientState(GL_NORMAL_ARRAY);
            glDisableClientState(GL_VERTEX_ARRAY);

               我相信些代碼是很簡單的,通過mesh儲存到的material名稱,載入GL,然后渲染就可以了。思路就是這么明顯。

              不知道你有沒有注意,在glDrawArrays時,我沒有使用任何的glTranslatef、glRotatef等函數,原因我已經解釋過了,我將整個場景當作了整個世界,那么我就沒有必要再多此一舉。

              那么此時我的Reshape函數呢?

            static void Reshape(int w,int h)
            {
                glMatrixMode(GL_MODELVIEW);
                glLoadIdentity();
                gluLookAt(Eye.m_Position[0],Eye.m_Position[1],Eye.m_Position[2],Eye.m_Center[0],Eye.m_Center[1],Eye.m_Center[2],Eye.m_Up[0],Eye.m_Up[1],Eye.m_Up[2]);
                glMatrixMode(GL_PROJECTION);
                glLoadIdentity();
                gluPerspective(46,(double)w/(double)h,0.5,100);

                glViewport(0,0,w,h);

                glMatrixMode(GL_MODELVIEW);
            }

              是不是也極其簡單呢?那個Eye我是直接從lib3ds讀取得到的CAMERA的位置載入的,具體的實現很簡單。同樣,那個Spot的位置與方向也是這樣獲得的。

            五、Im Stillen lass ich ab von Dir

              到現在我們說的都是MV變換,沒有說到透視變換Projection Transform。

              對比MV變換,PT就顯得簡單了許多,它的根本作用是,將ES中的所有點“盡量”都轉換到X與Y方向上都是[-1,1]的這個屏幕區間里面去。那我們應該如何理解呢?

              打開你的Shader Designer,渲染busto人頭。在Fragment Shader中輸入,

            void main()
            {
                gl_FragColor =     gl_TexCoord[0];
            }

              在Vertex Shader中輸入,

            void main()
            {
                gl_TexCoord[0] = gl_MultiTexCoord0;
                gl_Position = vec4(gl_TexCoord[0].st,0,1.0);
            }

              這里估計很多朋友就迷糊了,這是什么意思啊,為什么gl_Position不是寫入ftransform()或者是gl_ModelViewProjectionMatrix*gl_Vertex,而是怪模怪樣的給它賦值為UV坐標?

              先不提這叫做什么,反正你將看到這個樣子,

            R2TSWrg

              我們再把Vertex Shader改成

            void main()
            {
                gl_TexCoord[0] = gl_MultiTexCoord0;
                gl_Position = vec4(gl_TexCoord[0].st*2.0-1.0,0,1.0);
            }

              這個時候就塞滿了整個屏幕,

            R2TS

              其實這就是Render To Texture Space,NVIDIA在用Cheating的方式處理SSS的時候做的那個“渲染到紋理空間”。

              我們當然知道,UV空間是[0,1],我如果給它乘以2減去1就是變換到[-1,1]的空間中去。但是在這里我們可以明白一個道理,頂點經過MVP處理后,要么被裁減掉,要么將得到在[-1,1]中間的一對數值。

              其實當我們明白了這個道理后,就可以完全的發揮GPGPU的功能,因為我們已經明白了數值的Framebuffer的準確寫入位置。這樣就可以隨心所欲的在Framebuffer中寫入數據,讀取想要的數據,甚至做遍歷。

              我們可以用glFrustum或者是gluPerspective函數獲得一個透視矩陣,gluPerspective更常用一些。gluPerspective定義的了一個***模型,它有fovy、aspect、zNear、zFar組成。這4個參數定義了一個平截頭體,這個平截頭體就是攝像機,在這個Volumn中的東西才是可見的,其他都是不可見的。示意圖如下,

            Camera

              但是我們又知道,GL其實是把人眼放在了(0,0,0,0)位置,每一個象素都遵循(0,0,-1,0)這個方向。其實這就是(x,y,z,w)的最后一個分量的作用,當我們寫入gl_Position后,硬件會自動的除以w分量以獲得裁減坐標系,然后再使用Viewport參數將這些玩意與屏幕象素對應在一起。

              但是我們一定一定要知道:無論是OpenGL、Direct3D,甚至是RenderMan、mental ray都有一些這樣的問題,那就是,它們其實都是***模型。RenderMan和mental ray有些特殊,它們即有光柵化又有光線跟蹤模塊,所以處理攝像機就比較靈活,可是對于GL和DX來說,模擬一些攝像機效果如DOF就只能用Trick。

              再來一個紋理投射Projection Texture。

              紋理投射是個很經常的問題,比如在做Shadow Mapping的時候,還有就是做光照的時候,還有比如模擬一個電影放映機的過程,都是紋理投射。它的根本意義是:“假想我們從投射物體比如光源去看場景,我們希望得到世界里的每個頂點在那個虛擬攝像機屏幕上的XY位置”。寫成連續矩陣的形式就是,

            glMatrixMode(GL_TEXTURE);
            glLoadIdentity();
            glTranslatef(.5f, .5f, .5f);
            glScalef(.5f, .5f, .5f);
            gluPerspective(/*LIght Shape*/);
            gluLookAt(/*Light Position,direction,up vector*/)

              這個時候,GL的紋理矩陣其實就是我們想要的那個矩陣:它將世界頂點變換到光源攝像機空間去,在Shader中我們可以直接使用texture2DProj去做紋理采樣。比如這個聚光燈效果,

            NoInDir

            六、結束

              在這篇文章中,我把GL中的矩陣處理流程,手動生成矩陣的方式,結合了真實的場景教了大家如何去認識矩陣變換問題,后面還說了一些與矩陣變換相關的應用,如果你覺得有錯可以聯系我謝謝,如果你覺得本文對你有幫助、幫助大多數新手進步、分享知識是我的責任。轉載時請附上我的聯系方式,謝謝。

            周波 Bo Schwarzstein

            Mailbox 242,Nanjing Forestry University,Jiangsu,China

            jedimaster.cnblogs.com

            zhoubo22 'at' hotmail.com

            posted on 2009-01-06 17:17 doing5552 閱讀(851) 評論(0)  編輯 收藏 引用
            老司机午夜网站国内精品久久久久久久久| 大香伊人久久精品一区二区| 久久久久久综合一区中文字幕| 国产日产久久高清欧美一区| 久久强奷乱码老熟女网站| 亚洲精品无码专区久久同性男| 日韩人妻无码一区二区三区久久| 久久精品国产免费一区| 亚洲精品无码久久久久AV麻豆| 国产精品对白刺激久久久| 91亚洲国产成人久久精品网址| 久久婷婷五月综合国产尤物app| 97精品依人久久久大香线蕉97| 国产69精品久久久久99| 午夜不卡久久精品无码免费| 久久国产精品偷99| 97精品国产91久久久久久| 一级a性色生活片久久无| 91精品国产高清久久久久久国产嫩草| 久久久久久免费视频| 精品久久人人妻人人做精品| 国产精品美女久久久久久2018| 欧美亚洲国产精品久久| 欧美色综合久久久久久| 久久久WWW成人| 99久久99久久精品国产片果冻| 国产精品9999久久久久| 日产精品99久久久久久| 一本久久知道综合久久| 中文字幕久久亚洲一区| 亚洲精品成人网久久久久久| 久久久久九国产精品| 久久久久亚洲爆乳少妇无| 久久国产成人亚洲精品影院| 9191精品国产免费久久| 精品综合久久久久久88小说| 日本久久久久久中文字幕| 一本久久久久久久| 国产福利电影一区二区三区久久久久成人精品综合 | 久久精品国产亚洲AV无码麻豆| 午夜肉伦伦影院久久精品免费看国产一区二区三区 |