🍻 国庆节快乐!
有了纹理的正方形 ??
上一篇文章地址链接:【OpenGL学习笔记】计算机图形学③——?着色器【GLSL Uniform 彩色三角形 变色正方形】?. 下一篇文章地址链接:🚧 🚧…
零、成果预览图:
?????? ????
??◆ 说明:左图是一张简单的图片(纹理)。右图是通过一张图片经过 “镜像复制——纹理环绕” 的方式生成的。
一、SOIL2的配置:
??● SOIL 是简易 OpenGL 图像库(Simple OpenGL Image Library)的缩写,它能帮我们读取图片并做相应的处理。
??● 当前最新版的是 SOIL2,需要我们自己动手配置。推荐一篇SOIL2环境配置的文章,写得很详细:链接: 《(图文)SOIL2环境配置》
三、纹理坐标
??● 装载各顶点位置的数组:
GLfloat vertices_1[] =
{
0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f
};
??● 在绘制纹理映射场景时,不仅要给每个顶点定义几何坐标(这里OpenGL用的是标准化设备坐标,如上图),而且也要定义OpenGL纹理坐标(如右下图)。经过多种变换后,几何坐标决定顶点在屏幕上绘制的位置,而纹理坐标决定纹理图像中的哪一个纹素赋予该顶点。并且顶点之间的纹理坐标插值与平滑着色插值方法相同。
??● 纹理图像是方形数组,纹理坐标通常可定义成一、二、三或四维形式,称为 s,t,r和q坐标 ,分别对应于物体坐标的几何坐标(x, y, z, w)和其他坐标。首先,其中的q是一个缩放因子,相当于顶点坐标中的 w 。实际在纹理读取中的坐标应该分别是 s/q、t/q、r/q。默认情况下,q 是1.0。通常情况下貌似没什么用,但是在一些产生纹理坐标的高级算法比如阴影贴图中,比较有用。
??● s、t、r 分别相当于普通坐标系中的 x、y、z 三个方向。(分别对应 glTexImage3D 中的参数width、height、depth)。所以,一维纹理常用 s 坐标表示,二维纹理常用 (s,t) 坐标表示。;因为我们要绘制的是 2D 图像,所以只需要用二维纹理 (s,t) 即可。所以在代码里面,“texture coords(纹理坐标)” 只有两列。
??◆ 补充说明:一般的 Windows 电脑坐标系如左上图。可以看出它和 OpenGL 的纹理坐标是 “相反的” 。所以在通过 “计算机方式” 读取图像后,再通过 OpenGL 绘出时,会出现图像倒着的情况,后面我们将会对其进行处理。
四、改写顶点着色器
#version 330 core
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 color;
layout(location = 2) in vec2 textureCoords;
out vec3 ourColor;
out vec2 ourTextureCoords;
void main()
{
gl_Position = vec4(position, 1.0f);
ourTextureCoords = vec2(textureCoords.x, 1-textureCoords.y);
}
??◆ 补充说明: ????① 因为要翻转图片,所以不能简单地用 “ourTextureCoords = textureCoords.x;”。 ????② 注释掉颜色是因为,我们只需要绘制纹理,并不需要图像做 “画图” 处理。( 但因为我定义的 “Shader.h” 头文件中有对颜色的处理,所以在 vertices_1[] 数组中依旧要把颜色的值加上 )
五、改写片元着色器
#version 330 core
in vec3 ourColor;
in vec2 ourTextureCoords;
out vec4 FragColor;
uniform sampler2D ourTexture;
void main()
{
FragColor = texture(ourTexture, ourTextureCoords);
}
??◆ 补充说明: ????① 顶点着色器传给片元着色器的只有纹理坐标,但还差纹理对象。但是我们怎样能把纹理对象传给片段着色器呢?GLSL 有一个供纹理对象使用的内建数据类型,叫做采样器(Sampler) ,它以纹理类型作为后缀,比如 sampler1D 、sampler3D ,或在我们的例子中的 sampler2D 。我们可以简单声明一个 uniform sampler2D 把一个纹理添加到片段着色器中,稍后我们会在主函数中把这个纹理对象赋值给这个 “ourTexture”。 ????② 我们使用 GLSL 内建的 texture() 函数来采样纹理的颜色,它第一个参数是纹理采样器,第二个参数是对应的纹理坐标。texture() 函数会使用之前设置的 “纹理参数对” 相应的颜色值进行采样。这个片段着色器的输出就是纹理在 (插值) 纹理坐标上 (过滤后的) 颜色。
六、读取纹理
int width, height;
unsigned char* image = SOIL_load_image("T_image3.png", &width, &height, 0, SOIL_LOAD_RGB);
??◆ 补充说明:函数首先需要输入图片文件的路径。然后需要两个 int 类型的指针作为第二个和第三个参数,SOIL 会分别返回图片的宽度和高度到其中。因为后面我们在生成纹理的时候会用图像的宽度和高度。第四个参数指定图片的通道 (Channel) 数量,但是这里我们只需留为 0 。最后一个参数告诉 SOIL 如何来加载图片,结果会储存为一个很大的 char/byte 数组。
七、生成纹理
??● 和之前生成的 OpenGL 对象一样 (比如 VAO),纹理也是使用 ID 引用的。
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
??◆ 补充说明: ????① glGenTexture() 函数中第一个参数为 1 的原因是,后面我们绑定的 VAO、VBO、EBO 的 ID 都是人为地设置的 1 。 ????② glGenTextures() 函数:将一个命名的纹理绑定到一个纹理目标上。 ????③ 我们可以这样理解,GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D等就是很多变量,当使用 glBindTexture() 函数,我们就会使用一张纹理对这些变量进行赋值。这些函数里面的 GL_TEXTURE_2D 就等价与我们之前绑定的纹理,所以我们对 GL_TEXTURE_2D 的操作就会影响到之前的纹理,这和C++中的引用有点类似。
??● 现在纹理已经绑定了,我们可以使用前面载入的图片数据生成一个纹理了。纹理可以通过glTexImage2D() 来生成:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
??◆ 补充说明一: ????① 第一个参数:指定了纹理目标。 ????② 第二个参数:为纹理指定多级渐远纹理的级别。这里我们填 0,也就是基本级别。(因为,我们还没有“摄像机”) ????③ 第三个参数:告诉 OpenGL 我们希望把纹理储存为何种格式。 ????④ 第四个参数:设置最终纹理宽度。 ????⑤ 第五个参数:设置最终纹理高度。 ????⑥ 第六个参数:这个参数应该总是被设为0(历史遗留的问题)。 ????⑦ 第七个参数:定义了源图的格式。我们使用 RGBA 值加载这个图像. ????⑧ 第八个参数:设置源图的数据类型。 ????⑨ 第九个参数:源图像数据。
??◆ 补充说明二:如果要使用多级渐远纹理,我们需要生成纹理之后调用 glGenerateMipmap() 。这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。(多级纹理可以这样理解:当站在远处看图像时,很多像素点就可以“凑”成一个主要的像素点;当站在进除看图像时,像素点就需要“铺开”,让图像变得清晰)但是因为我们还没学“摄影机”,所以也可以不用做这不处理。而且如果要生成多级渐远纹理,则图像的宽和高都必须是 2 的次方,宽和高不一定相同,但其值必须是 2 的次方。
??● 生成了纹理和相应的多级渐远纹理后,释放图像的内存并解绑纹理对象是一个很好的习惯。(就像我们在上一篇文章写 “Shader.h” 头文件时,在生成着色器程序后,就删除顶点着色器和片元着色器,因为它俩已经链接到了着色器程序中了,已经不再需要了。)
SOIL_free_image_data(image);
??● 纹理坐标的范围通常是从 (0, 0) 到 (1, 1) ,那如果我们把纹理坐标设置在范围之外会发生什么?OpenGL 默认的行为是重复这个纹理图像,但 OpenGL 提供了更多的选择:
纹理环绕方式 | 描述 |
---|
GL_REPEAT | 对纹理的默认行为。重复纹理图像。 | GL_MIRRORED_REPEAT | 和GL_REPEAT一样,但每次重复图片是镜像放置的。 | GL_CLAMP_TO_EDGE | 纹理坐标会被约束在0到1之间,超出的部分会重复纹理坐标的边缘,产生一种边缘被拉伸的效果。 | GL_CLAMP_TO_BORDER | 超出的坐标为用户指定的边缘颜色。 |
??每种选择的示意图:
??● 纹理环绕方式的设置:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
??● 除了要设置纹理环绕方式外,还要设置纹理过滤方式:GL_NEAREST(邻近过滤) 产生了颗粒状的图案,我们能够清晰看到组成纹理的像素,而 GL_LINEAR(线性过滤) 能够产生更平滑的图案,很难看出单个的纹理像素。这是两种常用的过滤方式。
??● 纹理过滤方式的设置:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
八、绘制纹理
??● 显卡中有 N 个纹理单元(具体数目依赖你的显卡能力),每个纹理单元(GL_TEXTURE0、GL_TEXTURE1等)都有 GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D 等纹理目标。
??● 纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。通过把纹理单元赋值给采样器(即 Uniform 属性的 “ourTexture变量” ),我们可以一次绑定多个纹理。只要我们首先激活对应的纹理单元,就像glBindTexture() 一样,我们可以使用glActiveTexture() 来激活纹理单元,传入我们需要使用的纹理单元:
??● 我们还要通过使用glUniform1i() 设置每个采样器的方式告诉 OpenGL 每个着色器采样器属于哪个纹理单元。我们只需要设置一次即可,所以这个会放在渲染循环的前面。
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
int textureLocation = glGetUniformLocation(ourShader.Program, "ourTexture");
glUniform1i(textureLocation, 0);
九、完整代码(主函数)
??● 头文件 Shader.h 依旧沿用上一篇的代码?着色器【GLSL Uniform 彩色三角形 变色正方形】?。主函数如下:
#include <iostream>
using namespace std;
#define GLEW_STATIC
#include"Shader.h"
#include"glew-2.2.0\include\GL\glew.h"
#include"glfw-3.3.4.bin.WIN32\include\GLFW\glfw3.h"
#include"SOIL2\include\stb_image.h"
#include"SOIL2\include\SOIL2.h"
int width, height;
GLfloat vertices_1[] =
{
0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f
};
unsigned char* image = SOIL_load_image("T_image4.png", &width, &height, 0, SOIL_LOAD_RGBA);
GLuint indices_1[] =
{
0, 1, 3,
1, 2, 3
};
const GLint WIDTH = 600, HEIGHT = 600;
int main()
{
glfwInit();
GLFWwindow* window_1 = glfwCreateWindow(WIDTH, HEIGHT, "Learn OpenGL Texture test", nullptr, nullptr);
int screenWidth_1, screenHeight_1;
glfwGetFramebufferSize(window_1, &screenWidth_1, &screenHeight_1);
cout << "screenWidth_1 = " << screenWidth_1 << ", screenHeight = " << screenHeight_1 << endl;
glfwMakeContextCurrent(window_1);
glewInit();
Shader ourShader = Shader("shader_v.txt", "shader_f.txt");
GLuint VAO, VBO, EBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices_1), vertices_1, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices_1), indices_1, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (GLvoid*)(3*sizeof(GLfloat)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8*sizeof(GLfloat), (GLvoid*)(6*sizeof(GLfloat)));
glEnableVertexAttribArray(2);
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
SOIL_free_image_data(image);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
ourShader.Use();
int textureLocation = glGetUniformLocation(ourShader.Program, "ourTexture");
glUniform1i(textureLocation, 0);
while (!glfwWindowShouldClose(window_1))
{
glViewport(0, 0, screenWidth_1, screenHeight_1);
glfwPollEvents();
glClearColor(0.5f, 0.8f, 0.5f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
ourShader.Use();
glBindVertexArray(VAO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindTexture(GL_TEXTURE_2D, 0);
glfwSwapBuffers(window_1);
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glfwTerminate();
return 0;
}
??● 运行结果:(注:样例二也在代码中,把注释打开即可)
?????? ????
十、参考附录:
[1] 《LearnOpenGL CN —— 纹理》 链接: https://learnopengl-cn.github.io/01%20Getting%20started/06%20Textures/.
[2] 《OpenGL三维纹理坐标》 链接: http://blog.sina.com.cn/s/blog_687960370101gyh8.html.
[3] 《(图文)SOIL2环境配置(OpenGL)》 链接: https://blog.csdn.net/weixin_44165937/article/details/117261166.
[4] 《OpenGL纹理坐标 与 Cocos2d-x 纹理坐标》 链接: https://blog.csdn.net/wlk1229/article/details/85077819.
[5] 《【OpenGL】关于OpenGL中glBindTexture函数的理解》 链接: https://blog.csdn.net/u010029439/article/details/98500262.
上一篇文章地址链接:【OpenGL学习笔记】计算机图形学③——?着色器【GLSL Uniform 彩色三角形 变色正方形】?.
下一篇文章地址链接:🚧 🚧…
🍻 国庆节快乐!
|