OpenGL学习4——纹理

纹理(Texture)

  • 纹理(Texture) 是一个2D图像(也有1D和3D纹理存在)用于给一个对象添加细节信息。
  • 注意:与图像相似,纹理也可以用于存储一个大型的任意数据集合并发送给着色器。
  • 为了映射纹理,我们需要为顶点关联一个纹理坐标(texture coordinate) 来指定从纹理那部分进行取样(sample)。纹理坐标值的范围在x和y坐标轴上都为0到1。使用纹理坐标来获取纹理颜色就称为采样(sampling)。三角形的纹理坐标如下所示:
float texCoords[] = {
    0.0f, 0.0f,
    1.0f, 0.0f,
    0.5f, 1.0f
}

1. 纹理扭曲(texture wrapping)

  • 当纹理坐标落在(0, 0)和(1, 1)范围外OpenGL可能的处理方式:(下图取自书中

    • GL_REPEAT:重复纹理图像(默认行为)。
    • GL_MIRRORED_REPEAT:与GL_REPEAT行为一样,只是每次重复时都镜像图像。
    • GL_CLAMP_TO_EDGE:相当于将纹理图形进行拉伸。
    • GL_CLAMP_TO_BORDER:位于范围外的坐标被指定为一个边界颜色值。
      Texture Wrapping
  • 使用函数glTexParameter*设置上述选项,其中s, t和r分别对应坐标轴的x, y和z。

// 设置x坐标轴范围外坐标处理方式为重复镜像纹理图形
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
// 设置y坐标轴范围外坐标处理方式为重复镜像纹理图形
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
1. 第一个参数设置纹理目标,该示例指定2D纹理坐标。
2. 第二个参数指定要设置的坐标轴。
3. 第三个参数指定纹理扭曲选项。
  • 如果设置为GL_CLAMP_TO_BORDER需设置一个边界颜色:
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glRexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

2. 纹理过滤(texture filtering)

  • 纹理过滤:纹理坐标不取决于图像的分辨率,而是可以是任何浮点数,因此OpenGL需要知道如何将纹理坐标映射到纹理像素(也称为texel)。
  • OpenGL两种常见和重要的过滤方式:
    • GL_NEAREST:也称为最邻近(nearest neighbor)或点过滤,OpenGL直接选择中心离纹理坐标最近的纹理元素。
    • GL_LINEAR:也称为线性过滤,采用线性插值算法从纹理坐标邻近像素估算颜色值。
  • 纹理过滤通过设置放大(magnifying)和缩减(minifying)操作来实现:
// 缩减时使用最邻近过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
// 放大时使用线性过滤
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  • mipmaps:简单说就是一个纹理图像序列组成的集合,其中每个子纹理是其前者的二分之一。mipmaps的使用原理就是,物体与观察者的距离每经过一个阈值,OpenGL就会使用适合该距离不同的mipmap纹理。(下图取自书中

    Texture Mipmaps

  • mipmaps纹理图像不同层级之间的切换可能造成边缘锐化,像普通纹理过滤一样,也可以为切换mipmap层级设置不同的过滤方法,四个选项如下:

    • GL_NEAREST_MIPMAP_NEAREST
    • GL_LINEAR_MIPMAP_NEAREST
    • GL_NEAREST_MIPMAP_LINEAR
    • GL_LINEAR_MIPMAP_LINEAR
  • mipmap层级过滤的设置:

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  • 注意:纹理放大不使用mipmaps,给纹理放大设置mipmap过滤选项会产生一个OpenGL的GL_INVALID_ENUM错误编码。

3. 加载和创建纹理

  • 使用单头文件图像加载类库stb_image.h
      1. 下载:stb_image.h
    • 使用:将头文件stb_image.h添加到你的项目;创建一个C++文件添加以下内容:
    #define STB_IMAGE_IMPLEMENTATION
    #include "stb_image.h"
    
    • 加载图像:
    int width, height, nChannels;
    unsigned char* data = stbi_load("container.jpg", &width, &height, &nChannels, 0);
    

4. 生成一个纹理

  • 创建纹理对象
unsigned int texture;
glGenTextures(1, &texture);
  • 绑定纹理
glBindTexture(GL_TEXTURE_2D, texture);
  • 生成纹理
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
// 生成当前绑定纹理所需的mipmaps
glGenerateMipmap(GL_TEXTURE_2D);
1. 第1个参数:指定纹理目标。
2. 第2个参数:指定我们创建的纹理的mipmap层级。
3. 第3个参数:告诉OpenGL纹理的存储格式。
4. 第4和5个参数:纹理的宽高。
5. 第6个参数:总是0.
6. 第7和8个参数:指定源图像的格式和数据类型。
7. 最后一个参数:实际图像数据。
  • 释放图像数据
stbi_image_free(data);

5. 应用纹理

  • 顶点数据:包含位置坐标,颜色数据和纹理坐标。
float vertices[] = {
    // positions            // colors         // texture
     0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,
     0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,
    -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,
    -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f
};
  • 设置纹理顶点属性
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
  • 修改顶点着色器:添加纹理属性输入并设置纹理坐标和一个颜色传递到片元着色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;

out vec3 ourColor;
out vec2 TexCoord;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor;
    TexCoord = aTexCoord;
}
  • GLSL中内置一种纹理对象的数据类型,称为取样器(sampler),携带一个指定纹理类型的后缀,如sampler1D, sampler3Dsampler2D
  • 修改片元着色器:读取纹理和接受顶点着色器的纹理坐标和颜色
#version 330 core
out vec4 FragColor;

in vec3 ourColor;
in vec2 TexCoord;

uniform sampler2D ourTexture;

void main()
{
    // 使用GLSL内置的texture函数从纹理取样颜色
    FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);
}
  • 渲染循环中绑定纹理和绘制图形
glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
  • 渲染效果


    纹理渲染效果图
  • 混合纹理颜色和顶点颜色
// 片元着色器修改
FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);
纹理和顶点颜色混合

6. 纹理单元(Texture Units)

  • 使用glUniform1i函数我们可以给纹理取样器(texture sampler)设置一个location值,这样我们就可以在一个片元着色器设置多个纹理。这个纹理的location通常称为纹理单元。一个纹理默认的纹理单元是0,所以我们前面的程序没有设置纹理单元值。
  • 纹理单元的主要目的就是让我们可以在一个着色器中使用不止一个纹理。只要我们在绑定纹理前激活相应的纹理单元,我们就可以绑定多个纹理。
glActiveTexture(GL_TEXTURE0);     // 先激活纹理单元
glBindTexture(GL_TEXTURE_2D, texture1);
  • OpenGL至少有16个纹理单元可以使用,GL_TEXTURE0GL_TEXTURE15。它们是按顺序定义的,所以如果我们想要获取GL_TEXTURE8,可以使用GL_TEXTURE0+8
  • 修改片元着色器以包含两个纹理取样器
#version 330 core
out vec4 FragColor;

in vec3 ourColor;
in vec2 TexCoord;

uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
    // 第一个纹理取色80%,第二个纹理取色20%
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}
  • 加载第二个纹理图像(png格式图像,格式设置为GL_RGBA
unsigned char* data2 = stbi_load("./Panda.png", &width, &height, &nChannels, 0);
if (data2)
{
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data2);
    glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
    std::cout << "Failed to load texture2" << std::endl;
}   
stbi_image_free(data2);
  • 设置片元着色器纹理取样器的uniform变量
ourShader.use();
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0);  // 手工设置
ourShader.setInt("texture2", 1);   // 使用自定义着色器类设置
  • 渲染循环中激活和绑定纹理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
  • 渲染效果


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

推荐阅读更多精彩内容