[OpenGL]相机环境

news/2025/9/23 10:27:31/文章来源:https://www.cnblogs.com/lxjshuju/p/19106704

目录

一、逻辑分析

1.1正交投影相机与透视投影相机

1.2轨迹球相机控制和游戏相机控制

二、相机(Camera)的设计

2.1Camera基类设计

2.2正交投影相机(OrthographicCamera)的设计 

2.3透视投影相机(PerspectiveCamera)的设计

三、相机控制(Camera Control)的设计

3.1相机控制基类(CameraControl)的设计

3.2轨迹球相机控制(TrackballCameraControl)类设计

3.3游戏相机控制(GameCameraControl)类的设计


前言:刚学OpenGL,主要想要记录下学到的东西,也当作一个笔记,部分理解可能有偏差,也是不全面的,如果有发现问题的话也很高兴大家可以指正,我会尽快修改的,在后续学习过程中,也会进行相应的补充与修改。(课件截图来源于bilibili赵新政老师)

(OpenGL老师:赵新政的个人空间-赵新政个人主页-哔哩哔哩视频)

一、逻辑分析

1.1正交投影相机与透视投影相机

首先知道,相机会与两个关键量相关,分别是视图变换矩阵与投影矩阵,这两个变量决定了点的最终位置。因而,我们肯定需要从相机中去获得这两个变量。

视图变换变换矩阵与相机的位置,朝向,与穹顶相关,投影矩阵又分为正交投影和透视投影,但是两种投影矩阵的计算与相机本身的位置和朝向并无直接关联。那么,相机就可以分出两种相机,一个是正交投影相机,另一个则是透视投影相机。

在实现层面,我们就可以充分利用C++的多态特性,以Camera为基类,去分别实现OrthographicCamera和PerspectiveCamera。其中Camera持有相机的基础位置信息与方向信息,对外暴露获取视图矩阵与投影矩阵的接口。而派生出来的相机则需要持有构建相机所需要的基础数据,并重写获取投影矩阵的接口。

1.2轨迹球相机控制和游戏相机控制

摄像机确定了之后,便需要有一个控制器CameraControl去控制摄像机。而摄像机的变换是与当前的鼠标键盘等操作相关的,因而必须要记录下当前的按键与鼠标信息。同时提供一个on_update接口,来在程序运行的每一帧当中更新摄像机。

轨迹球相机控制:在3D建模、场景编辑的过程中,往往会需要相机围绕着一个目标点进行旋转、缩放、平移。

游戏相机控制:基于玩家的移动而移动(部分场景也会根据剧情或脚本自动运动,如过场动画镜头)

这里也是相同的实现思路,以CameraControl为基类,分别再去实现TrackballCameraControl和GameCameraControl的内容,在里面实现其对应的pitch,yaw逻辑与按键控制逻辑

二、相机(Camera)的设计

2.1Camera基类设计

从相机的基本信息讲,需要包含他的位置与本地坐标系(分别是up轴,right轴,front轴),而实际上front轴可以通过up向量与right向量叉乘得到,所以只需要记录其中两个向量(这里使用up和right)即可。

从相机的功能角度讲,我们需要从中获取视图矩阵(ViewMatrix)与投影矩阵(Projection),这对于任何相机而言都是一样的,所以必须要对外获取这两个矩阵的接口。此外,相机同时可以拥有放缩的功能,外界想要进行放缩就必须用到摄像机的相关信息,因而也需要提供进行放缩的接口。

其中,投影矩阵与放缩的逻辑因投影方式而异,需要设计成虚函数,再在具体的相机中进行实现

camera.h

#pragma once
#include"../../glframework/core.h"
class Camera
{
public:
Camera();
~Camera();
glm::mat4 get_view_matrix();
virtual glm::mat4 get_projection_matrix();
virtual void scale(float delta_scale);
public:
glm::vec3 m_position{ 0.0f, 0.0f, 100.0f };
glm::vec3 m_up{ 0.0f, 1.0f, 0.0f };
glm::vec3 m_right{ 1.0f, 0.0f, 0.0f };
};

camera.cpp

