从零开始写一个opengl渲染器——基础设施搭建篇
基于OpenGL书《计算机图形学编程(使用OpenGL和C++)》中的描述,已经可以在屏幕上输出物体了。但是代码复用的比较多,所以要把复用的代码封装成类,方便后期的维护。先从原始代码中抽象出3个类:窗口类,相机类和控制器类。
窗口类
最开始的窗口代码
GLFWwindow* window = glfwCreateWindow(600, 600, "TEST", nullptr, nullptr);//创建GLFW窗口
glfwMakeContextCurrent(window);//将创建的GLFW窗口与opengl的上下文关联起来
这里的窗口需要指定长度和宽度。写在main函数中,不方便别的地方调用。
为了便于其他方法调用,封装窗口管理类,类中使用单例模式,因为整个程序中只会出现一个窗口。单例出来的实例可以在任意类中调用。
封装代码
//
// Filename: WindowManager.h
// Created by W. Mysterio on 2022-07-04 02:01:39.
// Description:
// Mail: woden3702@gmail.com
//
#ifndef __WINDOWMANAGER_H__
#define __WINDOWMANAGER_H__
#include "GLFW/glfw3.h"
struct WindowSize
{
unsigned int w;//width
unsigned int h;//height
};
class WindowManager
{
private:
static WindowManager* ins_;
WindowManager();
WindowSize wSize_;
GLFWwindow* window_;
public:
~WindowManager();
static WindowManager* instance();
void createMainWindow(unsigned int w, unsigned int h, const char* title, bool fullScreen);
WindowSize getWindowSize() const;
GLFWwindow* getWindow() const;
};
#endif //__WINDOWMANAGER_H__
//
// Filename: WindowManager.cpp
// Created by W. Mysterio on 2022-07-04 02:01:39.
// Description:
// Mail: woden3702@gmail.com
//
#include "WindowManager.h"
WindowManager* WindowManager::ins_ = nullptr;
WindowManager::WindowManager() = default;
WindowManager::~WindowManager()
{
delete ins_;
ins_ = nullptr;
}
WindowManager* WindowManager::instance()
{
if (ins_ == nullptr)
ins_ = new WindowManager();
return ins_;
}
void WindowManager::createMainWindow(unsigned w, unsigned h, const char* title, bool fullScreen)
{
wSize_.w = w;
wSize_.h = h;
if (fullScreen)
window_ = glfwCreateWindow(static_cast<int>(wSize_.w), static_cast<int>(wSize_.h), title, glfwGetPrimaryMonitor(), nullptr);
else
window_ = glfwCreateWindow(static_cast<int>(wSize_.w), static_cast<int>(wSize_.h), title, nullptr, nullptr);
glfwSetWindowAttrib(window_, GLFW_RESIZABLE, GLFW_FALSE);//控制是否缩放
glfwMakeContextCurrent(window_);
glViewport(0, 0, static_cast<GLsizei>(wSize_.w), static_cast<GLsizei>(wSize_.h));
}
WindowSize WindowManager::getWindowSize() const
{
return wSize_;
}
GLFWwindow* WindowManager::getWindow() const
{
return window_;
}
封装好窗口管理类后,修改原来main函数中的代码,先调用createMainWindow
方法初始化窗口,后面就可以通过单例方法直接调用窗口。
代码如下:
WindowManager::instance()->createMainWindow(1920, 1080, "test_1", false);
//调用窗口
WindowManager::instance()->getWindow())
相机类
第二个需要的是相机类,有了相机类可以方便后序实现相机移动。原本的代码需要先定义相机的位置坐标、投影矩阵和视图矩阵等信息,现在封装到一个类里,调用的时候会全部初始化。
原来的代码
封装好的相机类
//
// Filename: Camera.h
// Created by W. Mysterio on 2022-07-04 03:22:45.
// Description:
// Mail: woden3702@gmail.com
//
#ifndef __CAMERA_H__
#define __CAMERA_H__
#include <glm\glm.hpp>
#include <glm\gtc\type_ptr.hpp> // glm::value_ptr
#include <glm\gtc\matrix_transform.hpp>
#include "WindowManager.h"
class Camera
{
private:
glm::vec3 position_; // 相机位置
glm::vec3 cameraTarget_; //相机朝向
glm::vec3 cameraDirection_; //相机的方向
glm::vec3 cameraUp_; //相机的上方
glm::vec3 cameraRight_; //相机的右轴
float fov_; //度
float farClip_;
float nearClip_;
float inWidth_;
float inHeight_;
float aspect_;
glm::mat4 projectionMatrix_;
glm::mat4 viewMatrix_;
glm::mat4 modelMatrix_;
glm::mat4 modelViewMatrix_;
public:
Camera();
~Camera();
void SetPerspectiveCamera(float iFov, float iNearClip, float iFarClip); void SetNearClip(float iNearClip);
void setPosition(glm::vec3 ipos);
void setDirection(glm::vec3 idir);
float GetNearClip() const;
void SetFarClip(float iFarClip);
float GetFarClip() const;
void SetFov(float iFov);
float GetFov() const;
void updateProjectionMatrix();
void updateViewMatrix();
void updateModelMatrix();
void updateModelViewMatrix();
glm::mat4 getModelViewMatrix() const;
glm::mat4 getProjectionMatrix() const;
glm::mat4 getViewMatrix() const;
glm::vec3 getPosition() const;
glm::vec3 getRight() const;
glm::vec3 getDirection() const;
glm::vec3 getUp() const;
};
#endif //__CAMERA_H__
//
// Filename: Camera.cpp
// Created by W. Mysterio on 2022-07-04 03:22:45.
// Description:
// Mail: woden3702@gmail.com
//
#include "Camera.h"
float toRadians(float degrees) { return (degrees * 2.0f * 3.14159f) / 360.0f; }
glm::vec3 up= glm::vec3(0.0, 1.0f, 0.0f);
glm::vec3 front = glm::vec3(0.0, 0.0f, -1.0f);
Camera::Camera()
{
position_ = glm::vec3(0.0f, 0.0f, 8.0f);
cameraTarget_ = glm::vec3(0.0f, 0.0f, 0.0f);
cameraDirection_ = glm::vec3(0.0, 0.0f, -1.0f);
cameraUp_ = glm::vec3(0.0, 1.0f, 0.0f);
cameraRight_ = glm::normalize(glm::cross(cameraDirection_, cameraUp_));
fov_ = 60.0f; //度
farClip_ = 1000.0f;
nearClip_ = 0.1f;
inWidth_ = WindowManager::instance()->getWindowSize().w;
inHeight_ = WindowManager::instance()->getWindowSize().h;
aspect_ = inWidth_ / inHeight_;
updateProjectionMatrix();
updateViewMatrix();
}
Camera::~Camera() = default;
void Camera::updateProjectionMatrix()
{
projectionMatrix_= glm::perspective(toRadians(fov_), aspect_, nearClip_, farClip_);
}
void Camera::SetNearClip(float iNearClip)
{
nearClip_ = iNearClip;
}
void Camera::setPosition(glm::vec3 ipos)
{
position_ = ipos;
}
void Camera::setDirection(glm::vec3 idir)
{
cameraDirection_ = idir;
}
float Camera::GetNearClip() const
{
return nearClip_;
}
void Camera::SetFarClip(float iFarClip)
{
farClip_ = iFarClip;
}
float Camera::GetFarClip() const
{
return farClip_;
}
void Camera::SetFov(float iFov)
{
fov_ = iFov;
}
float Camera::GetFov() const
{
return fov_;
}
void Camera::updateViewMatrix()
{
viewMatrix_= glm::lookAt(position_,position_+cameraDirection_,cameraUp_);
}
void Camera::updateModelMatrix()
{
modelMatrix_ = glm::translate(glm::mat4(1.0f),cameraTarget_);
}
void Camera::updateModelViewMatrix()
{
modelViewMatrix_ = viewMatrix_ * modelMatrix_;
}
glm::mat4 Camera::getModelViewMatrix() const
{
return modelViewMatrix_;
}
glm::mat4 Camera::getProjectionMatrix() const
{
return projectionMatrix_;
}
glm::mat4 Camera::getViewMatrix() const
{
return viewMatrix_;
}
glm::vec3 Camera::getPosition() const
{
return position_;
}
glm::vec3 Camera::getRight() const
{
return glm::normalize(glm::cross(cameraDirection_, cameraUp_));
}
glm::vec3 Camera::getDirection() const
{
return cameraDirection_;
}
glm::vec3 Camera::getUp() const
{
return cameraUp_;
}
目前相机类仍然是比较复杂,后期需要重构,可能还要抽象出一个物体类,封装坐标等信息
如果想要在别的函数中直接获取已有的相机对象,则需要一个相机管理类,管理创建删除等操作。
//
// Filename: CameraManager.h
// Created by W. Mysterio on 2022-07-07 03:20:01.
// Description:
// Mail: woden3702@gmail.com
//
#ifndef __CAMERAMANAGER_H__
#define __CAMERAMANAGER_H__
#include <stack>
#include <memory>
#include "Camera.h"
class CameraManager
{
private:
CameraManager();
static CameraManager* ins_;
std::stack<std::shared_ptr<Camera>> cams_;
public:
~CameraManager();
static CameraManager* instance();
std::shared_ptr<Camera> push();
void pop();
std::shared_ptr<Camera> getCurCamera();
};
#endif //__CAMERAMANAGER_H__
//
// Filename: CameraManager.cpp
// Created by W. Mysterio on 2022-07-07 03:20:01.
// Description:
// Mail: woden3702@gmail.com
//
#include "CameraManager.h"
CameraManager* CameraManager::ins_ = nullptr;
CameraManager::CameraManager() = default;
CameraManager::~CameraManager()
{
ins_ = nullptr;
}
CameraManager* CameraManager::instance()
{
if (ins_ == nullptr)
ins_ = new CameraManager();
return ins_;
}
std::shared_ptr<Camera> CameraManager::push()
{
std::shared_ptr<Camera> newCam = std::make_shared<Camera>();
cams_.push(newCam);
return newCam;
}
void CameraManager::pop()
{
cams_.pop();
}
std::shared_ptr<Camera> CameraManager::getCurCamera()
{
return cams_.top();
}
管理类都长一个样子,后面的管理类大概都是这个模式。
控制类
创建好相机后,要实现移动相机,需要绑定一些回调函数。为了方便管理,把这些操作封装成一个控制类。
//
// Filename: Controller.h
// Created by W. Mysterio on 2022-07-07 03:58:35.
// Description:
// Mail: woden3702@gmail.com
//
#ifndef __CONTROLLER_H__
#define __CONTROLLER_H__
#include <glm/vec3.hpp>
#include <glm\gtc\type_ptr.hpp> // glm::value_ptr
#include <glm\gtc\matrix_transform.hpp> // glm::translate, glm::rotate, glm::scale, glm::perspective
#include "WindowManager.h"
#define FIXEDUPDATE_TIME 0.01f//In seconds
class Controller
{
private:
double lastMousePos[2];
int curMouseButton;
float moveSpeed=3.0f;
glm::vec3 moveVec;
float rotSensitivity[2];
float mouseMovSensitivity[3];
float offsetX, offsetY;
float yaw, pitch;//两种欧拉角 俯仰角(Pitch)、偏航角(Yaw)
public:
Controller();
void init();
void update();
void MouseMotionCallback(GLFWwindow* window, double x, double y);
void MouseKeyCallback(GLFWwindow* window, int button, int state, int mods);
void KeyInputCallback(GLFWwindow* window, int key, int scanCode, int action, int mods);
};
#endif //__CONTROLLER_H__
//
// Filename: Controller.cpp
// Created by W. Mysterio on 2022-07-07 03:58:35.
// Description:
// Mail: woden3702@gmail.com
//
#include "Controller.h"
#include "CameraManager.h"
Controller::Controller()
{
init();
}
void Controller::init()
{
rotSensitivity[0] = 0.05f;
rotSensitivity[1] = 0.03f;
mouseMovSensitivity[0] = 0.02f;
mouseMovSensitivity[1] = 0.01f;
mouseMovSensitivity[2] = 0.1f;
moveVec = glm::vec3(0.0f, 0.0f, 0.0f);
yaw = -90.0f;
pitch = 0.0f;
}
void Controller::update()
{
if (curMouseButton == GLFW_MOUSE_BUTTON_RIGHT)
{
glm::vec3 curPos = CameraManager::instance()->getCurCamera()->getPosition();
// printf("按下右键 position: ");
// printf("(%f,%f,%f)\n", curPos.x, curPos.y, curPos.z);
// printf("sasd%f\n", moveVec.x);
curPos = curPos + (normalize(CameraManager::instance()->getCurCamera()->getDirection()) * moveSpeed * moveVec.x +
normalize(CameraManager::instance()->getCurCamera()->getRight()) * moveSpeed * moveVec.y +
normalize(CameraManager::instance()->getCurCamera()->getUp()) * moveSpeed * moveVec.z) * FIXEDUPDATE_TIME;
// printf("______处理完后: ");
// printf("(%f,%f,%f)\n", curPos.x, curPos.y, curPos.z);
CameraManager::instance()->getCurCamera()->setPosition(curPos);
}
}
void Controller::MouseMotionCallback(GLFWwindow* window, double x, double y)
{
switch (curMouseButton)
{
case GLFW_MOUSE_BUTTON_RIGHT:
{
//参考https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera/#_6
offsetX = x - lastMousePos[0];
offsetY = lastMousePos[1] - y;
// printf("(%f,%f)\n", x, y);
// printf("(%f,%f)\n", offsetX, offsetY);
offsetX *= rotSensitivity[0];
offsetY *= rotSensitivity[1];
yaw += offsetX;
pitch += offsetY;
if (pitch > 89.0f)
pitch = 89.0f;
if (pitch < -89.0f)
pitch = -89.0f;
glm::vec3 front;
front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
front.y = sin(glm::radians(pitch));
front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
CameraManager::instance()->getCurCamera()->setDirection(normalize(front));
}
break;
}
//记录鼠标松开的位置
lastMousePos[0] = x;
lastMousePos[1] = y;
}
void Controller::MouseKeyCallback(GLFWwindow* window, int button, int state, int mods)
{
switch (state)
{
case GLFW_PRESS:
{
curMouseButton = button;
}
break;
case GLFW_RELEASE:
{
curMouseButton = -1;
}
break;
}
glfwGetCursorPos(window, &lastMousePos[0], &lastMousePos[1]);
}
void Controller::KeyInputCallback(GLFWwindow* window, int key, int scanCode, int action, int mods)
{
switch (key)
{
default:
break;
case GLFW_KEY_W:
moveVec.x = (action == GLFW_PRESS || action == GLFW_REPEAT) ? 1.0f : 0.0f;
break;
case GLFW_KEY_S:
moveVec.x = (action == GLFW_PRESS || action == GLFW_REPEAT) ? -1.0f : 0.0f;
break;
case GLFW_KEY_A:
moveVec.y = (action == GLFW_PRESS || action == GLFW_REPEAT) ? -1.0f : 0.0f;
break;
case GLFW_KEY_D:
moveVec.y = (action == GLFW_PRESS || action == GLFW_REPEAT) ? 1.0f : 0.0f;
break;
case GLFW_KEY_E:
moveVec.z = (action == GLFW_PRESS || action == GLFW_REPEAT) ? 1.0f : 0.0f;
break;
case GLFW_KEY_Q:
moveVec.z = (action == GLFW_PRESS || action == GLFW_REPEAT) ? -1.0f : 0.0f;
break;
}
switch (key)
{
default:
break;
case GLFW_KEY_W:
if (action == GLFW_RELEASE)
{
moveVec.x = 0.0f;
}
break;
case GLFW_KEY_S:
if (action == GLFW_RELEASE)
{
moveVec.x = 0.0f;
}
break;
case GLFW_KEY_A:
if (action == GLFW_RELEASE)
{
moveVec.y = 0.0f;
}
break;
case GLFW_KEY_D:
if (action == GLFW_RELEASE)
{
moveVec.y = 0.0f;
}
break;
case GLFW_KEY_E:
if (action == GLFW_RELEASE)
{
moveVec.z = 0.0f;
}
break;
case GLFW_KEY_Q:
if (action == GLFW_RELEASE)
{
moveVec.z = 0.0f;
}
break;
case GLFW_KEY_SPACE:
if (action == GLFW_PRESS)
{
//回到原位
CameraManager::instance()->getCurCamera()->setPosition(glm::vec3(0.0f, 0.0f, 8.0f));
CameraManager::instance()->getCurCamera()->setDirection(glm::vec3(0.0f, 0.0f, -1.0f));
CameraManager::instance()->getCurCamera()->updateViewMatrix();
}
}
}
在该类中,实现了按住鼠标右键的同时按住wasdqe方向键实现相机的前后左右上下移动,操作方式和unity中的一致。
主要逻辑是在主循环中调用控制类中的update方法,该方法实现的功能是按住鼠标右键获取当前相机的位置坐标,通过回调函数监听各个坐标的改变情况,再实现位置坐标的改变,目前相机类缺少旋转坐标,后期要想办法加上去。
后期可能还有别的种类的控制类,还需要创建一个控制类的父类,绑定鼠标按键等重复操作。
实现控制管理类:
//
// Filename: ControllerManager.h
// Created by W. Mysterio on 2022-07-07 04:20:43.
// Description:
// Mail: woden3702@gmail.com
//
#ifndef __CONTROLLERMANAGER_H__
#define __CONTROLLERMANAGER_H__
#include <stack>
#include <memory>
#include "Controller.h"
class ControllerManager
{
private:
ControllerManager();
static ControllerManager* ins_;
std::stack<std::shared_ptr<Controller>> ctrls_;
public:
~ControllerManager();
static ControllerManager* instance();
void push();
void push(std::shared_ptr<Controller>);
void pop();
std::shared_ptr<Controller> getCurController();
};
#endif //__CONTROLLERMANAGER_H__
//
// Filename: ControllerManager.cpp
// Created by W. Mysterio on 2022-07-07 04:20:43.
// Description:
// Mail: woden3702@gmail.com
//
#include "ControllerManager.h"
ControllerManager* ControllerManager::ins_ = nullptr;
ControllerManager::ControllerManager()= default;
ControllerManager::~ControllerManager()
{
ins_ = nullptr;
}
ControllerManager* ControllerManager::instance()
{
if (ins_ == nullptr)
ins_ = new ControllerManager();
return ins_;
}
void ControllerManager::push()
{
auto newCtrl = std::make_shared<Controller>();
ctrls_.push(newCtrl);
}
void ControllerManager::push(std::shared_ptr<Controller> controller)
{
ctrls_.push(controller);
}
void ControllerManager::pop()
{
ctrls_.pop();
}
std::shared_ptr<Controller> ControllerManager::getCurController()
{
return ctrls_.top();
}