纹理的综合应用

1. MIP贴图

MIP贴图的产生主要是为了解决渲染时的闪烁和性能耗费的问题,简单来说就是当需要屏幕上只需要处理一小部分片段时,但是这时候确加载了大的纹理图像,而大的纹理图像需要更多的变换操作这就带来了性能的消耗,而图片过大与屏幕显示区域不成正比,这样各种变换从采样到纹理应用的过程中就容易带来闪烁的效果。
MIP贴图可以解决掉上述问题,当载入某个纹理时,会为加载的这个纹理图像生成多个不同大小的纹理图像,最大的为纹理图像本身大小依次减半,直到最小的为纹理单元的大小,这样当处理小片段时可以选用小的纹理,大的片段可以选用大的纹理,这样就避免了闪烁和性能的耗费的问题了。但是使用MIP贴图之后加载的纹理图像需要的内存会多三分之一左右。
加载MIP贴图我们可以使用glTexImage函数而其中的level会指定使用那层的MIP层纹理,如果MIP贴图没有被使用那么久加载第零层,使用的话会加载所有MIP层的。我们也可以通过glTexParameteri限制MIP加载范围和使用范围。

2.MIP贴图过滤

没有使用MIP时,我们使用最邻近过滤或者线性过滤,都会给第零层的纹理贴图进行过滤,而使用了MIP贴图,我们也只会为第零层也就是基层纹理进行过滤,其它的MIP层会被忽略,但是我们可以通过下面过滤参数来指定那层需要过滤。
MIP贴图纹理的过滤.png

3. 生成MIP层

上面说到了可以在加载已经构建好的MIP层,但有时我们是不需要处理小片段纹理贴图的,这时候加载所有的MIP层就有些不合适,这时候我们我们可以在使用某个纹理时在生成MIP层,我们可以使用

void glGaneraterMapMip(GLenum target)

需要注意的是在运行过程中生成MIP层要比记载已经创建好的MIP层慢很多,所以我们也要根据使用场景来确定使用哪种MIP层加载方式。

4. 各向滤性

对于纹理过滤我们可以使用基本的最邻近过滤和线性过滤来处理纹理图像显示时的变形,但有时候由于观察角度的改变物体各个方向上的排布也会发生改变,变的不那么规整,这时候如果纹理过滤的方式不做调整的话,就会失去一些真实性,各向滤性的纹理过滤方式会解决这个问题,它会根据物体各个方向上的不同的排布,而进行不同的采样,进而达到更准确的过滤。

5. 压缩纹理

我们可以通过glTexImage来加载纹理并对其压缩,其中internalFormat会指定压缩的格式,需要注意的是不同的硬件支持的压缩格式不同的,所以我们要在纹理压缩之前对齐做出兼容判断,glTexImage是当使用纹理时对其压缩加载,我们也可以预先加载纹理,然后保存本地在使用时直接加载预先压缩好的纹理,这时我们可以通过

void glCompressTexImage1d(GLenum target, GLint level, GLint internalformat , GLseizei width, GLint border , GLenum format, GLenum type, void *data)
void glCompressTexImage2d(GLenum target, GLint level, GLint internalformat , GLseizei width, GLseizei height, GLint border , GLenum format, GLenum type, void *data)
void glCompressTexImage3d(GLenum target, GLint level, GLint internalformat , GLseizei width, GLseizei height,  GLseizei depth, GLint border , GLenum format, GLenum type, void *data)

这个函数和glTexImage函数参数一样,仅有的区别是internalformat参数必须指定一种本地支持的压缩格式。
需要注意的是经过压缩的纹理会占用更小的内存,对纹理的读写效率更高,但是弊端就是可能不能保证纹理的质量,这要依据使用场景选择是否要对纹理进行压缩。

6. 下面是一个纹理贴图的例子

GLShaderManager        shaderManager;
GLMatrixStack          modelViewMatrix;
GLMatrixStack          projectionMatrix;
GLFrustum              viewFrustum;
GLGeometryTransform    transformPipeline;

GLBatch                floorBatch;
GLBatch                ceilingBatch;
GLBatch                leftWallBatch;
GLBatch                rightWallBatch;

GLfloat                viewZ = -65.0f;

