测试功能基类
本节搭建一个简单的测试框架,实现在窗口上显示一个菜单栏,点击不同的选项,进入不同的功能。
附加目录中添加src,方便添加头文件。
新建目录如下:
测试基类 Test.h,测试菜单TestMenu,用于管理所有的测试
#pragma once
#include <functional>
#include <vector>
#include <string>
#include <iostream>
namespace test {
class Test
{
public:
Test(){}
virtual ~Test(){}
virtual void OnUpdate(float deltaTime){}
virtual void OnRender(){}
/// <summary>
/// 使用ImGui绘制UI
/// </summary>
virtual void OnImGuiRender(){}
};
/// <summary>
/// 包含所有测试的集合
/// </summary>
class TestMenu : public Test
{
public:
TestMenu(Test*& currentTestPointer);
void OnImGuiRender() override;
template<typename T>
void RegisterTest(const std::string& name)
{
std::cout << "Registering test " << name << std::endl;
//lambda函数
m_Test.push_back(std::make_pair(name, []() { return new T(); }));
}
private:
//当前测试,指针的引用
Test*& m_CurrentTest;
//测试名字,测试指针的函数
std::vector<std::pair<std::string, std::function<Test*()>>> m_Test;
};
}
Test.cpp
#include "Test.h"
#include "ImGui/imgui.h"
namespace test {
TestMenu::TestMenu(Test*& currentTestPointer)
: m_CurrentTest(currentTestPointer)
{
}
void TestMenu::OnImGuiRender()
{
for (auto& test : m_Test) {
if (ImGui::Button(test.first.c_str()))
m_CurrentTest = test.second();
}
}
}
菜单项 – 修改窗口背景色
清除颜色,这里指修改背景色 TestClearColor.h
#pragma once
#include "Test.h"
namespace test {
/// <summary>
/// 清除颜色: 指的是用指定颜色填充背景色
/// </summary>
class TestClearColor : public Test
{
public:
TestClearColor();
~TestClearColor();
void OnUpdate(float deltaTime) override;
void OnRender() override;
void OnImGuiRender() override;
private:
float m_ClearColor[4];
};
}
TestClearColor.cpp
#include "TestClearColor.h"
#include "Renderer.h"
#include "imgui/imgui.h"
namespace test {
TestClearColor::TestClearColor()
:m_ClearColor{ 0.2f, 0.3f, 0.8f, 1.0f }
{
}
TestClearColor::~TestClearColor()
{
GLCall(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
}
void TestClearColor::OnUpdate(float deltaTime)
{
}
void TestClearColor::OnRender()
{
GLCall(glClearColor(m_ClearColor[0], m_ClearColor[1], m_ClearColor[2], m_ClearColor[3]));
GLCall(glClear(GL_COLOR_BUFFER_BIT));
}
void TestClearColor::OnImGuiRender()
{
ImGui::ColorEdit4("ClearColor", m_ClearColor);
}
}
菜单项 – 渲染贴图
这部分将之前写的渲染贴图的代码移动到单独的类中方便管理
TestTexture2D.h
#pragma once
#include "Test.h"
#include "VertexBuffer.h"
#include "VertexBufferLayout.h"
#include "Texture.h"
#include <memory>
namespace test {
/// <summary>
/// 渲染图片
/// </summary>
class TestTexture2D : public Test
{
public:
TestTexture2D();
~TestTexture2D();
void OnUpdate(float deltaTime) override;
void OnRender() override;
void OnImGuiRender() override;
private:
//unique_ptr持有对对象的独有权,即两个unique_ptr不能指向一个对象,不能进行复制操作只能进行移动操作
std::unique_ptr<VertexArray> m_VAO;
std::unique_ptr<VertexBuffer> m_VertexBuffer;
std::unique_ptr<IndexBuffer> m_IndexBuffer;
std::unique_ptr<Shader> m_Shader;
std::unique_ptr<Texture> m_Texture;
glm::mat4 m_Proj, m_View;
glm::vec3 m_TranslationA, m_TranslationB;
};
}
TestTexture2D.cpp
#include "TestTexture2D.h"
#include "Renderer.h"
#include "imgui/imgui.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
namespace test {
TestTexture2D::TestTexture2D()
: m_Proj(glm::ortho(0.0f, 960.0f, 0.0f, 540.0f, -1.0f, 1.0f)),
m_View(glm::translate(glm::mat4(1.0f), glm::vec3(-100, 0, 0))),
m_TranslationA(200, 200, 0), m_TranslationB(400, 200, 0)
{
float positions[] = {
100.0f, 100.0f, 0.0f, 0.0f,//0
200.0f, 100.0f, 1.0f, 0.0f,//1
200.0f, 200.0f, 1.0f, 1.0f,//2
100.0f, 200.0f, 0.0f, 1.0f,//3
};
unsigned int indices[] = {
0, 1, 2,
2, 3, 0,
};
//启用混合(默认不会启用)
GLCall(glEnable(GL_BLEND));
/**
* glBlendFunc(src, dest) 指定颜色因子
* src 指定输出颜色(RGBA)因子的计算方式, 默认为GL_ONE
* dest 指定目标颜色因子的计算方式, 默认为GL_ZERO
* GL_SRC_ALPHA 因为src的alpha为0, GL_ONE_MINUS_SRC_ALPHA 1-src.alpha
* RGBA = Srgba * GL_SRC_ALPHA + Drgba * GL_ONE_MINUS_SRC_ALPHA
**/
GLCall(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
GLCall(glBlendEquation(GL_FUNC_ADD));
m_VAO = std::make_unique<VertexArray>();
//4个顶点,每个顶点有4个浮点数
m_VertexBuffer = std::make_unique<VertexBuffer>(positions, 4 * 4 * sizeof(float));
VertexBufferLayout layout;
layout.Push<float>(2);
layout.Push<float>(2);
m_VAO->AddBuffer(*m_VertexBuffer, layout);
m_IndexBuffer = std::make_unique<IndexBuffer>(indices, 6);
m_Shader = std::make_unique<Shader>(("res/shaders/Basic.shader"));
m_Shader->Bind();
m_Texture = std::make_unique<Texture>("res/textures/ChernoLogo.png");
//纹理绑定到插槽0
m_Shader->SetUniform1i("u_Texture", 0);
}
TestTexture2D::~TestTexture2D()
{
GLCall(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
}
void TestTexture2D::OnUpdate(float deltaTime)
{
}
void TestTexture2D::OnRender()
{
GLCall(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
GLCall(glClear(GL_COLOR_BUFFER_BIT));
Renderer renderer;
m_Texture->Bind();
//绘制第一个icon
{
glm::mat4 model = glm::translate(glm::mat4(1.0f), m_TranslationA);
glm::mat4 mvp = m_Proj * m_View * model;
renderer.Draw(*m_VAO, *m_IndexBuffer, *m_Shader);
m_Shader->SetUniformMat4f("u_MVP", mvp);
}
//修改mvp矩阵,绘制第二个icon
{
glm::mat4 model = glm::translate(glm::mat4(1.0f), m_TranslationB);
glm::mat4 mvp = m_Proj * m_View * model;
renderer.Draw(*m_VAO, *m_IndexBuffer, *m_Shader);
m_Shader->SetUniformMat4f("u_MVP", mvp);
}
}
void TestTexture2D::OnImGuiRender()
{
ImGui::SliderFloat3("Translation A", &m_TranslationA.x, 0.0f, 960.0f);
ImGui::SliderFloat3("Translation B", &m_TranslationB.x, 0.0f, 960.0f);
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
}
}
菜单项 – 批渲染
批渲染是指将所有图形的顶点都放入同一个顶点缓冲区里,相应的扩大索引缓冲区,从而只绘制一次。
比如画两个正方形,如果不用批渲染
顶点缓冲区 = { position0, position1, position2, position3 }
索引缓冲区 = { 0, 1, 2, 2, 3, 0 }
渲染一个正方形就调用一次glDrawElements
使用批渲染
顶点缓冲区 = { position0, position1, position2, position3, position4, position5, position6, position7 }
索引缓冲区 = { 0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4 }
调用一次glDrawElements就可以渲染两个正方形
下面是代码实现
TestBatchRender.h
#pragma once
#include "Test.h"
#include "VertexBuffer.h"
#include "VertexBufferLayout.h"
#include "Texture.h"
#include <memory>
namespace test {
/// <summary>
/// 批渲染
/// </summary>
class TestBatchRender : public Test
{
public:
TestBatchRender();
~TestBatchRender();
void OnUpdate(float deltaTime) override;
void OnRender() override;
void OnImGuiRender() override;
private:
//unique_ptr持有对对象的独有权,即两个unique_ptr不能指向一个对象,不能进行复制操作只能进行移动操作
std::unique_ptr<VertexArray> m_VAO;
std::unique_ptr<VertexBuffer> m_VertexBuffer;
std::unique_ptr<IndexBuffer> m_IndexBuffer;
std::unique_ptr<Shader> m_Shader;
std::unique_ptr<Texture> m_Textures[2];
glm::mat4 m_Proj, m_View;
glm::vec3 m_Translation;
//控制第一个四边形左下角位置
float m_QuadPosition[2] = { 100, 100 };
};
}
TestBatchRender.cpp
#include "TestBatchRender.h"
#include "Renderer.h"
#include <array>
#include "imgui/imgui.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
namespace test {
struct Vec2
{
float x, y;
};
struct Vec3
{
float x, y, z;
};
struct Vec4
{
float x, y, z, w;
};
/// <summary>
/// 顶点
/// </summary>
struct Vertex
{
Vec3 Position;
Vec4 Color;
Vec2 TexCoords; //纹理坐标
float TexID; //第几个插槽
};
//最多四边形数量
const unsigned int MaxQuadCount = 1000;
//最大顶点数量
const unsigned int MaxVertexCount = MaxQuadCount * 4;
//最大索引缓冲区的数量
const unsigned int MaxIndexCount = MaxQuadCount * 6;
/// <summary>
/// 创建四边形
/// </summary>
static Vertex* CreateQuad(Vertex* target, float x, float y, float textureID)
{
//边长
float size = 100.0f;
//以左下角是四边形原点
target->Position = { x, y, 0.0f };
target->Color = { 0.2f, 0.6f, 1.0f, 1.0f };
target->TexCoords = { 0.0f, 0.0f };
target->TexID = textureID;
target++;
target->Position = { x + size, y, 0.0f };
target->Color = { 0.2f, 0.6f, 1.0f, 1.0f };
target->TexCoords = { 1.0f, 0.0f };
target->TexID = textureID;
target++;
target->Position = { x + size, y + size, 0.0f };
target->Color = { 0.2f, 0.6f, 1.0f, 1.0f };
target->TexCoords = { 1.0f, 1.0f };
target->TexID = textureID;
target++;
target->Position = { x, y + size, 0.0f };
target->Color = { 0.2f, 0.6f, 1.0f, 1.0f };
target->TexCoords = { 0.0f, 1.0f };
target->TexID = textureID;
target++;
return target;
}
TestBatchRender::TestBatchRender()
: m_Proj(glm::ortho(0.0f, 960.0f, 0.0f, 540.0f, -1.0f, 1.0f)),
m_View(glm::translate(glm::mat4(1.0f), glm::vec3(-100, 0, 0))),
m_Translation(0, 0, 0)
{
//float vertices[] = {
// 100.0f, 100.0f, 0.0f, 0.2f, 0.6f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f,
// 200.0f, 100.0f, 0.0f, 0.2f, 0.6f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
// 200.0f, 200.0f, 0.0f, 0.2f, 0.6f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f,
// 100.0f, 200.0f, 0.0f, 0.2f, 0.6f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f,
// 300.0f, 100.0f, 0.0f, 1.0f, 0.9f, 0.2f, 1.0f, 0.0f, 0.0f, 1.0f,
// 400.0f, 100.0f, 0.0f, 1.0f, 0.9f, 0.2f, 1.0f, 1.0f, 0.0f, 1.0f,
// 400.0f, 200.0f, 0.0f, 1.0f, 0.9f, 0.2f, 1.0f, 1.0f, 1.0f, 1.0f,
// 300.0f, 200.0f, 0.0f, 1.0f, 0.9f, 0.2f, 1.0f, 0.0f, 1.0f, 1.0f,
//};
//unsigned int indices[] = {
// 0, 1, 2, 2, 3, 0,
// 4, 5, 6, 6, 7, 4
//};
unsigned int indices[MaxIndexCount];
unsigned int offset = 0;
for (unsigned int i = 0; i < MaxIndexCount; i += 6)
{
indices[i + 0] = 0 + offset;
indices[i + 1] = 1 + offset;
indices[i + 2] = 2 + offset;
indices[i + 3] = 2 + offset;
indices[i + 4] = 3 + offset;
indices[i + 5] = 0 + offset;
offset += 4;
}
GLCall(glEnable(GL_BLEND));
GLCall(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
GLCall(glBlendEquation(GL_FUNC_ADD));
m_VAO = std::make_unique<VertexArray>();
m_VertexBuffer = std::make_unique<VertexBuffer>(nullptr, sizeof(Vertex) * MaxVertexCount);
VertexBufferLayout layout;
layout.Push<float>(3); //坐标x y z
layout.Push<float>(4); //颜色
layout.Push<float>(2); //纹理坐标
layout.Push<float>(1); //第几个纹理插槽
m_VAO->AddBuffer(*m_VertexBuffer, layout);
m_IndexBuffer = std::make_unique<IndexBuffer>(indices, MaxIndexCount);
m_Shader = std::make_unique<Shader>(("res/shaders/Batch.shader"));
m_Shader->Bind();
m_Textures[0] = std::make_unique<Texture>("res/textures/ChernoLogo.png");
m_Textures[1] = std::make_unique<Texture>("res/textures/HazelLogo.png");
//纹理绑定到两个插槽上
m_Textures[0]->Bind(0);
m_Textures[1]->Bind(1);
int samplers[2] = { 0, 1 };
m_Shader->SetUniform1iv("u_Textures", 2, samplers);
}
TestBatchRender::~TestBatchRender()
{
GLCall(glClearColor(0.0f, 0.0f, 0.0f, 1.0f));
GLCall(glBufferSubData(GL_ARRAY_BUFFER, 0, 0, nullptr));
GLCall(glBufferData(GL_ARRAY_BUFFER, 0, nullptr, GL_DYNAMIC_DRAW));
}
void TestBatchRender::OnUpdate(float deltaTime)
{
std::array<Vertex, MaxVertexCount> vertices;
Vertex* buffer = vertices.data();
for (int y = 0; y < 500; y += 100) {
for (int x = 300; x < 800; x += 100) {
buffer = CreateQuad(buffer, x, y, (x + y) / 100 % 2);
}
}
//创建一个可以移动的四边形
buffer = CreateQuad(buffer, m_QuadPosition[0], m_QuadPosition[1], 0.0f);
//每一帧,动态更新缓冲区数据
glBufferSubData(GL_ARRAY_BUFFER, 0, vertices.size() * sizeof(Vertex), vertices.data());
}
void TestBatchRender::OnRender()
{
GLCall(glClearColor(0.6f, 1.0f, 1.0f, 1.0f));
GLCall(glClear(GL_COLOR_BUFFER_BIT));
Renderer renderer;
glm::mat4 model = glm::translate(glm::mat4(1.0f), m_Translation);
glm::mat4 mvp = m_Proj * m_View * model;
renderer.Draw(*m_VAO, *m_IndexBuffer, *m_Shader);
m_Shader->SetUniformMat4f("u_MVP", mvp);
}
void TestBatchRender::OnImGuiRender()
{
ImGui::Begin("Controls");
ImGui::DragFloat2("Quad Position", m_QuadPosition, 0.1f);
ImGui::End();
}
}
为了支持动态修改顶点缓冲区,缓冲区使用类型改为动态
VertexBuffer.cpp
VertexBuffer::VertexBuffer(const void* data, unsigned int size)
{
GLCall(glGenBuffers(1, &m_RendererID));
GLCall(glBindBuffer(GL_ARRAY_BUFFER, m_RendererID));
//可以动态填充顶点缓冲区
GLCall(glBufferData(GL_ARRAY_BUFFER, size, data, GL_DYNAMIC_DRAW));
}
为了支持批渲染,新增一个shader
Batch.shader
#shader vertex
#version 330 core
layout(location = 0) in vec4 a_Position; //因为gl_Position是vec4,所有这里转换
layout(location = 1) in vec4 a_Color; //顶点颜色
layout(location = 2) in vec2 a_TexCoord; //纹理坐标
layout(location = 3) in float a_TexIndex; //第几个纹理
out vec4 v_Color;
out vec2 v_TexCoord;
out float v_TexIndex;
uniform mat4 u_MVP;
void main()
{
//正交投影与顶点位置相乘,把它们转换到-1至1空间(NDC空间)
gl_Position = u_MVP * a_Position;
v_Color = a_Color;
v_TexCoord = a_TexCoord;
v_TexIndex = a_TexIndex;
};
#shader fragment
#version 330 core
layout(location = 0) out vec4 color;
in vec4 v_Color;
in vec2 v_TexCoord;
in float v_TexIndex;
//采样数组
uniform sampler2D u_Textures[2];
void main()
{
int index = int(v_TexIndex);
//从纹理上采样的颜色
vec4 texColor = texture(u_Textures[index], v_TexCoord);
color = texColor;
};
最后主程序
Application.cpp
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "Renderer.h"
#include "VertexBufferLayout.h"
#include "Texture.h"
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"
#include "imgui/imgui.h"
#include "imgui/imgui_impl_glfw.h"
#include "imgui/imgui_impl_opengl3.h"
#include "tests/TestClearColor.h"
#include "tests/TestTexture2D.h"
#include "tests/TestBatchRender.h"
int main(void)
{
GLFWwindow* window;
if (!glfwInit())
return -1;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
window = glfwCreateWindow(960, 540, "Hello World", NULL, NULL);
if (!window)
{
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
GLenum err = glewInit();
if (GLEW_OK != err)
std::cout << err << std::endl;
std::cout << glGetString(GL_VERSION) << std::endl;
{
GLCall(glEnable(GL_BLEND));
GLCall(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
GLCall(glBlendEquation(GL_FUNC_ADD));
Renderer renderer;
//创建上下文环境,初始化
ImGui::CreateContext();
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui::StyleColorsDark();
//需要指定GLSL版本, 也就是shader中的version
const char* glsl_version = "#version 330";
ImGui_ImplOpenGL3_Init(glsl_version);
test::Test* currentTest = nullptr;
test::TestMenu* testMenu = new test::TestMenu(currentTest);
//从菜单开始
currentTest = testMenu;
testMenu->RegisterTest<test::TestClearColor>("Clear Color");
testMenu->RegisterTest<test::TestTexture2D>("2D Texture");
testMenu->RegisterTest<test::TestBatchRender>("Batch Render");
while (!glfwWindowShouldClose(window))
{
renderer.Clear();
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
if (currentTest)
{
currentTest->OnUpdate(0.0f);
currentTest->OnRender();
ImGui::Begin("Test");
//当前不在测试菜单,而在某个具体的测试中,上面加个返回箭头
if (currentTest != testMenu && ImGui::Button("<-"))
{
delete currentTest;
currentTest = testMenu;
}
currentTest->OnImGuiRender();
ImGui::End();
}
ImGui::Render();
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
glfwSwapBuffers(window);
glfwPollEvents();
}
delete currentTest;
if (currentTest != testMenu)
delete testMenu;
}
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext();
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}
效果
参考代码:地址