歡迎來到另一個(gè)激動(dòng)人心的課程,這課的代碼是Banu Cosmin所寫,當(dāng)然教程還是我自己寫的。在這課里,我將教你創(chuàng)建真正的反射,基于物理的。
由于它將用到蒙板緩存,所以需要耗費(fèi)一些資源。當(dāng)然隨著顯卡和CPU的發(fā)展,這些都不是問題了,好了讓我們開始吧!
下面我們?cè)O(shè)置光源的參數(shù)
static GLfloat LightAmb[] = {0.7f, 0.7f, 0.7f, 1.0f}; // 環(huán)境光
static GLfloat LightDif[] = {1.0f, 1.0f, 1.0f, 1.0f}; // 漫射光
static GLfloat LightPos[] = {4.0f, 4.0f, 6.0f, 1.0f}; // 燈光的位置
下面用二次幾何體創(chuàng)建一個(gè)球,并設(shè)置紋理
GLUquadricObj *q; // 使用二次幾何體創(chuàng)建球
GLfloat xrot = 0.0f; // X方向的旋轉(zhuǎn)角度
GLfloat yrot = 0.0f; // Y方向的旋轉(zhuǎn)角的
GLfloat xrotspeed = 0.0f; // X方向的旋轉(zhuǎn)速度
GLfloat yrotspeed = 0.0f; // Y方向的旋轉(zhuǎn)速度
GLfloat zoom = -7.0f; // 移入屏幕7個(gè)單位
GLfloat height = 2.0f; // 球離開地板的高度
GLuint texture[3]; // 使用三個(gè)紋理
ReSizeGLScene() 和LoadBMP() 沒有變化
GLvoid ReSizeGLScene(GLsizei width, GLsizei height)
AUX_RGBImageRec *LoadBMP(char *Filename)
下面的代碼載入紋理
int LoadGLTextures() // 載入*.bmp文件,并轉(zhuǎn)化為紋理
{
int Status=FALSE;
AUX_RGBImageRec *TextureImage[3]; // 創(chuàng)建三個(gè)圖象
memset(TextureImage,0,sizeof(void *)*3);
if ((TextureImage[0]=LoadBMP("Data/EnvWall.bmp")) && // 載入地板圖像
(TextureImage[1]=LoadBMP("Data/Ball.bmp")) && // 載入球圖像
(TextureImage[2]=LoadBMP("Data/EnvRoll.bmp"))) // 載入強(qiáng)的圖像
{
Status=TRUE;
glGenTextures(3, &texture[0]); // 創(chuàng)建紋理
for (int loop=0; loop<3; loop++) // 循環(huán)設(shè)置三個(gè)紋理參數(shù)
{
glBindTexture(GL_TEXTURE_2D, texture[loop]);
glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[loop]->sizeX, TextureImage[loop]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[loop]->data);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
}
for (loop=0; loop<3; loop++)
{
if (TextureImage[loop])
{
if (TextureImage[loop]->data)
{
free(TextureImage[loop]->data);
}
free(TextureImage[loop]);
}
}
}
return Status; // 成功返回
}
一個(gè)新的函數(shù)glClearStencil被加入到初始化代碼中,它用來設(shè)置清空操作后蒙板緩存中的值。其他的操作保持不變。
int InitGL(GLvoid) // 初始化OpenGL
{
if (!LoadGLTextures()) // 載入紋理
{
return FALSE;
}
glShadeModel(GL_SMOOTH);
glClearColor(0.2f, 0.5f, 1.0f, 1.0f);
glClearDepth(1.0f);
glClearStencil(0); // 設(shè)置蒙板值
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
glEnable(GL_TEXTURE_2D); // 使用2D紋理
下面的代碼用來啟用光照
glLightfv(GL_LIGHT0, GL_AMBIENT, LightAmb);
glLightfv(GL_LIGHT0, GL_DIFFUSE, LightDif);
glLightfv(GL_LIGHT0, GL_POSITION, LightPos);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
下面的代碼使用二次幾何體創(chuàng)建一個(gè)球體,在前面的教程中都已經(jīng)詳纖,這里不再重復(fù)。
q = gluNewQuadric(); // 創(chuàng)建一個(gè)二次幾何體
gluQuadricNormals(q, GL_SMOOTH); // 使用平滑法線
gluQuadricTexture(q, GL_TRUE); // 使用紋理
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); // 設(shè)置球紋理映射
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
return TRUE; // 初始化完成,成功返回
}
下面的代碼繪制我們的球
void DrawObject() // 繪制我們的球
{
glColor3f(1.0f, 1.0f, 1.0f); // 設(shè)置為白色
glBindTexture(GL_TEXTURE_2D, texture[1]); // 設(shè)置為球的紋理
gluSphere(q, 0.35f, 32, 16); // 繪制球
繪制完一個(gè)白色的球后,我們使用環(huán)境貼圖來繪制另一個(gè)球,把這兩個(gè)球按alpha混合起來。
glBindTexture(GL_TEXTURE_2D, texture[2]); // 設(shè)置為環(huán)境紋理
glColor4f(1.0f, 1.0f, 1.0f, 0.4f); // 使用alpha為40%的白色
glEnable(GL_BLEND); // 啟用混合
glBlendFunc(GL_SRC_ALPHA, GL_ONE); // 把原顏色的40%與目標(biāo)顏色混合
glEnable(GL_TEXTURE_GEN_S); // 使用球映射
glEnable(GL_TEXTURE_GEN_T);
gluSphere(q, 0.35f, 32, 16); // 繪制球體,并混合
glDisable(GL_TEXTURE_GEN_S); // 讓OpenGL回到默認(rèn)的屬性
glDisable(GL_TEXTURE_GEN_T);
glDisable(GL_BLEND);
}
繪制地板
void DrawFloor()
{
glBindTexture(GL_TEXTURE_2D, texture[0]); // 選擇地板紋理,地板由一個(gè)長(zhǎng)方形組成
glBegin(GL_QUADS);
glNormal3f(0.0, 1.0, 0.0);
glTexCoord2f(0.0f, 1.0f); // 左下
glVertex3f(-2.0, 0.0, 2.0);
glTexCoord2f(0.0f, 0.0f); // 左上
glVertex3f(-2.0, 0.0,-2.0);
glTexCoord2f(1.0f, 0.0f); // 右上
glVertex3f( 2.0, 0.0,-2.0);
glTexCoord2f(1.0f, 1.0f); // 右下
glVertex3f( 2.0, 0.0, 2.0);
glEnd();
}
現(xiàn)在到了我們繪制函數(shù)的地方,我們將把所有的模型結(jié)合起來創(chuàng)建一個(gè)反射的場(chǎng)景。
向往常一樣先把各個(gè)緩存清空,接著定義我們的剪切平面,它用來剪切我們的圖像。這個(gè)平面的方程為equ[]={0,-1,0,0},向你所看到的它的法線是指向-y軸的,這告訴我們你只能看到y(tǒng)軸坐標(biāo)小于0的像素,如果你啟用剪切功能的話。
關(guān)于剪切平面,我們?cè)诤竺鏁?huì)做更多的討論。繼續(xù)吧:)
int DrawGLScene(GLvoid)
{
// 清除緩存
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
// 設(shè)置剪切平面
double eqr[] = {0.0f,-1.0f, 0.0f, 0.0f};
下面我們把地面向下平移0.6個(gè)單位,因?yàn)槲覀兊难劬υ趛=0的平面,如果不平移的話,那么看上去平面就會(huì)變?yōu)橐粭l線,為了看起來更真實(shí),我們平移了它。
glLoadIdentity();
glTranslatef(0.0f, -0.6f, zoom); // 平移和縮放地面
下面我們?cè)O(shè)置了顏色掩碼,在默認(rèn)情況下所有的顏色都可以寫入,即在函數(shù)glColorMask中,所有的參數(shù)都被設(shè)為GL_TRUE,如果設(shè)為零表示這部分顏色不可寫入?,F(xiàn)在我們不希望在屏幕上繪制任何東西,所以把參數(shù)設(shè)為0。
glColorMask(0,0,0,0);
下面來設(shè)置蒙板緩存和蒙板測(cè)試。
首先我們啟用蒙板測(cè)試,這樣就可以修改蒙板緩存中的值。
下面我們來解釋蒙板測(cè)試函數(shù)的含義:
當(dāng)你使用glEnable(GL_STENCIL_TEST)啟用蒙板測(cè)試之后,蒙板函數(shù)用于確定一個(gè)顏色片段是應(yīng)該丟棄還是保留(被繪制)。蒙板緩存區(qū)中的值與參考值ref進(jìn)行比較,比較標(biāo)準(zhǔn)是func所指定的比較函數(shù)。參考值和蒙板緩存區(qū)的值都可以與掩碼進(jìn)行為AND操作。蒙板測(cè)試的結(jié)果還導(dǎo)致蒙板緩存區(qū)根據(jù)glStencilOp函數(shù)所指定的行為進(jìn)行修改。
func的參數(shù)值如下:
常量 含義
GL_NEVER 從不通過蒙板測(cè)試
GL_ALWAYS 總是通過蒙板測(cè)試
GL_LESS 只有參考值<(蒙板緩存區(qū)的值&mask)時(shí)才通過
GL_LEQUAL 只有參考值<=(蒙板緩存區(qū)的值&mask)時(shí)才通過
GL_EQUAL 只有參考值=(蒙板緩存區(qū)的值&mask)時(shí)才通過
GL_GEQUAL 只有參考值>=(蒙板緩存區(qū)的值&mask)時(shí)才通過
GL_GREATER 只有參考值>(蒙板緩存區(qū)的值&mask)時(shí)才通過
GL_NOTEQUAL 只有參考值!=(蒙板緩存區(qū)的值&mask)時(shí)才通過
接下來我們解釋glStencilOp函數(shù),它用來根據(jù)比較結(jié)果修改蒙板緩存區(qū)中的值,它的函數(shù)原形為:
void glStencilOp(GLenum sfail, GLenum zfail, GLenum zpass),各個(gè)參數(shù)的含義如下:
sfail
當(dāng)蒙板測(cè)試失敗時(shí)所執(zhí)行的操作
zfail
當(dāng)蒙板測(cè)試通過,深度測(cè)試失敗時(shí)所執(zhí)行的操作
zpass
當(dāng)蒙板測(cè)試通過,深度測(cè)試通過時(shí)所執(zhí)行的操作
具體的操作包括以下幾種
常量 描述
GL_KEEP 保持當(dāng)前的蒙板緩存區(qū)值
GL_ZERO 把當(dāng)前的蒙板緩存區(qū)值設(shè)為0
GL_REPLACE 用glStencilFunc函數(shù)所指定的參考值替換蒙板參數(shù)值
GL_INCR 增加當(dāng)前的蒙板緩存區(qū)值,但限制在允許的范圍內(nèi)
GL_DECR 減少當(dāng)前的蒙板緩存區(qū)值,但限制在允許的范圍內(nèi)
GL_INVERT 將當(dāng)前的蒙板緩存區(qū)值進(jìn)行逐位的翻轉(zhuǎn)
當(dāng)完成了以上操作后我們繪制一個(gè)地面,當(dāng)然現(xiàn)在你什么也看不到,它只是把覆蓋地面的蒙板緩存區(qū)中的相應(yīng)位置設(shè)為1。
glEnable(GL_STENCIL_TEST); // 啟用蒙板緩存
glStencilFunc(GL_ALWAYS, 1, 1); // 設(shè)置蒙板測(cè)試總是通過,參考值設(shè)為1,掩碼值也設(shè)為1
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); // 設(shè)置當(dāng)深度測(cè)試不通過時(shí),保留蒙板中的值不變。如果通過則使用參考值替換蒙板值
glDisable(GL_DEPTH_TEST); // 禁用深度測(cè)試
DrawFloor(); // 繪制地面
我們現(xiàn)在已經(jīng)在蒙板緩存區(qū)中建立了地面的蒙板了,這是繪制影子的關(guān)鍵,如果想知道為什么,接著向后看吧:)
下面我們啟用深度測(cè)試和繪制顏色,并相應(yīng)設(shè)置蒙板測(cè)試和函數(shù)的值,這種設(shè)置可以使我們?cè)谄聊簧侠L制而不改變蒙板緩存區(qū)的值。
glEnable(GL_DEPTH_TEST); //啟用深度測(cè)試
glColorMask(1,1,1,1); // 可以繪制顏色
glStencilFunc(GL_EQUAL, 1, 1); //下面的設(shè)置指定當(dāng)我們繪制時(shí),不改變蒙板緩存區(qū)的值
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
下面的代碼設(shè)置并啟用剪切平面,使得只能在地面的下方繪制
glEnable(GL_CLIP_PLANE0); // 使用剪切平面
glClipPlane(GL_CLIP_PLANE0, eqr); // 設(shè)置剪切平面為地面,并設(shè)置它的法線為向下
glPushMatrix(); // 保存當(dāng)前的矩陣
glScalef(1.0f, -1.0f, 1.0f); // 沿Y軸反轉(zhuǎn)
由于上面已經(jīng)啟用了蒙板緩存,則你只能在蒙板中值為1的地方繪制,反射的實(shí)質(zhì)就是在反射屏幕的對(duì)應(yīng)位置在繪制一個(gè)物體,并把它放置在反射平面中。下面的代碼完成這個(gè)功能
glLightfv(GL_LIGHT0, GL_POSITION, LightPos); // 設(shè)置燈光0
glTranslatef(0.0f, height, 0.0f);
glRotatef(xrot, 1.0f, 0.0f, 0.0f);
glRotatef(yrot, 0.0f, 1.0f, 0.0f);
DrawObject(); // 繪制反射的球
glPopMatrix(); // 彈出保存的矩陣
glDisable(GL_CLIP_PLANE0); // 禁用剪切平面
glDisable(GL_STENCIL_TEST); // 關(guān)閉蒙板
下面的代碼繪制地面,并把地面顏色和反射的球顏色混合,使其看起來像反射的效果。
glLightfv(GL_LIGHT0, GL_POSITION, LightPos);
glEnable(GL_BLEND); // 啟用混合
glDisable(GL_LIGHTING); // 關(guān)閉光照
glColor4f(1.0f, 1.0f, 1.0f, 0.8f); // 設(shè)置顏色為白色
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // 設(shè)置混合系數(shù)
DrawFloor(); // 繪制地面
下面的代碼在距地面高為height的地方繪制一個(gè)真正的球
glEnable(GL_LIGHTING); // 使用光照
glDisable(GL_BLEND); // 禁用混合
glTranslatef(0.0f, height, 0.0f); // 移動(dòng)高位height的位置
glRotatef(xrot, 1.0f, 0.0f, 0.0f); // 設(shè)置球旋轉(zhuǎn)的角度
glRotatef(yrot, 0.0f, 1.0f, 0.0f);
DrawObject(); // 繪制球
下面的代碼用來處理鍵盤控制等常規(guī)操作
xrot += xrotspeed; // 更新X軸旋轉(zhuǎn)速度
yrot += yrotspeed; // 更新Y軸旋轉(zhuǎn)速度
glFlush(); // 強(qiáng)制OpenGL執(zhí)行所有命令
return TRUE; // 成功返回
}
下面的代碼處理鍵盤控制,上下左右控制球的旋轉(zhuǎn)。PageUp/Pagedown控制球的上下。A,Z控制球離你的遠(yuǎn)近。
void ProcessKeyboard()
{
if (keys[VK_RIGHT]) yrotspeed += 0.08f;
if (keys[VK_LEFT]) yrotspeed -= 0.08f;
if (keys[VK_DOWN]) xrotspeed += 0.08f;
if (keys[VK_UP]) xrotspeed -= 0.08f;
if (keys['A']) zoom +=0.05f;
if (keys['Z']) zoom -=0.05f;
if (keys[VK_PRIOR]) height +=0.03f;
if (keys[VK_NEXT]) height -=0.03f;
}
KillGLWindow() 函數(shù)沒有任何改變
GLvoid KillGLWindow(GLvoid)
CreateGLWindow()函數(shù)基本沒有改變,只是添加了以行啟用蒙板緩存
static PIXELFORMATDESCRIPTOR pfd=
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW |
PFD_SUPPORT_OPENGL |
PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
bits,
0, 0, 0, 0, 0, 0,
0,
0,
0,
0, 0, 0, 0,
16,
下面就是在這個(gè)函數(shù)中唯一改變的地方,記得把0變?yōu)?,它啟用蒙板緩存。
1, // 使用蒙板緩存
0,
PFD_MAIN_PLANE,
0,
0, 0, 0
};
WinMain()函數(shù)基本沒有變化,只是加上以行鍵盤控制的處理函數(shù)
ProcessKeyboard(); // 處理按鍵相應(yīng)
我真的希望你能喜歡這個(gè)教程,我清楚地知道我想做的每一件事,以及如何一步一步實(shí)現(xiàn)我心中想創(chuàng)建的效果。但把它表達(dá)出來又是另一回事,當(dāng)你坐下來并實(shí)際的去向那些從來沒聽到過蒙板緩存的人解釋這一切時(shí),你就會(huì)清楚了。好了,如果你有什么不清楚的,或者有更好的建議,請(qǐng)讓我知道,我想些最好的教程,你的反饋很重要!