开发环境
- Windows 10
- Visual Studio Community 2022
准备项目
1.新建一个空的C++项目:07_Texture,并设置为启动项目。
2.配置GLEW和GLFW。根据【LearnOpenGL】01.创建窗口和【LearnOpenGL】03.配置GLEW,对这2项进行配置,保证编译通过。
3.将【LearnOpenGL】06.着色器基础 中 Shader.h 和 Shader.cpp 文件拷贝到当前项目目录下。
纹理
在OpenGL中,纹理是一种常用的技术,用于将图像或图案映射到3D模型的表面上,以增加图形的细节和真实感。
纹理坐标是对纹理图像(通常是2D)中的像素的引用。纹理图像中的像素被称为纹素(Texel),以便将它们与在屏幕上呈现的像素区分开。
为了能够把纹理映射(Map)到三角形上,我们需要指定三角形的每个顶点各自对应纹理的哪个部分。这样每个顶点就会关联着一个纹理坐标(Texture Coordinate),用来标明该从纹理图像的哪个部分采样。之后在图形的其它片段上进行片段插值(Fragment Interpolation)。
纹理坐标在x和y轴上,范围为0到1之间(注意我们使用的是2D纹理图像)。使用纹理坐标获取纹理颜色叫做采样 (Sampling)。纹理坐标起始于 (0, 0),也就是纹理图片的左下角,终始于 (1, 1),即纹理图片的右上角。
流程
要想在自己的程序中使用纹理映射,需要遵循以下几个步骤:
1.加载纹理图像文件
使用纹理之前要做的第一件事就是把纹理图像文件加载到我们程序中。在这里我们使用一个持多种流行格式的图像加载库来为我们解决这个问题。比如:stb_image.h。
- 在工程目录 src 下创建 vendor 文件夹存放外部代码(非自己编写的)。
- 在 vendor 文件夹内新建一个文件夹 stb_image 用来存放该库相关的文件。
- GitHub地址:https://github.com/nothings/stb/blob/master/stb_image.h,把stb_image.h文件下载到 src/vendor/stb_image 目录下。
- 在 src/vendor/stb_image 目录下新建一个 stb_image.cpp 文件,输入以下代码:
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
- 加载纹理图像文件:
// 宽度、高度、颜色通道数
int width, height, comp;
unsigned char *data = stbi_load("container.jpg", &width, &height, &comp, 0);
2.创建纹理对象
void glGenTextures(GLsizei n, GLuint *textures);
参数:
- n
指定要生成的纹理名称的数量。
- textures
指向存储生成的纹理名称的数组的指针。
3.绑定纹理对象
void glBindTexture(GLenum target, GLuint texture);
参数:
- target
需要绑定的特定纹理对象类型。必须是 GL_TEXTURE_1D、GL_TEXTURE_2D 或 GL_TEXTURE_3D等之一。
- texture
绑定完纹理对象后,所有的纹理加载和纹理参数设置只影响当前绑定的纹理对象。
4.设置纹理参数
void glTexParameteri(GLenum target, GLenum pname, GLint param);
参数:
- target
指定这些参数将要应用到哪个纹理模式上,它可以是 GL_TEXTURE_1D、GL_TEXTURE_2D 或 GL_TEXTURE_3D 等。
- pname
指定需要设置哪个纹理参数。
- param
用于设置特定的纹理参数的值。
纹理过滤
纹理坐标不依赖于分辨率(Resolution),它可以是任意浮点值,当你有一个很大的物体但是纹理的分辨率很低的时候这就变得很重要了。
根据一个拉伸或收缩的纹理贴图计算颜色片段的过程称为纹理过滤(Texture Filtering)。
- 放大过滤器参数名:GL_TEXTURE_MAG_FILTER
- 缩小过滤器参数名:GL_TEXTURE_MIN_FILTER
纹理过滤有很多个选项,但有两种是最重要:
- 邻近过滤(GL_NEAREST):OpenGL会选择中心点最接近纹理坐标的那个像素。
- 线性过滤(GL_LINEAR):基于纹理坐标附近的纹理像素,计算出一个插值,近似得出这些纹理像素之间的颜色。
纹理环绕
纹理坐标的范围通常是从(0, 0)到(1, 1),那如果我们把纹理坐标设置在范围之外会发生什么?OpenGL 会根据当前纹理环绕模式(Wrapping Mode)进行处理。
GL_REPEAT | 重复纹理图像。默认行为。 |
GL_CLAMP_TO_EDGE | 超出的部分会重复纹理坐标的边缘 |
GL_CLAMP_TO_BORDER | 超出的坐标为用户指定的边缘颜色。 |
5.附加纹理图像
void glTexImage2D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid * data);
参数:
- target
指定了纹理目标类型。必须是 GL_TEXTURE_1D、GL_TEXTURE_2D 或 GL_TEXTURE_3D等之一。
- level
指定多级渐远纹理的基本。如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别。
- internalFormat
告诉OpenGL我们希望把纹理储存为何种格式。
- width
纹理的宽度
- height
纹理的高度
- border
默认设置为0(历史遗留问题)。
- format
源图的格式。
- type
源图的数据类型。
- data
源图的图像数据。
6.生成多级渐远纹理
调用 glTexImage2D 后,当前绑定的纹理对象就会附加纹理图像。然而,目前只有基本级别(Base-level)的纹理图像被加载。
void glGenerateMipmap(GLenum target);
参数:
- target
指定纹理对象绑定到的目标。必须是 GL_TEXTURE_1D、GL_TEXTURE_2D 或 GL_TEXTURE_3D 等之一。
7.顶点数据增加纹理坐标
// 顶点数据
float vertices[] = {
// --- position --- --- color --- - texture -
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // 左上角
0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // 右上角
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f // 右下角
};
// --- 位置属性 ---
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GL_FLOAT), 0);
glEnableVertexAttribArray(0);
// --- 颜色属性 ---
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GL_FLOAT), (void*)(3 * sizeof(GL_FLOAT)));
glEnableVertexAttribArray(1);
// --- 纹理属性 ---
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GL_FLOAT), (void*)(6 * sizeof(GL_FLOAT)));
glEnableVertexAttribArray(2);
8.激活纹理单元
在OpenGL中,一个纹理的位置值通常称为一个纹理单元(Texture Unit)。
可用纹理单元的数量取决于图形卡上提供的数量。OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从 GL_TEXTURE0 到 GL_TEXTRUE15。
一个纹理的默认纹理单元是0,它是默认的激活纹理单元。纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。
void glActiveTexture(GLenum texture);
参数:
- texture
指定激活的纹理单元。
9.绑定纹理
void glBindTexture(GLenum target, GLuint texture);
参数:
- target
指定纹理对象绑定到的目标。必须是 GL_TEXTURE_1D、GL_TEXTURE_2D 或 GL_TEXTURE_3D 等之一。
- texture
指定的纹理对象名。
10.在着色器中通过纹理采样器获取纹素数据
顶点shader
#version 330 core
layout(location=0) in vec3 position;
layout(location=1) in vec3 color;
layout(location=2) in vec2 texCoord;
out vec3 v_Color;
out vec2 v_TexCoord;
void main()
{
gl_Position = vec4(position, 1.0f);
v_Color = color;
v_TexCoord = texCoord;
}
片元shader
#version 330 core
in vec3 v_Color;
in vec2 v_TexCoord;
uniform sampler2D u_Texture;
void main()
{
vec4 texColor = texture(u_Texture, v_TexCoord);
gl_FragColor = texColor * vec4(v_Color, 1.0f);
}
第5行:定义一个 uniform 变量的纹理采样器。
第9行:通过texture()函数获取纹理采样器上的纹素数据。
第10行:将纹理颜色和顶点颜色融合输出。
完整项目
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "src/Shader.h"
#include "src/Texture2D.h"
unsigned int VBO;
unsigned int IBO;
// !!!使用指针全局变量,为了保证 tex 在执行完 init() 函数后不被析构。
Texture2D* tex;
void init() {
// 顶点数据
float vertices[] = {
// --- position --- --- color --- - texture -
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, // 左下角
-0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f, // 左上角
0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, // 右上角
0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f // 右下角
};
// 绘制顶点的顺序
unsigned int indices[] = {
0, 1, 2, // 第一个三角形的顶点数组下标
2, 0, 3 // 第二个三角形的顶点数组下标
};
// 创建一个缓存对象名,并存储到VBO
glGenBuffers(1, &VBO);
// 将缓存对象绑定到GL_ARRAY_BUFFER
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// 把顶点数据复制到当前绑定的缓存对象
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 创建一个缓存对象名,并存储到IBO
glGenBuffers(1, &IBO);
// 将缓存对象绑定到GL_ELEMENT_ARRAY_BUFFER
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
// 把顶点索引数据复制到当前绑定的缓存对象
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// --- 位置属性 ---
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GL_FLOAT), 0);
glEnableVertexAttribArray(0);
// --- 颜色属性 ---
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(GL_FLOAT), (void*)(3 * sizeof(GL_FLOAT)));
glEnableVertexAttribArray(1);
// --- 纹理属性 ---
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(GL_FLOAT), (void*)(6 * sizeof(GL_FLOAT)));
glEnableVertexAttribArray(2);
// 纹理
tex = new Texture2D("res/textures/OpenGL.png");
// 将纹理绑定到相应的纹理单元上并激活
tex->Bind();
// 着色器
Shader shader("res/shaders/Basic.vert", "res/shaders/Basic.frag");
shader.Bind();
}
void display() {
// 绘制
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}
void clear() {
// 删除缓存对象
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &IBO);
}
// main
int main(void)
{
GLFWwindow* window;
/* Initialize the library */
if (!glfwInit())
return -1;
/* Create a windowed mode window and its OpenGL context */
window = glfwCreateWindow(640, 480, "Texture", NULL, NULL);
if (!window)
{
glfwTerminate();
return -1;
}
/* Make the window's context current */
glfwMakeContextCurrent(window);
// glew init
if (glewInit() != GLEW_OK) {
std::cout << "glew init error!" << std::endl;
}
// print opengl version
std::cout << "OpenGL version :" << glGetString(GL_VERSION) << std::endl;
/* init */
init();
/* Loop until the user closes the window */
while (!glfwWindowShouldClose(window))
{
/* Render here */
glClear(GL_COLOR_BUFFER_BIT);
/* display */
display();
/* Swap front and back buffers */
glfwSwapBuffers(window);
/* Poll for and process events */
glfwPollEvents();
}
glfwTerminate();
/* clear */
clear();
return 0;
}
运行结果:
GitHub[作揖]
https://github.com/zGameDeveloper/LearnOpenGL/tree/main/Project/07_Texture
本文暂时没有评论,来添加一个吧(●'◡'●)