紋理環境
OpenGL是如何把紋理元素的顏色和幾何圖元的顏色結合起來的?是通過紋理環境的模式來控制。設置紋理環境模式的函數如下:
void glTexEnvi(GLenum target, GLenum pname, GLint param);
void glTexEnvf(GLenum target, GLenum pname, GLfloat param);
void glTexEnviv(GLenum target, GLenum pname, GLint *param);
void glTexEnvfv(GLenum target, GLenum pname, GLfloat *param);
這個函數有許多的選項,一些高級的選項會在下一節進行介紹。在前面的金字塔例子中,我們在把紋理映射到幾何圖元之前,設置環境模式為GL_MODULATE。
GL_MODULATE模式是把紋理元素的顏色乘以幾何圖元(進行光照計算之後)的顏色。通過這種模式,我們可以用彩色的幾何圖形與紋理結合來產生不同的效果。
我們還可以用GL_REPLACE模式簡單地覆蓋掉紋理下面的結合圖形的顏色。這樣片段的顏色值將直接采用紋理的顏色。這樣就消除了幾何圖形對紋理的影響。如果紋理有alpha通道,我們還可以開啟混合效果,來創建透明的紋理。
還有一種貼紙模式參數為GL_DECAL。當紋理沒有alpah通道的時候,其效果和GL_REPLACE是一樣的。如果有alpah通道,那麼將會把紋理元素的顏色和紋理下面片段的顏色進行alpha混合。
紋理還可以使用GL_BLEND環境模式,來與一個常量值進行混合。在設置這個模式的時候,必須設置紋理環境的顏色。
GLfloat fColor[4] = {1.0f, 0.0f, 0.0f, 0.0f};
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, fColor);
還有一種GL_ADD模式,簡單地把紋理元素的顏色與下面的片段顏色進行相加,超過1.0的調整為1.0. 這樣會使得很多地方看起來是白色的或接近白色。
在紋理映射的過程中,有許多參數會影響渲染的方式和效果。可以通過下面的四個函數來設置紋理參數:
void glTexParameterf(GLenum target, GLenum pname, GLfloat param);
void glTexParameteri(GLenum target, GLenum pname, GLint param);
void glTexParameterfv(GLenum target, GLenum pname, GLfloat *param);
void glTexParameteriv(GLenum target, GLenum pname, GLint *param);
target參數可以是GL_TEXTURE_1D,GL_TEXTURE_2D,GL_TEXTURE_3D.第二個參數pname是告訴OpenGL哪個參數被設置。最後一個參數是紋理的參數值。
不像像素圖渲染到顏色緩沖區一樣,在紋理映射過程中,紋理元素與屏幕上的像素幾乎不是一一對應的。一般情況下,在把紋理映射到幾何圖形的表面上時,我們需要對其進行拉伸或收縮。
根據一個紋理貼圖的拉伸或收縮來計算顏色片段的過程稱為紋理過濾。OpenGL中有放大過濾器和縮小過濾器。這兩種過濾器的pname參數分別為GL_TEXTURE_MAG_FILTER,GL_TEXTURE_MIN_FILTER。目前為止,我們使用GL_NEAREST(最鄰近) 和 GL_LINEAR(線性)兩種過濾方式。注意:你需要為GL_TEXTURE_MIN_FILTER選擇其中一種,因為在默認的過濾器不能用於Mipmap。
最鄰近過濾方法是一種最簡單、最快速的過濾方式。不管紋理坐標落入哪個紋理單元,這個紋理單元的顏色就作為這個片段的紋理顏色。最鄰近過濾的方式雖然快,但會走樣特別是在紋理被拉伸到特別大的時候有大片的斑駁狀像素。設置最鄰近的方法:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
線性過濾比最鄰近過濾做更多的工作,但它的效果更好。而且在現代的硬件下,這種開銷可以忽略不計。線性過濾會取周圍的紋理單元進行加權平均得到當前紋理單元的顏色。線性過濾的特征是當紋理被放大時,會產生“模糊”的效果。設置線性過濾:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
在紋理映射中,紋理坐標的范圍為[0.0,1.0]。當紋理坐標超出這個值時,OpenGL會根據你設置的環繞模式來處理這種情況。我們可以分別為每個紋理坐標軸(s,t,r)設置一個環繞模式,通過glTexParameteri 第二個參數為GL_TEXTURE_WRAP_S,GL_TEXTURE_WRAP_T或者GL_TEXTURE_WRAP_R.環繞的模式有GL_REPEAT,GL_CLAMP,GL_CLAMP_TO_EDGE, GL_WRAP_TO_BORDER.
GL_REPEAT環繞模式只是簡單的重復紋理。例如:1.1出的紋理單元與0.1處的紋理單元是相同的。在平鋪式的紋理應用到大型幾何圖形的時候,非常有用。一個設計良好的無縫小型紋理緊挨著平鋪到大型幾何圖形上看起來像是無縫的大型紋理。
GL_CLAMP環繞模式是超過1.0的截取為1.0。
如果環繞紋理的意義是是否對紋理進行重復那麼使用GL_REPEAT和GL_CLAMP兩種模式就足夠了。然而紋理的環繞模式,會影響到如何在紋理貼圖邊緣進行紋理過濾。對於GL_NEAREST過濾方式來講,紋理的環繞模式對它並沒有什麼影響,因為紋理坐標總是對應到紋理貼圖上的某一個單元。但對GL_LINEAR模式來說,紋理的環繞模式會影響它對紋理邊緣的處理效果。因為它是對周圍的紋理元素作一個平均值。
對於GL_CLAMP截取型的環繞模式,還提供了一些選項來處理紋理邊緣。GL_CLAMP_TO_EDGE環繞模式簡單地忽略邊緣的紋理采樣,不把它們包括在平均值中。GL_CLAMP_TO_BORDER環繞模式在紋理坐標在0.0到1.0范圍之外時只使用邊界紋理單元。邊界紋理單元是作為圍繞基本圖像的額外的行和列,與基本紋理圖像一起加載的。
下圖顯示不同參數的效果:
將1張2*2的紋理(分別為紅,綠,藍,黑),貼到一個方塊左下角。
方塊四個點的紋理坐標依次指定為(0,0), (2,0), (2,2), (0,2)。
根據上述的規則,坐標將進行截取,截取到相應的范圍。
我們繪制暗白色線將紋理坐標為1的地方在圖中標注出來:
注:border顏色默認為黑色
濾鏡:GL_NEAREST 纏繞模式:GL_CLAMP 或 GL_CLAMP_TO_EDGE
濾鏡:GL_NEAREST 纏繞模式:GL_CLAMP_TO_BORDER。
濾鏡:GL_LINEAR 纏繞模式:GL_CLAMP。
濾鏡:GL_NEAREST 纏繞模式:GL_CLAMP_TO_EDGE。
濾鏡:GL_NEAREST 纏繞模式:GL_CLAMP_TO_BORDER。
Toon-shading(也叫單元格著色)使用一維貼圖作為一個查找顏色的查找表格去找到其中一個固定的顏色(使用GL_NEAREST)來填充幾何圖形。
通過幾何圖形的表面法線與指向光源的向量來計算光源照射到模型表面的強度。表面法線與指向光源向量的點擊取值范圍為0.0到1.0。以這個值作為頂點紋理坐標,給這個頂點找到對應的紋理元素。下面是一個使用一維紋理來制造卡通效果的例子:
#include "gltools.h" #include "math3d.h" #include <math.h> //指向光線的向量 M3DVector3f vLightDir = {-1.0f, 1.0f, 1.0f}; void SetupRC() { //一維紋理,逐漸加深的紅色 GLbyte toonMap[4][3] = {{32, 0, 0}, {64, 0, 0}, {128, 0, 0}, {192, 0, 0}}; glClearColor(0.0f, 0.0f, 0.5f, 1.0f); //開啟隱藏面裁剪和深度測試 glCullFace(GL_BACK); glFrontFace(GL_CCW); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); //設置紋理參數和紋理環境 glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_WRAP_S, GL_CLAMP); glTexEnvf(GL_TEXTURE_1D, GL_TEXTURE_ENV_MODE, GL_DECAL); //加載紋理 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glTexImage1D(GL_TEXTURE_1D, 0, GL_RGB, 4, 0, GL_RGB, GL_UNSIGNED_BYTE, toonMap); glEnable(GL_TEXTURE_1D); } //畫甜甜圈 void DrawDoughnut(GLfloat majorRadius, GLfloat minorRadius, GLint numMajor, GLint numMinor, M3DVector3f vLightDir) { M3DMatrix44f mInvertLight; M3DMatrix44f mModelView; M3DVector3f vNewLight; M3DVector3f vNormal; glGetFloatv(GL_MODELVIEW_MATRIX, mModelView); double majorStep = 2.0 * M3D_PI / numMajor; double minorStep = 2.0 * M3D_PI / numMinor; //把光變換到物體坐標空間中,即乘以反轉的模型視圖矩陣 m3dInvertMatrix44(mInvertLight, mModelView); m3dTransformVector3(vNewLight, vLightDir, mInvertLight); vNewLight[0] -= mInvertLight[12]; vNewLight[1] -= mInvertLight[13]; vNewLight[2] -= mInvertLight[14]; m3dNormalizeVector(vNewLight); for (int i = 0; i < numMajor; ++i) { double a0 = i * majorStep; double a1 = a0 + majorStep; GLfloat x0 = (GLfloat)cos(a0); GLfloat y0 = (GLfloat)sin(a0); GLfloat x1 = (GLfloat)cos(a1); GLfloat y1 = (GLfloat)sin(a1); glBegin(GL_TRIANGLE_STRIP); for (int j = 0; j <= numMinor; ++j) { double s = j * minorStep; GLfloat c = (GLfloat)cos(s); GLfloat r = minorRadius * c + majorRadius; GLfloat z = minorRadius * (GLfloat)sin(s); //第一個點 vNormal[0] = x0*c; vNormal[1] = y0*c; vNormal[2] = z/minorRadius; m3dNormalizeVector(vNormal); //設置紋理坐標為光的強度 //數學中向量點積就是來判斷兩個向量的夾角的大小 glTexCoord1f(m3dDotProduct(vNewLight, vNormal)); glVertex3f(x0*r, y0*r, z); //第二個點 vNormal[0] = x1*c; vNormal[1] = y1*c; vNormal[2] = z/minorRadius; m3dNormalizeVector(vNormal); //設置紋理坐標為光的強度 glTexCoord1f(m3dDotProduct(vNewLight, vNormal)); glVertex3f(x1*r, y1*r, z); } glEnd(); } } void RenderScene() { static GLfloat yRot = 0.0f; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glPushMatrix(); glTranslatef(0.0f, 0.0f, -2.5f); glRotatef(yRot, 0.0f, 1.0f, 0.0f); DrawDoughnut(0.35f, 0.15f, 50, 25, vLightDir); glPopMatrix(); glutSwapBuffers(); yRot += 0.5f; } void TimerFunc(int value) { glutPostRedisplay(); glutTimerFunc(50, TimerFunc, 1); } void ChangeSize(GLsizei w, GLsizei h) { if (h == 0) h = 1; GLfloat fAspect = (GLfloat)w/(GLfloat)h; glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(35.5, fAspect, 1.0, 50.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glutPostRedisplay(); } int main(int args, char *argv[]) { glutInit(&args, argv); glutInitWindowSize(800, 600); glutInitDisplayMode(GL_DOUBLE | GL_RGB | GL_DEPTH); glutCreateWindow("CARTOON"); SetupRC(); glutDisplayFunc(RenderScene); glutReshapeFunc(ChangeSize); glutTimerFunc(50, TimerFunc, 1); glutMainLoop(); return 0; }效果圖:
OpenGL超級寶典 第4版 中文版PDF+英文版+源代碼 見 http://www.linuxidc.com/Linux/2013-10/91413.htm
OpenGL編程指南(原書第7版)中文掃描版PDF 下載 http://www.linuxidc.com/Linux/2012-08/67925.htm
OpenGL 渲染篇 http://www.linuxidc.com/Linux/2011-10/45756.htm
Ubuntu 13.04 安裝 OpenGL http://www.linuxidc.com/Linux/2013-05/84815.htm
OpenGL三維球體數據生成與繪制【附源碼】 http://www.linuxidc.com/Linux/2013-04/83235.htm
Ubuntu下OpenGL編程基礎解析 http://www.linuxidc.com/Linux/2013-03/81675.htm
如何在Ubuntu使用eclipse for c++配置OpenGL http://www.linuxidc.com/Linux/2012-11/74191.htm
更多《OpenGL超級寶典學習筆記》相關知識 見 http://www.linuxidc.com/search.aspx?where=nkey&keyword=34581