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

前言

精美的游戏画面离不开纹理的使用,这章我们实现游戏引擎中非常基础的功能-纹理加载(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

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

  1. 将纹理加载、绑定的重复逻辑封装起来
  2. 考虑跨平台,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();
}

完整代码&总结

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

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

github.com/summer-go/H...

  1. Texture封装

github.com/summer-go/H...

相关推荐
闲晨1 分钟前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
UestcXiye1 小时前
《TCP/IP网络编程》学习笔记 | Chapter 3:地址族与数据序列
c++·计算机网络·ip·tcp
霁月风3 小时前
设计模式——适配器模式
c++·适配器模式
jrrz08283 小时前
LeetCode 热题100(七)【链表】(1)
数据结构·c++·算法·leetcode·链表
咖啡里的茶i3 小时前
Vehicle友元Date多态Sedan和Truck
c++
海绵波波1073 小时前
Webserver(4.9)本地套接字的通信
c++
@小博的博客3 小时前
C++初阶学习第十弹——深入讲解vector的迭代器失效
数据结构·c++·学习
爱吃喵的鲤鱼4 小时前
linux进程的状态之环境变量
linux·运维·服务器·开发语言·c++
7年老菜鸡5 小时前
策略模式(C++)三分钟读懂
c++·qt·策略模式
Ni-Guvara5 小时前
函数对象笔记
c++·算法