Hazel游戏引擎(034-035)正交摄像机实现

该文详细介绍了正交摄像机的实现原理,包括摄像机的观察矩阵计算,OpenGL中的代码顺序以及窗口比例对图形显示的影响。文章通过GLM库函数展示了如何计算投影矩阵和观察矩阵,并给出了相应代码示例,解释了为何需要在CPU上预先计算投影观察矩阵以提高效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文中若有代码、术语等错误,欢迎指正

摄像机实现原理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();
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘建杰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值