Projective Texture的原理與實現

小 樓 一 夜 聽 春 雨發表於2013-10-24

http://blog.csdn.net/xukunn1226/article/details/775644

 

Projective Texture是比較常見的一種技術,實現起來程式碼也就區區的不過百行,瞭解其原理及技術細節是我們的重點,知其然,知其所以然。
       粗略的說就是想象場景中有臺投影儀(Projector),texture就是我們投影的內容,把紋理放在近裁剪面(near clip plane)上,沿著投影儀的方向把紋理投影到場景中。Xheartblue兄翻譯了一篇文章,很好的給投影紋理的原理進行的闡述[1],有興趣閱讀原文的可以訪問這裡[2],這本書可以是好東東啊!!
在這裡有幾個概念不能混淆了:Camera——人眼座標;Projector——投影儀座標。在紋理座標自動生成過程中,關鍵的就是如何把人眼座標系中的vertex轉換到Projector Space,進而轉換到Projector clip plane,最後規範化為紋理座標[0,1]。在我的實現中是把Projector定義為一個Camera(注意:不同於顯示場景的Camera),所以Projector有Camera的各項屬性,我們可以通過gluPerspective,gluLookAt對其進行設定。如何為Projective texture自動生成紋理座標是重點,這個過程和物體頂點變換為視窗座標是類似的。在NV Developer Document[3]中有篇文件說的很詳細,截圖如下:
 
 

對上圖可以這麼理解,Camera用於物體頂點座標到規範化裝置的生成,Projector用於物體頂點紋理座標的生成。而在不同模式下紋理座標的生成方式是不同。

根據glTexGen的不同引數GL_OBJECT_LINEAR,GL_EYE_LINEAR來確定紋理生成的函式。在Projective texture mapping一文中給出的紋理座標生成公式是:

 

注意:此處Vo是基於物體座標系的,無論物體在人眼座標系中如何變換,其物體座標是不變的,根據公式其紋理座標也是不變的。所以在GL_OBJECT_LINEAR模式下看到的紋理是緊貼在物體表面的。而Ve是基於人眼座標系的,在Projector設定好位置後是基於人眼座標不變的。

我們來看Object Linear模式下紋理座標是如何生成的:

 

 

此處的M(Model Matrix)是模型變換矩陣,不同於OPENGL的MODELVIEW MATRIX(這在模型變換的基礎上還進行了檢視變換)。頂點座標左乘M後變換到World Space,為什麼要變換到World Space呢??這是因為Camera和Projector都是通過gluLookAt而定義在世界座標中,這就像座橋樑,唯有通過它才能使得人眼檢視體中的頂點轉換到Projector定義的檢視體內,才能進一步求出相應的紋理座標。Vp是projector的view matrix(由gluLookAt定義),累加左乘得到projector space中的座標。Pp是projector的projection matrix(由gluPerspective定義),累加左乘得到projector clip space中的座標。最後累加偏移矩陣,使紋理座標的s、t、r對映到[0,1]內。

本文關注的是Eye Linear模式下紋理座標的生成,有了以上對Object Linear的理解就好辦了,公式如下:

 

Eye Linear模式是把人眼座標下的頂點左乘OPENGL的MODELVIEW逆矩陣轉換到world space中。Eye Linear和Object Linear的最後一項略有不同,Ve-1是Camera檢視矩陣的逆矩陣,目的是把人眼座標下的頂點轉換到世界座標系中(還記得為什麼一定要轉換到世界座標中嗎?橋樑的作用,前面已經講過了^_^)。總之,無論何種模式下使用何種方法都需要把物體頂點轉換到世界座標系中,這樣才能通過累加Vp(Projector的view matrix)、Pp(Projector的projection matrix)、偏移矩陣得到紋理座標。

Pointer在其BLOG中對上述問題也有詳細的描述[4],有很好的啟發作用。值得拜讀!

 

紋理座標的自動生成大致就如此了,看點程式碼或許能更好的理解吧!

 

//----------------------------------------ProjTexture.h------------------------------------

/********************************************************

Usage Instruction:

       //in init()

       glGenTextures(1, &id);

       glBindTexture(GL_TEXTURE_2D, id);

       glTexImage2D(GL_TEXTURE_2D, ......, texImage);

 

       ProjectiveTexture lightmap;

       lightmap.SetupTexture(id);

       lightmap.SetupMatrix(Camera* lightCam);

 

       //in the render pipe loop...

       lightmap.SetupMatrix(lightCam);

       lightmap.BeginRender();

       draw scene...

       lightmap.EndRender();

 

********************************************************/

#ifndef    _PROJTEXTURE_H_

#define    _PROJTEXTURE_H_

 

#include "stdafx.h"

#include <gl/gl.h>

#include <gl/glu.h>

#include <gl/glext.h>

#include "Camera.h"

 

class ProjectiveTexture

{

private:

       GLuint    textureID;

       float        matrix[16];

 

public:

       ProjectiveTexture() {}

       virtual ~ProjectiveTexture() { glDeleteTextures(1, &textureID); }

 

       //繫結紋理,設定紋理單元過濾操作、環境應用等引數

       void SetupTexture(GLuint id)

       {

              textureID = id;

 

              glBindTexture(GL_TEXTURE_2D, id);

              glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);

              glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

              glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

              glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

              glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD);

