OpenGl 筆記--轉載

http://blog.chinaunix.net/uid-20622737-id-3256220.html


 OpenGl 筆記--轉載 2012-06-27 17:07:43

分類: C/C++

OpenGL顏色

幾乎所有OpenGL應用目的都是在屏幕窗口內繪製彩色圖形,所以顏色在OpenGL編程中佔有很重要的地位。這裏的顏色與繪畫中的顏色概念不一樣,它屬於RGB顏色空間,只在監視器屏幕上顯示。另外,屏幕窗口座標是以象素爲單位,因此組成圖形的每個象素都有自己 的顏色,而這種顏色值是通過對一系列OpenGL函數命令的處理最終計算出來的。本章將講述計算機顏色的概念以及OpenGL的顏色模式、顏色定義和兩種模式應用場合等內容,若掌握好顏色的應用,你就能走進繽紛絢麗的色彩世界,從中享受無窮的樂趣。

9.1、計算機顏色

  9.1.1 顏色生成原理
  計算機顏色不同於繪畫或印刷中的顏色,顯示於計算機屏幕上每一個點的顏色都是由監視器內部的電子槍激發的三束不同顏色的光(紅、綠、藍)混合而成,因此,計算機顏色通 常用R(Red)、G(Green)、B(Blue)三個值來表示,這三個值又稱爲顏色分量。顏色生成原理 示意圖見圖9-1所示。

 
圖9-1 計算機顏色生成原理

  9.1.2 RGB色立體(RGB Color Cube)
  所有監視器屏幕的顏色都屬於RGB顏色空間,如果用一個立方體形象地表示RGB顏色組成關係,那麼就稱這個立方體爲RGB色立體,如圖9-2所示。

 
圖9-2 RGB色立體

  在圖中,R、G、B三值的範圍都是從0.0到1.0。如果某顏色分量越大,則表示對應的顏色分量越亮,也就是它在此點所貢獻的顏色成分越多;反之,則越暗或越少。當R、G、B三個值都爲0.0時,此點顏色爲黑色(Black);當三者都爲1.0時,此點顏色爲白色(White);當三個顏色分量值相等時,表示三者貢獻一樣,因此呈現灰色(Grey),在圖中表現爲從黑色頂點到白色頂點的那條對角線;當R=1.0、G=1.0、B=0.0時,此點顏色爲黃色(Yellow);同理,R=1.0、G=0.0、B=1.0時爲洋紅色,也叫品色(Magenta);R=0.0、G=1.0、B=1.0時爲青色(Cyan)。

9.2、顏色模式
  OpenGL顏色模式一共有兩個:RGB(RGBA)模式和顏色表模式。在RGB模式下,所有的顏色定義全用R、G、B三個值來表示,有時也加上Alpha值(與透明度有關),即RGBA模式。在顏色表模式下,每一個象素的顏色是用顏色表中的某個顏色索引值表示,而這個索引值指向了相應的R、G、B值。這樣的一個表成爲顏色映射(Color Map)。

  9.2.1 RGBA模式(RGBA Mode)
  在RGBA模式下,可以用glColor*()來定義當前顏色。其函數形式爲:

  void glColor3{b s i f d ub us ui}(TYPE r,TYPE g,TYPE b);
  void glColor4{b s i f d ub us ui}(TYPE r,TYPE g,TYPE b,TYPE a);
  void glColor3{b s i f d ub us ui}v(TYPE *v);
  void glColor4{b s i f d ub us ui}v(TYPE *v);

  設置當前R、G、B和A值。這個函數有3和4兩種方式,在前一種方式下,a值缺省爲1.0,後一種Alpha值由用戶自己設定,範圍從0.0到1.0。同樣,它也可用指針傳遞參數。另外,函數的第二個後綴的不同使用,其相應的參數值及範圍不同,見下表9-1所示。雖然這些參數值不同,但實際上OpenGL已自動將它們映射在0.0到1.0或-1.0或範圍之內。因此,靈活使用這些後綴,會給你編程帶來很大的方便。

後綴 數據類型 最小值 最小值映射 最大值 最大值映射 
b 1字節整型數 -128 -1.0 127 1.0 
s 2字節整型數 -32,768 -1.0 32,767 1.0 
i 4字節整型數 -2,147,483,648 -1.0 2,147,483,647 1.0 
ub 1字節無符號整型數 0 0.0 255 1.0 
us 2字節無符號整型數 0 0.0 65,535 1.0 
ui 4字節無符號整型數 0 0.0 4,294,967,295 1.0 
表9-1 整型顏色值到浮點數的轉換

  9.2.2 顏色表模式(Color_Index Mode)
  在顏色表方式下,可以調用glIndex*()函數從顏色表中選取當前顏色。其函數形式爲:

  void glIndex{sifd}(TYPE c);
  void glIndex{sifd}v(TYPE *c);

  設置當前顏色索引值,即調色板號。若值大於顏色位面數時則取模。

  9.2.3 兩種模式應用場合
  在大多情況下,採用RGBA模式比顏色表模式的要多,尤其許多效果處理,如陰影、光照、霧、反走樣、混合等,採用RGBA模式效果會更好些;另外,紋理映射只能在RGBA模式下進行。下面提供幾種運用顏色表模式的情況(僅供參考):
  1)若原來應用程序採用的是顏色表模式則轉到OpenGL上來時最好仍保持這種模式,便於移植。
  2)若所用顏色不在缺省提供的顏色許可範圍之內,則採用顏色表模式。
  3)在其它許多特殊處理,如顏色動畫,採用這種模式會出現奇異的效果。

