• 作者:老汪软件技巧
  • 发表时间: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);

以上是使用单张图片的全部内容,如何使用多张图片,我们下一章再看。


上一条查看详情 +Redis 大 key 处理全攻略
下一条 查看详情 +没有了