#define TEXTURE_BRICK   0
#define TEXTURE_FLOOR   1
#define TEXTURE_CEILING 2
#define TEXTURE_COUNT   3
GLuint  textures[TEXTURE_COUNT];
const char *szTextureFiles[TEXTURE_COUNT] = { "brick.tga", "floor.tga", "ceiling.tga" };

void ProcessMenu(int value)
{
    GLfloat fLargest;
    GLint iLoop;
    //便利纹理对象
    for(iLoop = 0; iLoop < TEXTURE_COUNT; iLoop++)
    {
        //根据纹理对象句柄得到纹理对象
        glBindTexture(GL_TEXTURE_2D, textures[iLoop]);
        switch(value)
        {
            case 0:
                //纹理缩小过滤,最邻近滤波纹理坐标会与最近纹理单元进行映射完成一一对应的关系,达到过滤的作用。
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                break;
            case 1:
                //纹理缩小过滤,线性过滤,纹理坐标会落在最近的纹理单元中心并对周围纹理单元进行加权平均运算得到一个插值纹理单元与纹理坐标映射完成一一对应达到过滤作用。
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
                break;
            case 2:
                //MIP贴图缩小过滤,最邻近的MIP贴图最邻近过滤,对当前应用的MIP贴图的纹理图像的最邻近的纹理图像进行最邻近过滤。
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
                break;
            case 3:
                //MIP贴图缩小过滤,最邻近的MIP贴图线性过滤,对当前应用的MIP贴图的纹理图像的最邻近的纹理图像进行线性过滤
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
                break;
            case 4:
                //MIP贴图缩小过滤,线性的MIP贴图对最邻近过滤,对当前应用的MIP贴图的纹理图像的周围纹理图像进行加权平均运算得到插值的纹理图像然后对齐进行最邻近过滤。
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
                break;
            case 5:
                 //MIP贴图缩小过滤,线性的MIP贴图对最邻近过滤,对当前应用的MIP贴图的纹理图像的周围纹理图像进行加权平均运算得到插值的纹理图像然后对齐进行线性过滤。
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
                break;
            case 6:
                //得到本地硬件支持的最大各向异性滤波数量
                glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &fLargest);
                //纹理缩小过滤,各向异性过滤,因纹理在各个方向可能存在不同的颜色和空间排布,所以针对方向上不同的纹理状态采取不同的采样方式,进而达到更准确的过滤
                glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, fLargest);
                break;
            case 7:
                //关闭各向滤性过滤
                glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 1.0f);
                break;
        }
    }
    // 重新渲染
    glutPostRedisplay();
}

