輔助顏色
一般情況下,我們設置紋理的環境為GL_MODULATE模式,在這種情況下,受到光照的幾何圖形會和紋理的顏色進行結合。正常情況下,OpenGL進行光照計算,並根據標准的光照模型進行單個片段的顏色計算。然後,再把片段的顏色乘以紋理的顏色,等到結合後的顏色。但是這樣的話會削弱圖形的光照效果。因為經過光照計算過後的片段的顏色值最大值是1.0(即最亮的顏色),任何值乘以小於1.0的值,必定小於其本身(即不可能比原來更亮)。(if y <= 1.0 then x * y <= x. x y是正數)。
沒有應用紋理之前:
應用紋理之後光照效果被削弱了:
要解決這個問題,我們可以在紋理映射之後再應用鏡面光高亮的效果(通過加而不是乘的方式)。這個技巧成為輔助鏡面光顏色。通過設置光照的模型來達到此目的,函數調用如下:
glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);
加了這一行之後的效果如下:
要切回正常狀態,指定光照模型為GL_SINGLE_COLOR即可,函數調用如下:
glLightModeli(GL_LIGHT_COLOR_CONTROL, GL_COLOR_SINGLE);
使用沒有開啟光照,我們可以手動來設置輔助顏色,通過glSecondarycolor函數調用設置輔助顏色和通過glEnable(GL_COLOR_SUM);來開啟。手動設置的輔助顏色只有在沒有開啟光照的情況下有作用。
各向異性過濾並非OpenGL核心API的一部分,但其作為擴展被廣泛用於提升紋理過濾操作的質量。在先前學習的兩個基本的過濾器最鄰近過濾(GL_NEAREST)和線性過濾(GL_LINEAR)。OpenGL使用紋理坐標計算得到紋理將映射到幾何圖形的哪一個片段上。然後通過對該位置周圍的紋理元素以GL_NEAREST過濾或GL_LINEAR過濾方式進行采樣。
當我們的視角是垂直於該幾何圖形的時候,這樣的方式沒有問題。然而當我們的視角與幾何圖形形成一個斜角的時候,以常規的方式對周邊紋理進行采樣會丟失一些紋理的信息,它看起來變模糊了。更真實和精確的采樣是,沿著平面傾斜的方向,拉長紋理的采樣。如下的第二個圖:
我們可以把各向異性過濾應用去基本的和mipmap方式的紋理過濾模式上。在使用之前我們需要檢查各向異性過濾擴展是否被支持,使用glTools函數裡的函數:
if(gltIsExtSupported(“GL_EXT_texture_filter_anisotropic”))
如果擴展是被支持的,我們可以查到支持各向異性過濾的最大值。通過調用glGetFloatv參數為GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT。
GLfloat fLargest;
glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
值越大各向異性過濾的粒度越大,如果值為1.0就代表普通的紋理過濾。各向異性過濾會帶來一定的開銷。現代的顯卡都已經支持各向異性過濾,而且做了優化。最後我們通過glTexParameterf函數來設置各向異性過濾的最大值,如下:
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
非開啟各向異性過濾的通道效果圖,可以看到遠處的磚塊較模糊:
開啟各向異性過濾之後的效果圖:
使用紋理的缺陷是紋理需要大量的內存來存儲和處理。在早期我們會把紋理壓縮成JPG的格式,然後在加載之前(調用glTexImage之前)對其進行解壓。這樣僅僅是節省了磁盤的空間以及加快了在網絡上傳輸紋理的速度,但並沒有減少對顯存(加載到顯存中還是原格式那麼大)。
在OpenGL1.3後,OpenGL原生支持了紋理壓縮的特性。在更低的版本中,通過擴展來支持,你可以通過GL_ARB_texture_compression來檢查是否支持這個擴展。OpenGL對紋理的壓縮不僅僅是加載壓縮的紋理,而且在顯卡內存中也是保存著壓縮的紋理。這可以減少加載紋理時使用的內存以及提升處理紋理的性能(減少了移動紋理和切換紋理的時間,因為要操作的內存空間變小了)。
你可以通過下表的一個常量作為glTexImage函數中internalFormat參數的值,來達到壓縮紋理的目的。當紋理無法被壓縮時,將使用對應的基本內部格式。
壓縮格式 基本內部格式 GL_COMPRESSED_ALPHA GL_ALPHA GL_COMPRESSED_LUMINANCE GL_LUMINANCE GL_COMPRESSED_LUMINANCE_ALPHA GL_LUMINANCE_ALPHA GL_COMPRESSED_RGB GL_RGB GL_COMPRESSED_RGBA GL_RGBA GL_COMPRESSED_INTENSITY GL_INTENSITY在這種方式下,加載壓縮的圖像會多耗一點時間,但卻提升了處理紋理內存的速度。但你使用這種方式壓縮了紋理之後,你可以通過glGetTexLevelParameteriv參數為GL_TEXTURE_COMPRESSED來檢查紋理是否壓縮成功。
GLint compFlag;
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &compFlag);
此函數接受的參數如下表:
參數 返回值 GL_TEXTURE_COMPRESSED 返回1代表壓縮成功,0代表失敗 GL_TEXTURE_COMPRESSED_IMAGE_SIZE 返回壓縮後紋理的大小(字節為單位) GL_TEXTURE_INTERNAL_FORMAT 使用的壓縮格式 GL_NUM_COMPRESSED_TEXTURE_FORMATS 支持的壓縮格式的數量 GL_COMPRESSED_TEXTURE_FORMATS 返回一個保存每一個被支持的壓縮格式的數組常量 GL_TEXTURE_COMPRESSION_HINT 紋理壓縮的提示值我們還可以通過glHint函數來告訴OpenGL我們要用的是最快的壓縮算法還是最高質量的壓縮算法。通過使用GL_NUM_COMPRESSED_TEXTURE_FORMATS和GL_COMPRESSED_TEXTURE_FORMATS來獲得被支持的壓縮格式的列表。幾乎所有的OpenGl實現都支持GL_EXT_texture_compression_s3tc紋理壓縮格式,如果這個擴展被支持那下面表格的所有格式都是支持的(僅適用於2維紋理)
格式 描述 GL_COMPRESSED_RGB_S3TC_DXT1 RGB數據被壓縮。alpha為1.0 GL_COMPRESSED_RGBA_S3TC_DXT1 RGB數據被壓縮。alpha值為1.0或0.0 GL_COMPRESSED_RGBA_S3TC_DXT3 RGB數據被壓縮。alpha值用4位存儲 GL_COMPRESSED_RGBA_S3TC_DXT5 RGB數據被壓縮。alpha為一些8位值的加權平均值
在前面我們已經介紹了,如何壓縮紋理數據。然後我們可以通過glGetCompressedTexImage(與glGetTexImage獲取未壓縮數據一樣)來獲取被壓縮的數據,並把它存到硬盤上。在隨後的加載中,直接加載已經壓縮過的紋理數據會更快。此技術完全依賴於硬件的實現。
加載已經預先壓縮過的紋理數據,可以調用下面的函數:
void glCompressedTexImage1D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLint border, GLsizei imageSize, void *data);
void glCompressedTexImage2D(GLenum target, GLint level, GLenum internalFormat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, void *data);
void glCompressedTexImage3D(GLenum target, GLint level, GLenum internalFormat, GLint width, GLint height, GLint depth, GLint border, GLint imageSize, void *data);
這個方法與glTexImage幾乎是一樣的,不一樣的是其internalFormat必須是壓縮的格式。如果實現支持GL_EXT_texture_compression_s3tc擴展,那麼其參數值就可以是上面的表格列出的值。當然也有glCompressedTexSubImage函數來更新部分已加載的壓縮過的紋理數據,就像glTexSubImage一樣。
紋理壓縮時非常流行的特性。更小的紋理意味著更快的加載速度,更快地在網上傳輸,更快地拷貝到顯卡中,可以加載更多的紋理。下面做了個簡單的實驗:
不壓縮和壓縮後的圖片大小的對比,壓縮前是196kb左右,壓縮後只有32kb了:
GLint flag;
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED, &flag);
printf("compress flag : %d\n", flag);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &flag);
printf("compress size : %d\n", flag);
在前面我們學習過使用紋理坐標來把紋理映射到幾何圖形上。在球體和平滑的平面上手動指定紋理坐標是簡單的。但是遇到了復雜的表面,我們要為其指定紋理坐標就有寫困難了。OpenGL提供了自動生成紋理坐標的特性來解決這個問題。
通過glEnable來開啟S,T,R和Q的紋理坐標自動生成:
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
glEnable(GL_TEXTURE_GEN_R);
glEnable(GL_TEXTURE_GEN_Q);
當自動生成紋理坐標的功能被開啟,那麼glTexCoord的函數調用將被忽略。OpenGL為自動為每一個頂點計算紋理坐標。我們可以通過相應的glDisable來關閉紋理坐標的自動生成。
我們可以通過下面的兩個函數來設置自動生成紋理坐標的方法:
void glTexGenf(GLenum coord, GLenum pname, GLfloat param);
void glTexGenfv(GLenum coord, GLenum pname, GLfloat *param);
第一個參數指定了紋理坐標軸,可以是GL_S,GL_T,GL_R或GL_Q。第二個參數必須是GL_TEXTURE_SPHERE,GL_OBJECT_PLANE或GL_EYE_PLANE.最後一個參數設置紋理生成的方法或模式。glTexGen也有相應的GLint和GLdouble模式。
下面是TEXGEN示例:
#include "gltools.h" #include <stdio.h> #define ENV 0 #define STRIPES 1 #define TEXTURENUM 2 const char* texFileName[] = {"..\\Environment.tga","..\\stripes.tga"}; static GLuint textureName[TEXTURENUM]; static GLfloat yRot = 0.0f; static GLfloat zPos = -2.0f; static GLint iRenderMode = 3; void ProcessMenu(int value) { //投影平面 GLfloat zPlane[] = {0.0f, 0.0f, 1.0f, 0.0f}; //渲染模式 iRenderMode = value; switch(value) { case 1: //物體線性 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); glTexGenfv(GL_S, GL_OBJECT_PLANE, zPlane); glTexGenfv(GL_T, GL_OBJECT_PLANE, zPlane); break; case 2: //視覺線性 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR); glTexGenfv(GL_S, GL_EYE_PLANE, zPlane); glTexGenfv(GL_T, GL_EYE_PLANE, zPlane); break; case 3: default: //球體貼圖 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); } glutPostRedisplay(); } void SetupRC() { glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glEnable(GL_DEPTH_TEST); GLint iWidth, iHeight, iComponents; GLenum eFormat; //設置紋理環境 glTexEnvi(GL_TEXTURE_2D, GL_TEXTURE_ENV, GL_REPLACE); //生成紋理名稱 glGenTextures(TEXTURENUM, textureName); for (int i = 0; i < TEXTURENUM; ++i) { //加載紋理圖像 void *pImage = gltLoadTGA(texFileName[i], &iWidth, &iHeight, &iComponents, &eFormat); if (pImage) { //綁定紋理 glBindTexture(GL_TEXTURE_2D, textureName[i]); //構建mimap gluBuild2DMipmaps(GL_TEXTURE_2D, iComponents, iWidth, iHeight, eFormat, GL_UNSIGNED_BYTE, pImage); //設置紋理參數 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); } free(pImage); } if (gltIsExtSupported("GL_EXT_texture_filter_anisotropic")) { GLfloat fLargest; glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest); } glEnable(GL_TEXTURE_2D); //設置為球體貼圖 glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP); //開啟S、T坐標的紋理坐標生成 glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); } void ShutdownRC() { glDeleteTextures(TEXTURENUM, textureName); } void RenderScene() { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_PROJECTION); //背景圖,使用正交投影 glPushMatrix(); glLoadIdentity(); gluOrtho2D(0.0, 1.0, 0.0, 1.0); glDepthMask(GL_FALSE); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glBindTexture(GL_TEXTURE_2D, textureName[ENV]); //關閉紋理坐標的生成 glDisable(GL_TEXTURE_GEN_S); glDisable(GL_TEXTURE_GEN_T); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0f, 0.0f); glTexCoord2f(1.0f, 0.0f); glVertex2f(1.0f, 0.0f); glTexCoord2f(1.0f, 1.0f); glVertex2f(1.0f, 1.0f); glTexCoord2f(0.0f, 1.0f); glVertex2f(0.0f, 1.0f); glEnd(); //還原投影矩陣 glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); glDepthMask(GL_TRUE); if (iRenderMode != 3) { glBindTexture(GL_TEXTURE_2D, textureName[STRIPES]); } glPushMatrix(); glTranslatef(0.0f, 0.0f, zPos); glRotatef(yRot, 0.0f, 1.0f, 0.0f); gltDrawTorus(0.35, 0.15, 61, 37); glPopMatrix(); glutSwapBuffers(); } void ChangeSize(GLsizei w, GLsizei h) { if (h == 1) h = 0; glViewport(0, 0, w, h); GLfloat aspect = (GLfloat)w/(GLfloat)h; glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(35.5, aspect, 1.0, 150.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glutPostRedisplay(); } void SpecialKey(int value, int x, int y) { if (value == GLUT_KEY_LEFT) { yRot += 0.5f; } if (value == GLUT_KEY_RIGHT) { yRot -= 0.5f; } if (value == GLUT_KEY_UP) { zPos += 0.5f; } if (value == GLUT_KEY_DOWN) { zPos -= 0.5f; } if (yRot > 365.5f) { yRot = 0.0f; } glutPostRedisplay(); } int main(int arg, char **argv) { glutInit(&arg, argv); glutInitDisplayMode(GL_RGB | GL_DOUBLE | GL_DEPTH); glutInitWindowSize(800, 600); glutCreateWindow("TEXGEN"); glutReshapeFunc(ChangeSize); glutDisplayFunc(RenderScene); glutSpecialFunc(SpecialKey); glutCreateMenu(ProcessMenu); glutAddMenuEntry("Object Linear", 1); glutAddMenuEntry("Eye linear", 2); glutAddMenuEntry("sphere map", 3); glutAttachMenu(GLUT_RIGHT_BUTTON); SetupRC(); glutMainLoop(); ShutdownRC(); return 0; }
當設置紋理生成的模式為GL_OBJECT_LINEAR的時候,紋理坐標生成使用的公式如下:
coord = P1*X + P2*Y + P3*Z + P4*W
其中X,Y,Z,W是被映射物體的頂點坐標值,P1-P4是平面方程的系數。紋理坐標是從此平面透視投影到幾何圖形上的。例如,為了從平面Z=0上投影紋理坐標S和T我們可以使用下面的代碼:
//投影平面
GLfloat zPlane[] = {0.0f, 0.0f, 1.0f, 0.0f};
...
...
//物體線性
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
glTexGenfv(GL_S, GL_OBJECT_PLANE, zPlane);
glTexGenfv(GL_T, GL_OBJECT_PLANE, zPlane);
注意每個坐標都可以用不同的平面方程來生成紋理坐標,我們這裡把S和T坐標的平面方程設置成一樣的。在這裡使用了物體線性的模式,不管你怎麼調整這個圓環,紋理總是固定在幾何圖元上的。效果如下:
當選擇視覺線性模式是,紋理坐標的生成方程與物體線性模式是相似的。不同的是現在的X,Y,Z和W值代表著視點的紋理(照相機或眼睛的位置)。平面方程的那些系數也要反轉過來。事實上現在所有東西都用視覺坐標來表示了。代碼如下:
//投影平面
GLfloat zPlane[] = {0.0f, 0.0f, 1.0f, 0.0f};
...
...
//視覺線性
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
glTexGenfv(GL_S, GL_EYE_PLANE, zPlane);
glTexGenfv(GL_T, GL_EYE_PLANE, zPlane);
效果如下,紋理會隨著你視角的旋轉而改變了:
當紋理生成模式設置為GL_SPHERE_MAP的時候,OpenGL生成坐標的方式是物體呈現著當前紋理的倒影。想象一下魚眼睛的效果。示例中設置球體映射模式的代碼如下:
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
效果如下:
為了獲得更為逼真的效果,使用立方體映射。但球體映射還是有一定用途的,因為它只要求1個紋理開銷較小,而立方體映射則要6個紋理,如果你不需要真正的反射,球體映射可以滿足你的要求了。
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