OpenGL 多視圖與截屏


最近看紅寶書學習 OpenGL 一段時間了,寫了簡單的 demo 程序溫習一下知識。 
主要是 使用 glScissor 多視圖顯示畫面和使用 glReadPixels 給畫面截屏,使用顯示列表(display list)加上一些簡單的光照。
程序運行后,按字母 P 鍵截屏,圖片存放在當前目錄,按字母 Q 鍵在單視圖與多視圖之間切換。
效果圖如下,代碼已上傳到 github 上,地址

 

下面是創建顯示列表的函數。

GLuint displayList(void)
{
    static GLfloat gold_ambient[4]  = { 0.24725f, 0.1995f, 0.0745f, 1.0f };
    static GLfloat gold_diffuse[4]  = { 0.75164f, 0.60648f, 0.22648f, 1.0f };
    static GLfloat gold_specular[4] = { 0.628281f, 0.555802f, 0.366065f, 1.0f };
    static GLfloat gold_shininess   = 41.2f;
    
    static GLfloat silver_ambient[4]  = { 0.05f, 0.05f, 0.05f, 1.0f };
    static GLfloat silver_diffuse[4]  = { 0.4f, 0.4f, 0.4f, 1.0f };
    static GLfloat silver_specular[4] = { 0.7f, 0.7f, 0.7f, 1.0f };
    static GLfloat silver_shininess   = 12.0f;

    GLuint list = glGenLists(1);
    glNewList(list, GL_COMPILE);
    
    glMaterialfv(GL_FRONT, GL_AMBIENT,  gold_ambient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE,  gold_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, gold_specular);
    glMaterialf(GL_FRONT, GL_SHININESS, gold_shininess);
    
    glMaterialfv(GL_BACK, GL_AMBIENT,  silver_ambient);
    glMaterialfv(GL_BACK, GL_DIFFUSE,  silver_diffuse);
    glMaterialfv(GL_BACK, GL_SPECULAR, silver_specular);
    glMaterialf(GL_BACK, GL_SHININESS, silver_shininess);
    
    glutWireTorus(0.3/*innerRadius*/, 0.5/*outerRadius*/, 64/*nsides*/, 128/*rings*/);
    glEndList();
    
    return list;
}

 

關於截圖的代碼有點多,請下載文件查看,不在這里粘貼了,這里簡單說一下步驟。

1. 分配足夠的內存 GLubyte *image=(GLubyte*)malloc(width*height*3*sizeof(GLubyte));

2. 調用 void glReadBuffer(GLenum mode); 函數聲明顏色緩沖區,單緩沖默認用 GL_FRONT,雙緩沖默認用 GL_BACK

3. 調用 void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid * data); 函數從 frame buffer 取數據,一般為 glReadPixels(0, 0, height, width, GL_RGB, GL_UNSIGNED_BYTE ,image); 讀取緩沖區RGB數據存入分配的 image 中,也可以根據需求調節參數配置。需要注意的一點是內存對齊問題,由於 bmp 文件數據按 4 字節(DWORD)對齊,設置 glPixelStorei(GL_PACK_ALIGNMENT,4);,雖然默認的也是 4 字節對齊,還是顯示寫一下比較好。jpg 文件沒有如此要求,所以設置 glPixelStorei(GL_PACK_ALIGNMENT,1);

4. 數據在手,剩下的就是調用 png, jpg, tiff 等圖像庫的 API 進行讀寫了。

 

        之前接觸過 DirectX,由於剛剛接觸 OpenGL,把自己遇到一些問題總結一下。

Direct3D 和 OpenGL 是在 GPU 上渲染 3D 圖形的兩大技術。拿 DirectX 跟 OpenGL 比是錯誤的,DirectX 包括許多其他的功能(DirectInput,DirectSound 等),末尾的X是未知數,也是一系列功能的統稱。
Direct3D 局限於 Windows 平台, OpenGL 跨平台;
Direct3D 使用 左手坐標系, OpenGL 使用右手坐標系。
Direct3D 使用 row_major      矩陣,變換矩陣之間左乘, 向量看成行向量;
OpenGL  使用 column_major 矩陣,變換矩陣之間右乘, 向量看成列向量。
Direct3D 函數多使用弧度制, OpenGL 函數多使用角度制。

 

1.  glColor* 函數使用
        OpenGL 偏向於 C 語言,很多函數通過添加后綴定義了不同的版本,以便使用不同類型的參數,有數量{3,4},類型{b,s,i,f,d,ub,us,ui},表示該函數接受3/4個8位整數/16位整數/32位整數/32位浮點數/64位浮點數/8位無符號整數/16位無符號整數/32位無符號整數類型的參數,有的最后還有一個字母v,表示該函數所接受的參數是一個指向向量或數組的指針,而不是一系列的單獨參數。
       
當時自己很討厭這些后綴,心想着如果用 C++ 的函數重載,也不用那么麻煩啊,直到我的膝蓋中了一箭。
        在用 Directx3D 時,經常用宏 D3DCOLOR_RGBA 來指定一種顏色,使用起來也很簡單。

// from d3d9types.h

// maps unsigned 8 bits/channel to D3DCOLOR
#define D3DCOLOR_ARGB(a,r,g,b) \
    ((D3DCOLOR)((((a)&0xff)<<24)|(((r)&0xff)<<16)|(((g)&0xff)<<8)|((b)&0xff)))