void SetupRC()
{
    GLbyte *pBytes;
    GLint iWidth, iHeight, iComponents;
    GLenum eFormat;
    GLint iLoop;
    
    //设置背景颜色
    glClearColor(0.0f, 0.0f, 0.0f,1.0f);
    //初始化存储着色器
    shaderManager.InitializeStockShaders();
    //设置纹理对象个数,与纹理对象句柄集合
    glGenTextures(TEXTURE_COUNT, textures);
    for(iLoop = 0; iLoop < TEXTURE_COUNT; iLoop++)
    {
        //根据纹理句柄得到纹理对象
        glBindTexture(GL_TEXTURE_2D, textures[iLoop]);
        //读取图片像素
        pBytes = gltReadTGABits(szTextureFiles[iLoop],&iWidth, &iHeight,
                                &iComponents, &eFormat);
        
    //设置纹理参数
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);//纹理放大过滤,最邻近过滤
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);//纹理缩小过滤,最邻近过滤
        //纹理环绕,这里是沿着S,T坐标进行纹理剪裁环绕,并对其坐标内的纹理单元采用上一个纹理单元的最后一行或者一列进行采样然后拼接,这样避免了纹理剪裁环绕模式因对齐纹理图像进行切割成小方块后而应用到几何体上出现缝隙的问题。
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        //对像素解包,这里用的tag图本身是没有紧密包装的,紧密包装是按照本地硬件寻址大小而对像素地址进行字节对齐的一种方式,方便一次取更多的数据提高读写效率
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
        //加载纹理到内存,纹理单元为2D图像模式,设置使用基层纹理,设置压缩方式为对RGB进行压缩方式,也可设置为原图的压缩方式通过iComponents指针找到,设置纹理的宽和高,设置纹理数据的类型为无符号字节,像素数据的指针,因无法将基本颜色值写入到内存,这里通过像素的指针来指向基本颜色值
        glTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB, iWidth, iHeight, 0, eFormat, GL_UNSIGNED_BYTE, pBytes);
        //为所有纹理生成2D纹理单元的MIP贴图层
        glGenerateMipmap(GL_TEXTURE_2D);
        // 释放图片像素
        free(pBytes);
    }
    GLfloat z;
    //批量管理顶点坐标,管理地板的顶点和纹理坐标,指定顶点坐标为三角带链接,指定顶点坐标和纹理对象数
    floorBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);
    for(z = 60.0f; z >= 0.0f; z -=10.0f)
    {
        //设置地板的顶点的坐标和纹理坐标与顶点坐标的映射关系,并且存储到floorBatch容器中进行批量管理
        floorBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
        floorBatch.Vertex3f(-10.0f, -10.0f, z);
        
        floorBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
        floorBatch.Vertex3f(10.0f, -10.0f, z);
    
        floorBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
        floorBatch.Vertex3f(-10.0f, -10.0f, z - 10.0f);
        
        floorBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
        floorBatch.Vertex3f(10.0f, -10.0f, z - 10.0f);
    }
    //批量管理结束标识
    floorBatch.End();
    //批量管理顶点坐标,管理天花板的顶点和纹理坐标,指定顶点坐标为三角带链接,指定顶点坐标和纹理对象数
    ceilingBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);
    for(z = 60.0f; z >= 0.0f; z -=10.0f)
    {
        //设置天花板的顶点的坐标和纹理坐标与顶点坐标的映射关系,并且存储到ceilingBatch容器中进行批量管理
        ceilingBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
        ceilingBatch.Vertex3f(-10.0f, 10.0f, z - 10.0f);
        
        ceilingBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
        ceilingBatch.Vertex3f(10.0f, 10.0f, z - 10.0f);
        
        ceilingBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
        ceilingBatch.Vertex3f(-10.0f, 10.0f, z);
        
        ceilingBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
        ceilingBatch.Vertex3f(10.0f, 10.0f, z);
    }
    //批量管理结束标识
    ceilingBatch.End();
     //批量管理顶点坐标,管理左边的墙的顶点和纹理坐标,指定顶点坐标为三角带链接,指定顶点坐标和纹理对象数
    leftWallBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);
    for(z = 60.0f; z >= 0.0f; z -=10.0f)
    {
        //设置地板的顶点的坐标和纹理坐标与顶点坐标的映射关系,并且存储到leftWallBatch容器中进行批量管理
        leftWallBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
        leftWallBatch.Vertex3f(-10.0f, -10.0f, z);
        
        leftWallBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
        leftWallBatch.Vertex3f(-10.0f, 10.0f, z);
        
        leftWallBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
        leftWallBatch.Vertex3f(-10.0f, -10.0f, z - 10.0f);
        
        leftWallBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
        leftWallBatch.Vertex3f(-10.0f, 10.0f, z - 10.0f);
    }
     //批量管理结束标识
    leftWallBatch.End();
    //批量管理顶点坐标,管理右边的墙的顶点和纹理坐标,指定顶点坐标为三角带链接,指定顶点坐标和纹理对象数
    rightWallBatch.Begin(GL_TRIANGLE_STRIP, 28, 1);
    for(z = 60.0f; z >= 0.0f; z -=10.0f)
    {
        //设置地板的顶点的坐标和纹理坐标与顶点坐标的映射关系,并且存储到rightWallBatch容器中进行批量管理
        rightWallBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
        rightWallBatch.Vertex3f(10.0f, -10.0f, z);
        
        rightWallBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
        rightWallBatch.Vertex3f(10.0f, 10.0f, z);
        
        rightWallBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
        rightWallBatch.Vertex3f(10.0f, -10.0f, z - 10.0f);
        
        rightWallBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
        rightWallBatch.Vertex3f(10.0f, 10.0f, z - 10.0f);
    }
     //批量管理结束标识
    rightWallBatch.End();
}

void ShutdownRC(void)
{
    //删除纹理对象
    glDeleteTextures(TEXTURE_COUNT, textures);
}

void SpecialKeys(int key, int x, int y)
{
    // 通过前后建控制模型视图的z坐标
    if(key == GLUT_KEY_UP)
        viewZ += 0.5f;
    if(key == GLUT_KEY_DOWN)
        viewZ -= 0.5f;
    //重新渲染
    glutPostRedisplay();
}

