用大量的顶点来指定大量的颜色来丰富图像会造成巨大的开销,因此可以使用纹理来解决,纹理是一种2D图片,它可以为物体添加更多的细节。
因为纹理坐标的范围是(0,0)~(1,1)。如果一个图形的纹理采样在这个坐标的范围之外,那么OpenGL提供了四种方式来处理这种情况:
使用glTexParameter···
函数可以单独对每一个坐标轴进行设置(s、t、(如果是3D纹理则还有r))。
例:
glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
当将分辨率较低的纹理图像放到一个较大的物体上时,需要使用纹理过滤,不同的纹理过滤方式就是告诉OpenGL如何将纹理像素映射到纹理坐标。
纹理过滤最重要的两个方式:
由于两种方式的不同,邻近过滤会产生颗粒状的效果,而线性过滤能够产生更为平滑的图案,所以一般在放大(magnify)缩小(minify)时,设置不同的纹理方式能够让图像有更好的表现:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
当一个物体距离观察者很远的时候,它只会产生很少的片段,解析度并不高。如果此时仍然保持高分辨率的纹理采样,那么可能会产生不真实的效果并且存在不必要的内存额外开销。因此OpenGL使用多级渐远纹理来处理这种情况,后一个纹理图像是前一个的二分之一,以此类推,不同距离对应着不同的纹理效果。
OpenGL使用glGenerateMipmaps
函数创建多级渐远纹理。
为了指定不同的多级渐远纹理级别之间的过滤方式,可以使用四种选项中的一个代替原有的过滤方式:
并且这些过滤方式只有在缩小操作的时候才有用,因为多级渐远纹理主要就是在纹理被缩小时使用的,纹理放大的时候不会使用多级渐远纹理,而是使用前一小节的普通纹理过滤方式。(为放大过滤设置多级渐远纹理的选项会产生一个GL_INVALID_ENUM错误代码)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
课后题最后一题:能够改变两个不同纹理的能见度(并且能够上下左右移动物体)
#include
#define GLEW_STATIC
#include
#include
#include "./shader/Shader.h"
#include
void processInput(GLFWwindow*); //键盘或鼠标的输入
float mix_vertical_offset = 0.0f;
float mix_level_offset = 0.0f;
float mix_visibility = 0.2f;
int main() {
#pragma region Init
//初始化glfw
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//创建窗口
GLFWwindow* window = glfwCreateWindow(800, 600, "Learning OpenGL", nullptr, nullptr);
if (window == nullptr) {
std::cout << "Create window failed." << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
//初始化glew
glewExperimental = true;
if (glewInit() != GLEW_OK) {
std::cout << "Init glew failed." << std::endl;
glfwTerminate();
return -1;
}
glViewport(0, 0, 800, 600);
/*glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);*/
#pragma endregion
#pragma region Shader
Shader* shader = new Shader("resource/vertexShader.vert", "resource/fragmentShader.frag");
#pragma endregion
float vertex_data[] = {
// positions // colors // texture coords
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top right
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom right
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f // top left
};
unsigned int vertex_index[] = {
0, 1, 3, // first triangle
1, 2, 3 // second triangle
};
unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
unsigned int VBO;
glGenBuffers(1, &VBO); //生成一个VBO
glBindBuffer(GL_ARRAY_BUFFER, VBO); //将VBO绑定到GL_ARRAY_BUFFER
glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, GL_STATIC_DRAW); //将顶点数据填充进缓冲区
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(vertex_index), vertex_index, GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); //告诉OpenGL该如何具体地使用这些数据
glEnableVertexAttribArray(0); //开启location = 0位置上的顶点属性的使用权限(默认为禁用)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
unsigned int texture1, texture2;
glGenTextures(1, &texture1);
glBindTexture(GL_TEXTURE_2D, texture1);
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_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
int width = 0, height, nrChannels;
stbi_set_flip_vertically_on_load(true);
unsigned char* data = stbi_load("resource/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);
glGenTextures(1, &texture2);
glBindTexture(GL_TEXTURE_2D, texture2);
// set the texture wrapping parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); // set texture wrapping to GL_REPEAT (default wrapping method)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// set texture filtering parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// load image, create texture and generate mipmaps
data = stbi_load("resource/awesomeface.png", &width, &height, &nrChannels, 0);
if (data)
{
// note that the awesomeface.png has transparency and thus an alpha channel, so make sure to tell OpenGL the data type is of GL_RGBA
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
shader->use();
//glUniform1i(glGetUniformLocation(shader->m_ID, "texture1"), 0);
// or set it via the texture class
shader->setInt("texture1", 0);
shader->setInt("texture2", 1);
//render loop
while (!glfwWindowShouldClose(window)) {
//input
processInput(window);
//rendering
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
//glDrawArrays(GL_TRIANGLES, 0, 3);
glActiveTexture(GL_TEXTURE0); //激活纹理采样器0 (texture1)
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1); //激活纹理采样器1 (texture2)
glBindTexture(GL_TEXTURE_2D, texture2);
glBindVertexArray(VAO);
shader->setFloat("visibility", mix_visibility);
shader->setFloat("level_offset", mix_level_offset);
shader->setFloat("vertical_offset", mix_vertical_offset);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
//check and call events and swap the buffers
glfwSwapBuffers(window); //swap double buffer(双缓冲)
glfwPollEvents(); //监听是否有鼠标或者键盘输入的事件
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteBuffers(1, &EBO);
glfwTerminate(); //释放所有资源
return 0;
}
void processInput(GLFWwindow* window) {
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {
glfwSetWindowShouldClose(window, true); //按下ESC 将窗口关闭
}
if (glfwGetKey(window, GLFW_KEY_UP) == GLFW_PRESS) {
mix_visibility += 0.001f;
if (mix_visibility >= 1.0f) mix_visibility = 1.0f;
}
if (glfwGetKey(window, GLFW_KEY_DOWN) == GLFW_PRESS) {
mix_visibility -= 0.001f;
if (mix_visibility <= 0.0f) mix_visibility = 0.0f;
}
if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) {
mix_vertical_offset += 0.05f;
if (mix_vertical_offset >= 1.0f) mix_vertical_offset = 1.0f;
}
if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) {
mix_vertical_offset -= 0.05f;
if (mix_vertical_offset <= -1.0f) mix_vertical_offset = -1.0f;
}
if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) {
mix_level_offset -= 0.05f;
if (mix_level_offset <= -1.0f) mix_level_offset = -1.0f;
}
if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) {
mix_level_offset += 0.05f;
if (mix_level_offset >= 1.0f) mix_level_offset = 1.0f;
}
}