#define D3DCOLOR_RGBA(r,g,b,a) D3DCOLOR_ARGB(a,r,g,b)
#define D3DCOLOR_XRGB(r,g,b)   D3DCOLOR_ARGB(0xff,r,g,b)

        有一天,我用上 OpenGL 的函數 glColor3i(255, 255, 255); 發現顯示的是黑色,但是換用 glColor3f(1.0, 1.0, 1.0); 就是白色,不明白錯誤所在。稍微查看了文檔 man glColor,明白了錯誤的所在。如果使用整數的話,應該使用 glColor3ub(255, 255, 255); 當前顏色值都是以浮點數存儲的,浮點值直接映射;無符號整形 [0,255] 線性映射到 [0.0,1.0],有符號整形 [-128,127] 線性映射到 [-1.0,1.0]。所以 glColor3i(255, 255, 255); 等價於 glColor3f(255.0/INT_MAX, 255.0/INT_MAX, 255.0/INT_MAX); 值只會在更新當前顏色的時候 clamp 到 [0.0,1.0] 區間。不過在插值或者寫入到顏色緩沖區前也會 clamp,多查看文檔學習的來源。所以,有時候看別人的代碼覺得似乎很簡單,但是輪到自己親自動手寫的時候不一定會那么寫,導致犯錯。

2. glGet* 函數使用
       OpenGL 是一個狀態機,打開某些狀態調用函數 void glEnable(GLenum capability);,關閉某些狀態調用函數 void glDisable(GLenum capability); 查詢用 glGet* 族函數,我想知道我這台機器有關 OpenGL 的版本信息。

void glInfo()
{
    printf("\n");
    printf("Vendor       : %s\n", glGetString(GL_VENDOR));
    printf("Renderer     : %s\n", glGetString(GL_RENDERER));
    printf("Version      : %s\n", glGetString(GL_VERSION));
    printf("GLSL version : %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
    printf("Extensions   : %s\n", glGetString(GL_EXTENSIONS));
    printf("\n");
}

我插入上面 glInfo 函數的位置太靠前,函數沒有返回任何結果。經過多次試驗發現,glGet* 函數在 glutCreateWindow 函數后調用,否則輸出為空。OpenGL 提供了功能強大且又非常基本的渲染函數,但是 OpenGL 程序必須使用窗口系統的底層機制。int glutCreateWindow(char* string); 創建了一個支持 OpenGL 渲染環境的窗口,函數返回唯一的標識符。而且,在調用 glutMainLoop(); 函數前窗口是不會顯示的。如果在 glutCreateWindow 之前沒有調用 glutInit(&argc, argv);會給出錯誤提示 freeglut  ERROR:  Function <glutCreateWindow> called without first calling 'glutInit'.
需要注意的是,在程序對性能有所要求的情況下盡量少調用 glGet*/glIs* 查詢狀態,但調試的時候使用沒什么。因為 OpenGL 是一個狀態機,設置狀態來得快,但查詢某些狀態會很耗時,想想多個流水線在跑,CPU 等待 GPU 忙玩一批渲染命令后停下來,找到並返回正確的而且是當前最新的狀態給你。

3. GLenum 錯誤
        有一次筆誤,將 glEnable(GL_DEPTH_TEST) 寫成 glEnable(GL_DEPTH),沒有出現預期的效果。OpenGL 的頭文件里,很多都是宏定義成的數值,寫錯了話,編譯不會出錯,運行也不會有錯,只能在調式的時候希望能快點找到錯誤。由於自己粗心,glMatrixMode 函數名字里面也有 matrix,導致自己寫程序時,順手就將 glMatrixMode(GL_MODELVIEW); 寫成了 glMatrixMode(GL_MODELVIEW_MATRIX); 很長一段時間,程序沒有輸出,我不知道錯在哪里。也然后調用了 glGetDoublev 打印當前矩陣棧信息

/*
 * @param mode  Specifies which matrix stack is the target for subsequent matrix
 * operations. Three values are accepted: GL_MODELVIEW_MATRIX,
 * GL_PROJECTION_MATRIX, and GL_TEXTURE_MATRIX. The initial value is
 * GL_MODELVIEW_MATRIX.
 */
void printMatrix(GLenum mode=GL_MODELVIEW_MATRIX)
{
    const int ORDER=4;
    GLdouble M[ORDER*ORDER]={0};
    glGetDoublev(mode, (GLdouble*)&M);
    
    for(int i=0; i<ORDER; ++i)
        printf("%10.6lf  %10.6lf  %10.6lf  %10.6lf\n",
            M[i], M[i+ORDER], M[i+ORDER*2], M[i+ORDER*3]);
}

發現打印的都是0,程序也沒有崩潰。(當然,上面是已經改正后的函數。)后來利用 glGetError 函數才看到打印出錯的信息
OpenGL error 1280: invalid enumerant,枚舉值有問題,然后一點點排除,又對比別人的代碼,才找出問題所在。

void printIfError()
{
    GLenum gl_error=glGetError();
    if(gl_error!=GL_NO_ERROR)
        printf("OpenGL error %d: %s\n", gl_error, gluErrorString(gl_error));
}

還需要注意的是記住當前的矩陣棧模式,是 GL_MODELVIEW,還是 GL_PROJECTION,還是 GL_TEXTURE?以免在錯誤的矩陣棧上面做了變換,較好的做法是每次一大堆矩陣變換前調用一次 glMatrixMode,最后的最后再調用一次 glMatrixMode(GL_MODELVIEW); 回到默認的模式 GL_MODELVIEW。

 

還有很多其他的陷阱(pitfall),下面列出一些很好的網址鏈接。

http://www.opengl.org/wiki/Common_Mistakes

http://www.opengl.org/archives/resources/features/KilgardTechniques/oglpitfall/

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM