文中若有代码、术语等错误,欢迎指正
摄像机实现原理BLOG:https://blog.csdn.net/qq_34060370/article/details/129391683
前言
-
此两节目的
为了实现一个简单的正交摄像机,能观看场景内的物体
-
摄像机相关概念
-
摄像机定义
为我们提供了一种观察世界的方式,摄像机是从特定角度观察场景的所有物体,不仅是有特定角度,还可以调整视野大小观察场景。
-
摄像机概念
摄像机并不存在,只是计算抽象摄像机的投影与观察矩阵坐标空间,将场景内的物体先变换到观察矩阵,最后变换到投影矩阵坐标空间。
-
摄像机属性
fov视野大小、自己的位置、宽高比、近远平面范围等
-
摄像机的观察矩阵推导
-
介绍工作方式
摄像机往后移动,其实是场景内的物体往前移动
摄像机往右旋转,其实是场景内的物体往左旋转
-
摄像机观察矩阵如何计算
摄像机的位置、旋转角度组成的变换矩阵transform取逆后的矩阵view就是观察矩阵,用此观察矩阵乘以场景内的物体变换到这个坐标空间。
取逆是一种计算观察矩阵方法,第二种是用欧拉角+LookAt计算观察矩阵方法(文章开头链接Blog是这种)
-
-
公式
-
OpenGL
-
写代码顺序: project * view * world * verpos
摄像机移动后,最好将最新的摄像机的proj*view矩阵在CPU上计算后再传入到GLSL上
因为若将proj与view传到GPU上,每个物体都要做一遍proj*view这个矩阵乘法,可以放到CPU上这样只需做一次+上传即可。
-
读顺序:从右往左
-
-
Directx
- 写代码顺序:verpos * world * view * project
-
-
-
API设计
Renderer::BeginScene(camera); Renderer::Submit(m_Shader1, VertexArray1);// 给场景提交要渲染的物体 Renderer::Submit(m_Shader2, VertexArray2);// 给场景提交要渲染的物体 Renderer::EndScene(); Renderer::Flush(); void BeingScene(Camera& camera){ // 在cpp中计算Project * view矩阵,并存储起来, this->ViewProjectionMatrix = camera.getProjectionMatrix() * camera.getViewMatrix(); } void Submit(Shader& shader, VertexArray& v){ shader->Bind(); // 着色器绑定 glUniformvec4("viewprojection", this->ViewProjectionMatrix);// 上传给Uniform v->bind(); // 绑定顶点数组 RenderCommand::DrawIndexed(vertexArray);// drawcall }
-
此节完成类图
记录思考点
关键代码
// 投影矩阵计算
OrthographicCamera::OrthographicCamera(float left, float right, float bottom, float top)
: m_ProjectionMatrix(glm::ortho(left, right, bottom, top, -1.0f, 1.0f)), m_ViewMatrix(1.0f){
m_ViewProjectionMatrix = m_ProjectionMatrix * m_ViewMatrix;
}
// 投影观察矩阵
void OrthographicCamera::RecalculateViewMatrix(){
glm::mat4 transform = glm::translate(glm::mat4(1.0f), m_Position) *
glm::rotate(glm::mat4(1.0f), glm::radians(m_Rotation), glm::vec3(0, 0, 1)); // 绕z轴旋转
m_ViewMatrix = glm::inverse(transform);
// 投影观察矩阵
m_ViewProjectionMatrix = m_ProjectionMatrix * m_ViewMatrix;
}
// 物体上传场景
void Renderer::Submit(const std::shared_ptr<Shader>& shader, const std::shared_ptr<VertexArray>& vertexArray){
shader->Bind();// 着色器邦迪
shader->UploadUniformMat4("u_ViewProjection", m_SceneData->ViewProjectionMatrix);// 上传到Uniform
vertexArray->Bind();// 顶点数组绑定
RenderCommand::DrawIndexed(vertexArray);// drawcall
}
void Shader::UploadUniformMat4(const std::string& name, const glm::mat4& matrix){
GLint location = glGetUniformLocation(m_RendererID, name.c_str());// 获取uniform名称的位置
glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(matrix));// 根据位置上传
}
// glsl
uniform mat4 u_ViewProjection;// 声明uniform
void main(){
v_Position = a_Position;
v_Color = a_Color;
gl_Position = vec4(a_Position, 1.0);
// 将顶点变换到投影矩阵坐标空间(裁剪空间)下:projection * view * world * vpos
gl_Position = u_ViewProjection * vec4(a_Position, 1.0);
}
关于窗口比例影响图形显示
OrthographicCamera::OrthographicCamera(float left, float right, float bottom, float top)
: m_ProjectionMatrix(glm::ortho(left, right, bottom, top, -1.0f, 1.0f)), m_ViewMatrix(1.0f){
m_ViewProjectionMatrix = m_ProjectionMatrix * m_ViewMatrix;
}
前置要点
-
窗口上下左右都是1与变为2的对比
可见窗口若从1到2,三角形0.5围成的范围缩小,所以图形会变小。
glm::ortho(-2,2,-2,2,-1.0f,1.0f)得出矩阵值为
0.5 0 0 0 0 0.5 0 0 0 0 -1 0 0 0 0 1
-
缩小后的结果
具体问题
-
正方形变成长方形
窗口是1280 * 720,当glm::ortho(-1.0f,1.0f, -1.0f, 1.0f, -1.0f, 1.0f);时候,本来正方形的蓝色quad变为长方形
-
修复变回正方形
在1280*720下,left right需传入1280/720=1.7左右,将宽放大,从而左右视角变大,物体围成的宽范围缩小,从而变回正方形。
/* 由于窗口的大小是1280 :720,是16 / 9 = 1.77777 那么设置m_Camera的宽设置 1.6范围,高设为0.9就可以解决。或者 1.7与1也行 */ Application::Application() :m_Camera(-1.6f, 1.6f, -0.9f, 0.9f){}
GLM库函数相关
-
glm::ortho
left = -1.0f;right = 1.0f;bottom = -1.0f;top = 1.0f
glm::ortho(left,right, bottom, top, -1.0f, 1.0f);得到的矩阵是
1 0 0 0 0 1 0 0 0 0 -1 0 0 0 0 1
-
glm::translate(glm::mat4(1.0f), m_Position);
m_Position= {0.5f, 0.5f, 0.5f};
glm::mat4(1.0f),是4x4的单位矩阵
1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1
glm::translate(glm::mat4(1.0f), m_Position);
/* glm::translate函数中 mat<4, 4, T, Q> Result(m); Result[3] = m[0] * v[0] + m[1] * v[1] + m[2] * v[2] + m[3]; Result[3]是第4行,m[0]是第1行,m[1]是第2行,m[2]是第3行。。。 第四行 = (1 * 0.5,0,0,0) + (0, 1 * 0.5, 0, 0) + (0, 0, 1 * 0.5, 0) + (0, 0, 0, 1) */ // 最后的结果是// 有可能反了:第四行应该与第四列交换 1 0 0 0 0 1 0 0 0 0 1 0 0.5 0.5 0.5 1
全部代码
-
OrthographicCamera
class OrthographicCamera{ public: OrthographicCamera(float left, float right, float bottom, float top); const glm::vec3& GetPosition() const { return m_Position; } void SetPosition(const glm::vec3& position) { m_Position = position; RecalculateViewMatrix(); } float GetRotation() const { return m_Rotation; } void SetRotation(float rotation) { m_Rotation = rotation; RecalculateViewMatrix(); } const glm::mat4& GetProjectionMatrix() const { return m_ProjectionMatrix; } const glm::mat4& GetViewMatrix() const { return m_ViewMatrix; } const glm::mat4& GetViewProjectionMatrix() const { return m_ViewProjectionMatrix; } private: void RecalculateViewMatrix(); private: glm::mat4 m_ProjectionMatrix; glm::mat4 m_ViewMatrix; glm::mat4 m_ViewProjectionMatrix; glm::vec3 m_Position = { 0.0f, 0.0f, 0.0f };// 位置 float m_Rotation = 0.0f; // 绕z轴的旋转角度 };
// 初始化用glm计算正交投影矩阵 OrthographicCamera::OrthographicCamera(float left, float right, float bottom, float top) : m_ProjectionMatrix(glm::ortho(left, right, bottom, top, -1.0f, 1.0f)), m_ViewMatrix(1.0f) { m_ViewProjectionMatrix = m_ProjectionMatrix * m_ViewMatrix; } // 投影观察矩阵计算 void OrthographicCamera::RecalculateViewMatrix() { // 观察矩阵 glm::mat4 transform = glm::translate(glm::mat4(1.0f), m_Position) * glm::rotate(glm::mat4(1.0f), glm::radians(m_Rotation), glm::vec3(0, 0, 1)); m_ViewMatrix = glm::inverse(transform); m_ViewProjectionMatrix = m_ProjectionMatrix * m_ViewMatrix; }
-
Renderer
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::SceneData* Renderer::m_SceneData = new Renderer::SceneData; void Renderer::BeginScene(OrthographicCamera& camera){ m_SceneData->ViewProjectionMatrix = camera.GetViewProjectionMatrix(); // 保存计算的Projection * view矩阵 } void Renderer::EndScene(){} void Renderer::Submit(const std::shared_ptr<Shader>& shader, const std::shared_ptr<VertexArray>& vertexArray){ shader->Bind(); // 着色器绑定 shader->UploadUniformMat4("u_ViewProjection", m_SceneData->ViewProjectionMatrix);// 上传投影观察矩阵 vertexArray->Bind();// 顶点数组绑定 RenderCommand::DrawIndexed(vertexArray);// drawcall }
-
Shader
void UploadUniformMat4(const std::string& name, const glm::mat4& matrix); void Shader::UploadUniformMat4(const std::string& name, const glm::mat4& matrix){ GLint location = glGetUniformLocation(m_RendererID, name.c_str()); glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(matrix)); }
-
Application
OrthographicCamera m_Camera; private: static Application* s_Instance;
Application::Application() : m_Camera(-1.6f, 1.6f, -0.9f, 0.9f){ // 着色器代码 std::string vertexSrc = R"( #version 330 core layout(location = 0) in vec3 a_Position; layout(location = 1) in vec4 a_Color; uniform mat4 u_ViewProjection; out vec3 v_Position; out vec4 v_Color; void main() { v_Position = a_Position; v_Color = a_Color; gl_Position = u_ViewProjection * vec4(a_Position, 1.0); } )"; void Application::Run(){ while (m_Running){ RenderCommand::SetClearColor({ 0.1f, 0.1f, 0.1f, 1 }); RenderCommand::Clear(); /* 5和6指明近和远平面范围 glm::ortho(left, right, bottom, top, -1.0f, 1.0f) 摄像机位置的z轴位置只要-1~1之间就行 */ m_Camera.SetPosition({ 0.5f, 0.5f, 0.0f }); m_Camera.SetRotation(45.0f); Renderer::BeginScene(m_Camera); // 绘制四边形 Renderer::Submit(m_BlueShader, m_SquareVA); // 绘制三角形 Renderer::Submit(m_Shader, m_VertexArray); Renderer::EndScene();