#include"camera.h"
Camera::Camera()
{
}
Camera::~Camera()
{
}
glm::mat4 Camera::get_view_matrix()
{
glm::vec3 front = glm::cross(m_up, m_right);
glm::vec3 center=m_position+front;
return glm::lookAt(m_position, center, m_up);
}
glm::mat4 Camera::get_projection_matrix()
{
return glm::identity();
}
void Camera::scale(float delta_scale)
{
}

2.2正交投影相机(OrthographicCamera)的设计 

 正交投影相机的逻辑基于正交投影的运算。其在生成正交投影矩阵的时候需要提供left,right,bottom,top,near,far这六个变量,因而他需要持有这六个变量,在构造正交投影相机的时候对变量进行赋值,在需要获取投影矩阵的时候只需要传入相关值并返回即可。

而对于正交投影矩阵的缩放操作,其本质上是去按比例修改正交投影盒的投影平面,其中为了确保可视范围不变,不可以修改其far与near变量。因而我们会需要记录一个缩放相关值m_scale。而在实际的操作当中,我们需要使用滚轮去对该数值进行加减更新,而当数值为负数的时候,缩放比例并不是负数,因而最终的缩放比例的计算是使用2的m_scale次方进行计算。这样就满足了:

(1)当m_scale=0的时候,没有进行缩放

(2)当m_scale<0的时候,进行缩小

(3)当m_scale>0的时候,进行放大

orthographic_camera.h

#pragma once
#include "camera.h"
class OrthographicCamera :public Camera
{
public:
OrthographicCamera(float l, float r, float t, float b, float n, float f);
~OrthographicCamera();
glm::mat4 get_projection_matrix()override;
void scale(float delta_scale)override;
public:
float m_left = 0.0f;
float m_right = 0.0f;
float m_bottom = 0.0f;
float m_top = 0.0f;
float m_near = 0.0f;
float m_far = 0.0f;
float m_scale{ 0.0f };
};

orthographic.cpp

#include"orthographic_camera.h"
OrthographicCamera::OrthographicCamera(float left, float right, float bottom, float top, float near, float far)
:m_left(left), m_right(right), m_bottom(bottom), m_top(top), m_near(near), m_far(far)
{
}
OrthographicCamera::~OrthographicCamera()
{
}
glm::mat4 OrthographicCamera::get_projection_matrix()
{
float scale=std::pow(2.0f, m_scale);
return glm::ortho(m_left*scale, m_right*scale, m_bottom*scale, m_top*scale, m_near, m_far);
}
void OrthographicCamera::scale(float delta_scale)
{
m_scale+= delta_scale;
}

2.3透视投影相机(PerspectiveCamera)的设计

从相同的思路出发,透视投影相机去提供透视变换矩阵的时候需要提供四个数据fovy,aspect,near,far,这些都是必须的变量,在构造透视投影相机的时候进行赋值,在获取的时候调用的时候传入相关值并返回相应地矩阵。

对于透视投影相机的放缩操作,可以通过相机在其朝向方向上进行移动来实现,当相机朝前方移动的时候,可视物体将会放大,反之缩小。

perspective_camera.h

#pragma once
#include"camera.h"
class PerspectiveCamera :public Camera
{
public:
PerspectiveCamera(float fovy, float aspect, float near, float far);
~PerspectiveCamera();
glm::mat4 get_projection_matrix()override;
void scale(float delta_scale)override;
public:
float m_fovy = 0.0f;
float m_aspect = 0.0f;
float m_near = 0.0f;
float m_far = 0.0f;
};

perspective.cpp

#include"perspective_camera.h"
PerspectiveCamera::PerspectiveCamera(float fov, float aspect, float near, float far)
:m_fovy(fov), m_aspect(aspect), m_near(near), m_far(far)
{
}
PerspectiveCamera::~PerspectiveCamera()
{
}
glm::mat4 PerspectiveCamera::get_projection_matrix()
{
//传入的是角度,需要转化为弧度
return glm::perspective(glm::radians(m_fovy), m_aspect, m_near, m_far);
}
void PerspectiveCamera::scale(float delta_scale)
{
auto front = glm::cross(m_up, m_right);
m_position += front * delta_scale;
}

三、相机控制(Camera Control)的设计

3.1相机控制基类(CameraControl)的设计

