【OpenGL/C++】面向对象扩展——测试环境
文章目录
- RunBase类展示:
- RunBase继承类展示:
- Main函数内容(使用示例):
在使用各种类对OpenGL的流程和API进行抽象和封装后,为了测试方便,还可以写一些类来对其进行进一步的封装。我写这个封装是为了方便测试使用。
封装的功能:
- 固定渲染流程,无需重复书写
- 外部调用Run来执行渲染代码
- 继承类重写Init和RenderLoop函数来执行渲染初始化和渲染循环中的操作
- 继承类重写UseCameraControl函数并修改返回值来确定是否开启摄像机操作
注意事项:
- 考虑实现的简单性,摄像机操作功能写在了类里并使用静态单例来注册到glfw上,并没有分离出去,我个人在使用时直接在main函数中替换具体类名来切换不同的测试示例。
- 类并不负责创建窗口等最开始的操作,下述有完整的代码展示。
RunBase类展示:
#pragma once
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "Shader.h"
#include "Camera.h"
#include <iostream>
#include <glm/glm.hpp>
using std::cout;
using namespace glm;
class RunBase
{
public:
//单例
static RunBase* Instance;
// Camera attributes
Camera camera;
bool keys[1024];
GLfloat lastX = 400, lastY = 300;
bool firstMouse = true;
float deltaTime;
// Light attributes
glm::vec3 lightPos;
//构造函数初始化参数
RunBase()
{
camera.Position = { 0, 0, 3 };
lightPos = { 1.2f, 1.0f, 2.0f };
Instance = this;
}
//渲染流程,外部调用
virtual void Run(GLFWwindow* window, float targetFrameTime)
{
deltaTime = targetFrameTime;
//开启深度测试
glEnable(GL_DEPTH_TEST);
if (UseCameraControl())
{
//隐藏指针
glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
//摄像机按键绑定
glfwSetKeyCallback(window, key_callback);
glfwSetCursorPosCallback(window, mouse_callback);
glfwSetScrollCallback(window, scroll_callback);
}
Init(window, targetFrameTime);
//在渲染循环中绘制指定的顶点数组对象,索引缓存,着色器
while (!glfwWindowShouldClose(window))
{
CheckFrameTimeOut(targetFrameTime);
RenderLoop(window, targetFrameTime);
glfwSwapBuffers(window);
if (UseCameraControl())
DoMovement();
glfwPollEvents();
}
}
//渲染前的准备,类可重写
virtual void Init(GLFWwindow* window, float targetFrameTime)
{
}
//渲染中操作,类可重写
virtual void RenderLoop(GLFWwindow* window, float targetFrameTime)
{
}
//是否开启相机控制
virtual bool UseCameraControl()
{
return true;
}
//移动函数
virtual void DoMovement()
{
if (keys[GLFW_KEY_W])
camera.ProcessKeyboard(FORWARD, deltaTime);
if (keys[GLFW_KEY_S])
camera.ProcessKeyboard(BACKWARD, deltaTime);
if (keys[GLFW_KEY_A])
camera.ProcessKeyboard(LEFT, deltaTime);
if (keys[GLFW_KEY_D])
camera.ProcessKeyboard(RIGHT, deltaTime);
}
void CheckFrameTimeOut(float targetFrameTime)
{
// 获取当前时间
double currentTime = glfwGetTime();
// 计算帧间隔
static double lastFrameTime = currentTime;
double frameDelta = currentTime - lastFrameTime;
// 计算帧时间
double frameTime = targetFrameTime - frameDelta;
// 等待剩余的帧时间
if (frameTime > 0.0)
{
glfwWaitEventsTimeout(frameTime);
}
}
static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (key >= 0 && key < 1024)
{
if (action == GLFW_PRESS)
Instance->keys[key] = true;
else if (action == GLFW_RELEASE)
Instance->keys[key] = false;
}
}
static void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
if (Instance->firstMouse)
{
Instance->lastX = xpos;
Instance->lastY = ypos;
Instance->firstMouse = false;
}
GLfloat xoffset = xpos - Instance->lastX;
GLfloat yoffset = Instance->lastY - ypos; // Reversed since y-coordinates go from bottom to left
Instance->lastX = xpos;
Instance->lastY = ypos;
Instance->camera.ProcessMouseMovement(xoffset, yoffset);
}
static void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
Instance->camera.ProcessMouseScroll(yoffset);
}
};
RunBase* RunBase::Instance = nullptr;
RunBase继承类展示:
LearnOpenGL的教程示例,十个箱子在固定位置旋转,并有各自的贴图,在制作了RunBase类后,使用时只需要专注于教程和测试的图形学部分。无需重复复制和束缚在其他C++代码上。
#pragma once
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include "Renderer.h"
#include "VertexBuffer.h"
#include "VertexArray.h"
#include "VertexBufferLayout.h"
#include "Texture.h"
#include "Shader.h"
#include "Camera.h"
#include <iostream>
#include "RunBase.hpp"
#include <glm/gtc/type_ptr.hpp>
using std::cout;
using namespace glm;
class MyTest : public RunBase
{
public:
VertexArray vao;
VertexBuffer vbo;
VertexBufferLayout layout;
Shader shader;
Renderer renderer;
Texture containerTexture;
glm::vec3* cubePositions;
MyTest() : RunBase()
{
shader.Init("res/shaders/Learn2.shader");
}
virtual void Init(GLFWwindow* window, float targetFrameTime) override
{
GLfloat vertices[] = {
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 1.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
0.5f, -0.5f, -0.5f, 1.0f, 1.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
0.5f, -0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, -0.5f, -0.5f, 0.0f, 1.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f,
0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
0.5f, 0.5f, 0.5f, 1.0f, 0.0f,
-0.5f, 0.5f, 0.5f, 0.0f, 0.0f,
-0.5f, 0.5f, -0.5f, 0.0f, 1.0f
};
cubePositions = new vec3[]{
glm::vec3(0.0f, 0.0f, 0.0f),
glm::vec3(2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3(2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3(1.3f, -2.0f, -2.5f),
glm::vec3(1.5f, 2.0f, -2.5f),
glm::vec3(1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};
vbo.Init(vertices, sizeof(vertices));
//创建顶点缓存布局,并链接到顶点数组上
layout.Push<float>(3);
layout.Push<float>(2);
//缓存和布局绑定到vao上
vao.AddBuffer(vbo, layout);
//设置纹理
containerTexture.Init("res/textures/container2.png");
containerTexture.Bind();
shader.Bind();
shader.SetUniform1i("u_Texture", 0);
}
virtual void RenderLoop(GLFWwindow* window, float targetFrameTime) override
{
renderer.Clear();
glm::mat4 view(1), proj(1);
//视图矩阵
view = camera.GetViewMatrix();
shader.SetUniformMat4f("view", glm::value_ptr(view));
//投影矩阵
proj = glm::perspective<GLfloat>(camera.Zoom, 640 / 480, .1f, 100.f);
shader.SetUniformMat4f("projection", glm::value_ptr(proj));
//模型矩阵(模型位置)
for (GLuint i = 0; i < 10; i++)
{
glm::mat4 model(1);
model = glm::translate(model, cubePositions[i]);
GLfloat angle = 20.0f * i;
model = glm::rotate(model, angle + ((GLfloat)glfwGetTime() * (i % 2 ? 1 : -1)), glm::vec3(1.0f, 0.3f, 0.5f));
shader.SetUniformMat4f("model", glm::value_ptr(model));
renderer.Draw(vao, 36, shader);
}
}
virtual bool UseCameraControl() override
{
return true;
}
};
Main函数内容(使用示例):
在制作该类前,我一直直接通过使用全局的函数名来调用不同的渲染代码,后来在使用glfw的按键绑定相关代码时出现了重定义的错误,后来想直接把这些东西放在类里,最后干脆再封装一下渲染流程,最后就成了这个RunBase类。
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include "RunBase.hpp"
#include "Test1.hpp"
#include "learn1.hpp"
#include "learn2.hpp"
#include "light_learn_1.hpp"
#include "MyTest.hpp"
using std::cout;
using std::endl;
#define TargetFrameRate 60
int main(void)
{
/* Initialize the library */
if (!glfwInit())
return -1;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
/* Create a windowed mode window and its OpenGL context */
GLFWwindow* window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
if (!window)
{
glfwTerminate();
return -1;
}
/* Make the window's context current */
glfwMakeContextCurrent(window);
//这样做能让GLEW在管理OpenGL的函数指针时更多地使用现代化的技术
glewExperimental = GL_TRUE;
//初始化GLEW(要在初始化了OpenGL上下文之后)
if (glewInit() != GLEW_OK)
return -1;
//目标帧时间
const double targetFrameTime = 1.0 / TargetFrameRate;
//课程代码
//Test1(window);
//Learn1(window, targetFrameTime);
//Learn2(window, targetFrameTime);
RunBase* runable = new MyTest();
runable->Run(window, targetFrameTime);
glfwTerminate();
return 0;
}