• 纹理高级


    一般情况下,我们设置纹理的环境为GL_MODULATE模式,在这种情况下,受到光照的几何图形会和纹理的颜色进行结合。正常情况下,OpenGL进行光照计算,并根据标准的光照模型进行单个片段的颜色计算。然后,再把片段的颜色乘以纹理的颜色,等到结合后的颜色。但是这样的话会削弱图形的光照效果。因为经过光照计算过后的片段的颜色值最大值是1.0(即最亮的颜色),任何值乘以小于1.0的值,必定小于其本身(即不可能比原来更亮)。(if y <= 1.0 then x * y <= x. x y是正数)。

    没有应用纹理之前:

    image

    应用纹理之后光照效果被削弱了:

    image

    要解决这个问题,我们可以在纹理映射之后再应用镜面光高亮的效果(通过加而不是乘的方式)。这个技巧成为辅助镜面光颜色。通过设置光照的模型来达到此目的,函数调用如下:

    glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);

    加了这一行之后的效果如下:

    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过滤方式进行采样。

    当我们的视角是垂直于该几何图形的时候,这样的方式没有问题。然而当我们的视角与几何图形形成一个斜角的时候,以常规的方式对周边纹理进行采样会丢失一些纹理的信息,它看起来变模糊了。更真实和精确的采样是,沿着平面倾斜的方向,拉长纹理的采样。如下的第二个图:

    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 ", flag); 
    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_COMPRESSED_IMAGE_SIZE, &flag); 
    printf("compress size : %d ", 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个纹理,如果你不需要真正的反射,球体映射可以满足你的要求了。

  • 相关阅读:
    深入分析java的clone
    java实现文件夹遍历
    插入排序的C语言实现
    java动态代理工厂类
    HTML 理解标签 帧
    贝塞尔曲线初探
    警告:未初始化的变量引起运行问题
    解释string类型的输入操作符和getline函数分别如何处理空白符
    程序书写规范笔记
    vector
  • 原文地址:https://www.cnblogs.com/lyx2018/p/7157642.html
Copyright © 2020-2023  润新知