美文网首页程序员OpenGL哲思
OpenGL纹理 - 常用API简介

OpenGL纹理 - 常用API简介

作者: 聪莞 | 来源:发表于2019-05-23 15:10 被阅读19次

原始图像数据与内存包装

图像的存储空间= 图像的宽度 * 图像的高度 * 每个像素的字节数(系统决定)

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

image.png

举个例子理解一下:

  • 如果一个图像每行有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#>)
image.png
    /** 从磁盘中载入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#>)

过滤方式

常用的过滤方式有两种:

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


    image.png
    image.png

设置方式:

//纹理放大时,使用临近过滤
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);

环绕方式

image.png
//纹理坐标里的 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);

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


image.png

纹理坐标

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


image.png
//设置纹理坐标
//注意这里的参数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 // 顶部位置
};


image.png

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

image.png

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


image.png

具体代码实现如下:(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);

相关文章

  • OpenGL纹理 - 常用API简介

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

  • OpenGL纹理API简介

    纹素和纹理坐标 纹理对象通常是通过纹理图片读取到的,这个数据保存到一个二维数组中,这个数组中的元素成为纹素(tex...

  • OpenGL纹理API简介

    以下为经常使用的纹理API笔记, 仅做记录学习 1. 读取文件 2. 载入纹理 至于glTexImage1D,gl...

  • OpenGL纹理常用API

    在OpenGL中使用纹理,是有一套步骤的,现将步骤记录如下: 1.读取纹理文件 //参数1:x,矩形左下⻆角的窗⼝...

  • OpenGL纹理常用API

    读取纹理 载⼊入纹理 其他纹理操作 纹理对象 设置纹理参数 关于贴图方式(GL_TEXTURE_WRAP_S、GL...

  • OpenGL笔记七:纹理常用API(二)

    前言 期待您移步上篇:OpenGL笔记六:纹理常用API(一) 从颜⾊缓存区内容 - 像素图直接读取 更新纹理 插...

  • OpenGL笔记八:2D纹理坐标解析

    前言 期待您移步上篇:OpenGL笔记七:纹理常用API(二) 纹理采样 为了能够把纹理映射(Map)到三角形上,...

  • 􏰗􏰘􏱄􏰮纹理常用API简介􏱅􏱆

    载入纹理 void glTexImage1D(GLenum target,GLint level,GLintint...

  • OpenGL 纹理 常用API 解析

    纹理只是一种能够应用到场景中的三角形上的图像数据。它经过过滤的纹理单元(texel,相当于基于纹理的像素)填充到实...

  • OpenGl纹理和常用API

    一. 概念理解 在 OpenGL 中我们不仅仅能着色, 还能将特殊的图案绘制到对应的面上, 这个特殊的图案就被称为...

网友评论

    本文标题:OpenGL纹理 - 常用API简介

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