程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 揭秘OpenGL紅寶書中棋盤生成程序

揭秘OpenGL紅寶書中棋盤生成程序

編輯:C++入門知識

OpenGL紅寶書中在內存中生成圖像時,多次提到了一個生成黑白棋盤的例子。相關代碼如下:
[cpp]
#define checkImageWidth 64 
#define checkImageHeight 64 
GLubyte checkImage[checkImageWidth][checkImageHeight][3]; 
 
...... 
 
void makeCheckImage(void) 

    int i, j, c; 
     
    for (i = 0; i < checkImageHeight; i++) { 
        for (j = 0; j < checkImageWidth; j++) { 
            c = ((((i & 0x8) == 0) ^ ((j & 0x8)) == 0)) * 255; 
            checkImage[i][j][0] = (GLubyte) c; 
            checkImage[i][j][1] = (GLubyte) c; 
            checkImage[i][j][2] = (GLubyte) c; 
        } 
    } 

 
...... 
 
void display() 

    ...... 
    glDrawPixels(checkImageWidth, checkImageHeight, GL_RGB, GL_UNSIGNED_BYTE, checkImage); 
    ...... 

checkImage的類型為GLubyte,對應於unsigned char,是8位的無符號整數,使用1字節來表示顏色的RGB值。
R: {1 1 1 1 1 1 1 1}
G: {0 0 0 0 0 0 0 0}
B: {1 1 1 1 1 1 1 1}
數組在內存中二進制的表示:

            第1列                                                第2列                  第64列
第1行 {1 1 1 1 1 1 1 1} {1 1 1 1 1 1 1 1} {1 1 1 1 1 1 1 1} {1 1 1 1 1 1 1 1} …… {0 0 0 0 0 0 0 0}
第2行 {0 0 0 0 0 0 0 0} {0 0 0 0 0 0 0 0} {0 0 0 0 0 0 0 0} {0 0 0 0 0 0 0 0} …… {1 1 1 1 1 1 1 1}
第3行 {1 1 1 1 1 1 1 1} {1 1 1 1 1 1 1 1} {1 1 1 1 1 1 1 1} {1 1 1 1 1 1 1 1} …… {0 0 0 0 0 0 0 0}

……

第64行 ……

下面看數組間的規律。

checkImage[0][0][0] = 0, checkImage[0][0][1] = 0, checkImage[0][0][2] = 0
checkImage[0][1][0] = 0, checkImage[0][1][1] = 0, checkImage[0][1][2] = 0
checkImage[0][2][0] = 0, checkImage[0][2][1] = 0, checkImage[0][2][2] = 0
checkImage[0][3][0] = 0, checkImage[0][3][1] = 0, checkImage[0][3][2] = 0
checkImage[0][4][0] = 0, checkImage[0][4][1] = 0, checkImage[0][4][2] = 0
checkImage[0][5][0] = 0, checkImage[0][5][1] = 0, checkImage[0][5][2] = 0
checkImage[0][6][0] = 0, checkImage[0][6][1] = 0, checkImage[0][6][2] = 0
checkImage[0][7][0] = 0, checkImage[0][7][1] = 0, checkImage[0][7][2] = 0

checkImage[0][8][0] = FF, checkImage[0][8][1] = FF, checkImage[0][8][2] = FF
checkImage[0][9][0] = FF, checkImage[0][9][1] = FF, checkImage[0][9][2] = FF
checkImage[0][10][0] = FF, checkImage[0][10][1] = FF, checkImage[0][10][2] = FF
checkImage[0][11][0] = FF, checkImage[0][11][1] = FF, checkImage[0][11][2] = FF
checkImage[0][12][0] = FF, checkImage[0][12][1] = FF, checkImage[0][12][2] = FF
checkImage[0][13][0] = FF, checkImage[0][13][1] = FF, checkImage[0][13][2] = FF
checkImage[0][14][0] = FF, checkImage[0][14][1] = FF, checkImage[0][14][2] = FF
checkImage[0][15][0] = FF, checkImage[0][15][1] = FF, checkImage[0][15][2] = FF