9.3、顏色應用舉例
  顏色是一個極具吸引力的應用,在前面幾章中已經逐步介紹了RGBA模式的應用方式,這裏就不再多述。下面着重說一下顏色表模式的應用方法,請看例程:

  例9-1 顏色表應用例程(cindex.c)

  #include "glos.h"
  #include <GL/gl.h>
  #include <GL/glaux.h>

  void myinit(void);
  void InitPalette(void);
  void DrawColorFans(void);
  void CALLBACK myReshape(GLsizei w,GLsizei h);
  void CALLBACK display(void);

  void myinit(void)
  {
    glClearColor(0.0,0.0,0.0,0.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glShadeModel(GL_FLAT);
  }

  void InitPalette(void)
  {
    GLint j;
    static GLfloat rgb[][3]={
      {1.0,0.0,0.0},{1.0,0.0,0.5},{1.0,0.0,1.0},{0.0,0.0,1.0},
      {0.0,1.0,1.0},{0.0,1.0,0.0},{1.0,1.0,0.0},{1.0,0.5,0.0}};

    for(j=0;j<8;j++)
      auxSetOneColor(j+1,rgb[j][0],rgb[j][1],rgb[j][2]);
  }

  void CALLBACK myReshape(GLsizei w,GLsizei h)
  {
    glViewport(0,0,w,h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if(w<=h)
      glOrtho(-12.0,12.0,-12.0*(GLfloat)h/(GLfloat)w, 12.0*(GLfloat)h/(GLfloat)w,-30.0,30.0);
    else
      glOrtho(-12.0*(GLfloat)h/(GLfloat)w, 12.0*(GLfloat)h/(GLfloat)w,-12.0,12.0,-30.0,30.0); 
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
  }

  void CALLBACK display(void)
  {
    InitPalette();
    DrawColorFans();
    glFlush();
  }

  void DrawColorFans(void)
  {
    GLint n;
    GLfloat pp[8][2]={
      {7.0,-7.0},{0.0,-10.0},{-7.0,-7.0},{-10.0,0.0},
      {-7.0,7.0}, {0.0,10.0},{7.0,7.0},{10.0,0.0}};

    /* draw some filled_fan_triangles */
    glBegin(GL_TRIANGLE_FAN);
      glVertex2f(0.0,0.0);
      glVertex2f(10.0,0.0);
      for(n=0;n<8;n++)
      {
        glIndexi(n+1);
        glVertex2fv(pp[n]);
      }
    glEnd();
  }

  void main(void)
  {
    auxInitDisplayMode(AUX_SINGLE|AUX_INDEX);
    auxInitPosition(0,0,500,500);
    auxInitWindow("Color Index");
    myinit();
    auxReshapeFunc(myReshape);
    auxMainLoop(display);
  }

  這個程序運行結果是在屏幕上顯示八個連成扇形的不同顏色的三角形,每個三角形的顏色定義採用顏色表模式。其中,調用了輔助庫函數auxSetOneColor()來裝載顏色映射表,即調色板。因爲將某個顏色裝載到顏色查找表(color lookup table)中的過程必須依賴窗口系統,而OpenGL函數與窗口系統無關,所以這裏就調用輔助庫的函數來完成這個過程,然後才調用OpenGL自己的函數glIndex()設置當前的顏色號。


OpenGL幀緩衝區


在OpenGL窗口中, 左下角的像素爲(0, 0). 一般而言, 像素(x, y)佔據的矩形區域左下角爲(x, y), 右上角爲(x+1, y+1).

10.1 緩存及其用途


1) 顏色緩存,  左前,右前,左後,右後和任意數量的輔助顏色緩存;
2) 深度緩存
3) 模板緩存
4) 累積緩存

注意:X窗口系統,RGBA模式至少保證1個顏色緩衝區,模板緩衝區,深度緩衝區,累計緩衝區
顏色索引模式至少保證1個眼色緩衝區,深度緩衝區,模板緩衝區.
可以使用glXGetConfig{}函數查詢.

用glGetIntegerv()查詢每個像素佔據的緩存空間的參數
GL_RED_BITS, GL_GREEN_BITS, GL_BLUE_BITS, GL_ALPHA_BITS --- 顏色緩存中R, G, B, A分量的位數
GL_INDEX_BITS --- 顏色緩存中每個顏色索引的位數
GL_DEPTH_BITS --- 深度緩存中每個像素的位數
GL_STENCIL_BITS --- 模板緩存中每個像素的位數
GL_ACCUM_RED_BITS, GL_ACCUM_GREEN_BITS, GL_ACCUM_BLUE_BITS, GL_ACCUM_ALPHA_BITS --- 累積緩存中R, G, B, A分量的位數.

10.1.1 顏色緩存


1) 顏色緩存存儲了顏色索引或RGB顏色數據, 還可能存儲了alpha值. 
2) 支持立體觀察(stereoscopic viewing)的OpenGL實現有左顏色緩存和右顏色緩存. 它們分別用於左立體圖像和右立體圖像.
3) 如不支持立體觀察, 則只使用左顏色緩存.
4) 雙顏色緩存有前緩存和後緩存, 單緩存系統只有前緩存.
5) 支持不可顯示的輔助顏色緩存
6) 函數glGetBooleanv()查詢是否支持立體觀察和雙緩存: GL_STEREO和GL_DOUBLE_BUFFER. 
   函數glGetIntegerv()查詢多少個輔助緩存可用: GL_AUX_BUFFERES.

10.1.2 深度緩存


深度緩存 --- 存儲了每個像素的深度值. 通常是離視點的距離, 因此深度值大的像素會被深度值小的像素覆蓋.

10.1.3 模板緩存


用途之一: 繪圖範圍限制在屏幕的特定區域內.

10.1.4 累積緩存


累積緩存也存儲RGBA顏色數據, 將一系列的圖像合成一幅圖像. 
可以對圖像進行超量採樣, 然後對樣本進行平均, 並將結果寫入顏色緩存中, 從而實現場景反走樣. 不能將數據直接寫入累積緩存.
累加操作總是針對一個矩形塊的, 通常是將數據移入或移出顏色緩存.

10.1.5 清空緩存


void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
void glClearIndex(GLfloat index);
void glClearDepth(GLclampd depth);
void glClearStencil(GLint s);
void glClearAccum(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha);
功能: 指定用於清除顏色緩存(RGBA模式或顏色索引模式), 深度緩存, 模板緩存和累積緩存的值. 
類型爲GLclampf和GLclampd的參數值被截取到[0.0, 1.0]. 深度緩存的默認清除值爲1.0, 其他緩存爲0.0.

設置清除值後, 便可以調用函數glClear()來清除緩存.
void glClear(GLbitfield mask);
功能: 清除指定的緩存. 
mask:GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT, GL_STENCIL_BUFFER_BIT, GL_ACCUM_BUFFER_BIT的邏輯或(OR).
清除顏色緩存時, 如果啓用了像素所有權測試, 裁剪測試和抖動操作, 它們都會在清除操作中執行. 
屏蔽操作(glColorMask()和glIndexMask())也會生效.alpha測試, 模版測試, 深度測試並不會影響glClear()函數的操作.

10.1.6 指定要讀寫的顏色緩存


指定要寫入的緩存 glDrawBuffer();
指定要讀取的緩存 glReadBuffer();

使用雙緩存, 通常只繪製後緩存, 並在繪製完成後交換緩存. 你可能想將雙緩存窗口視爲單緩存窗口: 通過調用函數glDrawBuffer()使得可以同時繪製前緩存和後緩存.
void glDrawBuffer(GLenum mode); 
功能: 指定要寫入或消除的顏色緩存以及禁用之前被啓用的顏色緩存. 可以一次性啓用多個緩存.
GL_FRONT: 單緩存的默認值    
GL_FRONT_RIGHT:    
GL_NONE:
GL_FRONT_LEFT:
GL_FRONT_AND_BACK:
GL_RIGHT:
GL_AUXi: i表示第幾個輔助緩存.
GL_LEFT:
GL_BACK_RIGHT:
GL_BACK: 雙緩存的默認值
GL_BACK_LEFT:
注意: 啓用多個緩存用於寫操作時, 只要其中一個緩存存在, 就不會發生錯誤. 如果指定的緩存都不存在, 就發生錯誤.

void glReadBuffer(GLenum mode); 
功能: 選擇接下來的函數調用glReadPixels(), glCopyPixels(), glCopyTexImage*(), glCopyTexSubImage*() 和 glCopyConvolutionFilter*()將讀取的緩存.
      並啓用以前被函數glReadBuffer()啓用的緩存.
參數mode取值:
GL_FRONT: 單緩存默認
GL_FRONT_RIGHT:
GL_BACK_RIGHT:
GL_FRONT_LEFT:
GL_LEFT:
GL_AUX:
GL_BACK_LEFT:
GL_BACK: 雙緩存默認
GL_RIGHT:
注意: 啓用緩存用於讀取操作時, 指定的緩存必須存在, 否則將發生錯誤.

10.1.7 屏蔽緩存


void glIndexMask(GLuint mask);
void glColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha);
void glDepthMask(GLboolean flag);
void glStencilMask(GLuint mask);
 
功能: 設置掩碼, 用於控制寫入到指定緩存中的數據,
glIndexMask: 只用於顏色索引模式中, 掩碼中1對應的數據位被寫入顏色索引緩存中. 0對應的位不寫入.
glColorMask: 隻影響RGBA模式下的寫入, red, green, blue, alpha決定是否寫入相應的分量, GL_TRUE時寫入.
glDepthMask(): 如果參數flag的值爲GL_TRUE, 則啓用深度緩存用於寫入, 否則禁用深度緩存.
glStencilMask(): 參數mask的含義與函數glIndexMask()中相同. 
所有GLboolean參數的默認值是GL_TRUE, 兩個GLuint參數的默認值都是1.

禁用深度緩存: 如果背景很複雜, 則在背景繪製之後, 禁用深度緩存. 繪製新增的物體, 只要不相互重疊.. 下一幀時, 只需恢復樹木圖像, 無需恢復深度緩存中的值.
這種方法很有效.

模板緩存的屏蔽操作讓你能夠使用一個多位模板緩存來存儲多個模板(每位一個)
函數glStencilMask()指定的掩碼用於控制哪些模板位面可寫, 與函數glStencileFunc()的第三個參數指定的掩碼無關, 後者指定模板函數將考慮哪些位面.

10.2 片段測試和操作


測試順序:
1. 裁剪測試 
2. alpha測試 
3. 模版測試
4. 深度測試
5. 混合
6. 抖動
7. 邏輯操作

10.2.1 裁剪測試


void glScissor(GLint x, GLint y, GLsizei width, GLsizei height);
設置裁剪矩形的位置和大小. 需要啓用GL_SCISSOR_TEST.

10.2.2 alpha測試


需要啓用GL_ALPHA_TEST
void glAlphaFunc(GLenum func, GLclampf ref); 
設置用於alpha測試的參考值和比較函數.
alpha測試可用於透明算法和貼花.

10.2.3 模板測試


void glStencilFunc(GLenum func, GLint ref, GLuint mask);
void glStencilFuncSeparate(GLenum face, GLenum func, GLint ref, GLuint mask);
 
設置模板測試所使用的比較函數(func),參考值(ref),掩碼(mask)

void glStencilOp(GLenum fail, GLenum zfail, GLenum zpass);
void glStencilOpSeparate(GLenum face, GLenum fail, GLenum zfail, GLenum zpass);
指定當一個片段通過或未通過模板測試時, 模板緩衝區中的數據如何進行修改.

下圖設置模板爲中間的方塊

11

模板查詢glGetInteger()可使用的參數:
GL_STENCIL_FUNC
GL_STENCIL_REF
GL_STENCIL_VALUE_MASK
GL_STENCIL_FAIL
GL_STENCIL_PASS_DEPTH_FAIL
GL_STENCIL_PASS_DEPTH_PASS

 

10.2.4 深度測試


void glDepthFunc(GLenum func); 
爲深度測試設置比較函數.

10.2.5 遮擋測試


遮擋測試允許我們判斷一組幾何圖形在進行深度測試之後是否可見.
步驟:
1. 爲每個所需的遮擋查詢生成一個查詢ID
2. 調用glBeginQuery(), 表示開始一個遮擋查詢
3. 渲染需要進行遮擋查詢的幾何圖形
4. 調用glEndQuery(), 表示已經完成了遮擋查詢
5. 提取通過遮擋查詢的樣本數量.

生成查詢對象
void glGenQueries(GLsizei n, GLuint* ids);

對遮擋查詢對象進行初始化
void glBeginQuery(GLenum target, GLuint id);
void glEndQuery(GLenum target);
 
target必須爲GL_SAMPLES_PASSED.

判斷遮擋查詢的結果
void glGetQueryObjectiv(GLenum id, GLenum pname, GLint *params);
void glGetQueryObjectuiv(GLenum id, GLenum pname, GLuint *params);

清除遮擋查詢對象
void glDeleteQueries(GLsizei n, const GLuint *ids);

10.2.6 混合,抖動和邏輯操作


void glLogicOp(GLenum opcode); 
選擇需要執行的邏輯操作.

10.3 累計緩衝區


void glAccum(GLenum op, GLfloat value); 
控制累積緩衝區
op參數:
GL_ACCUM--用glReadBuffer()所讀取的當前緩衝區中讀取每個像素.把R,G,B,A值乘以value.而後結果累加到累積緩衝區.
GL_LOAD--同上,只是用結果替換累積緩衝區中的值.
GL_RETURN--累積緩衝區中的值乘以value, 結果放在顏色緩衝區中.
GL_ADD和AL_MULT--累積緩衝區中的值與value相加或者相乘,結果寫回累積緩衝區. 另GL_MULT的結果截取[-1.0. 1.0].GL_ADD的結果不截取.

10.3.1 場景抗鋸齒