CameraControl的主要作用便是让相机响应外界消息,进行相应的旋转,平移等操作。而为了达成这一目的,就需要持有鼠标与键盘按键状态,设置其根据外界值变化的幅度比例,并提供相应的按键响应函数来获取外设的输入信息,同时更新按键状态。

camera_control.h

#pragma once
#include"../../glframework/core.h"
#include"camera.h"
#include
class CameraControl
{
public:
CameraControl();
~CameraControl();
virtual void on_mouse(int button, int action, double xpos, double ypos);
virtual void on_cursor(double xpos, double ypos);
virtual void on_key(int key, int action, int mods);
virtual void on_scroll(float offset);
//每一帧渲染之前都要进行调用,每一帧更新的行为可以放在这里
virtual void on_update();
void set_camera(Camera* camera) { m_camera = camera; }
void set_sensitivity(float sensitivity) { m_sensitivity = sensitivity; }
protected:
//1.鼠标按键状态
bool m_left_down = false;
bool m_right_down = false;
bool m_middle_down = false;
//2.当前鼠标位置
float m_current_x = 0.0f;
float m_current_y = 0.0f;
//3.敏感度
float m_sensitivity = 0.2f;
//4.记录键盘相关按键的按下状态
std::mapm_key_map;
//5.存储当前控制的摄像机
Camera* m_camera;
//6.记录相机缩放的速度
float m_scale_speed = 0.0001f;
};

camera_control.cpp

#include"camera_control.h"
#include
CameraControl::CameraControl()
{
}
CameraControl::~CameraControl()
{
}
void CameraControl::on_mouse(int button, int action, double xpos, double ypos)
{
//1.判断当前的按键是否按下
bool pressed = (action == GLFW_PRESS) ? true : false;
//2.如果按下,记录当前按下的位置
if (pressed)
{
m_current_x = xpos;
m_current_y = ypos;
}
//3.根据按下的鼠标按键不同,激活不同的记录
switch (button)
{
case GLFW_MOUSE_BUTTON_LEFT:
m_left_down = pressed;
break;
case GLFW_MOUSE_BUTTON_RIGHT:
m_right_down = pressed;
break;
case GLFW_MOUSE_BUTTON_MIDDLE:
m_middle_down = pressed;
break;
default:
break;
}
}
void CameraControl::on_cursor(double xpos, double ypos)
{
}
void CameraControl::on_key(int key, int action, int mods)
{
//过滤掉repeat的情况
if(action == GLFW_REPEAT)
return;
//1.检测按下或者抬起,给到一个变量
bool pressed = (action == GLFW_PRESS) ? true : false;
//2.记录在key_map中
m_key_map[key] = pressed;
}
void CameraControl::on_update()
{
}
void CameraControl::on_scroll(float offset)
{
}

3.2轨迹球相机控制(TrackballCameraControl)类设计

在轨迹球相机控制中,要实现(从视觉上来说的效果):

(1)点击鼠标左键能够翻转当前物体

(2)点击鼠标中间进行对物体进行移动

(3)滑动滚轮进行放缩

对于(1)操作,实际上是相机绕着其right向量和世界坐标的up轴进行旋转,纵向的变换为pitch,而横向的变换为yaw。

其中pitch操作与yaw操作均会改变相机的当前位置,而pitch操作不会改变right向量,yaw操作不会改变front向量指向(始终指向中心物体),因而其对于pitch操作需要用旋转矩阵乘以up向量,而front向量是经过叉乘计算得到,也会跟着改变。对于yaw操作,则是用旋转矩阵分别乘以up、right向量,front向量通过叉乘计算得到,其结果不会发生变化。其中,pitch操作受到鼠标指针纵向变化量的影响,而yaw操作则受到横向变化量的影响。

对于(2)操作,则是对相机位置位置进行改变,只需要在up、right方向上加上变化值。

对于(3)操作,则是对先前封装好的scale函数直接进行调用。

trackball_camera_control.h

#pragma once
#include"camera_control.h"
class TrackballCameraControl : public CameraControl
{
public:
TrackballCameraControl();
~TrackballCameraControl();
//父类当中的函数是否需要重写
void on_cursor(double xpos, double ypos) override;
void on_scroll(float offset)override;
private:
void pitch(float angle);
void yaw(float angle);
private:
float m_move_speed = 0.002f;
};