//            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

       }

 

       //lightCam是我們定義的一個Camera類,此處代表Projector

       void SetupMatrix(Camera* lightCam)

       {

              glMatrixMode(GL_TEXTURE);

 

              glPushMatrix();

              static float biasMatrix[16] = { 0.5, 0.0, 0.0, 0.0,

                                                                      0.0, 0.5, 0.0, 0.0,

                                                                      0.0, 0.0, 0.5, 0.0,

                                                                      0.5, 0.5, 0.5, 1.0 };

 

              static double modelviewMatrix[16];

              static double projMatrix[16];

 

              //獲得Projector的模型檢視矩陣,用於把world space的頂點轉換到projector space

              lightCam->GetModelViewMatrix(modelviewMatrix);

              //獲得Projector的投影矩陣,用於把projector space的頂點轉換到projector clip space

              lightCam->GetProjectionMatrix(projMatrix);

 

              glLoadMatrixf(biasMatrix);

              glMultMatrixd(projMatrix);

              glMultMatrixd(modelviewMatrix);

 

              glGetFloatv(GL_TEXTURE_MATRIX, matrix);          //獲得紋理矩陣

 

              glPopMatrix();

       }

 

       void BeginRender()

       {

              static float planeS[4] = { 1.0f, 0.0f, 0.0f, 0.0f };

              static float planeT[4] = { 0.0f, 1.0f, 0.0f, 0.0f };

              static float planeR[4] = { 0.0f, 0.0f, 1.0f, 0.0f };

              static float planeQ[4] = { 0.0f, 0.0f, 0.0f, 1.0f };

 

              glBindTexture(GL_TEXTURE_2D,textureID);

 

              glTexGeni(GL_S,GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR);

              glTexGeni(GL_T,GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR);

              glTexGeni(GL_R,GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR);

              glTexGeni(GL_Q,GL_TEXTURE_GEN_MODE,GL_EYE_LINEAR);

 

              glTexGenfv(GL_S,GL_EYE_PLANE,planeS);

              glTexGenfv(GL_T,GL_EYE_PLANE,planeT);

              glTexGenfv(GL_R,GL_EYE_PLANE,planeR);

              glTexGenfv(GL_Q,GL_EYE_PLANE,planeQ);

 

              glEnable(GL_TEXTURE_GEN_S);

              glEnable(GL_TEXTURE_GEN_T);

              glEnable(GL_TEXTURE_GEN_R);

              glEnable(GL_TEXTURE_GEN_Q);

 

              glMatrixMode(GL_TEXTURE);

              glLoadMatrixf(matrix);                               // load our texture matrix

 

              //渲染管線就像流水線,頂點是我們的操作物件,何時把相關的操作傳入渲染管線,

        //何時把不必要的操作卸下是我們該考慮的。物體頂點座標應該是在模型檢視矩陣

        //(GL_MODELVIEW)轉換到世界座標,然後進入紋理矩陣模式下求出紋理座標

              glMatrixMode(GL_MODELVIEW);            

       }

 

       void EndRender()

       {

              glDisable(GL_TEXTURE_GEN_S);

              glDisable(GL_TEXTURE_GEN_T);

              glDisable(GL_TEXTURE_GEN_R);

              glDisable(GL_TEXTURE_GEN_Q);

       }

};

 

#endif

 

void ProjectiveTextureViewer::Init()

{

       glEnable(GL_CULL_FACE);

 

       glGenTextures(1, &texdecal);

       glBindTexture(GL_TEXTURE_2D, texdecal);

       read_ppm("Data//decal_image.ppm");

 

       glGenTextures(1, &texspotlight);

       glBindTexture(GL_TEXTURE_2D, texspotlight);

       read_ppm("Data//spotlight_image.ppm");

 

       ……

       ……

       ……

 

       pLightMap = new Camera();         //create Projector

 

       lightmap.SetupTexture(texspotlight);     //ProjectiveTexture lightmap

       lightmap.SetupMatrix(pLightMap);

}

 

void ProjectiveTextureViewer::Draw()

{

       glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

 

       glMatrixMode(GL_MODELVIEW);

       glEnable(GL_TEXTURE_2D);

 

       //畫一個圓球,代表Projector

       glPushMatrix();

       glMultMatrixd(pLightMap->frame()->matrix());

       glColor3f(1.0, 1.0, 0.0);

       gluSphere(q, 0.02, 12, 12);

       glPopMatrix();

 

       //因為Projector是可以控制的,所以需要實時更新紋理矩陣

       lightmap.SetupMatrix(pLightMap);

       lightmap.BeginRender();

 

       glMatrixMode(GL_MODELVIEW);

 

       glPushMatrix();

       glMultMatrixd(pRoom->matrix());

       DrawRoom2();

       glPopMatrix();

 

       glPushMatrix();

       glMultMatrixd(pObject->matrix());

       DrawObject2();

       glPopMatrix();

 

       lightmap.EndRender();

}

 

    現在來看看效果圖吧:)

    黃色小球為Projector(可控),ProjectiveTexture用的是一張笑臉紋理。

 

 

 

    上面三副圖從不同角度給出了投影紋理的效果圖,效果還是可以的。這裡沒有考慮遮擋的問題,導致牆上的一些紋理本應被立方體阻擋的也渲染出來了,或許獲取一個基於Projector的depth map可以解決,這就該是Shadow Mapping了,有待解決^_^,希望高人可以指點一下了!!

 

 

 

 

    又一問題,從這兩張圖中可以看出Reverse Projection的問題,當Projector出現在兩堵牆的同側,牆上的紋理方向一致,如果Projector出現在兩堵牆的中間,則一個沿著projector的視線方向,另一個則為相反方向,通過定義一個裁剪平面能否解決這個問題呢?思考一下……

 

       只是簡單實現了一個投影紋理,發現問題還是瞞多的,fighting……

 

 

 

see also:

[1]、http://dev.gameres.com/Program/Visual/3D/ProjectTexture.htm

[2]、http://www.bluevoid.com/opengl/sig00/advanced00/notes/node81.html

[3]、http://www.nvidia.com/object/Projective_Texture_Mapping.html

[4]、http://pointer.cnblogs.com/archive/2004/12/30/84367.html

相關文章