首先清除累積緩衝區, 並啓用前緩衝區用於讀取和寫入.
然後循環執行幾次(例如n次)代碼, 對圖像進行微移和繪製.
對數據進行累積的方法:
glAccum(GL_ACCUM, 1.0/n);    // 繪製到不會顯示的顏色緩衝區中, 避免顯示中間圖像.
並最終調用
glAccum((GL_RETURN, 1.0);    // 繪製到可顯示的顏色緩衝區中(或即將交換的後緩衝區).

可提供一用戶接口, 來顯示每次圖像累積之後所獲得的改善, 如圖像足夠滿意, 可隨時終止累積.

本例主要是逐步在窗口累積各顏色分量.
一共累積八次,且其中每次都用j8數組的數據微移場景, 使用glFrustum函數可以是的我們場景不必對稱.

正交投影的偏移只需要用glTranslatef()移動一個像素內的偏移即可.

下圖爲沒有反鋸齒沒有用累積緩存的圖像

 2

下圖爲使用了累積緩存反鋸齒的圖像

1

 

累積緩存我分步驟顯示,看看效果

 3 4 5 6 7

10.3.2 運動模糊


按照相同的方式設置累積緩衝區, 但不是對圖像進行空間上的微移, 而是進行時間上的微移.
glAccum(GL_MULT, decayFactor); 
這樣隨着場景繪製到累積緩衝區中,整個場景越來越模糊. 其中decayFactor是個0.0到1.0之間的數字, 其值越小, 運動速度越快.
然後使用
glAccum(GL_RETURN, 1.0); 
轉移到眼色緩衝區中.

9

10.3.3 景深


距離聚焦平面越遠,物體就越模糊.

accPerspective函數
第五個和第六個參數表示在x和y方向上微移, 實現場景抗鋸齒
第九個參數設定聚焦平面.
模糊程度有第七個和第八個參數決定, 由這兩個參數的乘積決定.

10

10.3.4 柔和陰影


多個光源所產生的柔和陰影-- 可以多次渲染場景,每次只打開一個光源, 然後將渲染結果累積起來.

10.3.5 微移


樣本的微移值


OpenGL位圖和圖像


在前面的章節中,已經講述了幾何數據(點、線、多邊形)繪製的有關方法,但OpenGL還有另外兩種重要的數據類:一是位圖,二是圖像。這兩種數據都是以象素矩陣形式存儲,即用一個矩形數組來表示某一位圖或圖像。二者不同之處是位圖包含每個象素的一位信息,而圖像數據一般包含每個象素的多位信息(如,紅、綠、藍和Alpha值);還有位圖類似於掩碼,可用於遮掩別的圖像,而圖像數據則簡單地覆蓋先前已經存在的數據或者與之混合。下面將詳述這些內容。

11.1、位圖

  11.1.1 位圖Bitmap與字符Font
  位圖是以元素值爲0或1的矩陣形式存儲的,通常用於對窗口中相應區域的繪圖屏蔽。比如說,當前顏色設置爲紅色,則在矩陣元素值爲1的地方象素用紅色來取代,反之,在爲0的地方,對應的象素不受影響。位圖普遍用於字符顯示,請看下面例子:

  例11-1 位圖字符例程font.c

#include "glos.h"#include <GL/gl.h>#include <GL/glu.h>#include <GL/glaux.h> void myinit(void);void CALLBACK myReshape(GLsizei w, GLsizei h);void CALLBACK display(void); GLubyte rasters[12] = { 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xfc, 0xfc, 0xc0, 0xc0, 0xc0, 0xff, 0xff};  void myinit(void) {   glPixelStorei (GL_UNPACK_ALIGNMENT, 1);   glClearColor (0.0,0.00.00.0);   glClear(GL_COLOR_BUFFER_BIT)}  void CALLBACK display(void) {   glColor3f (1.00.01.0);   glRasterPos2i (100200);   glBitmap (8120.00.0,20.020.0, rasters);   lBitmap (8120.00.00.00.0, rasters);    glColor3f (1.01.00.0); glRasterPos2i (150200);   glBitmap (8120.00.00.00.0, rasters);    glFlush()}  void CALLBACK myReshape(GLsizei w, GLsizei h) {   glViewport(00, w, h);   glMatrixMode(GL_PROJECTION);   glLoadIdentity();   glOrtho (0, w, 0, h, -1.01.0);   glMatrixMode(GL_MODELVIEW)}  void main(void) {   auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);   auxInitPosition (00500500);   auxInitWindow("Bitmap");   myinit();   auxReshapeFunc (myReshape);   auxMainLoop(display)} 

  以上程序運行結果是顯示三個相同的字符F。OpenGL函數庫只提供了最底層操作,即用glRasterPos*()和glBitmap()在屏幕上定位和畫一個位圖,圖11-1顯示了F的位圖和相應的位圖數據。

  在圖中,字符大小爲12*8的方陣,每一行數據用8位16進製表示。注意:位圖數據總是按塊存儲,每塊的位數總是8的倍數,但實際位圖的寬並不一定使8的倍數。組成位圖的位從位圖的左下角開始畫:首先畫最底下的一行,然後是這行的上一行,依此類推。這個程序中的幾個重要函數的解釋將在下面幾個小節,其中函數glPixelstorei()描述了位圖數據在計算機內存中存儲的方式。

  11.1.2 當前光柵位置
  當前光柵位置函數就是:
 

void glRasterPos{234}{SIFD}[V](TYPE x,TYPE y,TYPE z,TYPE w);

  設置當前所畫位圖或圖像的原點。其中參數x、y、z、w給出了光柵位置座標。在變換到屏幕座標時(即用模型變換和透視變換),光柵位置座標與glVertex*()提供的座標同樣對待。也就是說,變換後要麼確定一個有效點,要麼認爲位於視口以外的點的當前光柵位置無效。
  在上一例中,顏色設置的位置與當前光柵位置函數調用的位置有關,glColor*()必須放 在glRasterPos*()前,則緊跟其後的位圖就都繼承當前的顏色,例前兩個紫色的F;若要改變當前位圖顏色,則需重新調用glColor*()和glRasterPos*(),如第三個黃色字符F的顯示。

  11.1.3 位圖顯示
  當設置了光柵位置後,就可以調用glBitmap()函數來顯示位圖數據了。這個函數形式爲:
 

void glBitmap( GLsizei width,GLsizei height,GLfloat xbo,GLfloat ybo,GLfloat xbi,GLfloat ybi,const GLubyte *bitmap);

  顯示由bitmap指定的位圖,bitmap是一個指向位圖的指針。位圖的原點放在最近定義的當前光柵位置上。若當前光柵位置是無效的,則不顯示此位圖或其一部分,而且當前光柵位置仍然無效。參數width和height一象素爲單位說明位圖的寬行高。寬度不一定是8的倍數。參數xbo和ybo定義位圖的原點(正值時,原點向上移動;負值時,原點向下移動)。參數xbi和ybi之處在位圖光柵化後光柵位置的增量。在上一例中:
 

glColor3f (1.00.01.0);glRasterPos2i (100200);glBitmap (8120.00.020.020.0, rasters);glBitmap (8120.00.00.00.0, rasters);

  第一個字符F與第二個字符F的間距是由glBitmap()的兩個增量參數決定的,即第二個字符F在第一個字符F的基礎上分別向X正軸和Y負軸移動20個象素單位。

11.2 圖像
  一般來說,OpenGL圖像(image)操作包括象素讀寫、象素拷貝和圖像縮放,下面分別來介紹。

  11.2.1 象素讀寫
  OpenGL提供了最基本的象素讀和寫函數,它們分別是:

  讀取象素數據:
 

void glReadPixels(GLint x,GLint y,GLsizesi width,GLsizei height,GLenum format,GLenum type,GLvoid *pixel);

  函數參數(x, y)定義圖像區域左下角點的座標,width和height分別是圖像的高度和寬度,*pixel是一個指針,指向存儲圖像數據的數組。參數format指出所讀象素數據元素的格式(索引值或R、G、B、A值,如表11-1所示),而參數type指出每個元素的數據類型(見表11-2)。
  寫入象素數據:
 

void glDrawPixels(GLsizesi width,GLsizei height,GLenum format,GLenum type,GLvoid *pixel);

  函數參數format和type與glReadPixels()有相同的意義,pixel指向的數組包含所要畫的象素數據。注意,調用這個函數前必須先設置當前光柵位置,若當前光柵位置無效,則給出該函數時不畫任何圖形,並且當前光柵位置仍然保持無效。



名稱 象素數據類型
GL_INDEX 單個顏色索引
GL_RGB 先是紅色分量,再是綠色分量,然後是藍色分量
GL_RED 單個紅色分量
GL_GREEN 單個綠色分量
GL_BLUE 單個藍色分量
GL_ALPHA 單個Alpha值
GL_LUMINANCE_ALPHA 先是亮度分量,然後是Alpha值
GL_STENCIL_INDEX 單個的模板索引
GL_DEPTH_COMPONENT 單個深度分量
表11-1 函數glReadPixels()及glDrawPixels()的象素格式

名稱 數據類型
GL_UNSIGNED_BYTE 無符號的8位整數
GL_BYTE 8位整數
GL_BITMAP 無符號的8位整數數組中的單個數位
GL_UNSIGNED_SHORT 無符號的16位整數
GL_SHORT 16位整數
GL_UNSIGNED_INT 無符號的32位整數
GL_INT 32位整數
GL_FLOAT 單精度浮點數
表11-2 函數glReadPixels()及glDrawPixels()的象素數據類型

圖像的每個元素按表11-2給出的數據類型存儲。若元素表示連續的值,如紅、綠、藍或亮度分量,每個值都按比例放縮使之適合於可用的位數。例如,紅色分量是0.0到1.0之 間的浮點值。若它需要放到無符號單字節整數中,也僅有8位精度保存下來,其他無符號整數類型同理。對於有符號的數據類型還要少一位,例如顏色索引存到有符號的8位整數中,它的第一位被0xfe屏蔽掉了(即這個掩碼包含7個1)。若類型是GL_FLOAT,索引值簡單地轉化成單精度浮點值,例如索引17轉化成17.0,同理。

  11.2.2 象素拷貝
  象素拷貝函數是:

void glCopyPixels(GLint x,GLint y,GLsizesi width,GLsizei height, GLenum type);

  這個函數使用起來有點類似於先調用glReadPixels()函數後再調用glDrawPixels()一樣,但它不需要將數據寫到內存中去,因它只將數據寫到framebuffer裏。函數功能就是拷貝framebuffer中左下角點在(x, y)尺寸爲width、height的矩形區域象素數據。數據拷貝到一個新的位置,其左下角點在當前光柵的位置,參數type可以是GL_COLOR、GL_STENCIL、GL_DEPTH。在拷貝過程中,參數type要按如下方式轉換成format:
  1)若type爲GL_DEPTH或GL_STENCIL,那麼format應分別是GL_DEPTH_COMPONENT或GL_STENCIL_INDEX;
  2)若type爲GL_COLOR,format則用GL_RGB或GL_COLOR_INDEX,這要依賴於圖形系統是處於RGBA方式還是處於顏色表方式。

  11.2.3 圖像縮放
  一般情況下,圖像的一個象素寫到屏幕上時也是一個象素,但是有時也需要將圖像放大或縮小,OpenGL提供了這個函數:
 

