2021SC@SDUSC
目录
1.Texture
2.TextureLoader
2.1Create
2.1.1生成纹理对象
?2.1.2图像加载
2.1.3环绕与过滤
2.2CreateColor
2.3CreateFromMemory
本节主要讲解的内容是有texture(纹理)相关的部分。
1.Texture
从注释可以看出,Texture类是对OpenGL texture的封装,它将TextureLoader作为友元类,有关TextureLoader的内容将会在下文讲到,现在先回到texture中,作为一个纹理的使用工具,texture的成员变量包括了:
? ? ? ? const uint32_t?id(纹理id);
????????const uint32_t width(纹理宽度);
? ? ? ? const uint32_t height(纹理高度);
? ? ? ? const uint32_t bitsPerPixel(每像素的比特位数);
? ? ? ? const Settings::ETextureFilteringMode firstFilter(第一滤波模式);
? ? ? ? const Settings::ETextureFilteringMode secondFilter(第二滤波模式);
? ? ? ? const std::string path(纹理路径);
? ? ? ? const bool isMimapped(是否具有多级渐远纹理);
namespace Loaders { class TextureLoader; }
/**
* OpenGL texture wrapper
*/
class Texture
{
friend class Loaders::TextureLoader;
public:
void Bind(uint32_t p_slot = 0) const;
void Unbind() const;
private:
Texture(const std::string p_path, uint32_t p_id, uint32_t p_width, uint32_t p_height, uint32_t p_bpp, Settings::ETextureFilteringMode p_firstFilter, Settings::ETextureFilteringMode p_secondFilter, bool p_generateMipmap);
~Texture() = default;
public:
const uint32_t id;
const uint32_t width;
const uint32_t height;
const uint32_t bitsPerPixel;
const Settings::ETextureFilteringMode firstFilter;
const Settings::ETextureFilteringMode secondFilter;
const std::string path;
const bool isMimapped;
};
其中这里使用了ETextureFilteringMode类型的枚举值,封装了6种OpenGL的滤波类型:
/**
* OpenGL texture filtering mode enum wrapper
*/
enum class ETextureFilteringMode
{
NEAREST = 0x2600,
LINEAR = 0x2601,
NEAREST_MIPMAP_NEAREST = 0x2700,
LINEAR_MIPMAP_LINEAR = 0x2703,
LINEAR_MIPMAP_NEAREST = 0x2701,
NEAREST_MIPMAP_LINEAR = 0x2702
};
同时texture中的函数很简单,但还需要了解一下有关纹理单元的内容。
一个纹理的位置值通常称为一个纹理单元(Texture Unit)。一个纹理的默认纹理单元是0,它是默认的激活纹理单元,所以此处p_slot的默认值是0。
纹理单元的主要目的是让我们在着色器中可以使用多于一个的纹理。通过把纹理单元赋值给采样器,我们可以一次绑定多个纹理,只要我们首先激活对应的纹理单元。就像glBindTexture一样,我们可以使用glActiveTexture激活纹理单元,传入我们需要使用的纹理单元:
glActiveTexture(GL_TEXTURE0); // 在绑定纹理之前先激活纹理单元
glBindTexture(GL_TEXTURE_2D, texture);
激活纹理单元之后,接下来的glBindTexture函数调用会绑定这个纹理到当前激活的纹理单元.
OpenGL至少保证有16个纹理单元供你使用,也就是说你可以激活从GL_TEXTURE0到GL_TEXTRUE15。
它们都是按顺序定义的,所以我们也可以通过GL_TEXTURE0 + 8的方式获得GL_TEXTURE8,这在当我们需要循环一些纹理单元的时候会很有用。
解除绑定的过程也是类似。
2.TextureLoader
TextureLoader与上一节提到了两种loader类似,是对于纹理加载流程的封装,通过其中对的几个函数,我们会对于OpenGL的纹理加载流程有更深的了解。
class TextureLoader
{
public:
/**
* Disabled constructor
*/
TextureLoader() = delete;
/**
* Create a texture from file
* @param p_filePath
* @param p_firstFilter
* @param p_secondFilter
* @param p_generateMipmap
*/
static Texture* Create(const std::string& p_filepath, OvRendering::Settings::ETextureFilteringMode p_firstFilter, OvRendering::Settings::ETextureFilteringMode p_secondFilter, bool p_generateMipmap);
/**
* Create a texture from a single pixel color
* @param p_data
* @param p_firstFilder
* @param p_secondFilter
* @param p_generateMipmap
*/
static Texture* CreateColor(uint32_t p_data, OvRendering::Settings::ETextureFilteringMode p_firstFilter, OvRendering::Settings::ETextureFilteringMode p_secondFilter, bool p_generateMipmap);
/**
* Create a texture from memory
* @param p_data
* @param p_width
* @param p_height
* @param p_firstFilder
* @param p_secondFilter
* @param p_generateMipmap
*/
static Texture* CreateFromMemory(uint8_t* p_data, uint32_t p_width, uint32_t p_height, OvRendering::Settings::ETextureFilteringMode p_firstFilter, OvRendering::Settings::ETextureFilteringMode p_secondFilter, bool p_generateMipmap);
/**
* Reload a texture from file
* @param p_texture
* @param p_filePath
* @param p_firstFilter
* @param p_secondFilter
* @param p_generateMipmap
*/
static void Reload(Texture& p_texture, const std::string& p_filePath, OvRendering::Settings::ETextureFilteringMode p_firstFilter, OvRendering::Settings::ETextureFilteringMode p_secondFilter, bool p_generateMipmap);
/**
* Destroy a texture
* @param p_textureInstance
*/
static bool Destroy(Texture*& p_textureInstance);
};
2.1Create
2.1.1生成纹理对象
为了创建一个新的纹理对象,我们首先需要定义纹理的几个参数,并用OpenGL内置的glGenTextures函数生成一个纹理对象,并将它的id赋值给我们自定义的变量。
GLuint textureID;
int textureWidth;
int textureHeight;
int bitsPerPixel;
glGenTextures(1, &textureID);
glGenTextures函数首先需要输入生成纹理的数量,然后把它们储存在第二个参数的unsigned int数组中(我们的例子中只是单独的一个unsigned int),就像其他对象一样,我们需要绑定它,让之后任何的纹理指令都可以配置当前绑定的纹理:
glBindTexture(GL_TEXTURE_2D, textureID);
?2.1.2图像加载
在生成纹理时,我们还需要用到一个非常重要的图像加载库stb_image.h ,它能够加载大部分流行的文件格式,并且能够很简单得整合到你的工程之中。
要使用stb_image.h 加载图片,我们需要使用它的stbi_load函数:
unsigned char* dataBuffer = stbi_load(p_filepath.c_str(), &textureWidth, &textureHeight, &bitsPerPixel, 4);
这个函数首先接受一个图像文件的位置作为输入。接下来它需要三个int 作为它的第二、第三和第四、第五个参数,stb_image.h 将会用图像的宽度、高度和每像素比特位数、颜色通道的个数填充这四个变量。我们之后生成纹理的时候会用到的图像的宽度和高度的。
现在纹理已经绑定了,我们可以使用载入的图片数据生成一个纹理了。纹理可以通过glTexImage2D来生成:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, textureWidth, textureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, dataBuffer);
glGenerateMipmap(GL_TEXTURE_2D);
函数很长,参数也不少,所以我们一个一个地讲解:
- 第一个参数指定了纹理目标(Target)。设置为GL_TEXTURE_2D意味着会生成与当前绑定的纹理对象在同一个目标上的纹理(任何绑定到GL_TEXTURE_1D和GL_TEXTURE_3D的纹理不会受到影响)。
- 第二个参数为纹理指定多级渐远纹理的级别,如果你希望单独手动设置每个多级渐远纹理的级别的话。这里我们填0,也就是基本级别。
- 第三个参数告诉OpenGL我们希望把纹理储存为何种格式。我们的图像只有
RGB 值,因此我们也把纹理储存为RGB 值。 - 第四个和第五个参数设置最终的纹理的宽度和高度。我们之前加载图像的时候储存了它们,所以我们使用对应的变量。
- 下个参数应该总是被设为
0 (历史遗留的问题)。 - 第七第八个参数定义了源图的格式和数据类型。我们使用RGB值加载这个图像,并把它们储存为
char (byte)数组,我们将会传入对应值。 - 最后一个参数是真正的图像数据。
当调用glTexImage2D时,当前绑定的纹理对象就会被附加上纹理图像。然而,目前只有基本级别(Base-level)的纹理图像被加载了,如果要使用多级渐远纹理,我们必须手动设置所有不同的图像(不断递增第二个参数)。或者,直接在生成纹理之后调用glGenerateMipmap。这会为当前绑定的纹理自动生成所有需要的多级渐远纹理。
2.1.3环绕与过滤
纹理坐标的范围通常是从(0, 0)到(1, 1),那如果我们把纹理坐标设置在范围之外会发生什么?OpenGL默认的行为是重复这个纹理图像(我们基本上忽略浮点纹理坐标的整数部分),但OpenGL提供了更多的选择:
前面提到的每个选项都可以使用glTexParameter*函数对单独的一个坐标轴设置(s 、t (如果是使用3D纹理那么还有一个r )它们和x 、y 、z 是等价的):?
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
纹理坐标不依赖于分辨率(Resolution),它可以是任意浮点值,所以OpenGL需要知道怎样将纹理像素(Texture Pixel,也叫Texel,译注1)映射到纹理坐标。当你有一个很大的物体但是纹理的分辨率很低的时候这就变得很重要了,所以OpenGL也有对于纹理过滤(Texture Filtering)的选项。
当进行放大(Magnify)和缩小(Minify)操作的时候可以设置纹理过滤的选项,比如你可以在纹理被缩小的时候使用邻近过滤,被放大时使用线性过滤。
我们需要使用glTexParameter*函数为放大和缩小指定过滤方式。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, static_cast<GLint>(p_firstFilter));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, static_cast<GLint>(p_secondFilter));
?最后,生成了纹理和相应的多级渐远纹理后,我们还需要释放图像的内存:
stbi_image_free(data);
呈现出的结果如下:
OvRendering::Resources::Texture* OvRendering::Resources::Loaders::TextureLoader::Create(const std::string& p_filepath, OvRendering::Settings::ETextureFilteringMode p_firstFilter, OvRendering::Settings::ETextureFilteringMode p_secondFilter, bool p_generateMipmap)
{
GLuint textureID;
int textureWidth;
int textureHeight;
int bitsPerPixel;
glGenTextures(1, &textureID);
stbi_set_flip_vertically_on_load(true);
unsigned char* dataBuffer = stbi_load(p_filepath.c_str(), &textureWidth, &textureHeight, &bitsPerPixel, 4);
if (dataBuffer)
{
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, textureWidth, textureHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, dataBuffer);
if (p_generateMipmap)
{
glGenerateMipmap(GL_TEXTURE_2D);
}
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, static_cast<GLint>(p_firstFilter));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, static_cast<GLint>(p_secondFilter));
stbi_image_free(dataBuffer);
glBindTexture(GL_TEXTURE_2D, 0);
return new Texture(p_filepath, textureID, textureWidth, textureHeight, bitsPerPixel, p_firstFilter, p_secondFilter, p_generateMipmap);
}
else
{
stbi_image_free(dataBuffer);
glBindTexture(GL_TEXTURE_2D, 0);
return nullptr;
}
}
2.2CreateColor
CreateColor的实现与上一个函数类似,它将生成一个像素大小的单色纹理,所以不需要加载图片,而是将需要传入颜色的RGB值,并将纹理大小改为1×1:
OvRendering::Resources::Texture* OvRendering::Resources::Loaders::TextureLoader::CreateColor(uint32_t p_data, OvRendering::Settings::ETextureFilteringMode p_firstFilter, OvRendering::Settings::ETextureFilteringMode p_secondFilter, bool p_generateMipmap)
{
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &p_data);
if (p_generateMipmap)
{
glGenerateMipmap(GL_TEXTURE_2D);
}
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, static_cast<GLint>(p_firstFilter));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, static_cast<GLint>(p_secondFilter));
glBindTexture(GL_TEXTURE_2D, 0);
return new Texture("", textureID, 1, 1, 32, p_firstFilter, p_secondFilter, p_generateMipmap);
}
2.3CreateFromMemory
该函数与上一个函数的不同就是纹理的长宽需要从外界传入:
OvRendering::Resources::Texture* OvRendering::Resources::Loaders::TextureLoader::CreateFromMemory(uint8_t* p_data, uint32_t p_width, uint32_t p_height, OvRendering::Settings::ETextureFilteringMode p_firstFilter, OvRendering::Settings::ETextureFilteringMode p_secondFilter, bool p_generateMipmap)
{
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, p_width, p_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, p_data);
if (p_generateMipmap)
{
glGenerateMipmap(GL_TEXTURE_2D);
}
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, static_cast<GLint>(p_firstFilter));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, static_cast<GLint>(p_secondFilter));
glBindTexture(GL_TEXTURE_2D, 0);
return new Texture("", textureID, 1, 1, 32, p_firstFilter, p_secondFilter, p_generateMipmap);
}
(博客内容参考LearnOpenGL)
|