checkImage[0][16][0] = 0, checkImage[0][16][1] = 0, checkImage[0][16][2] = 0
checkImage[0][17][0] = 0, checkImage[0][17][1] = 0, checkImage[0][17][2] = 0
checkImage[0][18][0] = 0, checkImage[0][18][1] = 0, checkImage[0][18][2] = 0
checkImage[0][19][0] = 0, checkImage[0][19][1] = 0, checkImage[0][19][2] = 0
checkImage[0][20][0] = 0, checkImage[0][20][1] = 0, checkImage[0][20][2] = 0
checkImage[0][21][0] = 0, checkImage[0][21][1] = 0, checkImage[0][21][2] = 0
checkImage[0][22][0] = 0, checkImage[0][22][1] = 0, checkImage[0][22][2] = 0
checkImage[0][23][0] = 0, checkImage[0][23][1] = 0, checkImage[0][23][2] = 0

……

checkImage是一個3維數組,第1維表示行,第2維表示列,第3維表示像素顏色的RGB值。一個FF為8位,因此第3維的3個FF可表示24位的真彩。

從上可看出,每隔8列,則在checkImage的值則在0與FF之間切換,即在黑白之間切換。即,對於第0行,0至7列為黑,8至15列為白,16至23列為黑。由於checkImageWidth值為64,因此可產生 64 / 8 = 8 的黑白格。如果需要修改顏色,則將第3維的數值改為相應的值即可。

為何要每隔8列才切換黑白?每隔1列也可以,但在高分辨率的顯示器下面非常不明顯。因此這個8列,實際上控制著黑白格的寬度。

同理,每隔8行,則在黑白間切換。這就產生了錯落有致的正方形黑白格。

需要注意的是,glDrawPixels函數雖然依次讀取數據數值,但卻是從最下面的1行開始繪制的。即checkImage[0][x][x]對於於最下面第1行,checkImage[1][x][x]對於於最下面第2行。因此,下面的代碼可將左下角的單元格顏色值設為紅色。

   for (i =0; i <8; i++) {
       for (j =0; j <8; j++) {
           checkImage[i][j][0] = (GLubyte)255;
           checkImage[i][j][1] = (GLubyte)0;
           checkImage[i][j][2] = (GLubyte)0;
        }
    }

現在的問題是,代碼是如何自動產生顏色的黑白值的?

0x8的二進制是1000,表達式 i & 0x8 只有在i值的高位為1時才能得到1000,否則全為0000。 因此表達式 (i & 0x8) == 0 只在i值的高位為1時才返回false,否則為true。上面的說法是嚴格從語義的角度來進行的解釋,但不好理解,沒有太多實際意義。而實際上,對位與操作符“&”,更好的理解是,位與操作符“&”經常用於通過選擇特定位為0的屏蔽因子而屏蔽特定的位,從而留下只關心的位。例如,對於例中所選擇的屏蔽因子0x8,二進制為1000,

  1100      (i)
& 1000      (屏蔽因子)
----
  1000      (低3位被屏蔽掉,只剩最高位)

因為低3位為000,因此,無論i為何值,位與後的結果其低3位必為0值,而最高位則可保留原值。因此,通過與0x8位與,我們將i值的低3位全部屏蔽了,而只關心i值的千分位。這樣,表達式

(i & 0x8) == 0

的意思是,i的千分位是否為0?

同樣,表達式(j & 0x8) == 0 的意思是,j的千分位是否為0?

位或操作符“|”通常用於將特定位打開。如,下面將最低位打開。

  0100      (i)
| 0001      (打開因子)
----
  0101      (最低位必然為1,從而打開最低位)

位異或操作符“^”在操作數不同時返回1,在操作數相同時返回0。通常用於反轉特定的位。

   0101      (i)
|  0011      (反轉因子)
----
   0110      (將最低2位反轉)

回到我們的例子,對於

c = ((((i &0x8) ==0) ^ ((j &0x8)) ==0)) *255;

設 i & 0x8 == 0 的值為a,則表達式變為

c = ((a ^ ((j &0x8)) ==0)) *255;

設 ((j & 0x8)) 的值為b,則表達式變為

c = ((a ^ b ==0)) *255;

因為 == 優先於 ^,因此將先算 b == 0,設 b == 0 的值為d,則表達式變為

c = ((a ^ d)) *255;

很明顯,例子代碼中的書寫格式並不規范,一是 ((j & 0x8)) 的括號重復; 二是a ^ b ==0 讓讀者去猜^與==到底哪個優先,在編譯器中也檢測出該問題而發出警告; 三是((a ^ c)) *255的括號再次重復。綜上,例子代碼的公式可改為:

