前言
精美的游戏画面离不开纹理的使用,这章我们实现游戏引擎中非常基础的功能-纹理加载(Texture)。
正式开始之前,我们调整一下之前的代码,将std::unique、std::shared改成自定义类型,方便以后重构,实现自定义的资源生命周期管理。
std::shared是通用的智能指针,给c++编程带来极大的便利,一点点负面的影响是:
- 会增加额外的内存开销(计数)和内存碎片(不是通过make_shared的方式创建智能指针时会出现)
- 多线程访问时可能会有略微的性能劣化
替换std::unqiue、std::shared
设计自定义的智能指针,暂时只是一个模板别名,但是把调用的入口收拢到此处,方便以后修改。
分别用Scope和Ref替换std::unique_ptr和std::shared_ptr
Sandbox/Hazel/src/Hazel/Core.h
c++
#pragma once
#include <memory>
namespace Hazel {
...
template<typename T>
using Scope = std::unique_ptr<T>;
template<typename T>
using Ref = std::shared_ptr<T>;
}
然后将项目中用到智能指针的地方替换成Scope、Ref。
完整代码修改参考:
github.com/summer-go/H...
改完后,进入本章节正式的内容。
纹理(Texture)
设计Texture类,考虑两点:
- 将纹理加载、绑定的重复逻辑封装起来
- 考虑跨平台,OpenGL和Windows的dx对纹理的操作是不一样的
基于这两点,设计一个基类Texture,衍生出Texture2D、Texture2D的接口,再往下实现各个平台的纹理功能,当前我们仅实现OpenGL平台的2D纹理。继承关系如下:
基类Texture
参考上图,设计基类Texture、Texture2D,其中Texture抽象了纹理最基本的接口。当前仅有获取纹理宽高和Bind等方法。
Sandbox/Hazel/src/Hazel/Renderer/Texture.h
c++
#pragma once
#include <string>
#include <stdint.h>
#include "Core.h"
namespace Hazel {
class Texture {
public:
virtual ~Texture() = default;
virtual uint32_t GetWidth() const = 0;
virtual uint32_t GetHeight() const = 0;
virtual void Bind(uint32_t slot = 0) const = 0;
};
class Texture2D : public Texture {
public:
static Ref<Texture2D> Create(const std::string & path);
};
}
Texture2D类中区分平台创建对应的实现,这里我们暂时只有OpenGL平台实现
Sandbox/Hazel/src/Hazel/Renderer/Texture.cpp
c++
#include "Texture.h"
#include "Renderer/Renderer.h"
#include "Renderer/RendererAPI.h"
#include "Platform/OpenGL/OpenGLTexture.h"
namespace Hazel {
Ref<Texture2D> Texture2D::Create(const std::string &path) {
switch (Renderer::GetAPI()) {
case RendererAPI::API::None: HZ_CORE_ASSERT(false, "RendererAPI::None is currently not supported!"); return nullptr;
case RendererAPI::API::OpenGL: return std::make_shared<OpenGLTexture2D>(path);
}
HZ_CORE_ASSERT(false, "Unknow RendererAPI!");
return nullptr;
}
}
可以看到整个引擎中,只要和平台相关的地方,都会有switch的逻辑,还没想到更好的办法去优化。
图片加载库stb_image
引入一个很常见的图片加载库,stb_image,是一个头文件形式的库:
github.com/nothings/st...
将stb_image.h拷贝到工程中:
Sandbox/Hazel/vendor/stb_image/stb_image.h
按照注释说明,要使用stb_image,先声明一个变量:
Sandbox/Hazel/vendor/stb_image/stb_image.cpp
c++
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
实现类OpenGLTexture
OpenGLTexture中实现Texture的接口:
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLTexture.h
c++
#pragma once
#include <string>
#include "Renderer/Texture.h"
namespace Hazel {
class OpenGLTexture2D : public Texture2D{
public:
explicit OpenGLTexture2D(const std::string& path);
~OpenGLTexture2D() override;
uint32_t GetWidth() const override { return m_width;}
uint32_t GetHeight() const override { return m_height;}
void Bind(uint32_t slot = 0) const override;
private:
std::string m_Path;
uint32_t m_width, m_height;
uint32_t m_RendererID;
};
}
注意:OpenGL不同版本加载纹理的API有差异,我这里使用的是3.3版本,对OpenGL纹理操作不熟悉的参考:LearnOpenGL 纹理、LearnGL - 05 - Texture
注意OpenGL中纹理坐标的原点在左下角:
所以一般在加载图片资源时,需要flip操作,stbi_set_flip_vertically_on_load(1)。
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLTexture.cpp
c++
#include "OpenGLTexture.h"
#include "stb_image.h"
#include "Base.h"
#include "glad/glad.h"
namespace Hazel {
OpenGLTexture2D::OpenGLTexture2D(const std::string &path) : m_Path(path){
int width, height, channels;
stbi_set_flip_vertically_on_load(1);
stbi_uc* data = stbi_load(path.c_str(), &width, &height, &channels, 0);
HZ_CORE_ASSERT(data, "Failed to load image!");
m_width = width;
m_height = height;
glGenTextures(1, &m_RendererID);
glBindTexture(GL_TEXTURE_2D, m_RendererID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
// OpenGL 4.5 +版本的API发生变化,用下面的设置方法
/*
glTextureStorage2D(m_RendererID, 1, GL_RGB8, m_Width, m_Height);
glTextureParameteri(m_RendererID, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTextureParameteri(m_RendererID, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureSubImage2D(m_RendererID, 0, 0, 0, m_Width, m_Height, GL_RGB, GL_UNSIGNED_BYTE, data);
*/
stbi_image_free(data);
}
OpenGLTexture2D::~OpenGLTexture2D() {
glDeleteTextures(1, &m_RendererID);
}
void OpenGLTexture2D::Bind(uint32_t slot) const {
glActiveTexture(GL_TEXTURE0 + slot);
glBindTexture(GL_TEXTURE_2D, m_RendererID);
// OpenGL4.5+
// glBindTextureUnit(slot, m_RendererID);
}
}
Texture的使用
在SandBoxApp中使用Texture,并加载一张格子图,效果如下:
格子图原图:
用这种上下左右不对称的格子图,方便校验加载中的小错误,比如图片加载方向不正确。
拓展顶点数据,增加纹理属性
拓展矩形顶点:1)增加color属性,2)squareVB->SetLayout中增加color布局。
Sandbox/src/SandBoxApp.cpp
c++
// -----------------矩形--------------------
m_SquareVA.reset(Hazel::VertexArray::Create());
// 注意矩形,顶点按照逆时针排列
float squareVertices[5*4] = {
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f
};
};
Hazel::Ref<Hazel::VertexBuffer> squareVB(Hazel::VertexBuffer::Create(squareVertices, sizeof(squareVertices)));
squareVB->SetLayout({
{Hazel::ShaderDataType::Float3, "a_Position"},
{Hazel::ShaderDataType::Float2, "a_TexCoord"}
});
m_SquareVA->AddVertexBuffer(squareVB);
ExampleLayer的构造函数中,增加使用texture的着色器。
c++
std::string textureShaderVertexSrc = R"(
#version 330 core
layout(location = 0) in vec3 a_Position;
layout(location = 1) in vec2 a_Texture;
uniform mat4 u_ViewProjection;
uniform mat4 u_Transform;
out vec2 v_TexCoord;
void main() {
v_TexCoord = a_Texture;
gl_Position = u_ViewProjection * u_Transform * vec4(a_Position, 1.0);
}
)";
std::string textureShaderFragmentSrc = R"(
#version 330 core
layout(location=0) out vec4 color;
in vec2 v_TexCoord;
uniform sampler2D u_Texture;
void main() {
color = texture(u_Texture, v_TexCoord);
}
)";
m_TextureShader.reset(Hazel::Shader::Create(textureShaderVertexSrc, textureShaderFragmentSrc));
m_Texture = Hazel::Texture2D::Create("../assets/textures/Checkerboard.png");
// std::dynamic_pointer_cast<Hazel::OpenGLShader>(m_TextureShader)->Bind();
m_TextureShader->Bind();
std::dynamic_pointer_cast<Hazel::OpenGLShader>(m_TextureShader)->UploadUniformInt("u_Texture", 0);
OnUpdate中增加矩形纹理的绘制,去掉之前的三角形绘制。
c++
void OnUpdate(Hazel::Timestep ts) override{
...
m_Texture->Bind();
Hazel::Renderer::Submit(m_TextureShader, m_SquareVA, glm::scale(glm::mat4(1.0f), glm::vec3(1.5f)));
// Triangle
// Hazel::Renderer::Submit(m_Shader, m_VertexArray);
Hazel::Renderer::EndScene();
}
完整代码&总结
本章节对应的代码修改如下:
- std::unique、std::shared替换
- Texture封装