每個像素都有自己對應的 Buffer,其實就是一個 32bit 的數,如 Color Buffer, Depth Buffer, Stencil Buffer. Stencil Buffer 與 Depth Buffer 有點特別,因為他們共用同一個 Buffer, Depth Buffer 占用 Buffer 前面的 24Bit, Stencil Buffer 占用后面的 8Bit. Stencil Buffer 可以使用從 1Bit-8Bit. 如在繪制反射時,就像照鏡子一樣,因為只需要在反射平面上繪制物體的鏡像,即要么在反射平面上繪制,要不就不繪制,所以只需要用到 1Bit 的 Stencil Buffer.
什么叫 Stencil Buffer ?
即是一個模板,也就是說,他可以是一個平面,也可以是一個立體幾何圖形,如一個四邊形,一個Teapot. 在模板所占據的空間中,他的值為 1(values stored in the stencil buffer), 在啟用 Stencil Buffer 時,我們所畫的圖形只有在這個空間中的部分才能顯示出來,所以我們可以創建一個模板,他是一個字,然后以后畫的圖形最多只能把這個字給顯示出來,這個圖形有其他部分都沒有被寫進 Color Buffer.
Stencil Buffer 最簡單的運用,用來生成鏡面反射。
1. 先要使編程環境支持 Stencil Buffer
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_STENCIL);
2. 設置清除 Stencil Buffer 使用的函數
glClearStencil(0);
3. 在我們創建模板的時候,要先關掉 Depth Test und Color Mask,
因為我們并不想把模板畫到屏幕上
glDisable(GL_DEPTH_TEST);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
// 為了把我們的模板圖形不顯示到屏幕上,但又要寫入 Stencil Buffer 中。
我們什么時候創建模板的?就是在啟用模板緩存后進行的第一次進繪制的圖形就是模板。
OpenGL會根據我們所設定的 glStencilFunc 的值和 glStencilOp 來比較,
然后在 plane 中(即視口所對應的那個二維數組)寫入比較的結果值。
4. 開始創建模板
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 0x1, 0x1);
// 把模板圖形所在的區域的 Buffer 值設置成 1, 其余的還是 0.
// 這時用的就是給 glStencilFunc 指定的 ref 的值,現在是 1
// 當然可以有其他的操作,如 GL_INCR, GL_INVERT(bitwise invert)
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
// 開始創建模板的圖形
drawFloor();
// 模板創建好后,我們就要設置下一次進行繪制時的模板函數
// 只有通過條件的像素才能被顯示到屏幕上,否則就被丟棄
// 但要注意,現在我們要進行繪制的就是鏡像了,所以是要被顯示到屏幕上的,
// 所以在繪制之前,要把顏色屏蔽關掉和啟用深度測試
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_DEPTH_TEST);
// Stencil buffer 值等于 1 的地方才繪制到屏幕上
glStencilFunc(GL_EQUAL, 0x1, 0x1);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
5. 繪制鏡像圖像
// 現在繪制我們的鏡像圖像
// 鏡像是跟原來的物體對稱的, 所以用 glScalef 來進行反轉,實現對稱
// 在繪制鏡像物體的時候,燈光也要相應的反轉
glPushMatrix();
glScalef(1, -1, 1);
glutSolidTeapot(1.0f);
glPopMatrix();
6. 在模板中顯示的鏡像圖像已經創建好,不再需要模板了,所以我們關掉 stencil buffer
glDisable(GL_STENCIL_TEST);
7. 繪制鏡像所在的平面,就如鏡子
// 使用 Blend 與鏡像圖像混合起來
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
drawFloor();
glDisable(GL_BLEND);
8. 繪制產生鏡像的物體
glutSolidTeapot(1.0f);
至此,真正的鏡面反射已經創建完成。
非真正的反射可以如下實現:
先畫對稱物體,畫出鏡面(使用 Blend), 然后畫出原物體,但這時如果旋轉Camera,就會發現,那個對稱的物體并不是平面的,還是原來的空間立體物體。但用 Stencil Buffer 實現的鏡面反射是真正的鏡面反射,鏡像是只在鏡面上顯示,即是平面的。
下面的代碼可以很好的工作
//****************************************************************//
if (useStencil) {
glClearStencil(0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
} else {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
if (useStencil) {
glDisable(GL_DEPTH_TEST);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
/* Draw 1 into the stencil buffer. */
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
/* Now render floor; floor pixels just get their stencil set to 1. */
drawFloor();
/* Re-enable update of color and depth. */
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glEnable(GL_DEPTH_TEST);
/* Now, only render where stencil is set to 1. */
glStencilFunc(GL_EQUAL, 1, 0xffffffff); /* draw if ==1 */
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
}
glPushMatrix();
glScalef(1, -1, 1);
glTranslatef(0.0, 0.8, 0);
glColor3f(0, 1, 0);
glutSolidTeapot(1);
glPopMatrix();
if (useStencil) {
glDisable(GL_STENCIL_TEST);
}
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4f(0.7, 0.0, 0.0, 0.3);
drawFloor();
glDisable(GL_BLEND);
glTranslatef(0, -0.0001, 0);
glFrontFace(GL_CW);
glColor3f(1, 1, 1);
drawFloor();
glTranslatef(0.0, 0.8, 0);
glColor3f(0, 1, 0);
glutSolidTeapot(1);
//****************************************************************//