trackball_camera_control.cpp

#include"trackball_camera_control.h"
TrackballCameraControl::TrackballCameraControl()
{
}
TrackballCameraControl::~TrackballCameraControl()
{
}
void TrackballCameraControl::on_cursor(double xpos, double ypos)
{
if (m_left_down)
{
//调整相机的各类参数
//1.计算经线跟纬线旋转的增量角度(正负都有可能)
float delta_x = (xpos - m_current_x) * m_sensitivity;
float delta_y = (ypos - m_current_y) * m_sensitivity;
//2.分开pitch跟yaw各自计算
pitch(-delta_y);
yaw(-delta_x);
}
else if (m_middle_down)
{
float delta_x = (xpos - m_current_x) * m_move_speed;
float delta_y = (ypos - m_current_y) * m_move_speed;
m_camera->m_position += m_camera->m_up * delta_y;
m_camera->m_position -= m_camera->m_right * delta_x;
}
m_current_x = xpos;
m_current_y = ypos;
}
void TrackballCameraControl::pitch(float angle)
{
//绕着m_right向量在旋转
auto mat = glm::rotate(glm::mat4(1.0f), glm::radians(angle), m_camera->m_right);
//影响当前相机的up向量和位置
m_camera->m_up = mat * glm::vec4(m_camera->m_up, 0.0f);
m_camera->m_position = mat * glm::vec4(m_camera->m_position, 1.0f);
}
void TrackballCameraControl::yaw(float angle)
{
//绕着m_front向量在旋转
auto mat = glm::rotate(glm::mat4(1.0f), glm::radians(angle), glm::vec3(0.0f, 1.0f, 0.0f));
//影响当前相机的up向量和位置
m_camera->m_up = mat * glm::vec4(m_camera->m_up, 0.0f);
m_camera->m_right = mat * glm::vec4(m_camera->m_right, 0.0f);
m_camera->m_position = mat * glm::vec4(m_camera->m_position, 1.0f);
}
void TrackballCameraControl::on_scroll(float offset)
{
m_camera->scale(m_scale_speed * offset);
}

3.3游戏相机控制(GameCameraControl)类的设计

在游戏相机控制中,要实现(从视觉上来说的效果):

(1)鼠标右键按下的时候实现相机视角的俯仰(pitch)和偏转(yaw)

(2)通过WASD的按键移动当前相机的位置

对于(1)的实现,依旧是只需要关注相机是在围绕着哪一个轴进行旋转,并修改其他量即可。在这里由于不会影响到相机的位置,所以位置信息不需要进行更改

对于(2)的实现,则是根据当前按键的状态值去计算运动的方向,在进行归一化之后,对当前相机的坐标位置进行更新。其中尤其需要注意在归一化过程中分母为0的情况需要进行特殊判断

game_camera_control.h

#pragma once
#include"camera_control.h"
class GameCameraControl : public CameraControl
{
public:
GameCameraControl();
~GameCameraControl();
void on_cursor(double xpos, double ypos) override;
void on_update()override;
void set_speed(float speed){ m_speed = speed;}
private:
void pitch(float angle);
void yaw(float angle);
private:
float m_pitch{ 0.0f };
float m_speed{ 0.01f };
};

game_camera_control.cpp

