OpenGL纹理 常用API简介

#原始图像数据与内存包装 图像的存储空间= 图像的宽度 * 图像的高度 * 每个像素的字节数(系统决定)

内存对齐: 字长32位的计算机上,如果数据在内存中按照32位的边界对齐(地址为4字节的倍数),那么硬件提取数据的速度就会快得多,同样在64位计算机上,如数据地址按照8字节对齐,他对数据存取效率会非常高。 在许多硬件平台上,考虑到性能的原因位图和像素图的每一行的数据会从特殊的字节对齐地址开始。绝大多数编译器会自动把变量和缓冲区放置在当前计算机架构优化的对齐地址上。OpenGL默认是4字节对齐的,可以通过glPixelStorei来设置像素的存储方式,通过glPixelStoref来恢复像素的存储方式: glPixelStorei(GL_UNPACK_ALIGNMENT,1); 参数如下图:

举个例子理解一下:

  • 如果一个图像每行有99个像素,每个像素有RGB 3个颜色通道,那么该图像每行需要的存储空间为:99*3 = 297个字节。按照默认的4字节对齐,可以算出,每行实际分配的存储空间为 300个字节,虽然这会浪费存储空间,但会提升CPU抓取数据的效率。

#读取纹理

    /** 图形硬件中复制数据,通常通过总线传输到系统内存
     * <#GLint x#> <#GLint y#>: 坐标
     * <#GLsizei width#> <#GLsizei height#> : 读取的宽度、高度(以像素为单位)
     * <#GLenum format#> : 像素格式(见下图)
     * <#GLenum type#> : 告诉OpenGL使用缓冲区中的什么数据类型来存储颜色分量(见下图)
     * <#GLvoid *pixels#> :指向图形数据的指针
     */
    glReadPixels(<#GLint x#>, <#GLint y#>, <#GLsizei width#>, <#GLsizei height#>, <#GLenum format#>, <#GLenum type#>, <#GLvoid *pixels#>)
复制代码

    /** 从磁盘中载入Targa文件
     * <#const char *szFileName#>: 文件名称
     * <#GLint *iWidth#> <#GLint *iHeight#>: 读取文件的宽度地址、高度地址
     * <#GLint *iComponents#> :文件组件地址
     * <#GLenum *eFormat#> :文件格式地址
     * 返回值:pBits,指向图像数据的指针
     */
    gltReadTGABits(<#const char *szFileName#>, <#GLint *iWidth#>, <#GLint *iHeight#>, <#GLint *iComponents#>, <#GLenum *eFormat#>)
复制代码

#载入纹理

    /** 载入纹理
     <#GLenum target#> :纹理纬度,一般都是 GL_TEXTURE_2D
     <#GLint level#> : mip贴图层次
     <#GLint internalformat#> :纹理单元存储的颜色成分(读取时获得)
     <#GLsizei width#> <#GLsizei height#>: 纹理的宽高 (读取时获得)
     <#GLint border#> : 为纹理指定一个边界宽度,一般传0
     <#GLenum format#> :指定纹理数据的格式 :GL_RGB,GL_RGBA等(见上图)。
     <#GLenum type#> :指定纹理数据的数据类型 : GL_UNSIGNED_BYTE (见上图)
     <#const GLvoid *pixels#> :指向纹理图像数据的指针
     */
    glTexImage2D(<#GLenum target#>, <#GLint level#>, <#GLint internalformat#>, <#GLsizei width#>, <#GLsizei height#>, <#GLint border#>, <#GLenum format#>, <#GLenum type#>, <#const GLvoid *pixels#>)

    //更新纹理
    glTexSubImage2D(<#GLenum target#>, <#GLint level#>, <#GLint xoffset#>, <#GLint yoffset#>, <#GLsizei width#>, <#GLsizei height#>, <#GLenum format#>, <#GLenum type#>, <#const GLvoid *pixels#>)

    //插入替换纹理
    glCopyTexSubImage2D(<#GLenum target#>, <#GLint level#>, <#GLint xoffset#>, <#GLint yoffset#>, <#GLint x#>, <#GLint y#>, <#GLsizei width#>, <#GLsizei height#>)
复制代码

#设置纹理参数

    // 参数一:纹理纬度     参数二:参数名称     参数三:参数的值
    glTexParameteri(<#GLenum target#>, <#GLenum pname#>, <#GLint param#>)
    glTexParameterf(<#GLenum target#>, <#GLenum pname#>, <#GLfloat param#>)
复制代码

###过滤方式 常用的过滤方式有两种:

  • 临近过滤:是我们能够选择的最简单、最快速的过滤方法,其最显著的特征就是当纹理被拉伸到特别大的时候所出现的大片斑驳状像素。
  • 线性过滤:更接近真实,没有人工操作的痕迹

设置方式:

//纹理放大时,使用临近过滤
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);

//纹理缩小时,使用临近过滤(推荐)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);

//纹理放大时,使用线性过滤 (推荐)
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

//纹理缩小时,使用线性过滤
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
复制代码

###环绕方式

//纹理坐标里的 s t r 分别代表 x y z轴

//指定横轴的环绕方式为GL_CLAMP_TO_EDGE
glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_S,GL_CLAMP_TO_EDGE);

//指定纵轴的环绕方式为GL_CLAMP_TO_EDGE
glTextParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_T,GL_CLAMP_TO_EDGE);
复制代码

不同的环绕方式效果如下:

#纹理坐标 纹理坐标系以纹理左下角为坐标原点,向右为x正轴方向,向上为y轴正轴方向。他的总长度是1。即纹理图片的四个角的坐标分别是:(0,0)、(1,0)、(0,1)、(1,1),分别对应左下、右下、左上、右上四个顶点。二维纹理常用(s, t)坐标表示:

//设置纹理坐标
//注意这里的参数1:texture,纹理层次,对于使用存储着色器来进行渲染,设置为0
//后面两个参数对应  x  y
void MultiTexCoord2f(GLuint texture, GLclampf s, GLclampf t);
复制代码

如何把纹理坐标应用到三角形上:其纹理坐标就是: GLfloat texCoords[] = { 0.0f, 0.0f, // 左下角 1.0f, 0.0f, // 右下角 0.5f, 1.0f // 顶部位置 };

接下来我们来实践一下,给OpenGL固定管线着色器这篇文章里的金字塔加上纹理:其实渲染的流程基本一致,主要是要给每个顶点添加纹理坐标,并读取纹理图像进行渲染: 纹理图片:

我们来分析下金字塔的底面纹理坐标: 底面其实是两个三角形: 纹理坐标如下:

前面/背面/两个侧面都是单独的三角形,纹理坐标如下:

具体代码实现如下:(Normal3f是为了光照效果设置的法线,不影响金字塔的纹理实现,可以自行去掉看看效果)

    //塔顶
    M3DVector3f vApex = { 0.0f, 1.0f, 0.0f };
    M3DVector3f vFrontLeft = { -1.0f, -1.0f, 1.0f };
    M3DVector3f vFrontRight = { 1.0f, -1.0f, 1.0f };
    M3DVector3f vBackLeft = { -1.0f,  -1.0f, -1.0f };
    M3DVector3f vBackRight = { 1.0f,  -1.0f, -1.0f };
    M3DVector3f n;
    
    //金字塔底部
    //底部的四边形 = 三角形X + 三角形Y
    //三角形X = (vBackLeft,vBackRight,vFrontRight)
    
    //1.找到三角形X 法线
    m3dFindNormal(n, vBackLeft, vBackRight, vFrontRight);
   
    //vBackLeft
    pyramidBatch.Normal3fv(n);
//    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
    pyramidBatch.Vertex3fv(vBackLeft);
    
    //vBackRight
    pyramidBatch.Normal3fv(n);
//    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
    pyramidBatch.Vertex3fv(vBackRight);
    
    //vFrontRight
    pyramidBatch.Normal3fv(n);
//    pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontRight);
    
    
    //三角形Y =(vFrontLeft,vBackLeft,vFrontRight)
   
    //1.找到三角形X 法线
    m3dFindNormal(n, vFrontLeft, vBackLeft, vFrontRight);
    
    //vFrontLeft
    pyramidBatch.Normal3fv(n);
//    pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontLeft);
    
    //vBackLeft
    pyramidBatch.Normal3fv(n);
//    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 1.0f);
    pyramidBatch.Vertex3fv(vBackLeft);
    
    //vFrontRight
    pyramidBatch.Normal3fv(n);
//    pyramidBatch.MultiTexCoord2f(0, 1.0f, 1.0f);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontRight);

    
    // 金字塔前面
    //三角形:(Apex,vFrontLeft,vFrontRight)
    m3dFindNormal(n, vApex, vFrontLeft, vFrontRight);
   
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    pyramidBatch.Vertex3fv(vApex);
    
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontLeft);

    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontRight);
    
    //金字塔左边
    //三角形:(vApex, vBackLeft, vFrontLeft)
    m3dFindNormal(n, vApex, vBackLeft, vFrontLeft);
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    pyramidBatch.Vertex3fv(vApex);
    
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackLeft);
    
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontLeft);
    
    //金字塔右边
    //三角形:(vApex, vFrontRight, vBackRight)
    m3dFindNormal(n, vApex, vFrontRight, vBackRight);
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    pyramidBatch.Vertex3fv(vApex);
    
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vFrontRight);
    
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackRight);
    
    //金字塔后边
    //三角形:(vApex, vBackRight, vBackLeft)
    m3dFindNormal(n, vApex, vBackRight, vBackLeft);
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.5f, 1.0f);
    pyramidBatch.Vertex3fv(vApex);
    
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 0.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackRight);
    
    pyramidBatch.Normal3fv(n);
    pyramidBatch.MultiTexCoord2f(0, 1.0f, 0.0f);
    pyramidBatch.Vertex3fv(vBackLeft);
