当前位置:   article > 正文

OpenGL从“入门”到“摔门而走”(三)

OpenGL从“入门”到“摔门而走”(三)

Textures

我们通过给顶点颜色来增加图形的细节。要让图形看起来更加真实就要足够多的顶点来添加更多的颜色。这样会产生额外的开销(每个模型都要很多的顶点,每个顶点要一个颜色)
所以我们就要一个图片来添加细节,这个图片就叫纹理(也可以是1D或3D)

纹理坐标

要想把纹理映射到图形上,我们需要指定图形的每个顶点各自对应纹理的哪个部分
纹理坐标在X和Y轴上,范围0到1。使用纹理获取纹理颜色叫做采样(Sampling)。纹理坐标起始与(0,0),也就是纹理图片的左下角,纹理的右上角为(1,1)
文档中的图

float texCoords[] = {
0.0f, 0.0f, //左下角
1.0f, 0.0f, // 右下角
0.5f, 1.0f // 中上

我们只要给顶点着色器传输这三个纹理坐标就行了,然后会被传到片段着色器中。

创建纹理对象

跟VAO和VBO是一样的

unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);

因为是二维的纹理,所以绑在GL_TEXTURE_2D上

纹理环绕方式(WRAP)

纹理坐标的范围是(0,0)到(1,1)如果把纹理坐标的范围设置到这之外会发生什么呢?这就涉及到了WARP参数。

  • GL_REPEAT:坐标的整数部分被忽略,重复纹理,这是OpenGL纹理默认的处理方式.
  • GL_MIRRORED_REPEAT: 纹理也会被重复,但是当纹理坐标的整数部分是奇数时会使用镜像重复。
  • GL_CLAMP_TO_EDGE: 坐标会被截断到[0,1]之间。结果是坐标值大的被截断到纹理的边缘部分,形成了一个拉伸的边缘(stretched edge pattern)。
  • GL_CLAMP_TO_BORDER: 不在[0,1]范围内的纹理坐标会使用用户指定的边缘颜色。
    文档中的图
    设置环绕方式的方法

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

s,t(3D纹理还有个r)跟下,有,z是等价的

纹理过滤(Filter)

GL_NEAREST(也叫邻近过滤,Nearest Neighbor Filtering)是OpenGL默认的纹理过滤方式。当设置为GL_NEAREST的时候,OpenGL会选择中心点最接近纹理坐标的那个像素。下图中你可以看到四个像素,加号代表纹理坐标。左上角那个纹理像素的中心距离纹理坐标最近,所以它会被选择为样本颜色。

GL_LINEAR(也叫线性过滤,(Bi)linear Filtering)它会基于纹理坐标附近的纹理像素,计算出一个插值,近似出这些纹理像素之间的颜色。一个纹理像素的中心距离纹理坐标越近,那么这个纹理像素的颜色对最终的样本颜色的贡献越大。下图中你可以看到返回的颜色是邻近像素的混合色。

(邻方法获取的纹素看起来有明显的像素块,而线性滤波方法获取的纹素看起来比较平滑。两种方法各自有不同的应用场合)

当进行放大(Magnify)和缩小(Minify)操作的时候可以设置纹理过滤的选项,比如你可以在纹理被缩小的时候使用邻近过滤,被放大时使用线性过滤。

设置纹理过滤方式的方法

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

多级渐远纹理(Mipmaps)

由于远处的物体可能只产生很少的片段,OpenGL从高分辨率纹理中为这些片段获取正确的颜色值就很困难,因为它需要对一个跨过纹理很大部分的片段只拾取一个纹理颜色。在小物体上这会产生不真实的感觉,更不用说对它们使用高分辨率纹理浪费内存的问题了。

Mipmaps纹理大小每级是前一等级的一半。OpenGL会根据物体离观察者的距离选择使用合适大小的Mipmap纹理。

  • GL_NEAREST_MIPMAP_NEAREST: 使用最接近像素大小的Mipmap,纹理内部使用最近邻滤波。
  • GL_LINEAR_MIPMAP_NEAREST: 使用最接近像素大小的Mipmap,纹理内部使用线性滤波。
  • GL_NEAREST_MIPMAP_LINEAR: 在两个最接近像素大小的Mipmap中做线性插值,纹理内部使用最近邻滤波。
  • GL_LINEAR_MIPMAP_LINEAR: 在两个最接近像素大小的Mipmap中做线性插值,纹理内部使用线性滤波。

需要注意的是:一个常见的错误是,将放大过滤的选项设置为多级渐远纹理过滤选项之一。这样没有任何效果,因为多级渐远纹理主要是使用在纹理被缩小的情况下的:纹理放大不会使用多级渐远纹理,为放大过滤设置多级渐远纹理的选项会产生一个GL_INVALID_ENUM错误代码。

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

加载原始纹理

从图片加载纹理这部分工作不是OpenGL函数完成的,可以通过外部库实现。我使用的是stb_image.h库可以在这里下载

需要引入头文件

#define STB_IMAGE_IMPLEMENTATION
#include “stb_image.h”

通过定义STB_IMAGE_IMPLEMENTATION,预处理器会修改头文件,让其只包含相关的函数定义源码,等于是将这个头文件变为一个 .cpp 文件了。现在只需要在你的程序中包含stb_image.h并编译就可以了。

加载图片:

int width, height, nrChannels;
unsigned char *data = stbi_load(“container.jpg”, &width, &height, &nrChannels, 0);

这个函数首先接受一个图像文件的位置作为输入。接下来它需要三个int作为它的第二、第三和第四个参数,stb_image.h将会用图像的宽度、高度和颜色通道的个数填充这三个变量。我们之后生成纹理的时候会用到的图像的宽度和高度的。

完整过程

首先,首先要指定纹理坐标,这个坐标和顶点位置、顶点颜色一样处理,使用索引绘制

// 指定顶点属性数据 顶点位置 颜色 纹理
GLfloat vertices[] = {
-0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f,0.0f, 0.0f, // 0
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f,1.0f, 0.0f, // 1
0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f,1.0f, 1.0f, // 2
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f,0.0f, 1.0f // 3
};
GLuint indices[] = {
0, 1, 2, // 第一个三角形
0, 2, 3 // 第二个三角形
};
文档中的图

着色器;

#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;
}

#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main()
{
FragColor = texture(ourTexture, TexCoord);
}

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);

纹理单元

OpenGL可以支持的纹理单元数目,一般至少有16个,依次为GL_TEXTURE0 到GL_TEXTURE15

glActiveTexture(GL_TEXTURE0); //激活0号位纹理单元
glBindTexture(GL_TEXTURE_2D, texture);

片段着色器:

#version 330 core

uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

最终输出颜色现在是两个纹理的结合。GLSL内建的mix函数需要接受两个值作为参数,并对它们根据第三个参数进行线性插值。如果第三个值是0.0,它会返回第一个输入;如果是1.0,会返回第二个输入值。0.2会返回80%的第一个输入颜色和20%的第二个输入颜色,即返回两个纹理的混合色。

我们还要通过使用glUniform1i设置每个采样器的方式告诉OpenGL每个着色器采样器属于哪个纹理单元。我们只需要设置一次即可,所以这个会放在渲染循环的前面:

glUniform1i(glGetUniformLocation(shader.programId, “tex”), 0);

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/574250
推荐阅读
相关标签
  

闽ICP备14008679号