- 作者:老汪软件技巧
- 发表时间:2024-09-07 21:02
- 浏览量:
最新大型开源项目-云游戏,云桌面系统,欢迎关注
GammaRay源码地址
本项目代码仓库
点击这里
1.为什么使用纹理
在之前的章节中,我们绘制了一个矩形,给了它特定的颜色,也通过矩阵操作了它平移旋转。但无论是炫酷的3A大作,还是简单的益智游戏,都是很漂亮,很有设计感的。单纯通过给顶点颜色,几乎很难做到,那么我们可以通过给这个矩形贴上一张图片,让他变得华丽起来。比如我的一个场景(可看这里的视频):
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gfyjvkX3-1623148877864)(/oscnet/up-f…)]
上图中,背景的图片,圆形的图片,小雨滴,甚至81192这几个字也是图片,也就是纹理。它几乎无处不在,极大的丰富我们的画面。
2.纹理采样与纹理环绕
纹理坐标在几何上是连续的,而片段或者像素是离散的,当我们要将坐标映射到纹理上,从纹理上去除颜色值给OpenGL,就是采样的过程。
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_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
如果自己设置颜色,则可以通过这样设置:
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
mipmap本质上说就是一堆图片,以原始分辨率的图开始,以后每一张是上一张尺寸的一半,看下面的图:
我们按照距离来划分,在不同的距离范围内,从不同的尺寸上采样,这样就不会出现离得非常远,还要从原始分辨率上采样了,直接在一个小尺寸的图像上采样即可。
需要声明的一个问题是,这些图片也不是连续变化的,是以除以2的尺寸递减,那么必然会出现断档的情况,比如要用0.3倍的图像时,怎么办呢?
答案是让不同尺寸下的图片也进行所谓的纹理过滤,也就是OpenGL通过前后两张图帮我们生成一个中间的过渡图,然后再采样。
生成mipmap非常简单,OpenGL已经提供了一个方法,只需要调用一下即可:
glGenerateMipmap(GL_TEXTURE_2D);
可以用如下代码设置缩小时的纹理过滤:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
3.如何使用纹理
与颜色一样,纹理坐标也是顶点的一个属性,所以我们把纹理坐标追加到颜色属性的后面,代码如下:
float vertices[] = {
0.5f, 0.5f, 0.0f, 1.0, 0.0, 0.0, 1.0f, 1.0f,
0.5f, -0.5f, 0.0f, 0.0, 1.0, 0.0, 1.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0, 0.0, 1.0, 0.0f, 0.0f,
-0.5f, 0.5f, 0.0f, 0.9, 0.6, 0.8, 0.0f, 1.0f
};
最后的两列就是我们的纹理坐标。内存分布如下图所示:
以一个点为例:(0.5, 0.5) 是顶点的位置坐标,代表矩形右上角的位置,0.5是它在屏幕的坐标系下的位置。对应到纹理上就是 (1,1),因为纹理的右上角要贴在这个矩形的右上角,而不关心矩形到底有多大。参考下图:
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTex;
uniform mat4 model;
out vec3 outColor;
out vec2 outTex;
void main()
{
gl_Position = model * vec4(aPos, 1.0);
outColor = aColor;
outTex = aTex;
}
在我们的顶点着色器中,添加了一个新的属性纹理坐标 layout (location = 2) in vec2 aTex;, 并用相同的类型将这个属性的值传递给片段着色器 outTex = aTex;。
此时还要在C++代码中,告诉OpenGL如何用刚才添加的数据,这与之前颜色的做法相同:
注意此时stride要修改为8。
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 2, GL_FLOAT, false, stride, (void*)(6*sizeof(float)));
紧接着在片段着色器中:
#version 330 core
in vec3 outColor;
in vec2 outTex;
uniform sampler2D image;
void main()
{
vec4 color = texture(image, outTex);
gl_FragColor = color;
}
我们接收到了顶点坐标,并声明了一个uniform的2D的纹理对象。因为纹理对于所有的片段都是一致的,所以是uniform变量。然后用texture函数从纹理上在outTex的位置进行采样,得到最终的颜色值并输出给下一个环节,最终通过各种测试后,显示到屏幕上。
4.加载纹理并使用
万事俱备,只差把图片数据传给OpenGL使用了。
int width, height, channels;
unsigned char* data = stbi_load("../resources/images/person.jpg", &width, &height, &channels, 0 );
// Gen Texture
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTextureParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTextureParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTextureParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTextureParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// Load Image
int width, height, channels;
unsigned char* data = stbi_load("../resources/images/person.jpg", &width, &height, &channels, 0 );
if (data) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
stbi_image_free(data);
glBindTexture(GL_TEXTURE_2D, 0);
最后记得要释放内存,因为已经传递给OpenGL了,显存上已经存在,那么主内存的数据就不需要了。
stbi_set_flip_vertically_on_load(true);
以上是使用单张图片的全部内容,如何使用多张图片,我们下一章再看。