OpenGL超級寶典筆記——紋理高級(一)

輔助顏色

通常狀況下,咱們設置紋理的環境爲GL_MODULATE模式,在這種狀況下,受到光照的幾何圖形會和紋理的顏色進行結合。正常狀況下,OpenGL進行光照計算,並根據標準的光照模型進行單個片斷的顏色計算。而後,再把片斷的顏色乘以紋理的顏色,等到結合後的顏色。可是這樣的話會削弱圖形的光照效果。由於通過光照計算事後的片斷的顏色值最大值是1.0(即最亮的顏色),任何值乘以小於1.0的值,一定小於其自己(即不可能比原來更亮)。(if y <= 1.0 then x * y <= x. x y是正數)。 html

沒有應用紋理以前: 算法

image

應用紋理以後光照效果被削弱了: 數組

image

要解決這個問題,咱們能夠在紋理映射以後再應用鏡面光高亮的效果(經過加而不是乘的方式)。這個技巧成爲輔助鏡面光顏色。經過設置光照的模型來達到此目的,函數調用以下: 網絡

glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR); 函數

加了這一行以後的效果以下: oop

image

要切回正常狀態,指定光照模型爲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過濾方式進行採樣。 ui

當咱們的視角是垂直於該幾何圖形的時候,這樣的方式沒有問題。然而當咱們的視角與幾何圖形造成一個斜角的時候,以常規的方式對周邊紋理進行採樣會丟失一些紋理的信息,它看起來變模糊了。更真實和精確的採樣是,沿着平面傾斜的方向,拉長紋理的採樣。以下的第二個圖:

image

咱們能夠把各向異性過濾應用去基本的和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);

非開啓各向異性過濾的通道效果圖,能夠看到遠處的磚塊較模糊:

image

開啓各向異性過濾以後的效果圖:

image

紋理壓縮

使用紋理的缺陷是紋理須要大量的內存來存儲和處理。在早期咱們會把紋理壓縮成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同樣。

紋理壓縮時很是流行的特性。更小的紋理意味着更快的加載速度,更快地在網上傳輸,更快地拷貝到顯卡中,能夠加載更多的紋理。下面作了個簡單的實驗:

image

不壓縮和壓縮後的圖片大小的對比,壓縮前是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);

image

生成紋理座標

在前面咱們學習過使用紋理座標來把紋理映射到幾何圖形上。在球體和平滑的平面上手動指定紋理座標是簡單的。可是遇到了複雜的表面,咱們要爲其指定紋理座標就有寫困難了。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座標的平面方程設置成同樣的。在這裏使用了物體線性的模式,無論你怎麼調整這個圓環,紋理老是固定在幾何圖元上的。效果以下:

image

視覺線性映射

當選擇視覺線性模式是,紋理座標的生成方程與物體線性模式是類似的。不一樣的是如今的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);

效果以下,紋理會隨着你視角的旋轉而改變了:

image

球體映射

當紋理生成模式設置爲GL_SPHERE_MAP的時候,OpenGL生成座標的方式是物體呈現着當前紋理的倒影。想象一下魚眼睛的效果。示例中設置球體映射模式的代碼以下:

glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);

glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);

效果以下:

image

爲了得到更爲逼真的效果,使用立方體映射。但球體映射仍是有必定用途的,由於它只要求1個紋理開銷較小,而立方體映射則要6個紋理,若是你不須要真正的反射,球體映射能夠知足你的要求了。

相關文章
相關標籤/搜索