复制代码

顶点和纹理坐标设置好之后,开始去读取纹理图像: 使用流程:(4、5顺序不是严格的,也可以放在前面两步)

  1. 读取
  2. 载入
  3. 设置纹理属性参数(过滤方式和环绕方式)
  4. 分配纹理对象
  5. 绑定纹理对象
  6. 清除纹理对象
  7. 判断纹理对象是否清除
    //分配纹理对象 参数1:纹理对象个数,参数2:纹理对象指针
    glGenTextures(1, &textureID);

    //绑定纹理状态 参数1:纹理状态2D 参数2:纹理对象
    glBindTexture(GL_TEXTURE_2D, textureID);

    GLbyte *pBits;
    int nWidth, nHeight, nComponents;
    GLenum eFormat;

    //读纹理位,读取像素
    //参数1:纹理文件名称
    //参数2:文件宽度地址
    //参数3:文件高度地址
    //参数4:文件组件地址
    //参数5:文件格式地址
    //返回值:pBits,指向图像数据的指针
    pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);

 if(pBits == NULL)
        return false;
    
    //设置纹理参数
    //参数1:纹理维度
    //参数2:为S/T坐标设置模式
    //参数3:wrapMode,环绕模式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
    
    

    //参数1:纹理维度
    //参数2:线性过滤
    //参数3:wrapMode,环绕模式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    

    //载入纹理
    //参数1:纹理维度
    //参数2:mip贴图层次
    //参数3:纹理单元存储的颜色成分(从读取像素图是获得)
    //参数4:加载纹理宽
    //参数5:加载纹理高
    //参数6:加载纹理的深度
    //参数7:像素数据的数据类型(GL_UNSIGNED_BYTE,每个颜色分量都是一个8位无符号整数)
    //参数8:指向纹理图像数据的指针
    
    glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0,
                 eFormat, GL_UNSIGNED_BYTE, pBits);
    
    
    
    //使用完毕释放pBits
    free(pBits);
    
    
    //加载Mip,纹理生成所有的Mip层
    //参数:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
    glGenerateMipmap(GL_TEXTURE_2D);

复制代码

最终效果图如下:

#####ps:因为计算机的坐标是从左上角开始算的,所以如果纹理是一张图片并且图片有方向的话,对纹理坐标的计算需要做一个运算,纵轴坐标 t 的值为 (1 - t)。上面的例子中,图片并没有方向所以做与不做这步运算都是可以的。

转载于:https://juejin.im/post/5d22f35051882536652cba9e

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值