OpenGl L4纹理

一.纹理

  1. 为了让一张图片上呈现很多细节,我们必须要有足够多的顶点来定义足够多的颜色,但这样会增大我们的开销,于是我们可以将一张2D(1D,3D也有)的图片贴在我们之前的图片上,这张贴上去的图片就是纹理
    在这里插入图片描述

在贴上纹理的时候,我们需要把纹理和图片上的坐标一一对应,于是引入了纹理坐标
在这里插入图片描述

于是图片上的每一个点都关联着一个纹理坐标,使用纹理坐标获取纹理颜色的过程叫做采样
注意,在给图片进行纹理映射的时候,我们只需要给顶点着色器中输入的点设置纹理坐标就行了,接下来片段着色器会对其中的小片段进行纹理坐标的插值。但是有几种不同的插值方式,需要我们告诉OpenGL如何进行纹理采样。

  1. 纹理环绕方式

纹理坐标的范围一般是从(0,0)到(1,1),如果把纹理坐标设置在范围外会发生什么?重复这个图像,当然也有其他选择

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

在这里插入图片描述
我们使用glTexParameter函数对单独的一个坐标轴设置(s,t

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

其中,p1代表对应的纹理图,p2代表我们设定的选项和应用的纹理轴。由于我们配置的是纹理的环绕方式,所以是GL_TEXTURE_WRAP,然后针对s,t轴上进行设置,p4为环绕方式。

注意,如果环绕方式为GL_CLAMP_TO_BORDER的话,还要设置边缘颜色值。
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

  1. .纹理过滤
    纹理坐标不依赖于分辨率,所以可能会出现浮点值的情况,所以我们需要设置如何让OpenGl将纹理像素映射到纹理坐标上,这个过程叫做纹理过滤。

Texture Pixel也叫Texel,你可以想象你打开一张.jpg格式图片,不断放大你会发现它是由无数像素点组成的,这个点就是纹理像素;注意不要和纹理坐标搞混,纹理坐标是你给模型顶点设置的那个数组,OpenGL以这个顶点的纹理坐标数据去查找纹理图像上的像素,然后进行采样提取纹理像素的颜色

有两种纹理过滤方法,一个是邻近过滤GL_NEAREST:选择距离纹理坐标和周围纹理像素中心点位置最近的哪一个纹理像素值。
在这里插入图片描述
还有一个是线性过滤GL_LINEAR,基于纹理坐标附近的纹理像素,加权平均计算出一个插值,近似出这些纹理像素之间的颜色。
在这里插入图片描述
当在放大和缩小时,我们就可以定义纹理过滤的选项,这里使用glTexParameteri函数指定过滤方式。这里和设置环绕方式相似。

//设置环绕方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  1. 多级纹理渐变

对于一个很大空间里面铺满了相同大小的瓷砖,在远处的瓷砖和近处的瓷砖对应的纹理是完全一样的,分辨率也一样,在片段获取远处场景的颜色时就会出现跨过很大的纹理部分只拾取一个颜色,会产生很多错误的情况,所以,这里引入了一个多级纹理渐变来解决这个问题。

多级纹理渐变也就是从一个纹理出发,定义了一系列的纹理图像,后一个纹理图像是前一个的二分之一。
在这里插入图片描述

原理为:当观察者的距离超过一定阈值的时候,我们会使用不同的多级渐变纹理,选择最适合物体的距离的那一个。
创建多级渐变纹理使用glGenerateMipmaps函数,于是,我们可以用多级纹理过滤代替之前的过滤方式。

过滤方式描述
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);

二.生成纹理

1.图像加载库

使用纹理的第一步是把纹理加载到我们的应用中。而纹理会被存储为各种格式,我们首先要把图像转换为字节序列才行,我们可以使用一个图像加载库 stb_image.h库,链接:https://github.com/nothings/stb/blob/master/stb_image.h

//包含图像加载库
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

以及载入图片

int width, height, nrChannels;
//p2为纹理宽度,p3为图像高度,p4为图像颜色的通道数(1表示灰度图,3表示彩色图)
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
  1. 生成纹理
    和任何的OpenGl对象一样,任何纹理都需要进行引用和绑定
unsigned int texture;
//`1`表示生成纹理的数量为1
glGentextures(1,&texture);
glBindTexture(GL_TEXTURE_2D,texture);
  1. 将载入的图片赋到纹理上
//p1为绑定的纹理对象,0为初始多级渐变级别,这里先不生成为0,p3为纹理存储格式,p4p5为最终纹理的宽度和高度,
//p7p8为原图格式和数据类型,p9为图像数据
glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,width,height,0,GL_RGB,GL_UNSIGNED_BYTE,data);
//手动生成多级纹理渐变
glGenerateMipmap(GL_TEXTURE_2D);
//释放图像内存
stbi_image_free(data);

生成纹理过程如下:

unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// 为当前绑定的纹理对象设置环绕、过滤方式
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);   
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// 加载并生成纹理
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
if (data)
{glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);glGenerateMipmap(GL_TEXTURE_2D);
}
else
{std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);

三.应用纹理

  1. 同样的,我们将纹理坐标信息加载到顶点属性中:
float vertices[] = {
//     ---- 位置 ----       ---- 颜色 ----     - 纹理坐标 -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    // 左上
};
  1. 然后就是重新设置顶点属性读取规则
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
  1. 调整顶点着色器以接受纹理坐标值以便传给片段着色器
#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;
}
  1. 片段着色器接受来自顶点着色器的纹理坐标和uniform的纹理对象,并使用texture函数进行采样
    着色器会把顶点着色器输出的TexCoord作为输入变量,并使用uniform声明一个sampler2D采样器类型(这里是2D纹理)进行采样。
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main()
{//texture函数中p1为采样器,p2为纹理坐标FragColor = texture(ourTexture, TexCoord);
}

5.激活并绑定纹理单元(位置)
注意,我们在为片段着色器传递这个纹理对象时,首先需要激活和绑定纹理单元(也就是纹理的位置值)。

//一个纹理单元的默认值为0
glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元,而默认的`GL_TEXTURE0`总是激活状态
glBindTexture(GL_TEXTURE_2D, ourTexture);

6.然后定义uniform采样器对应哪一个纹理单元:

ourShader.use(); // 不要忘记在设置uniform变量之前激活着色器程序!
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); // 手动设置
ourShader.setInt("texture2", 1); // 或者使用着色器类设置

示例代码:
https://learnopengl.com/code_viewer_gh.php?code=src/1.getting_started/4.2.textures_combined/textures_combined.cpp


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部