void glPixelZoom(GLfloat zoomx,GLfloat zoomy);

  設置象素寫操作沿X和Y方向的放大或縮小因子。缺省情況下,zoomx、zoomy都是1.0。如果它們都是2.0,則每個圖像象素被畫到4個屏幕象素上面。注意:小數形式的縮放因子和負數因子都是可以的。



11.2.4 圖像例程
  下面舉出一個圖像應用的例子:


  1.  例11-2 圖像應用例程(image.c)
  2.  
  3. #include "glos.h"
  4. #include <GL/gl.h>
  5. #include <GL/glu.h>
  6. #include <GL/glaux.h> 
  7. void myinit(void);
  8. void triangle(void);
  9. void SourceImage(void);
  10. void CALLBACK display(void);
  11. void CALLBACK myReshape(GLsizei w, GLsizei h); 
  12. void myinit (void)
  13. { 
  14.   glClear (GL_COLOR_BUFFER_BIT);
  15. } 
  16. void triangle(void)
  17. { 
  18.   glBegin (GL_TRIANGLES); 
  19.   glColor3f (0.0, 1.0, 0.0);  
  20.   glVertex2f (2.0, 3.0); 
  21.   glColor3f(0.0,0.0,1.0);  
  22.   glVertex2f (12.0, 3.0); 
  23.   glColor3f(1.0,0.0,0.0);  
  24.   glVertex2f (7.0, 12.0); 
  25.   glEnd ();
  26. } 
  27. void SourceImage(void)
  28. { 
  29.   glPushMatrix(); 
  30.   glLoadIdentity(); 
  31.   glTranslatef(4.0,8.0,0.0); 
  32.   glScalef(0.5,0.5,0.5); 
  33.   triangle (); 
  34.   glPopMatrix();
  35. } 
  36. void CALLBACK display(void)
  37. { 
  38.   int i; 
  39.   /* 繪製原始圖像 */ 
  40.   SourceImage(); 
  41.   /* 拷貝圖像 */ 
  42.   for(i=0;i<5;i++) 
  43.   { 
  44.     glRasterPos2i( 1+i*2,i); 
  45.     glCopyPixels(160,310,170,160,GL_COLOR); 
  46.   } 
  47.   glFlush ();
  48. } 
  49. void CALLBACK myReshape(GLsizei w, GLsizei h)
  50. { 
  51.   glViewport(0, 0, w, h); 
  52.   glMatrixMode(GL_PROJECTION); 
  53.   glLoadIdentity(); 
  54.   if (<= h) 
  55.     gluOrtho2D (0.0, 15.0, 0.0, 15.* (GLfloat) h/(GLfloat) w); 
  56.   else 
  57.     gluOrtho2D (0.0, 15.* (GLfloat) w/(GLfloat) h, 0.0, 15.0); g
  58.   lMatrixMode(GL_MODELVIEW);
  59. } 
  60. void main(void)
  61. { 
  62.   auxInitDisplayMode (AUX_SINGLE | AUX_RGBA); 
  63.   auxInitPosition (0, 0, 500, 500); 
  64.   auxInitWindow ("Pixel Processing"); 
  65.   myinit(); 
  66.   auxReshapeFunc (myReshape); 
  67.   auxMainLoop(display);
  68. }

  以上程序運行的結果是在屏幕正上方顯示一個最初的五彩三角形,然後在下半部顯示一串拷貝的三角形。當然,讀者自己可以再加上圖像放大縮小等,試試看,會發生怎樣的情形?


OpenGL像素操作


今天我們先簡單介紹Windows中常用的BMP文件格式,然後講OpenGL的像素操作。雖然看起來內容可能有點多,但實際只有少量幾個知識點,如果讀者對諸如顯示BMP圖象等內容比較感興趣的話,可能不知不覺就看完了。
像素操作可以很複雜,這裏僅涉及了簡單的部分,讓大家對OpenGL像素操作有初步的印象。

學過多媒體技術的朋友可能知道,計算機保存圖象的方法通常有兩種:一是矢量圖,一是像素圖。矢量圖保存了圖象中每一幾何物體的位置、形狀、大小等信息,在顯示圖象時,根據這些信息計算得到完整的圖象。像素圖是將完整的圖象縱橫分爲若干的行、列,這些行列使得圖象被分割爲很細小的分塊,每一分塊稱爲像素,保存每一像素的顏色也就保存了整個圖象。
這兩種方法各有優缺點。矢量圖在圖象進行放大、縮小時很方便,不會失真,但如果圖象很複雜,那麼就需要用非常多的幾何體,數據量和運算量都很龐大。像素圖無論圖象多麼複雜,數據量和運算量都不會增加,但在進行放大、縮小等操作時,會產生失真的情況。
前面我們曾介紹瞭如何使用OpenGL來繪製幾何體,我們通過重複的繪製許多幾何體,可以繪製出一幅矢量圖。那麼,應該如何繪製像素圖呢?這就是我們今天要學習的內容了。

