本节目录
纹理基础
图片实际上就是一张纹理,为了表现丰富的计算机世界,纹理是必不可少的.
首先需要解决的是如何将一张图片映射到我们的计算机世界中.我们使用x和y轴的两个0-1的数来表示纹理的坐标,即uv坐标
对于我们的三角形,我们可以为每个顶点指定uv坐标,这样就可以和纹理对应起来了.
纹理技术
Wrapping
当纹理z轴坐标超过1时,我们需要对纹理坐标进行wrap,也就是如何处理uv大于1的情况.
OpenGL提供了四种选择:
- GL_REPEAT: 重复纹理
- GL_MIRRORED_REPEAT: 镜像重复纹理
- GL_CLAMP_TO_EDGE: 贴边纹理
- GL_CLAMP_TO_BORDER: 边缘纹理
效果如下:

MipMap
想象一下,当镜头拉远,一个很远的物体只占屏幕的4个像素时,如果还让其在一张512x512的纹理上采样,想想都不太高效.
所以可以生成各种分辨率的纹理,然后根据距离摄像机的远近选择合适的纹理.

这种方法带来的开销也非常小.
纹理过滤
纹理是有分辨率的,但uv坐标”没有”.这就导致opengl需要知道如何将纹理像素映射到uv坐标上.
有三种过滤方式:
- point: 点采样,只取一个像素
- linear: 线性采样,取四个相邻像素的平均值
- trilinear: 三线性采样,取八个相邻像素(上下两层)的平均值
分别对应了OpenGL给定的三个枚举值:
- GL_NEAREST: 点采样
- GL_LINEAR: 线性采样
- GL_LINEAR_MIPMAP_LINEAR: 三线性采样
应用纹理
更改我们的vertex:
float vertices[] = {
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f };
|
更改顶点属性
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,8*sizeof(float),(void*)0); glEnableVertexAttribArray(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);
|
在VertexShader中添加纹理坐标的输入和输出
#version 330 core
layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor; layout (location = 2) in vec2 aTexCoord;
out vec4 vertexColor; out vec2 texCoord;
void main() { gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); vertexColor = vec4(aColor.x, aColor.y, aColor.z, 1.0); texCoord = aTexCoord; }
|
在 FragmentShader 中添加纹理采样
#version 330 core
out vec4 FragColor;
in vec4 vertexColor; in vec2 texCoord;
uniform vec4 ourColor; uniform sampler2D Texture0; uniform sampler2D Texture1;
void main() { vec4 texColor0 = texture(Texture0, texCoord); vec4 texColor1 = texture(Texture1, texCoord); FragColor = texColor0 * texColor1 * ourColor; }
|
问题来了,Texture0和Texture1这两个uniform变量是怎么来的?
和OpenGL其他资源一样,典型创建纹理代码如下:
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);
|
在使用纹理时,我们需要绑定纹理对象到对应的纹理单元(texture unit)上,然后在FragmentShader中使用texture
函数来采样纹理.
当只有一个纹理时,opengl会自动将其绑定到GL_TEXTURE0上,当有多个纹理时,我们需要手动绑定到对应的纹理单元上.
glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture1); glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, texture2);
glBindVertexArray(VAO); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
|
纹理类
显而易见,我们可以为纹理封装一个类减少代码量.
为了读取图片内容,我们使用(stb_image.h库)[https://github.com/nothings/stb/blob/master/stb_image.h]
Texture.hpp
#pragma once
#include <string> #include <iostream> #include "glad/glad.h"
#include "stb_image.h"
class Texture { public: unsigned int id; int width, height, nrChannels; GLenum textureSlotIndex; std::string texName;
public: Texture(std::string filename,GLenum textureSlotIndex,std::string texName = "default"); ~Texture(){};
public: void Activate(unsigned int shaderProgramID,std::string uniformName); };
|
Texture.cpp
#include "Texture/Texture.hpp"
Texture::Texture(std::string filename,GLenum textureSlotIndex,std::string texName) { this->texName = texName; this->textureSlotIndex = textureSlotIndex;
stbi_set_flip_vertically_on_load(true); unsigned char* data = stbi_load(filename.c_str(), &width, &height, &nrChannels, 0); if (!data) { std::cout << "Failed to load texture" << std::endl; return; }
glGenTextures(1, &id); glActiveTexture(textureSlotIndex); glBindTexture(GL_TEXTURE_2D, id); if(nrChannels == 3) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); else if(nrChannels == 4) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); else std::cout << "Texture format not supported" << std::endl;
glGenerateMipmap(GL_TEXTURE_2D); stbi_image_free(data);
}
void Texture::Activate(unsigned int shaderProgramID,std::string uniformName) { glActiveTexture(textureSlotIndex); glBindTexture(GL_TEXTURE_2D, id); glUniform1i(glGetUniformLocation(shaderProgramID, uniformName.c_str()), textureSlotIndex-GL_TEXTURE0); }
|