当前位置: 首页 > news >正文

[OpenGL]相机环境

目录

一、逻辑分析

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.hskmm.com/?act=detail&tid=14442

相关文章:

  • 指令流水线的影响因素
  • Gitee本土化创新实践:中国企业研发效能提升的新引擎
  • 画面拼接后推流/64路画面同时拼接到一路流/指定程序窗口采集推流/另一种解决方案
  • Markdown的基本语法
  • 工业级CAD数据优化工具:PiXYZ Studio 2025 图文安装指南
  • BIM建模利器 Tekla Structures 2025 全流程安装指南
  • containerd离线安装
  • (转)使用 Embarcadero Delphi FMX 应用程序实现多点触控
  • 百度云服务ubtuntu安装docker
  • ubuntu安装mysql8并切换数据存储目录
  • WCF-双工通讯
  • 跨网文件安全交换系统:打破数据壁垒的高效之选!
  • 【F#学习】可区分联合 Discriminated Unions
  • Midscene.js - 开源的 AI 操作助手 - 广东靓仔
  • 详细介绍:【Datawhale25年9月组队学习:llm-preview+Task1:大模型介绍与环境配置】
  • Git仓库ssh不同环境配置
  • 超大附件怎么发送的高效解决方案与技巧
  • dm sql 缓存区
  • 给国外传输大文件的最佳策略与解决方案
  • idea mvn package 报错java head space/ java.lang.OutOfMemoryError: Java heap space
  • 大环境不好,这几个赚钱网站可以试试
  • Day20类与对象的小结
  • 电流探头的测试原理
  • 第四届云计算、大数据应用与软件工程国际学术会议(CBASE 2025)
  • Neo4j常用的语句记录
  • p1-1002
  • Model Context Protocol (MCP) 完整协议流程详解
  • 常用的 HTTP 请求方法和区别
  • 第七届机器学习、大数据与商务智能国际会议(MLBDBI 2025)
  • 【OpenCV】11 形态学操作