前语

精美的游戏画面离不开纹路的运用,这章咱们完成游戏引擎中非常根底的功能-纹路加载(Texture)。

正式开端之前,咱们调整一下之前的代码,将std::unique、std::shared改成自定义类型,便利今后重构,完成自定义的资源生命周期办理。

std::shared是通用的智能指针,给c++编程带来极大的便利,一点点负面的影响是:

  1. 会添加额定的内存开支(计数)和内存碎片(不是通过make_shared的方式创立智能指针时会呈现)
  2. 多线程拜访时可能会有略微的性能劣化

替换std::unqiue、std::shared

设计自定义的智能指针,暂时只是一个模板别名,可是把调用的入口收拢到此处,便利今后修正。

分别用Scope和Ref替换std::unique_ptr和std::shared_ptr

Sandbox/Hazel/src/Hazel/Core.h


#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类,考虑两点:

  1. 将纹路加载、绑定的重复逻辑封装起来
  2. 考虑跨渠道,OpenGL和Windows的dx对纹路的操作是不一样的

基于这两点,设计一个基类Texture,衍生出Texture2D的接口,再往下完成各个渠道的纹路功能,当前咱们仅完成OpenGL渠道的2D纹路。继承联系如下:

游戏引擎从零开始(23)-纹理封装

基类Texture

参考上图,设计基类Texture、Texture2D,其中Texture笼统了纹路最基本的接口。当前仅有获取纹路宽高和Bind等办法。
Sandbox/Hazel/src/Hazel/Renderer/Texture.h

#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

#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

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

完成类OpenGLTexture

OpenGLTexture中完成Texture的接口:

Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLTexture.h

#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中纹路坐标的原点在左下角,图片坐标的原点在左上角:

游戏引擎从零开始(23)-纹理封装

游戏引擎从零开始(23)-纹理封装

所以一般在加载图片资源时,需求flip操作,stbi_set_flip_vertically_on_load(1)。

Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLTexture.cpp

#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,并加载一张格子图,作用如下:

游戏引擎从零开始(23)-纹理封装

格子图原图:

游戏引擎从零开始(23)-纹理封装

用这种上下左右不对称的格子图,便利校验加载中的小错误,比如图片加载方向不正确。

拓宽极点数据,添加纹路特点

拓宽矩形极点:1)添加color特点,2)squareVB->SetLayout中添加color布局。
Sandbox/src/SandBoxApp.cpp

// -----------------矩形--------------------
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的着色器。

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中添加矩形纹路的绘制,去掉之前的三角形绘制。

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();
}

假如你的代码运行正确,能看到窗口中有如上文所述的格子图片。

完好代码&总结

本章节对应的代码修正如下:

  1. std::unique、std::shared替换

github.com/summer-go/H…

  1. Texture封装

github.com/summer-go/H…