上一篇中,有个类名笔误写错了,更正为:
Renderer2D-->Sandbox2D,代码参考:
github.com/summer-go/H…
前言
当前实现的是一个2D的渲染器,所有的2D的绘制都可以抽象为矩形绘制、2D纹理处理。而所有的2D矩形绘制都可以用相同的4个顶点表达。
可以将Sandbox2D中这部分通用的逻辑抽象出来,要达到的目标是:绘制一个矩形只需要传矩形的坐标、尺寸、纹理即可,不用每次都声明顶点数据。
这里简单处理,约定所有的矩形都是4个坐标,即两组三角形,矩形的中心位于OpenGL的原点。
2D渲染-变换与纹理
Renderer2D实现
仿照Renderer流程设计Renderer2D,除了初始化接口,增加Renderer2D特有的绘制矩形的接口DrawQuad。
DrawQuad()有两个重载的实现,其中一个position传的是vec3类型的,用于表示前后顺序,position第三个分量z越小越靠前,默认为0.
Sandbox/Hazel/src/Hazel/Renderer/Renderer2D.h
#pragma once
#include "OrthographicCamera.h"
namespace Hazel {
class Renderer2D {
public:
static void Init();
static void Shutdown();
static void BeginScene(const OrthographicCamera& camera);
static void EndScene();
// Primitives
static void DrawQuad(const glm::vec2& position, const glm::vec2& size, const glm::vec4& color);
static void DrawQuad(const glm::vec3& position, const glm::vec2& size, const glm::vec4& color);
};
}
大部分的逻辑是从Sandbox2D.cpp中抠出来的,设计Renderer2DStorage数据结构,封装Renderer2D中用到的静态数据。
原作者编程的功底很好,非常注意封装,不会让变量到处散落。
Sandbox/Hazel/src/Hazel/Renderer/Renderer2D.cpp
#include "Renderer2D.h"
#include "VertexArray.h"
#include "Shader.h"
#include "Platform/OpenGL/OpenGLShader.h"
#include "RenderCommand.h"
namespace Hazel {
struct Renderer2DStorage{
Ref<VertexArray> QuadVertexArray;
Ref<Shader> FlatColorShader;
};
static Renderer2DStorage* s_Data;
void Renderer2D::Init() {
s_Data = new Renderer2DStorage();
s_Data->QuadVertexArray = Hazel::VertexArray::Create();
float squareVertices[3 * 4] = {
-0.5, -0.5f, 0.0f,
0.5, -0.5f, 0.0f,
0.5, 0.5f, 0.0f,
-0.5, 0.5f, 0.0f,
};
Hazel::Ref<Hazel::VertexBuffer> squareVB;
squareVB.reset(Hazel::VertexBuffer::Create(squareVertices, sizeof(squareVertices)));
squareVB->SetLayout({
{Hazel::ShaderDataType::Float3, "a_Position"}
});
s_Data->QuadVertexArray->AddVertexBuffer(squareVB);
uint32_t squareIndices[6] = {0, 1, 2, 2,3, 0};
Hazel::Ref<Hazel::IndexBuffer> squareIB;
squareIB.reset(Hazel::IndexBuffer::Create(squareIndices, sizeof(squareIndices) / sizeof(uint32_t)));
s_Data->QuadVertexArray->SetIndexBuffer(squareIB);
s_Data->FlatColorShader = Hazel::Shader::Create("../assets/shaders/FlatColor.glsl");
}
void Renderer2D::Shutdown() {
delete s_Data;
}
void Renderer2D::BeginScene(const OrthographicCamera &camera) {
s_Data->FlatColorShader->Bind();
std::dynamic_pointer_cast<Hazel::OpenGLShader>(s_Data->FlatColorShader)->UploadUniformMat4("u_ViewProjection", camera.GetViewProjectionMatrix());
std::dynamic_pointer_cast<Hazel::OpenGLShader>(s_Data->FlatColorShader)->UploadUniformMat4("u_Transform", glm::mat4(1.0f));
}
void Renderer2D::EndScene() {
}
void Renderer2D::DrawQuad(const glm::vec2 &position, const glm::vec2 &size, const glm::vec4 &color) {
DrawQuad({position.x, position.y, 0.0f}, size, color);
}
void Renderer2D::DrawQuad(const glm::vec3 &position, const glm::vec2 &size, const glm::vec4 &color) {
s_Data->FlatColorShader->Bind();
std::dynamic_pointer_cast<Hazel::OpenGLShader>(s_Data->FlatColorShader)->UploadUniformFloat4("u_Color", color);
s_Data->QuadVertexArray->Bind();
RenderCommand::DrawIndexed(s_Data->QuadVertexArray);
}
}
Renderer2D初始化放到Renderer中
Sandbox/Hazel/src/Hazel/Renderer/Renderer.cpp
...
#include "Renderer2D.h"
...
void Renderer::Init() {
RenderCommand::Init();
Renderer2D::Init();
}
Sandbox2D改造
Sandbox/src/Sandbox2D.cpp
void Sandbox2D::OnUpdate(Hazel::Timestep ts) {
// Update
m_CameraController.OnUpdate(ts);
// Render
Hazel::RenderCommand::SetClearColor({0.1f, 0.1f, 0.1f, 1.0});
Hazel::RenderCommand::Clear();
Hazel::Renderer2D::BeginScene(m_CameraController.GetCamera());
Hazel::Renderer2D::DrawQuad({0.0f, 0.0f}, {1.0f, 1.0f}, {0.8f, 0.2f, 0.3f, 1.0f});
Hazel::Renderer2D::EndScene();
}
基于Renderer2D,现在绘制一个矩形就非常容易了,一行DrawQuad就完成了。
代码修改参考:
github.com/summer-go/H…
变换(transform)
现在绘制的矩形,position默认值都是0,即处于OpenGL坐标系原点处。这当然不行,我们不可能只绘制一个处于原点的矩形,现在我们在Shader中加上接口,支持更改矩形的坐标。
Sandbox/Hazel/src/Hazel/Renderer/Shader.h
...
virtual const std::string& GetName() const = 0;
virtual void SetMat4(const std::string& name, const glm::mat4& value) = 0;
virtual void SetFloat3(const std::string& name, const glm::vec3& value) = 0;
virtual void SetFloat4(const std::string& name, const glm::vec4& value) = 0;
...
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLShader.h
...
const std::string &GetName() const override;
void SetMat4(const std::string& name, const glm::mat4& value) override;
void SetFloat3(const std::string &name, const glm::vec3 &value) override;
void SetFloat4(const std::string &name, const glm::vec4 &value) override;
...
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLShader.cpp
void OpenGLShader::SetMat4(const std::string& name, const glm::mat4& value) {
UploadUniformMat4(name, value);
}
void OpenGLShader::SetFloat3(const std::string &name, const glm::vec3 &value) {
UploadUniformFloat3(name, value);
}
void OpenGLShader::SetFloat4(const std::string &name, const glm::vec4 &value) {
UploadUniformFloat4(name, value);
}
Renderer2D中增加坐标、缩放等变换
Sandbox/Hazel/src/Hazel/Renderer/Renderer2D.cpp
...
#include "RenderCommand.h"
// BeginScene中更新Camera变换矩阵和正交投影变换矩阵
void Renderer2D::BeginScene(const OrthographicCamera &camera) {
s_Data->FlatColorShader->Bind();
s_Data->FlatColorShader->SetMat4("u_ViewProjection", camera.GetViewProjectionMatrix());
// s_Data->FlatColorShader->SetMat4("u_Transform", glm::mat4(1.0f));
}
// DrawQuad中增加矩形坐标、缩放的处理
void Renderer2D::DrawQuad(const glm::vec3 &position, const glm::vec2 &size, const glm::vec4 &color) {
s_Data->FlatColorShader->Bind();
s_Data->FlatColorShader->SetFloat4("u_Color", color);
glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) * glm::scale(glm::mat4(1.0), {size.x, size.y, 1.0f});
s_Data->FlatColorShader->SetMat4("u_Transform", transform);
s_Data->QuadVertexArray->Bind();
RenderCommand::DrawIndexed(s_Data->QuadVertexArray);
}
Sandbox2D中绘制两个不同位置、不同颜色的矩形
Sandbox/src/Sandbox2D.cpp
Hazel::Renderer2D::BeginScene(m_CameraController.GetCamera());
Hazel::Renderer2D::DrawQuad({-1.0f, 0.0f}, {0.8f, 0.8f}, {0.8f, 0.2f, 0.3f, 1.0f});
Hazel::Renderer2D::DrawQuad({0.5f, -0.5f}, {0.5f, 0.5f}, {0.2f, 0.3f, 0.8f, 1.0f});
Hazel::Renderer2D::EndScene();
代码修改参考:
github.com/summer-go/H…
纹理(texture)
纯色的矩形不能表达复杂的外观,我们对Renderer2D拓展,支持纹理的设置。
shader中纹理用int值表达,修改Shader,增加int类型值的设置
Sandbox/Hazel/src/Hazel/Renderer/Shader.h
virtual void SetInt(const std::string& name, const int value) = 0;
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLShader.h
void SetInt(const std::string &name, const int value) override;
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLShader.cpp
void OpenGLShader::SetInt(const std::string &name, const int value) {
UploadUniformInt(name, value);
}
设置纹理环绕方式为repeat
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLTexture.cpp
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
Renderer2D中增加接口,支持绘制带纹理的矩形
Sandbox/Hazel/src/Hazel/Renderer/Renderer2D.h
static void DrawQuad(const glm::vec2& position, const glm::vec2& size, const Ref<Texture2D>& texture);
static void DrawQuad(const glm::vec3& position, const glm::vec2& size, const Ref<Texture2D>& texture);
Renderer2D.cpp中增加对纹理的支持
Sandbox/Hazel/src/Hazel/Renderer/Renderer2D.cpp
// 1. 增加TextureShader
struct Renderer2DStorage{
Ref<VertexArray> QuadVertexArray;
Ref<Shader> FlatColorShader;
Ref<Shader> TextureShader;
};
...
void Renderer2D::Init() {
s_Data = new Renderer2DStorage();
s_Data->QuadVertexArray = Hazel::VertexArray::Create();
// 2. 顶点增加纹理坐标
float squareVertices[5 * 4] = {
-0.5, -0.5f, 0.0f, 0.0f, 0.0f,
0.5, -0.5f, 0.0f, 1.0f, 0.0f,
0.5, 0.5f, 0.0f, 1.0f, 1.0f,
-0.5, 0.5f, 0.0f, 0.0f, 1.0f
};
Hazel::Ref<Hazel::VertexBuffer> squareVB;
squareVB.reset(Hazel::VertexBuffer::Create(squareVertices, sizeof(squareVertices)));
// 3. Layout增加纹理的布局
squareVB->SetLayout({
{Hazel::ShaderDataType::Float3, "a_Position"},
{Hazel::ShaderDataType::Float2, "a_TexCoord"},
});
s_Data->QuadVertexArray->AddVertexBuffer(squareVB);
uint32_t squareIndices[6] = {0, 1, 2, 2,3, 0};
Hazel::Ref<Hazel::IndexBuffer> squareIB;
squareIB.reset(Hazel::IndexBuffer::Create(squareIndices, sizeof(squareIndices) / sizeof(uint32_t)));
s_Data->QuadVertexArray->SetIndexBuffer(squareIB);
s_Data->FlatColorShader = Hazel::Shader::Create("../assets/shaders/FlatColor.glsl");
// 4. 创建TextureShader
s_Data->TextureShader = Hazel::Shader::Create("../assets/shaders/Texture.glsl");
s_Data->TextureShader->Bind();
s_Data->TextureShader->SetInt("u_Texture", 0);
}
// 5. 初始化Shader(ColorShader、TextureShader)
void Renderer2D::BeginScene(const OrthographicCamera &camera) {
s_Data->FlatColorShader->Bind();
s_Data->FlatColorShader->SetMat4("u_ViewProjection", camera.GetViewProjectionMatrix());
// s_Data->FlatColorShader->SetMat4("u_Transform", glm::mat4(1.0f));
s_Data->TextureShader->Bind();
s_Data->TextureShader->SetMat4("u_ViewProjection", camera.GetViewProjectionMatrix());
}
void Renderer2D::DrawQuad(const glm::vec2 &position, const glm::vec2 &size, const Ref<Texture2D> &texture) {
DrawQuad({position.x, position.y, 0.f}, size, texture);
}
// 6. DrawQuad增加纹理的绑定
void Renderer2D::DrawQuad(const glm::vec3 &position, const glm::vec2 &size, const Ref<Texture2D> &texture) {
s_Data->TextureShader->Bind();
glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) * glm::scale(glm::mat4(1.0), {size.x, size.y, 1.0f});
s_Data->TextureShader->SetMat4("u_Transform", transform);
texture->Bind();
s_Data->QuadVertexArray->Bind();
RenderCommand::DrawIndexed(s_Data->QuadVertexArray);
}
后面会将该带纹理的矩形放大,在Texture.glsl中,修改坐标,缩小10倍,让纹理显的小一点,抵消矩形坐标的放大。
Sandbox/assets/shaders/Texture.glsl
...
void main()
{
// 采样后的颜色做些处理,使其偏向红色
color = texture(u_Texture, v_TexCoord * 10.0) * vec4(1.0, 0.8, 0.8, 1.0);
}
Sandbox2D中增加纹理
Sandbox/src/Sandbox2D.h
Hazel::Ref<Hazel::Texture2D> m_CheckerboardTexture;
Sandbox/src/Sandbox2D.cpp
void Sandbox2D::OnAttach()
{
// 纹理初始化
m_CheckerboardTexture = Hazel::Texture2D::Create("assets/textures/Checkerboard.png");
}
void Sandbox2D::OnDetach()
{
}
void Sandbox2D::OnUpdate(Hazel::Timestep ts)
{
// Update
m_CameraController.OnUpdate(ts);
// Render
Hazel::RenderCommand::SetClearColor({ 0.1f, 0.1f, 0.1f, 1 });
Hazel::RenderCommand::Clear();
Hazel::Renderer2D::BeginScene(m_CameraController.GetCamera());
// 绘制三个矩形,第三个矩形是带纹理的,且放大5倍,作为背景
Hazel::Renderer2D::DrawQuad({ -1.0f, 0.0f }, { 0.8f, 0.8f }, { 0.8f, 0.2f, 0.3f, 1.0f });
Hazel::Renderer2D::DrawQuad({ 0.5f, -0.5f }, { 0.5f, 0.75f }, { 0.2f, 0.3f, 0.8f, 1.0f });
Hazel::Renderer2D::DrawQuad({ 0.0f, 0.0f, -0.1f }, { 5.0f, 5.0f }, m_CheckerboardTexture);
Hazel::Renderer2D::EndScene();
}
如果代码运行正常,能看到一个偏红色的棋盘,看不到另外两个矩形了。
如果不放大带纹理的矩形,能看到另外两个矩形
我们打开深度测试,就能看到另外两个矩形了,因为带纹理的矩阵z坐标是-0.1,更靠后,经过深度测试,会被另两个矩形遮挡。
深度测试在OpenGLRendererAPI的Init方法中打开
Hazel/src/Platform/OpenGL/OpenGLRendererAPI.cpp
void OpenGLRendererAPI::Init()
{
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_DEPTH_TEST);
}
运行正常能看到另两个矩形摆在前面
我们操作下w s a d q r,还有鼠标缩放,能看到camera的响应
增加纹理的代码:
github.com/summer-go/H…
优化-合并colorShader和TextureShader
当前的代码中,绘制3个矩形用到了两个shader,图形管线中,切换Shader有一定的性能损耗,我们考虑将这两个Shader合并成一个,以提升性能。
修改Texture.glsl
Sandbox/assets/shaders/Texture.glsl
#type fragment
#version 330 core
layout(location = 0) out vec4 color;
in vec2 v_TexCoord;
uniform vec4 u_Color;
uniform sampler2D u_Texture;
void main()
{
color = texture(u_Texture, v_TexCoord * 10.0) * u_Color;
}
将纹理采样和u_Color点乘,如果渲染成纯色,在外面控制将u_Texture设计成一个白色fffffff,则结果等于u_Color。
如果渲染成纹理色,则在外面将u_Color设置成{1,1,1,1},则结果等于u_Texture的颜色。
这只是一种简单的实现,但并不在工程上通用,这种接口很容易弄错,实际场景中的通用shader可能会实现的非常复杂。
增加按宽高创建纹理和设置纹理的接口,用于手动创建一个单色的纹理缓冲
Sandbox/Hazel/src/Hazel/Renderer/Texture.h
class Texture {
public:
...
virtual void SetData(void* data, uint32_t size) = 0;
virtual void Bind(uint32_t slot = 0) const = 0;
};
class Texture2D : public Texture {
public:
static Ref<Texture2D> Create(const std::string & path);
static Ref<Texture2D> Create(uint32_t width, uint32_t height);
};
Sandbox/Hazel/src/Hazel/Renderer/Texture.cpp
Ref<Texture2D> Texture2D::Create(uint32_t width, uint32_t height) {
switch (Renderer::GetAPI()) {
case RendererAPI::API::None: HZ_CORE_ASSERT(false, "RendererAPI::None is currently not supported!"); return nullptr;
case RendererAPI::API::OpenGL: return CreateRef<OpenGLTexture2D>(width, height);
}
HZ_CORE_ASSERT(false, "Unknow RendererAPI!");
return nullptr;
}
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 CreateRef<OpenGLTexture2D>(path);
}
HZ_CORE_ASSERT(false, "Unknow RendererAPI!");
return nullptr;
}
上面用到了一个模板接口,在Core.h中
Sandbox/Hazel/src/Hazel/Core/Core.h
template<typename T, typename ... Args>
constexpr Ref<T> CreateRef(Args&& ...args)
{
return std::make_shared<T>(std::forward<Args>(args)...);
}
子类实现
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLTexture.h
...
#include "OpenGLShader.h"
#include "glad/glad.h"
namespace Hazel {
class OpenGLTexture2D : public Texture2D{
public:
explicit OpenGLTexture2D(const std::string& path);
OpenGLTexture2D(uint32_t width, uint32_t height);
...
void SetData(void *data, uint32_t size) override;
private:
...
GLint m_InternalFormat = 0;
GLenum m_DataFormat = 0;
};
}
Sandbox/Hazel/src/Hazel/Platform/OpenGL/OpenGLTexture.cpp
// 按宽高生成一个空的纹理,先把内存占好
OpenGLTexture2D::OpenGLTexture2D(uint32_t width, uint32_t height)
: m_width(width), m_height(height)
{
m_InternalFormat = GL_RGBA8;
m_DataFormat = GL_RGBA;
glGenTextures(1, &m_RendererID);
glBindTexture(GL_TEXTURE_2D, m_RendererID);
glTexImage2D(GL_TEXTURE_2D, 0, m_InternalFormat, width, height, 0, m_DataFormat, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}
....
// 设置纹理数据,使用glTexSubImage2D
void OpenGLTexture2D::SetData(void *data, uint32_t size) {
uint32_t bpp = m_DataFormat == GL_RGBA ? 4 : 3;
HZ_CORE_ASSERT(size == m_width * m_height * bpp, "Data must be entire");
glBindTexture(GL_TEXTURE_2D, m_RendererID);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_width, m_height, m_DataFormat, GL_UNSIGNED_BYTE, data);
}
更新Renderer2D逻辑
Sandbox/Hazel/src/Hazel/Renderer/Renderer2D.cpp
...
// 增加WhiteTexture纹理,用于手动生成一个白色的纹理
struct Renderer2DStorage{
Ref<VertexArray> QuadVertexArray;
Ref<Shader> TextureShader;
Ref<Texture2D> WhiteTexture;
};
void Renderer2D::Init() {
...
// 初始化时,顶一个一个宽高1*1的纹理
s_Data->WhiteTexture = Texture2D::Create(1,1);
// 纹理为纯白色,只有一个像素,一个RGBA像素是32位8*4
uint32_t whiteTextureData = 0xffffffff;
s_Data->WhiteTexture->SetData(&whiteTextureData, sizeof(uint32_t));
s_Data->TextureShader = Hazel::Shader::Create("../assets/shaders/Texture.glsl");
s_Data->TextureShader->Bind();
s_Data->TextureShader->SetInt("u_Texture", 0);
}
// BeginScene中少了一个Shader的初始化了,只有两行,更清爽了
void Renderer2D::BeginScene(const OrthographicCamera &camera) {
//s_Data->FlatColorShader->Bind();
//s_Data->FlatColorShader->SetMat4("u_ViewProjection", camera.GetViewProjectionMatrix());
s_Data->TextureShader->Bind();
s_Data->TextureShader->SetMat4("u_ViewProjection", camera.GetViewProjectionMatrix());
}
// 绘制color型矩形,用TextureShader,替掉FlatColorShader
void Renderer2D::DrawQuad(const glm::vec3 &position, const glm::vec2 &size, const glm::vec4 &color) {
s_Data->TextureShader->SetFloat4("u_Color", color);
s_Data->WhiteTexture->Bind();
glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) * glm::scale(glm::mat4(1.0), {size.x, size.y, 1.0f});
s_Data->TextureShader->SetMat4("u_Transform", transform);
s_Data->QuadVertexArray->Bind();
RenderCommand::DrawIndexed(s_Data->QuadVertexArray);
}
void Renderer2D::DrawQuad(const glm::vec3 &position, const glm::vec2 &size, const Ref<Texture2D> &texture) {
s_Data->TextureShader->Bind();
s_Data->TextureShader->SetFloat4("u_Color", glm::vec4(1.0f));
glm::mat4 transform = glm::translate(glm::mat4(1.0f), position) * glm::scale(glm::mat4(1.0), {size.x, size.y, 1.0f});
s_Data->TextureShader->SetMat4("u_Transform", transform);
texture->Bind();
s_Data->QuadVertexArray->Bind();
RenderCommand::DrawIndexed(s_Data->QuadVertexArray);
}
合并Shader的代码:
github.com/summer-go/H…
总结
OpenGL4.5+ 创建纹理、设置纹理属性、设置纹理数据等API有一些变化,使用时注意区分版本。
高版本中使用:
// 创建纹理
glCreateTextures(GL_TEXTURE_2D, 1, &m_RendererID);
// 设置纹理属性
glTextureStorage2D(m_RendererID, 1, m_InternalFormat, m_Width, m_Height);
// 设置纹理真实数据
glTextureSubImage2D(m_RendererID, 0, 0, 0, m_Width, m_Height, dataFormat, GL_UNSIGNED_BYTE, data);
低版本中:
// 创建纹理
glGenTextures(1, &m_RendererID);
// 设置纹理属性,也可以同时设置纹理数据
glTexImage2D(GL_TEXTURE_2D, 0, m_InternalFormat, width, height, 0, m_DataFormat, GL_UNSIGNED_BYTE, nullptr);
// 设置纹理数据
glBindTexture(GL_TEXTURE_2D, m_RendererID);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_width, m_height, m_DataFormat, GL_UNSIGNED_BYTE, data);