XEngine开发日记(day10)
上一章我们完成了:
- RendererAPI层的抽象
- RendererAPI中对于OpenGL的特例化
- Renderer中对于RendererAPI的调用
- Application对于Renderer内容的替换
代码已上传至我的github
drunklx/XEngine: my GameEngine
这一章我们来完成:
- 正交相机的实现
- 沙盒迁移
正交相机的实现
正交相机的定义
正交相机(Orthographic Camera)是一种在计算机图形学和3D渲染中常用的投影方式。与透视相机不同,正交相机不会产生近大远小的透视效果,所有物体无论距离远近都以相同的大小呈现。这种投影方式通常用于工程制图、CAD设计或2D游戏开发。
正交相机的特点
- 无透视变形:所有平行线在投影后仍保持平行,不会因距离而收敛。
- 均匀缩放:物体的大小不受距离影响,适合需要精确尺寸表现的场景。
- 投影矩阵简单:通过定义视景体的左右、上下、近远平面直接映射到屏幕空间。
正交相机的应用场景
- 工程制图:用于机械设计、建筑蓝图等需要精确尺寸的场景。
- 2D游戏开发:在横版或俯视角游戏中保持物体比例一致。
- UI渲染:在3D场景中渲染UI元素时避免透视变形。
正交相机的参数设置
正交相机的视景体通常由以下参数定义:
- left/right:视景体的左右边界(X轴范围)。
- top/bottom:视景体的上下边界(Y轴范围)。
- near/far:视景体的近远裁剪平面(Z轴范围)。
在代码中(如OpenGL或Unity),正交相机的投影矩阵可通过以下公式生成:
如果对投影矩阵推导感兴趣的话可以参考这篇文章。
正交相机与透视相机的对比
| 特性 | 正交相机 | 透视相机 |
|---|---|---|
| 投影效果 | 无透视变形,物体大小一致 | 近大远小,符合人眼视觉 |
| 适用场景 | 精确尺寸表现(如CAD) | 真实感渲染(如3D游戏) |
| 矩阵复杂度 | 线性变换,简单 | 非线性变换,需透视除法 |
实现示例(Unity引擎)
在Unity中设置正交相机:
- 选择摄像机对象,在Inspector面板中将
Projection模式改为Orthographic。 - 调整
Size参数控制视景体高度(宽度自动根据屏幕比例计算)。
// 脚本动态调整正交相机大小
Camera.main.orthographic = true;
Camera.main.orthographicSize = 5; // 视景体高度的一半
从上面来看,要构建一个正交矩阵,我们需要上下左右前后,我们将条件限定一下,现在是一个平面,我们从z轴向里看,能看到xy平面,然后规定我们的相机只能在z轴上进行旋转。作为一个2D摄像机来使用,这样就无需考虑远近屏幕了。
作为相机它需要存储自己的透视矩阵(ProjectionMetrix)以及视野矩阵(ViewMetrix),因此需要对这两者进行get&set。同时通过二者相乘得到相机矩阵。我们能够获取到它的位置,由于它只能绕z轴旋转,因此,它的方向向量永远是(0,0,-1),而我们只需要知道它的具体位置就能够计算出它的视野矩阵,而透视矩阵是一创建就确定好的,后续也可以通过改变它的长宽来改变。
对于视野矩阵的云云,可以参考LearnOpenGL的文章。
摄像机 - LearnOpenGL CN
https://learnopengl-cn.github.io/01%20Getting%20started/09%20Camera/
OrthographicCamera.h
#pragma once
#include <glm/glm.hpp>
namespace XEngine
{
class OrthographicCamera
{
public:
OrthographicCamera(float left, float right, float bottom, float top);
private:
glm::vec3 m_Position = { 0.0f, 0.0f, 0.0f };
float m_Rotation = 0.0f;
glm::mat4 m_ProjectionMatrix;
glm::mat4 m_ViewMatrix;
glm::mat4 m_ViewProjectionMatrix;
public:
inline const glm::mat4& GetProjectionMatrix() const { return m_ProjectionMatrix; }
inline const glm::mat4& GetViewMatrix() const { return m_ViewMatrix; }
inline const glm::mat4& GetViewProjectionMatrix() const { return m_ViewProjectionMatrix; }
void SetProjection(float left, float right, float bottom, float top);
void SetPosition(const glm::vec3& position);
void SetRotation(float rotation);
private:
void UpdateViewProjectionMatrix();
};
}
OrthographicCamera.cpp
#include "xepch.h"
#include "OrthographicCamera.h"
#include <glm/gtx/transform.hpp>
namespace XEngine
{
OrthographicCamera::OrthographicCamera(float left, float right, float bottom, float top)
: m_Rotation(0.0f), m_ProjectionMatrix(glm::ortho(left, right, bottom, top)), m_ViewMatrix(1.0f)
{
m_ViewProjectionMatrix = m_ProjectionMatrix * m_ViewMatrix;
}
void OrthographicCamera::SetProjection(float left, float right, float bottom, float top)
{
m_ProjectionMatrix = glm::ortho(left, right, bottom, top);
m_ViewProjectionMatrix = m_ProjectionMatrix * m_ViewMatrix;
}
void OrthographicCamera::SetPosition(const glm::vec3& position)
{
m_Position = position;
UpdateViewProjectionMatrix();
}
void OrthographicCamera::SetRotation(float rotation)
{
m_Rotation = rotation;
UpdateViewProjectionMatrix();
}
void OrthographicCamera::UpdateViewProjectionMatrix()
{
glm::mat4 transform = glm::translate(glm::mat4(1.0f), m_Position) *
glm::rotate(glm::mat4(1.0f), glm::radians(m_Rotation), glm::vec3(0.0f, 0.0f, 1.0f));
m_ViewMatrix = glm::inverse(transform);
m_ViewProjectionMatrix = m_ProjectionMatrix * m_ViewMatrix;
}
}
glm库的方法很强大,比如ortho函数直接生成投影矩阵,以及通过translate生成位移矩阵,通过rotate函数生成旋转矩阵等。注意这里在计算完transform矩阵后,调用了glm::inverse,要将矩阵求逆。为什么要将矩阵求逆呢,因为我们始终是假设矩阵是禁止的,那么对于相机的一切运动,物体都是相当于相对于相机往反方向运动,因此要将物体变换到相机坐标系就需要对viewmatrix求逆。
然后我们对这个相机进行适配,将它应用到我们的代码中。
Renderer.h
#pragma once
#include "RendererCommand.h"
#include "OrthographicCamera.h"
#include "Shader.h"
namespace XEngine
{
class Renderer
{
public:
static void BeginScene(OrthographicCamera& camera);
static void EndScene();
static void Submit(const std::shared_ptr<Shader>& shader, const std::shared_ptr<VertexArray>& vertexArray);
inline static RendererAPI::API GetAPI() { return RendererAPI::GetAPI(); } //注意静态函数使用时的作用域标识
private: //注意静态函数使用时的作用域标识
struct SceneData {
glm::mat4 ViewProjectionMatrix;
};
static SceneData* m_SceneData;
};
}
Renderer.cpp
#include "xepch.h"
#include "Renderer.h"
namespace XEngine
{
Renderer::SceneData* Renderer::m_SceneData = new Renderer::SceneData;
void Renderer::BeginScene(OrthographicCamera& camera)
{
m_SceneData->ViewProjectionMatrix = camera.GetViewProjectionMatrix();
}
void Renderer::EndScene()
{
}
void Renderer::Submit(const std::shared_ptr<Shader>& shader, const std::shared_ptr<VertexArray>& vertexArray)
{
shader->Bind();
shader->UpdateUniformMat4("u_ViewProjection", m_SceneData->ViewProjectionMatrix);
vertexArray->Bind();
RendererCommand::DrawIndexed(vertexArray);
}
}
在渲染器中,我们将相机传入,获取相机的VP矩阵,这样就可以在渲染时,将VP矩阵的值传递给着色器的顶点着色器进行渲染。我们在着色器中新增通过名称设置Uniform的方法,基本上就是调用一下OpenGL的接口设置着色器的Uniform变量。
然后在application中存储我们的相机以及应用,因为只有几行代码,这里就不做展示了。
测试一下,完美。