1BMP文件格式簡單介紹
BMP文件是一種像素文件,它保存了一幅圖象中所有的像素。這種文件格式可以保存單色位圖、16色或256色索引模式像素圖、24位真彩色圖象,每種模式種單一像素的大小分別爲1/8字節,1/2字節,1字節和3字節。目前最常見的是256BMP24位色BMP。這種文件格式還定義了像素保存的幾種方法,包括不壓縮、RLE壓縮等。常見的BMP文件大多是不壓縮的。
這裏爲了簡單起見,我們僅討論24位色、不使用壓縮的BMP。(如果你使用Windows自帶的畫圖程序,很容易繪製出一個符合以上要求的BMP
Windows所使用的BMP文件,在開始處有一個文件頭,大小爲54字節。保存了包括文件格式標識、顏色數、圖象大小、壓縮方式等信息,因爲我們僅討論24位色不壓縮的BMP,所以文件頭中的信息基本不需要注意,只有大小這一項對我們比較有用。圖象的寬度和高度都是一個32位整數,在文件中的地址分別爲0x00120x0016,於是我們可以使用以下代碼來讀取圖象的大小信息:

GLint width, height; // 使用OpenGLGLint類型,它是32位的。
                     // C語言本身的int則不一定是32位的。
FILE* pFile;
// 
在這裏進行打開文件的操作
fseek(pFile, 0x0012, SEEK_SET);         // 移動到0x0012位置
fread(&width, sizeof(width), 1, pFile); // 讀取寬度
fseek(pFile, 0x0016, SEEK_SET);         // 移動到0x0016位置
                                        // 由於上一句執行後本就應該在0x0016位置
                                        // 所以這一句可省略
fread(&height, sizeof(height), 1, pFile); // 讀取高度

54個字節以後,如果是16色或256BMP,則還有一個顏色表,但24位色BMP沒有這個,我們這裏不考慮。接下來就是實際的像素數據了。24位色的BMP文件中,每三個字節表示一個像素的顏色。
注意,OpenGL通常使用RGB來表示顏色,但BMP文件則採用BGR,就是說,順序被反過來了。
另外需要注意的地方是:像素的數據量並不一定完全等於圖象的高度乘以寬度乘以每一像素的字節數,而是可能略大於這個值。原因是BMP文件採用了一種對齊的機制,每一行像素數據的長度若不是4的倍數,則填充一些數據使它是4的倍數。這樣一來,一個17*1524BMP大小就應該是834字節(每行17個像素,有51字節,補充爲52字節,乘以15得到像素數據總長度780,再加上文件開始的54字節,得到834字節)。分配內存時,一定要小心,不能直接使用圖象的高度乘以寬度乘以每一像素的字節數來計算分配空間的長度,否則有可能導致分配的內存空間長度不足,造成越界訪問,帶來各種嚴重後果。
一個很簡單的計算數據長度的方法如下:

int LineLength, TotalLength;
LineLength = ImageWidth * BytesPerPixel; // 
每行數據長度大致爲圖象寬度乘以
                                         // 每像素的字節數
while( LineLength % 4 != 0 )             // 修正LineLength使其爲4的倍數
    ++LineLenth;
TotalLength = LineLength * ImageHeight;  // 
數據總長 = 每行長度 * 圖象高度

這並不是效率最高的方法,但由於這個修正本身運算量並不大,使用頻率也不高,我們就不需要再考慮更快的方法了。

2、簡單的OpenGL像素操作
OpenGL提供了簡潔的函數來操作像素:
glReadPixels:讀取一些像素。當前可以簡單理解爲把已經繪製好的像素(它可能已經被保存到顯卡的顯存中)讀取到內存
glDrawPixels:繪製一些像素。當前可以簡單理解爲把內存中一些數據作爲像素數據,進行繪製
glCopyPixels:複製一些像素。當前可以簡單理解爲把已經繪製好的像素從一個位置複製到另一個位置。雖然從功能上看,好象等價於先讀取像素再繪製像素,但實際上它不需要把已經繪製的像素(它可能已經被保存到顯卡的顯存中)轉換爲內存數據,然後再由內存數據進行重新的繪製,所以要比先讀取後繪製快很多。
這三個函數可以完成簡單的像素讀取、繪製和複製任務,但實際上也可以完成更復雜的任務。當前,我們僅討論一些簡單的應用。由於這幾個函數的參數數目比較多,下面我們分別介紹。

3glReadPixels的用法和舉例
3.1 函數的參數說明
該函數總共有七個參數。前四個參數可以得到一個矩形,該矩形所包括的像素都會被讀取出來。(第一、二個參數表示了矩形的左下角橫、縱座標,座標以窗口最左下角爲零,最右上角爲最大值;第三、四個參數表示了矩形的寬度和高度)
第五個參數表示讀取的內容,例如:GL_RGB就會依次讀取像素的紅、綠、藍三種數據,GL_RGBA則會依次讀取像素的紅、綠、藍、alpha四種數據,GL_RED則只讀取像素的紅色數據(類似的還有GL_GREENGL_BLUE,以及GL_ALPHA)。如果採用的不是RGBA顏色模式,而是採用顏色索引模式,則也可以使用GL_COLOR_INDEX來讀取像素的顏色索引。目前僅需要知道這些,但實際上還可以讀取其它內容,例如深度緩衝區的深度數據等。
第六個參數表示讀取的內容保存到內存時所使用的格式,例如:GL_UNSIGNED_BYTE會把各種數據保存爲GLubyteGL_FLOAT會把各種數據保存爲GLfloat等。
第七個參數表示一個指針,像素數據被讀取後,將被保存到這個指針所表示的地址。注意,需要保證該地址有足夠的可以使用的空間,以容納讀取的像素數據。例如一幅大小爲256*256的圖象,如果讀取其RGB數據,且每一數據被保存爲GLubyte,總大小就是:256*256*3 = 196608字節,即192千字節。如果是讀取RGBA數據,則總大小就是256*256*4 = 262144字節,即256千字節。

注意:glReadPixels實際上是從緩衝區中讀取數據,如果使用了雙緩衝區,則默認是從正在顯示的緩衝(即前緩衝)中讀取,而繪製工作是默認繪製到後緩衝區的。因此,如果需要讀取已經繪製好的像素,往往需要先交換前後緩衝。

再看前面提到的BMP文件中兩個需要注意的地方:
3.2 解決OpenGL常用的RGB像素數據與BMP文件的BGR像素數據順序不一致問題
可以使用一些代碼交換每個像素的第一字節和第三字節,使得RGB的數據變成BGR的數據。當然也可以使用另外的方式解決問題:新版本的OpenGL除了可以使用GL_RGB讀取像素的紅、綠、藍數據外,也可以使用GL_BGR按照相反的順序依次讀取像素的藍、綠、紅數據,這樣就與BMP文件格式相吻合了。即使你的gl/gl.h頭文件中沒有定義這個GL_BGR,也沒有關係,可以嘗試使用GL_BGR_EXT。雖然有的OpenGL實現(尤其是舊版本的實現)並不能使用GL_BGR_EXT,但我所知道的Windows環境下各種OpenGL實現都對GL_BGR提供了支持,畢竟Windows中各種表示顏色的數據幾乎都是使用BGR的順序,而非RGB的順序。這可能與IBM-PC的硬件設計有關。

3.3 消除BMP文件中對齊帶來的影響
實際上OpenGL也支持使用了這種對齊方式的像素數據。只要通過glPixelStore修改像素保存時對齊的方式就可以了。像這樣:
int alignment = 4;
glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
第一個參數表示設置像素的對齊值,第二個參數表示實際設置爲多少。這裏像素可以單字節對齊(實際上就是不使用對齊)、雙字節對齊(如果長度爲奇數,則再補一個字節)、四字節對齊(如果長度不是四的倍數,則補爲四的倍數)、八字節對齊。分別對應alignment的值爲1, 2, 4, 8。實際上,默認的值是4,正好與BMP文件的對齊方式相吻合。
glPixelStorei也可以用於設置其它各種參數。但我們這裏並不需要深入討論了。


現在,我們已經可以把屏幕上的像素讀取到內存了,如果需要的話,我們還可以將內存中的數據保存到文件。正確的對照BMP文件格式,我們的程序就可以把屏幕中的圖象保存爲BMP文件,達到屏幕截圖的效果。
我們並沒有詳細介紹BMP文件開頭的54個字節的所有內容,不過這無傷大雅。從一個正確的BMP文件中讀取前54個字節,修改其中的寬度和高度信息,就可以得到新的文件頭了。假設我們先建立一個1*1大小的24位色BMP,文件名爲dummy.bmp,又假設新的BMP文件名稱爲grab.bmp。則可以編寫如下代碼:

FILE* pOriginFile = fopen("dummy.bmp", "rb);
FILE* pGrabFile = fopen("grab.bmp", "wb");
char  BMP_Header[54];
GLint width, height;



// 
讀取dummy.bmp中的頭54個字節到數組
fread(BMP_Header, sizeof(BMP_Header), 1, pOriginFile);
// 
把數組內容寫入到新的BMP文件
fwrite(BMP_Header, sizeof(BMP_Header), 1, pGrabFile);

// 
修改其中的大小信息
fseek(pGrabFile, 0x0012, SEEK_SET);
fwrite(&width, sizeof(width), 1, pGrabFile);
fwrite(&height, sizeof(height), 1, pGrabFile);

// 
移動到文件末尾,開始寫入像素數據
fseek(pGrabFile, 0, SEEK_END);



fclose(pOriginFile);
fclose(pGrabFile);

 

我們給出完整的代碼,演示如何把整個窗口的圖象抓取出來並保存爲BMP文件。



  1. #define WindowWidth 400
  2. #define WindowHeight 400

  3. #include <stdio.h>
  4. #include <stdlib.h>


  5. #define BMP_Header_Length 54
  6. void grab(void)
  7. {
  8.     FILE* pDummyFile;
  9.     FILE* pWritingFile;
  10.     GLubyte* pPixelData;
  11.     GLubyte BMP_Header[BMP_Header_Length];
  12.     GLint i, j;
  13.     GLint PixelDataLength;

  14.     // 計算像素數據的實際長度
  15.     i = WindowWidth * 3; // 得到每一行的像素數據長度
  16.     while( i%!= 0 ) // 補充數據,直到i是的倍數
  17.         ++i; // 本來還有更快的算法,
  18.                            // 但這裏僅追求直觀,對速度沒有太高要求
  19.     PixelDataLength = i * WindowHeight;

  20.     // 分配內存和打開文件
  21.     pPixelData = (GLubyte*)malloc(PixelDataLength);
  22.     if( pPixelData == 0 )
  23.         exit(0);

  24.     pDummyFile = fopen("dummy.bmp", "rb");
  25.     if( pDummyFile == 0 )
  26.         exit(0);

  27.     pWritingFile = fopen("grab.bmp", "wb");
  28.     if( pWritingFile == 0 )
  29.         exit(0);

  30.     // 讀取像素
  31.     glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
  32.     glReadPixels(0, 0, WindowWidth, WindowHeight,
  33.         GL_BGR_EXT, GL_UNSIGNED_BYTE, pPixelData);

  34.     // 把dummy.bmp的文件頭複製爲新文件的文件頭
  35.     fread(BMP_Header, sizeof(BMP_Header), 1, pDummyFile);
  36.     fwrite(BMP_Header, sizeof(BMP_Header), 1, pWritingFile);
  37.     fseek(pWritingFile, 0x0012, SEEK_SET);
  38.     i = WindowWidth;
  39.     j = WindowHeight;
  40.     fwrite(&i, sizeof(i), 1, pWritingFile);
  41.     fwrite(&j, sizeof(j), 1, pWritingFile);

  42.     // 寫入像素數據
  43.     fseek(pWritingFile, 0, SEEK_END);
  44.     fwrite(pPixelData, PixelDataLength, 1, pWritingFile);

  45.     // 釋放內存和關閉文件
  46.     fclose(pDummyFile);
  47.     fclose(pWritingFile);
  48.     free(pPixelData);
  49. }

把這段代碼複製到以前任何課程的樣例程序中,在繪製函數的最後調用grab函數,即可把圖象內容保存爲BMP文件了。(在我寫這個教程的時候,不少地方都用這樣的代碼進行截圖工作,這段代碼一旦寫好,運行起來是很方便的。)

 

4glDrawPixels的用法和舉例
glDrawPixels函數與glReadPixels函數相比,參數內容大致相同。它的第一、二、三、四個參數分別對應於glReadPixels函數的第三、四、五、六個參數,依次表示圖象寬度、圖象高度、像素數據內容、像素數據在內存中的格式。兩個函數的最後一個參數也是對應的,glReadPixels中表示像素讀取後存放在內存中的位置,glDrawPixels則表示用於繪製的像素數據在內存中的位置。
注意到glDrawPixels函數比glReadPixels函數少了兩個參數,這兩個參數在glReadPixels中分別是表示圖象的起始位置。在glDrawPixels中,不必顯式的指定繪製的位置,這是因爲繪製的位置是由另一個函數glRasterPos*來指定的。glRasterPos*函數的參數與glVertex*類似,通過指定一個二維/三維/四維座標,OpenGL將自動計算出該座標對應的屏幕位置,並把該位置作爲繪製像素的起始位置。
很自然的,我們可以從BMP文件中讀取像素數據,並使用glDrawPixels繪製到屏幕上。我們選擇Windows XP默認的桌面背景Bliss.bmp作爲繪製的內容(如果你使用的是Windows XP系統,很可能可以在硬盤中搜索到這個文件。當然你也可以使用其它BMP文件來代替,只要它是24位的BMP文件。注意需要修改代碼開始部分的FileName的定義),先把該文件複製一份放到正確的位置,我們在程序開始時,就讀取該文件,從而獲得圖象的大小後,根據該大小來創建合適的OpenGL窗口,並繪製像素。
繪製像素本來是很簡單的過程,但是這個程序在骨架上與前面的各種示例程序稍有不同,所以我還是打算給出一份完整的代碼。



  1. #include <gl/glut.h>

  2. #define FileName "Bliss.bmp"

  3. static GLint ImageWidth;
  4. static GLint ImageHeight;
  5. static GLint PixelLength;
  6. static GLubyte* PixelData;

  7. #include <stdio.h>
  8. #include <stdlib.h>

  9. void display(void)
  10. {
  11.     // 清除屏幕並不必要
  12.     // 每次繪製時,畫面都覆蓋整個屏幕
  13.     // 因此無論是否清除屏幕,結果都一樣
  14.     // glClear(GL_COLOR_BUFFER_BIT);

  15.     // 繪製像素
  16.     glDrawPixels(ImageWidth, ImageHeight,
  17.         GL_BGR_EXT, GL_UNSIGNED_BYTE, PixelData);

  18.     // 完成繪製
  19.     glutSwapBuffers();
  20. }

  21. int main(int argc, char* argv[])
  22. {
  23.     // 打開文件
  24.     FILE* pFile = fopen("Bliss.bmp", "rb");
  25.     if( pFile == 0 )
  26.         exit(0);

  27.     // 讀取圖象的大小信息
  28.     fseek(pFile, 0x0012, SEEK_SET);
  29.     fread(&ImageWidth, sizeof(ImageWidth), 1, pFile);
  30.     fread(&ImageHeight, sizeof(ImageHeight), 1, pFile);

  31.     // 計算像素數據長度
  32.     PixelLength = ImageWidth * 3;
  33.     while( PixelLength % 4 != 0 )
  34.         ++PixelLength;
  35.     PixelLength *= ImageHeight;

  36.     // 讀取像素數據
  37.     PixelData = (GLubyte*)malloc(PixelLength);
  38.     if( PixelData == 0 )
  39.         exit(0);

  40.     fseek(pFile, 54, SEEK_SET);
  41.     fread(PixelData, PixelLength, 1, pFile);

  42.     // 關閉文件
  43.     fclose(pFile);

  44.     // 初始化GLUT並運行
  45.     glutInit(&argc, argv);
  46.     glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);
  47.     glutInitWindowPosition(100, 100);
  48.     glutInitWindowSize(ImageWidth, ImageHeight);
  49.     glutCreateWindow(FileName);
  50.     glutDisplayFunc(&display);
  51.     glutMainLoop();

  52.     // 釋放內存
  53.     // 實際上,glutMainLoop函數永遠不會返回,這裏也永遠不會到達
  54.     // 這裏寫釋放內存只是出於一種個人習慣
  55.     // 不用擔心內存無法釋放。在程序結束時操作系統會自動回收所有內存
  56.     free(PixelData);

  57.     return 0;
  58. }

這裏僅僅是一個簡單的顯示24BMP圖象的程序,如果讀者對BMP文件格式比較熟悉,也可以寫出適用於各種BMP圖象的顯示程序,在像素處理時,它們所使用的方法是類似的。
OpenGL在繪製像素之前,可以對像素進行若干處理。最常用的可能就是對整個像素圖象進行放大/縮小。使用glPixelZoom來設置放大/縮小的係數,該函數有兩個參數,分別是水平方向係數和垂直方向係數。例如設置glPixelZoom(0.5f, 0.8f);則表示水平方向變爲原來的50%大小,而垂直方向變爲原來的80%大小。我們甚至可以使用負的係數,使得整個圖象進行水平方向或垂直方向的翻轉(默認像素從左繪製到右,但翻轉後將從右繪製到左。默認像素從下繪製到上,但翻轉後將從上繪製到下。因此,glRasterPos*函數設置的開始位置不一定就是矩形的左下角)。

5glCopyPixels的用法和舉例
從效果上看,glCopyPixels進行像素複製的操作,等價於把像素讀取到內存,再從內存繪製到另一個區域,因此可以通過glReadPixelsglDrawPixels組合來實現複製像素的功能。然而我們知道,像素數據通常數據量很大,例如一幅1024*768的圖象,如果使用24BGR方式表示,則需要至少1024*768*3字節,即2.25兆字節。這麼多的數據要進行一次讀操作和一次寫操作,並且因爲在glReadPixelsglDrawPixels中設置的數據格式不同,很可能涉及到數據格式的轉換。這對CPU無疑是一個不小的負擔。使用glCopyPixels直接從像素數據複製出新的像素數據,避免了多餘的數據的格式轉換,並且也可能減少一些數據複製操作(因爲數據可能直接由顯卡負責複製,不需要經過主內存),因此效率比較高。
glCopyPixels函數也通過glRasterPos*系列函數來設置繪製的位置,因爲不需要涉及到主內存,所以不需要指定數據在內存中的格式,也不需要使用任何指針。
glCopyPixels函數有五個參數,第一、二個參數表示複製像素來源的矩形的左下角座標,第三、四個參數表示複製像素來源的舉行的寬度和高度,第五個參數通常使用GL_COLOR,表示複製像素的顏色,但也可以是GL_DEPTHGL_STENCIL,分別表示複製深度緩衝數據或模板緩衝數據。
值得一提的是,glDrawPixelsglReadPixels中設置的各種操作,例如glPixelZoom等,在glCopyPixels函數中同樣有效。
下面看一個簡單的例子,繪製一個三角形後,複製像素,並同時進行水平和垂直方向的翻轉,然後縮小爲原來的一半,並繪製。繪製完畢後,調用前面的grab函數,將屏幕中所有內容保存爲grab.bmp。其中WindowWidthWindowHeight是表示窗口寬度和高度的常量。



  1. void display(void)
  2. {
  3.     // 清除屏幕
  4.     glClear(GL_COLOR_BUFFER_BIT);

  5.     // 繪製
  6.     glBegin(GL_TRIANGLES);
  7.         glColor3f(1.0f, 0.0f, 0.0f); glVertex2f(0.0f, 0.0f);
  8.         glColor3f(0.0f, 1.0f, 0.0f); glVertex2f(1.0f, 0.0f);
  9.         glColor3f(0.0f, 0.0f, 1.0f); glVertex2f(0.5f, 1.0f);
  10.     glEnd();
  11.     glPixelZoom(-0.5f, -0.5f);
  12.     glRasterPos2i(1, 1);
  13.     glCopyPixels(WindowWidth/2, WindowHeight/2,
  14.         WindowWidth/2, WindowHeight/2, GL_COLOR);

  15.     // 完成繪製,並抓取圖象保存爲BMP文件
  16.     glutSwapBuffers();
  17.     grab();
  18. }
小結:
本課結合Windows系統常見的BMP圖象格式,簡單介紹了OpenGL的像素處理功能。包括使用glReadPixels讀取像素、glDrawPixels繪製像素、glCopyPixels複製像素。
本課僅介紹了像素處理的一些簡單應用,但相信大家已經可以體會到,圍繞這三個像素處理函數,還存在一些外圍函數,比如glPixelStore*glRasterPos*,以及glPixelZoom等。我們僅使用了這些函數的一少部分功能。
本課內容並不多,例子足夠豐富,三個像素處理函數都有例子,大家可以結合例子來體會

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章