美文网首页
纹理基础

纹理基础

作者: silasjs | 来源:发表于2019-06-23 22:53 被阅读0次

    纹理基础

    之前我们对图元渲染都是用线段或者表面颜色,但这样的效果还不够现实,如果加上纹理贴图,会让渲染效果非常逼真。纹理只是一种图像数据,按照对应的位置把纹理图像中的像素填充到我们渲染的物体中就叫纹理贴图。先看下纹理贴图的效果:

    纹理贴图的效果.png

    图像存储

    图像大小的简单计算就是:

    图像存储空间 = 图像宽度 * 图像高度 * 每个像素的字节数

    假如有个RGB图像(每个颜色通道为8位),宽度为199个像素,那么每一行的存储就是:199 * 3 = 597字节。而默认情况下OpenGL采用4个字节的对齐方式。这样的图像,每一行都会有3个字节的空余。在OpenGL中使用纹理图像时,都是用Targa(.tga)文件格式。tga文件格式的像素数据使用1个字节排列。

    纹理常用API

    先了解下使用纹理中比较常用的函数:

    纹理读取

    1. 将颜色缓冲区的内容作为像素图直接读取:
    /*
    参数1: x, 矩形左下角的窗口坐标
    参数2: y, 矩形左下角的窗口坐标
    参数3: width, 矩形的宽,以像素为单位
    参数4: height, 矩形的高,以像素为单位
    参数5: format, OpenGL的像素格式(可以参考下面的图表:像素格式)
    参数6: type, (可以参考下面的图表:像素数据的数据类型)
    解释参数pixels指向的数据,告诉openGL使用缓存区中的什么数据类型来存储颜色分量
    参数7: pixels,指向图形数据的指针
    */
    void glReadPixels(GLint x, 
                      GLint y, 
                      GLSizei width, 
                      GLSizei height, 
                      GLenum format,
                      GLenum type, 
                      const void *pixels);
    

    像素格式

    常量 描述
    GL_RGB 按照红、绿、蓝顺序排列的颜色
    GL RGBA 按照红、绿、蓝、Alpha顺序排列的颜色
    GL_BGR 按照蓝、绿、红顺序排列的颜色
    GL_BGRA 按照蓝、绿、红、Aipha顺序排列的颜色
    GL_RED 每个像素只包含一个红色分量
    GL_GREEN 每个像素只包含一个绿色分量
    GL_BLUE 每个像素只包含一个蓝色分量
    GL_RG 每个像素依次包含一个红色和一个绿色分量
    GL_RED_INTEGER 每个像素包含一个整数形式的红色分量
    GL_GREEN INTEGER 每个像素包含一个整数形式的绿色分量
    GL_BLUE_INTETER 每个像素包含一个整数形式的蓝色分量
    GL_ RG_ INTEGER 每个像素依次包含一个整数形式的红色和一个整数形式的绿色分量
    GL_RGB_INTEGER 每个像素依次包含整数形式的红色、绿色和蓝色分量
    GL_RGBA_INTEGER 每个像素依次包含整数形式的红色、绿色、蓝色和Alpha分量
    GL_BGR_INTEGER 每个像素依次包含整数形式的蓝色、绿色和红色分量
    GL_BGRA_INTEGER 每个像素依次包含整数形式的蓝色、绿色、红色和Alpha分量
    GL_STENCIL_INDEX 每个像素只包含一个模板值
    GL_DEPTH_COMPONENT 每个像素只包含一个深度值
    GL_DEPTH_STENCIL 每个像素包含一个深度值和一个模板值

    像素数据的数据类型

    常量 描述
    GL_UNSIGNED_ BYTE 每种颜色分量都是一个8位无符号整数
    GL_BYTE 8位有符号整数
    GL_UNSIGNED_SHORT 16位无符号整数
    GL_SHORT 16位有符号整数
    GL_UNSIGNED_INT 32位无符号整数
    GL_INT 32位有符号整数
    GL_FLOAT 单精度浮点数
    GL_HALF_FLOAT 半精度浮点数
    GL_UNSIGNED_BYTE_3_2_2 包装的RGB值
    GL_UNSIGNED_BYTE_2_3_3_REV 包装的RGB值
    GL_UNSIGNED_SHORT_5_6_5 包装的RGB值
    GL_UNSIGNED_SHORT_5_6_5_REV 包装的RGB值
    GL_UNSIGNED_SHORT_4_4_4_4 包装的RGB值
    GL_UNSIGNED_SHORT_4_4_4_4_REV 包装的RGB值
    GL_UNSIGNED_SHORT_5_5_5_1 包装的RGB值
    GL_UNSIGNED_ SHORT_1_5_5_5_REV 包装的RGB值
    GL_UNSIGNED_INT_8_8_8_8 包装的RGB值
    GL_UNSIGNED_INT_8_8_8_8_REV 包装的RGB值
    GL_UNSIGNED_INT_10_10_10_2 包装的RGB值
    GL_UNSIGNED_INT_2_10_10_10_REV 包装的RGB值
    GL_UNSIGNED_INT_24_8 包装的RGB值
    GL_UNSIGNED_INT_10F_11F_REV 包装的RGB值
    GL_FLOAT_24_UNSIGNED_INT_24_8_REV 包装的RGB值
    1. 读取颜色缓冲区中的数据,并存储到一个tga文件中:

      //将屏幕图像保存为tga文件
      GLint gltWriteTGA(const char *szFileName);
      
    2. 读取tga文件

      /*
      参数1:纹理文件名称
      参数2:文件宽度地址
      参数3:文件高度地址
      参数4:文件组件地址
      参数5:文件格式地址
      返回值:pBits,指向图像数据的指针
      */
      GLbyte *gltReadTGABits(const char *szFileName, 
                              GLint *iWidth, 
                              GLint *iHeight, 
                              GLint *iComponents, 
                              GLenum *eFormat, 
                              GLbyte *pData = NULL);
      

      纹理载入

      void glTexImage1D(GLenum target, 
                        GLint level, 
                        GLint internalformat,
                        GLsizei width, 
                        GLint border,
                        GLenum format,
                        GLenum type,
                        void *data);
                        
      void glTexImage2D(GLenum target, 
                        GLint level, 
                        GLint internalformat,
                        GLsizei width,
                        GLSiZei heiqht,
                        GLint border, 
                        GLenum format, 
                        GLenum type, 
                        void *data);
                        
      void glTexImage3D(GLenum target, 
                        GLint level, 
                        GLint internalformat, 
                        GLsizei width,
                        GLsizei height, 
                        GLsizei depth, 
                        GLint border, 
                        GLenum format, 
                        GLenum type, 
                        void *data);
      /*
      target: GLTEXTURE_1D、 GL_TEXTURE_2D、 GL_TEXTURE_3D
      Level : 指定所加载的mip贴图层次。 一般非mip贴图的纹理,我们都把这个参数设置为0。
      internalformat : 每个纹理单元中存储多少颜色成分。
      width、height、depth参数 : 指加载纹理的宽度、高度、深度。
      注意!这些值必须是 2的整数次方。(这是因为openGL 旧版本上的遗留下的一个要求。当然现在已经可以支持不是2的整数次方。但是开发者们还是习惯使用以2的整数次方去设置这些参数。)
      border参数 : 允许为纹理贴图指定一个边界宽度。
      format、type、 data参数 : 与glReadPixels函数对应的参数相同
      */
      

      使用颜色缓冲区加载

      void glCopyTexImage1D(GLenum target, 
                            GLint level, 
                            GLenum internalformt, 
                            GLint x, 
                            GLint y,
                            GLsizei width, 
                            GLint border);
                            
      void glCopyTexImage2D(GLenum target,
                            GLint level, 
                            GLenum internalformt, 
                            GLint x, 
                            GLint y, 
                            GLsizei width, 
                            GLsizei height, 
                            GLint border);
      //加载颜色缓冲区中的数据,形成新的纹理。
      //并不存在glCopyTexImage3D,因为我们无法从2D颜色缓冲区获取体积数据。
      

      更新纹理

      void glTexSubImage1D(GLenum target, 
                           GLint level,
                           GLint xOffset, 
                           GLsizei width, 
                           GLenum format, 
                           GLenum type,
                           const GLvoid *data);
                           
      void glTexSubImage2D(GLenum target, 
                           GLint level, 
                           GLint xOffset, 
                           GLint yOffset,
                           GLsizei width,
                           GLsizei height,
                           GLenum format,
                           GLenum type,
                           const GLvoid *data);
                           
      void glTexSubImage3D(GLenum target, 
                           GLint level, 
                           GLint xOffset, 
                           GLint yOffset, 
                           GLint zOffset, 
                           GLsizei width, 
                           GLsizei height, 
                           GLsizei depth, 
                           Glenum type, 
                           const GLVoid *data);
      //如果不需要一个已加载的纹理,可以全部或者部分替换掉。
      

      插入替换纹理

      void glCopyTexSubImage1D(GLenum target, 
                            GLint level,
                            GLint xOffset,
                            GLint x, 
                            GLint y,
                            GLsizei width);
                            
      void glCopyTexSubImage2D(GLenum target, 
                            GLint level,
                            GLint xOffset, 
                            GLint yOffset,
                            GLint x, 
                            GLint y,
                            GLsizei width, 
                            GLsizei height);
                            
      void glCopyTexSubImage3D(GLenum target, 
                            GLint level,
                            GLint xOffset, 
                            GLint yOffset, 
                            Glint zOffset,
                            GLint x, 
                            GLint y,
                            GLsizei width, 
                            GLsizei height);
      //从颜色缓冲区读取纹理并插入或替换原来纹理的一部分
      

    纹理对象

    通常我们渲染的场景都是需要载入多个纹理的。这样就需要一个对象来管理纹理状态。纹理状态包括纹理的载入、切换、参数设置等。纹理图像本身也是纹理状态的一部分。

    1. 纹理状态是由当前绑定的纹理对象来管理的,分配纹理对象前要先声明一个无符号整数来标识这个纹理对象:
    GLuint textureID;//声明纹理标识
    /*
    分配纹理对象
    参数一:纹理对象的数量
    参数二:纹理对象标识的指针
    */
    void glGenTextures (GLsizei n, GLuint *textures);
    
    1. 绑定纹理
    /*
    绑定纹理
    参数一:target必须是GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
    参数二:需要绑定的纹理对象
    */
    void glBindTexture(GLenum target, GLuint texture);
    
    1. 测试纹理
    GLboolean glIsTexture (GLuint texture);
    
    1. 删除纹理
    //删除纹理,和分配纹理对应,参数也一样
    void glDeleteTextures (GLsizei n, const GLuint *textures);
    

    纹理坐标

    只是读取、载入纹理还不够,纹理贴图还需要知道纹理坐标。就像贴对联一样,上下句不能搞反了。纹理坐标就是用来指定顶点和纹理之间的对应关系。

    纹理坐标(s, t, r, q)和顶点位置(x, y, z, w)是一一对应的。纹理坐标的每个值,取值范围是0~1。

    纹理坐标.png

    纹理坐标的原点在图像左下角,设置纹理坐标的时候要注意和顶点一一对应。

    纹理和顶点一一对应.png

    需要调整方向来设置纹理坐标时也要按照纹理图像中的相对位置来修改坐标值。

    下图是错误示例:

    错误示例.png

    纹理参数设置

    纹理图像在填充到物体表面时,它们像素之间的大小很少能一一对应的。因此纹理图像最终都会被缩放后填充。根据一个拉伸或者收缩的纹理贴图计算颜色片段的过程叫做纹理过滤。

    void glTexParameterf (GLenum target, GLenum pname, GLfloat param);
    void glTexParameterfv (GLenum target, GLenum pname, const GLfloat *params);
    void glTexParameteri (GLenum target, GLenum pname, GLint param);
    void glTexParameteriv (GLenum target, GLenum pname, const GLint *params);
    /*
    设置纹理参数
    参数一:target,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
    参数二:需要设置哪个纹理参数
    参数三:设置的纹理参数值
    */
    

    过滤方式

    1. 邻近过滤:把纹理坐标所对应的纹理单元的颜色作为当前片元的纹理颜色。邻近过滤在纹理被拉伸到特别大的时候会出现大片的斑驳状像素。使用纹理参数函数可以设置放大和缩小过滤器。

      glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
      
    2. 线性过滤:把纹理坐标对应的纹理单元的加权平均值应用到当前的纹理上。线性过滤在纹理被拉伸时会出现“失真”效果。这种“失真”更接近真实。

      glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
      
    3. 效果图

    过滤方式比较.png

    环绕方式

    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_CLAMP_TO_EDGE);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_CLAMP_TO_EDGE);
    
    环绕方式 描述
    GL_REPEAT 对纹理的默认行为。重复纹理图像。
    GL_MIRRORED_REPEAT 和GL_ REPEAT一样,但每次重复图片是镜像放置的。
    GL_CLAMP_TO_EDGE 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。
    GL_CLAMP_TO_BORDER 超出的坐标为用户指定的边缘颜色。

    最直观的就是看图:

    环绕效果对比图.png

    相关文章

      网友评论

          本文标题:纹理基础

          本文链接:https://www.haomeiwen.com/subject/cgqgqctx.html