#include"game_camera_control.h"
GameCameraControl::GameCameraControl()
{
m_sensitivity = 0.002f;
}
GameCameraControl::~GameCameraControl()
{
}
void GameCameraControl::on_cursor(double xpos, double ypos)
{
float delta_x = (xpos - m_current_x) * m_sensitivity;
float delta_y = (ypos - m_current_y) * m_sensitivity;
if (m_right_down)
{
pitch(delta_y);
yaw(delta_x);
}
m_current_x = xpos;
m_current_y = ypos;
}
void GameCameraControl::pitch(float angle)
{
m_pitch += angle;
if (m_pitch > 89.0f|| m_pitch m_right);
m_camera->m_up = mat * glm::vec4(m_camera->m_up, 0.0f);
}
void GameCameraControl::yaw(float angle)
{
auto mat=glm::rotate(glm::mat4(1.0f), glm::radians(angle), glm::vec3(0.0f, 1.0f, 0.0f));
m_camera->m_right = mat * glm::vec4(m_camera->m_right, 0.0f);
m_camera->m_up = mat * glm::vec4(m_camera->m_up, 0.0f);
}
void GameCameraControl::on_update()
{
glm::vec3 direction(0.0f);
auto front=glm::cross(m_camera->m_up, m_camera->m_right);
auto right=m_camera->m_right;
if (m_key_map[GLFW_KEY_W])direction += front;
if (m_key_map[GLFW_KEY_S])direction -= front;
if (m_key_map[GLFW_KEY_A])direction -= right;
if (m_key_map[GLFW_KEY_D])direction += right;
//归一化
if (glm::length(direction) != 0)
{
direction=glm::normalize(direction);
m_camera->m_position += direction * m_speed;
}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/912209.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

指令流水线的影响因素

机器周期的设置 为了方便流水线设计,每个阶段耗时都设置成一样的,以最长耗时为准 影响流水线的因素 结构相关(资源冲突) 多条指令在同一个时间争夺使用同一个资源而形成的冲突称为结构相关 例子在书本第257页第一张…

HT-SC4PS-33+:小功分大胃口,300 MHz-3 GHz全场通吃

HT-SC4PS-33+:小功分大胃口,300 MHz-3 GHz全场通吃HT-SC4PS-33+ 是成都恒利泰推出的 300-3000 MHz 超宽带一分四贴片功分器,插损低到 1.6 dB,隔离度 17 dB,幅度不平衡仅 0.4 dB,-40 ℃~+85 ℃全温漂得住;5G 小基…

网站开发新乡网站建设服务

os模块 在Python中文件和文件夹的操作要借助os模块里面的相关功能&#xff0c;具体步骤如下&#xff1a; 第一步&#xff1a;导入os模块 import os 第二步&#xff1a;调用os模块中的相关方法 os.函数名() 与文件操作相关方法 编号函数功能1os.rename(目标文件名称&…

济南建设厅网站安全员威海建设局网站楼盘信息公布

文章目录 1. 概述2. 内连接3. 外连接4. 自连接5. 联合查询-union,union all6. 子查询 1. 概述 在项目开发中&#xff0c;在进行数据库表结构设计是&#xff0c;会根据业务需求和业务模块之间的关系&#xff0c;分析并设计表结构&#xff0c;由于业务之间相互关联&#xff0c;所…

东莞市建设安监局网站首页网站运行维护

1.摘要 在细粒度图像识别(FGIR)中&#xff0c;区域注意力的定位和放大是一个重要因素&#xff0c;基于卷积神经网络(cnn)的方法对此进行了大量探索。近年来发展起来的视觉变压器(ViT)在计算机视觉任务中取得了可喜的成果。与cnn相比&#xff0c;图像序列化是一种全新的方式。然…

win7网站后台无法编辑青原区城乡建设局门户网站

接入CDN对免备案网站服务器来说有以下几个好处&#xff1a; 提高网站的访问速度&#xff1a;CDN会将网站的静态资源&#xff08;如图片、文档等&#xff09;缓存到离用户最近的节点服务器上&#xff0c;用户访问时可以从离他们最近的节点服务器获取资源&#xff0c;加快访问速度…

个人网站带论坛 备案百度发布信息的免费平台

摘要&#xff1a;在本博客中介绍了基于YOLOv8/v7/v6/v5的机场航拍小目标检测系统。该系统的核心技术是采用YOLOv8&#xff0c;并整合了YOLOv7、YOLOv6、YOLOv5算法&#xff0c;从而进行性能指标的综合对比。我们详细介绍了国内外在机场航拍小目标检测领域的研究现状、数据集处理…

高端网站的制作做标签这个网站刷单安全吗

UI Automator为Android程序的UI开发提供了测试环境,这里我们就来看一下Android App开发的自动化测试框架UI Automator使用教程,需要的朋友可以参考下 Android的自动化测试有很多框架&#xff0c;其中ui automator是google官方提供的黑盒UI相关的自动化测试工具&#xff0c;&am…

电子商务网站用什么语言开发辽宁建设工程信息网站

“我讲课不割韭菜&#xff0c;宗旨是免费、分享、科普、交流。AI时代技术发展迅速&#xff0c;AI知识普及尤为重要。”2月29日&#xff0c;360公司创始人周鸿祎免费课正式开启&#xff0c;全网多平台直播了AI系列第一讲“预见AGI”&#xff0c;千万网友观看。免费课上&#xff…

像素时代网站建设手机站设计wordpress的.htaccess

目录 自动映射 表映射 字段映射 字段失效 视图属性 Mybatis框架之所以能够简化数据库操作&#xff0c;是因为他内部的映射机制&#xff0c;通过自动映射&#xff0c;进行数据的封装&#xff0c;我们只要符合映射规则&#xff0c;就可以快速高效的完成SQL操作的实现。既然…

[vscode] 快捷键记录

[vscode] 快捷键记录$(".postTitle2").removeClass("postTitle2").addClass("singleposttitle");1. 代码折叠 1.1 折叠所有: Windows/Linux: Ctrl + K, Ctrl + 0 (先按 Ctrl+K,然后按…

Gitee本土化创新实践:中国企业研发效能提升的新引擎

Gitee本土化创新实践:中国企业研发效能提升的新引擎 在数字化转型浪潮席卷全球的当下,软件开发效率已成为企业核心竞争力的关键指标。作为国内领先的代码托管平台,Gitee凭借其本土化创新优势,正在重塑中国企业研发…

画面拼接后推流/64路画面同时拼接到一路流/指定程序窗口采集推流/另一种解决方案

一、前言说明 近期遇到一个需求,需要将对个画面合并到一个流中推流出去,这个有很多种方案去实现,一个方案是采用ffmpeg的滤镜,视频水印的方式叠加上去,测试下来行是行,就是编码占用压力巨大,有点得不偿失。另一…

详细介绍:nvm使用和node使用

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

Markdown的基本语法

欢迎使用 OpenWrite 开始写作您的文章... 这是一个二级标题 您可以使用 Markdown 语法:粗体文字 斜体文字 行内代码// 代码块示例 console.log(Hello OpenWrite!);这是一个引用块链接示例

小说发表哪个网站赚钱网站建设炎陵

变量是只不过保留的内存位置用来存储值。这意味着&#xff0c;当创建一个变量&#xff0c;那么它在内存中保留一些空间。根据一个变量的数据类型&#xff0c;解释器分配内存&#xff0c;并决定如何可以被存储在所保留的内存中。因此&#xff0c;通过分配不同的数据类型的变量&a…

做淘宝网站用什么软件建设一个视频网站需要什么时候开始

曾经和一群可爱的人儿做的项目&#xff0c;获得了2019 年度中国质量协会质量技术优秀奖&#xff0c;无心插柳柳成荫。 那几年工作得很快乐&#xff0c;工作与家庭都兼顾&#xff0c;是同事也是朋友。2019年末去过一次移动宁波分公司&#xff0c;特意去看了原来驻场的办公室&am…

网站备案能查到什么东西网络营销策划方案3000字

前言在APP中启动相册选择器或者拍照上传图片这些功能是非常常见的。对于Ionic2&#xff0c;我们只能通过cordova插件实现调用原生的功能。下面将简单的封装一个选择相册或拍照上传图片的ImgService服务。具体如下。 Cordova准备下载安装所需的Cordovar插件&#xff1a; Image P…

设计工资一般多少云南网站建设优化

功率谱密度图以横轴为频率&#xff0c;纵轴为功率密度&#xff0c;表示信号功率密度随着频率的变化情况 python绘制功率谱密度&#xff1a; matplotlib.pyplot.psd(x, NFFT256, Fs2, Fc0, detrendmlab.detrend_none,windowmlab.window_hanning, noverlap0, pad_toNone,sidesd…

南宁公司的网站建设备案一个网站为什么需要域名

写在前面 在Excel文档的自动化处理流程中&#xff0c;有部分值需要通过已定义的宏来求解&#xff0c;所以延伸出了用C# 调用Excel中的宏代码的需求。 首先要从NuGet中引入Microsoft.Office.Interop.Excel 类库 using Excel Microsoft.Office.Interop.Excel; 代码实现 /// &l…