赞
踩
我们通过给顶点颜色来增加图形的细节。要让图形看起来更加真实就要足够多的顶点来添加更多的颜色。这样会产生额外的开销(每个模型都要很多的顶点,每个顶点要一个颜色)
所以我们就要一个图片来添加细节,这个图片就叫纹理(也可以是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上
纹理坐标的范围是(0,0)到(1,1)如果把纹理坐标的范围设置到这之外会发生什么呢?这就涉及到了WARP参数。
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是等价的
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);
由于远处的物体可能只产生很少的片段,OpenGL从高分辨率纹理中为这些片段获取正确的颜色值就很困难,因为它需要对一个跨过纹理很大部分的片段只拾取一个纹理颜色。在小物体上这会产生不真实的感觉,更不用说对它们使用高分辨率纹理浪费内存的问题了。
Mipmaps纹理大小每级是前一等级的一半。OpenGL会根据物体离观察者的距离选择使用合适大小的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);
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。