歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> OpenGL超級寶典學習筆記——紋理高級(一)

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

日期:2017/3/1 9:32:15   编辑:Linux編程

輔助顏色

一般情況下,我們設置紋理的環境為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

Copyright © Linux教程網 All Rights Reserved