可以通过改变相机的位置来改变渲染效果。
改变相机长度,图像变窄了。

改变相机宽度,图像变胖了。

改变相机中心,图像被移出去了哈哈哈。

沙盒迁移
之前在application中临时引入了那些库以及写了那些渲染功能,现在我们要全部迁移到Sandbox里去,保证我们的application只做他该做的事。

我们将对Renderer中的引入放在了XEngine的头文件中,然后把之前渲染的功能都移到了Sandbox中ExampleLayer内部。


Sandbox.cpp/ExampeLayer.cpp
void OnUpdate() override {
//X_TRACE("ExampleLayer::OnUpdate");
XEngine::RendererCommand::Clear();
XEngine::RendererCommand::SetClearColor({ 0.1f,0.1f,0.1f,1.0f });
m_Camera.SetPosition(m_CameraPosition);
m_Camera.SetRotation(m_CameraRotation);
XEngine::Renderer::BeginScene(m_Camera);
XEngine::Renderer::Submit(blueShader, std::shared_ptr<XEngine::VertexArray>(squVAO));
XEngine::Renderer::Submit(m_Shader, m_VertexArray);
XEngine::Renderer::EndScene();
}
然后运行看一下效果

嗯,完美。
相机的移动与旋转
我们在Sandbox中实现一下通过按键实现相机的移动。
通过在ExampleLayer中,使用变量存储一下我的相机的变量(因为Position和Rotation是私有的,我们没办法直接修改,只能通过Set来进行修改)。

然后在update函数中实现一下。
if (XEngine::Input::IsKeyPressed(X_KEY_UP)) {
m_CameraPosition.y+= m_CameraMoveSpeed;
}
else if (XEngine::Input::IsKeyPressed(X_KEY_DOWN))
{
m_CameraPosition.y-= m_CameraMoveSpeed;
}
if (XEngine::Input::IsKeyPressed(X_KEY_RIGHT))
{
m_CameraPosition.x += m_CameraMoveSpeed;
}
else if (XEngine::Input::IsKeyPressed(X_KEY_LEFT))
{
m_CameraPosition.x -= m_CameraMoveSpeed;
}
if (XEngine::Input::IsKeyPressed(X_KEY_A))
{
m_CameraRotation -= m_CameraRotateSpeed;
}
else if (XEngine::Input::IsKeyPressed(X_KEY_D))
{
m_CameraRotation += m_CameraRotateSpeed;
}
m_Camera.SetPosition(m_CameraPosition);
m_Camera.SetRotation(m_CameraRotation);
基本上就是修改一下Camera的属性,然后基于我们之前的实现,它可以自动更新VP矩阵。
测试一下。
正交相机测试
更多推荐
所有评论(0)