void ChangeSize(int w, int h)
{
    GLfloat fAspect;
    if(h == 0)
        h = 1;
    //设置视口大小
    glViewport(0, 0, w, h);
    fAspect = (GLfloat)w/(GLfloat)h;
    //进行透视投影
    viewFrustum.SetPerspective(80.0f,fAspect,1.0,120.0);
    //将投影矩阵载入投影堆栈中
    projectionMatrix.LoadMatrix(viewFrustum.GetProjectionMatrix());
    //设置管线管理模型视图矩阵堆栈和投影矩阵堆栈
    transformPipeline.SetMatrixStacks(modelViewMatrix, projectionMatrix);
    
}

void RenderScene(void)
{
    //清理颜色缓冲区
    glClear(GL_COLOR_BUFFER_BIT);
    //推入一个单位矩阵到模型视图矩阵堆栈当中
    modelViewMatrix.PushMatrix();
    //模型视图矩阵变换,沿z轴移动变换
    modelViewMatrix.Translate(0.0f, 0.0f, viewZ);
    //使用交换存储着色器可以将纹理坐标应用到几何体上,这里transformPipeline.GetModelViewProjectionMatrix()将投影矩阵转换成单元的模型视图投影矩阵,设置一重纹理贴图
    shaderManager.UseStockShader(GLT_SHADER_TEXTURE_REPLACE, transformPipeline.GetModelViewProjectionMatrix(), 0);
    //得到地板纹理对象
    glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_FLOOR]);
    //渲染地板
    floorBatch.Draw();
    //得到天花板纹理对象
    glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_CEILING]);
    //渲染天花板
    ceilingBatch.Draw();
    //得到墙纹理对象
    glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_BRICK]);
    //渲染左边的墙
    leftWallBatch.Draw();
    //渲染右边的墙
    rightWallBatch.Draw();
    //弹出模型视图矩阵,每次重新渲染要把结果给到ChangeSize中的管线进行管理
    modelViewMatrix.PopMatrix();
    // 双缓冲渲染时交换前后台渲染数据
    glutSwapBuffers();
}

int main(int argc, char *argv[])
{
    gltSetWorkingDirectory(argv[0]);
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
    glutInitWindowSize(800, 600);
    glutCreateWindow("Anisotropic Tunnel");
    glutReshapeFunc(ChangeSize);
    glutSpecialFunc(SpecialKeys);
    glutDisplayFunc(RenderScene);
    //创建菜单管理
    glutCreateMenu(ProcessMenu);
    //创建各个菜单管理的操作
    glutAddMenuEntry("GL_NEAREST",0);
    glutAddMenuEntry("GL_LINEAR",1);
    glutAddMenuEntry("GL_NEAREST_MIPMAP_NEAREST",2);
    glutAddMenuEntry("GL_NEAREST_MIPMAP_LINEAR", 3);
    glutAddMenuEntry("GL_LINEAR_MIPMAP_NEAREST", 4);
    glutAddMenuEntry("GL_LINEAR_MIPMAP_LINEAR", 5);
    glutAddMenuEntry("Anisotropic Filter", 6);
    glutAddMenuEntry("Anisotropic Off", 7);
    //菜单管理的事件触发方式
    glutAttachMenu(GLUT_RIGHT_BUTTON);
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }
    SetupRC();
    glutMainLoop();
    //删除纹理对象
    ShutdownRC();
    return 0;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容

  • 一、纹理基础 3D图形渲染中最基本的操作就是对一个表面应用纹理。纹理可以表现只从网格的几何形状无法得到的附加细节。...
    cain_huang阅读 8,579评论 0 7
  • 纹理的基础知识 2D 纹理 2d纹理是OpenGlES中最基础和普遍的一种纹理结构。一个2d纹理,就是图片的数据的...
    Zsj_Sky阅读 5,324评论 0 8
  • 纹理只是一种能够应用到场景中的三角形上的图像数据,它通过经过过滤的纹理单元(texel,相当于纹理的像素)填充到实...
    Gaolex阅读 2,817评论 0 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,071评论 25 707
  • Lind,我姐们,知性,漂亮。看完《明月千里寄江河》后提问兼感慨如下: 天啦,你在练那个法什么功不成? 眼睛还带神...
    师太你就从了吧阅读 333评论 1 1