本节目录

  • 纹理基础
  • 纹理技术
  • 应用纹理
  • 纹理类

纹理基础

图片实际上就是一张纹理,为了表现丰富的计算机世界,纹理是必不可少的.

首先需要解决的是如何将一张图片映射到我们的计算机世界中.我们使用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: 边缘纹理

效果如下:

alt text

MipMap

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

alt text

这种方法带来的开销也非常小.

纹理过滤

纹理是有分辨率的,但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);
}