c = ( ( (i &0x8) ==0 ) ^ ( (j &0x8) ==0) ) *255;

含義比較清楚了,i的千分位是否為0?j的千分位是否為0?將它們異或後再乘以255。最後結果是,只有1真1假時,才得到255即白色,否則為黑色。也即當i或j中有且僅有一個的千分位為1時才會產生白色。

不同字長的最高位為1的值分別有:

二進制            字長   十六進制  十進制
1000               4       0x8       8
10000000           8       0x80      128
100000000000       12      0x800     2048
1000000000000000   16      0x8000    32768
……

而在字長為4、最高位為1時,可能的值如下:

二進制 十六進制 十進制
1000      0x8      8
1001      0x9      9
1010      0xA      10
1011      0xB      11
1100      0xC      12
1101      0xD      13
1110      0xE      14
1111      0xF      15

  1xxx
& 1000
————
  1000

因此,當字長為4,行i或列j中有且僅有一個存在8到15的值域時才會產生白色。因此就有第0行中8至15列的白色方格:

checkImage[0][8][0]  = FF, checkImage[0][8][1] =  FF, checkImage[0][8][2] =  FF
checkImage[0][9][0]  = FF, checkImage[0][9][1] =  FF, checkImage[0][9][2] =  FF
checkImage[0][10][0] = FF, checkImage[0][10][1] = FF, checkImage[0][10][2] = FF
checkImage[0][11][0] = FF, checkImage[0][11][1] = FF, checkImage[0][11][2] = FF
checkImage[0][12][0] = FF, checkImage[0][12][1] = FF, checkImage[0][12][2] = FF
checkImage[0][13][0] = FF, checkImage[0][13][1] = FF, checkImage[0][13][2] = FF
checkImage[0][14][0] = FF, checkImage[0][14][1] = FF, checkImage[0][14][2] = FF
checkImage[0][15][0] = FF, checkImage[0][15][1] = FF, checkImage[0][15][2] = FF

16的二進制是10000,是第一個字長超過4的數。(准確地說,0xF只能表示0-15的數,16-255的數必須用0xFF的范圍來表示。)對於它,

  00010000     (16)
& 00001000    (屏蔽因子0x8)
------
  00000000

雖然最高位為1,但經0x8屏蔽後,結果為0,false。

來看24,這是字長超過4,且二進制的千分位為1的第一個數。

  00011000
& 00001000
------
  00001000
 
結果為true,白色。因此有

checkImage[0][24][0] = FF, checkImage[0][24][1] = FF, checkImage[0][24][2] = FF
checkImage[0][25][0] = FF, checkImage[0][25][1] = FF, checkImage[0][25][2] = FF
checkImage[0][26][0] = FF, checkImage[0][26][1] = FF, checkImage[0][26][2] = FF
checkImage[0][27][0] = FF, checkImage[0][27][1] = FF, checkImage[0][27][2] = FF
checkImage[0][28][0] = FF, checkImage[0][28][1] = FF, checkImage[0][28][2] = FF
checkImage[0][29][0] = FF, checkImage[0][29][1] = FF, checkImage[0][29][2] = FF
checkImage[0][30][0] = FF, checkImage[0][30][1] = FF, checkImage[0][30][2] = FF
checkImage[0][31][0] = FF, checkImage[0][31][1] = FF, checkImage[0][31][2] = FF

上面的規律是,不管最高位是否為1,只要二進制的千分位為1,就可在和0x8位與後其值為真。

在所有小於64的數中,符合二進制的千分位為1這個條件的數有:

8  - 15
24 - 31
40 - 47
56 - 63

揭秘完畢,總結規律:

位與運算不僅可以屏蔽特定位,巧用位與表達式可以在特定數值間來回翻轉。

例如:如果需要連續的4個數且每隔4位翻轉一次,則可用下面的代碼:

   for (int i = 0; i < 30; i++) {
       if ((i &0x4) == 0) {
           printf("%d ", i);
        }
    }

結果為:

0 1 2 3  8 9 10 11  16 17 18 19  24 25 26 27

多試幾個數,以期獲得規律的靈感。

下面將checkImageWidth及checkImageHeight均改為256之後,將屏蔽因子改為0x16後的


 圖像:

0x32的圖像:

實在是沒力去思考為什麼了。
 


摘自 Sarkuya的編程園地

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved