前语
精美的游戏画面离不开纹路的运用,这章咱们完成游戏引擎中非常根底的功能-纹路加载(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
#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的接口,再往下完成各个渠道的纹路功能,当前咱们仅完成OpenGL渠道的2D纹路。继承联系如下:
基类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中纹路坐标的原点在左下角,图片坐标的原点在左上角:
所以一般在加载图片资源时,需求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,并加载一张格子图,作用如下:
格子图原图:
用这种上下左右不对称的格子图,便利校验加载中的小错误,比如图片加载方向不正确。
拓宽极点数据,添加纹路特点
拓宽矩形极点: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();
}
假如你的代码运行正确,能看到窗口中有如上文所述的格子图片。
完好代码&总结
本章节对应的代码修正如下:
- std::unique、std::shared替换
github.com/summer-go/H…
- Texture封装
github.com/summer-go/H…