OSG开发笔记(三十二):深入理解相机视口、制作支持与主视图同步变换旋转的相机HUD
前言
深入理解相机视口,摸索相机视口旋转功能,背景透明或者不透明。
本篇,实现了一个左下角旋转HUD且背景透明的相机视口。
Demo
HUD相机的坐标
抬头HUD就是通过投影矩阵来实现,具体可参看《OSG开发笔记(二十):OSG使用HUD显示文字》
- Hud要单独创建一个新相机
- 注意关闭光照,不受光照影响,所以内容以同一亮度显示
- 关闭深度测试
- 渲染顺序设置为POST,否则可能会被场景中的其他图形所覆盖。
- 设置参考贴为绝对型:setReferenceFrame(osg::Transform:ABSOLUTE_RF)
- 使其不受父节点变换的影响:setMatrix(osg::Matrix::identity())
- 投影矩阵通常会设置为屏幕尺寸大小
相机(Camera)
相机(osg::Camera)和视口(Viewport)是两个核心概念,对于理解OSG中的三维场景渲染至关重要。
相机在OSG中用于模拟真实世界中的摄影机,它负责捕捉和渲染三维场景。相机类(osg::Camera)继承自osg::Transform和osg::CullSetting类,用来管理OSG中的模型——视图矩阵。相机的管理主要是通过各种变换实现的,这些变换包括:
- 视点变换:设置视点的方向和位置。默认情况下,视点定位为坐标原点,指向Y正方向。可以通过调整视点的位置和参考点的位置来改变相机的观察方向和角度。
- 投影变换:由于显示器只能用二维图像显示三维物体,因此要靠投影来降低维数。投影变换的目的是定义一个视景体,使视景体外多余的部分被裁减掉,最终进入图像的只是视景体内的有关部分。OSG支持两种投影方式:透视投影(Perspective Projection)和正视投影(Orthographic Projection)。透视投影能够模拟人眼的视觉效果,使远处的物体看起来更小,而正视投影则保持物体的大小不变,不受距离影响。
- 视口变换:将视景体内投影的物体显示在二维的视口平面上。即将经过几何变换、投影变换和裁剪变换后的物体显示于屏幕窗口内指定的区域内,这个区域通常为矩形,称为视口。
视口(ViewPort)
具体来说,视口变换涉及以下几个参数:
- 屏幕左下角的坐标:定义了视口在屏幕上的左下角位置。
- 屏幕宽度和高度:定义了视口的宽度和高度,即相机捕捉的场景在屏幕上显示的区域大小。
在OSG中,可以通过调用相机的setViewport方法来设置视口。例如:
pCamera->setViewport(new osg::Viewport(0, 0, width, height));
这行代码创建了一个新的视口,并将其设置为相机的当前视口。其中,0和0是屏幕左下角的坐标,width和height是视口的宽度和高度。
相机与视口的关系
相机和视口在OSG中紧密相连,共同决定了三维场景的渲染效果。相机负责捕捉和渲染场景,而视口则定义了相机捕捉的场景在屏幕上的显示位置和大小。通过调整相机的各种变换和设置视口的大小和位置,可以实现丰富的三维视觉效果和交互体验。
设置相机观察函数
void setViewMatrixAsLookAt(const osg::Vec3d& eye, const osg::Vec3d& center, const osg::Vec3d& up);
- eye:表示相机的位置。这是一个三维向量,指定了相机在世界坐标系中的位置。
- center:表示相机观察的中心点。这也是一个三维向量,指定了相机应该对准的物体或场景的中心位置。
- up:表示哪个方向是正方向。这同样是一个三维向量,通常用于指定相机的上方方向(例如,通常设置为 (0,0,1) 表示Y轴正方向为上方)。
设置相机位置和方向:通过指定 eye、center 和 up 三个参数,你可以精确地控制相机的位置和姿态。eye 和 center 之间的向量表示相机的观察方向,而 up 向量则用于确定相机的上方方向。
关闭漫游器:在使用 setViewMatrixAsLookAt 函数之前,通常需要关闭相机的漫游器(Camera Manipulator)。这是因为漫游器会自动更新相机的观察矩阵,从而覆盖你通过 setViewMatrixAsLookAt 设置的参数。可以通过调用 viewer->setCameraManipulator(NULL) 来关闭漫游器。
坐标系:OSG 使用右手坐标系,其中 X 轴向右,Y 轴向上,Z 轴向前。因此,在设置 eye、center 和 up 参数时,需要确保它们符合右手坐标系的规则。
视图矩阵:setViewMatrixAsLookAt 函数实际上是通过设置相机的视图矩阵来实现相机位置和姿态的调整。视图矩阵是一个 4x4 的矩阵,用于将相机坐标系中的点转换到世界坐标系中。
setViewMatrixAsLookAt 是一个强大的函数,它允许你以直观的方式设置相机的位置和姿态。通过合理地使用这个函数,你可以创建出各种复杂而逼真的三维场景和视觉效果。
Demo关键源码
创建Hud相机
// 步骤一:创建HUD摄像机
// osg::ref_ptr<osg::Camera> pCamera = new osg::Camera;
osg::ref_ptr<HudRotateCamera> pCamera = new HudRotateCamera;
pCamera->setMasterCamera(_pViewer->getCamera());
// 步骤二:设置投影矩阵
// pCamera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1280, 0, 800));
// 步骤三:设置视图矩阵,同时确保不被场景中其他图形位置变换影响, 使用绝对帧引用
pCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
pCamera->setViewMatrix(osg::Matrix::identity());
// 步骤四:清除深度缓存
pCamera->setClearMask(GL_DEPTH_BUFFER_BIT);
// 步骤五:设置POST渲染顺序(最后渲染)
// pCamera->setRenderOrder(osg::Camera::PRE_RENDER); // 渲染不显示
// pCamera->setRenderOrder(osg::Camera::NESTED_RENDER);
pCamera->setRenderOrder(osg::Camera::POST_RENDER);
// 步骤六:设置为不接收事件,始终得不到焦点
pCamera->setAllowEventFocus(false);
// osg::ref_ptr<osg::Geode> pGeode = new osg::Geode();
// pGeode = new osg::Geode();
osg::ref_ptr<osg::StateSet> pStateSet = pGeode->getOrCreateStateSet();
// 步骤七:关闭光照
pStateSet->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
// 步骤九:关闭深度测试
pStateSet->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF);
// pGeode->addDrawable(pGeometry.get());
pCamera->addChild(pGeode.get());
pGroup->addChild(pCamera.get());
HudRotateCamera.h
#ifndef HUDROTATECAMERA_H
#define HUDROTATECAMERA_H
#include "osg/Camera"
#include "osg/CopyOp"
class HudRotateCamera : public osg::Camera
{
public:
HudRotateCamera();
HudRotateCamera(const HudRotateCamera& copy, const osg::CopyOp ©Op = osg::CopyOp::SHALLOW_COPY);
META_Node(osg, HudRotateCamera);
public:
void setMasterCamera(Camera* camera);
public:
virtual void traverse(osg::NodeVisitor& nodeVisitor);
protected:
virtual ~HudRotateCamera();
protected:
osg::observer_ptr<Camera> _pMasterCamera; // 新增了相机,主要是用来获取举证的
};
#endif // HUDROTATECAMERA_H
HudRotateCamera.cpp
#include "HudRotateCamera.h"
HudRotateCamera::HudRotateCamera(): Camera()
{
}
HudRotateCamera::HudRotateCamera(const HudRotateCamera & copy, const osg::CopyOp & copyOp)
: Camera(copy, copyOp),
_pMasterCamera(copy._pMasterCamera)
{
}
HudRotateCamera::~HudRotateCamera()
{
}
void HudRotateCamera::setMasterCamera(osg::Camera *camera)
{
_pMasterCamera = camera;
}
void HudRotateCamera::traverse(osg::NodeVisitor &nodeVisitor)
{
double fovy, aspectRatio, vNear, vFar;
_pMasterCamera->getProjectionMatrixAsPerspective(fovy, aspectRatio, vNear, vFar);
// 设置投影矩阵,使缩放不起效果, 改为正投影,正投影不会随相机的拉近拉远而放大、缩小,这样就没有缩放效果,
// 放大缩小是根据左右,上下距离,越大就物体越小,越小就物体越大
this->setProjectionMatrixAsOrtho(-50 * aspectRatio,
50 * aspectRatio,
-50,
50,
vNear,
vFar);
// 让坐标轴模型位于窗体左下角
osg::Vec3 vec3(-40, -40, 0);
if (_pMasterCamera.valid())
{
// 改变视图矩阵, 让移动位置固定
osg::Matrix matrix = _pMasterCamera->getViewMatrix();
// 让移动固定, 即始终位于窗体右下角,否则鼠标左键按住模型可以拖动或按空格键时模型会动
matrix.setTrans(vec3);
this->setViewMatrix(matrix);
}
osg::Camera::traverse(nodeVisitor);
}
工程模板v1.35.0
入坑
入坑一:没有按照预期的方式全屏显示在正中间
问题
想一直显示在中间,且能旋转,移动中心,但是实际效果如下,方格100x100,间距1.0,测试:
尝试
将面方格缩小为10x10,线放小,测试:
缩小正交投影:
可能跟相机查看位置有关,新增相机位置和方向等信息:
没什么影响:
这里可能理解有问题,我们需要区域投影到视口,那么一个是投影的区域三维区域的大小,一个是投影到桌面2D他的大小,这里其实类似于HUD,通过HUD的方式,添加了几行代码设置投影矩阵:
经过测试,可以跳过相机调整视口、中心改变后也会移动,所以他一种在其位置区域。
然后回到前面,发现也可以,再次摸索,发现如下特点:
所以此时,纵横都是10,所以窗口大小要是符合1:1(10:10=1:1)的比例,改为400,400测试,还是一样:
但是调整为800x800就好了:
放最大也不会截取少了:
所以这个有点搞不明白了,总之是解决了,且投影矩阵和正交矩阵都可以解决,测试投影矩阵和正交举证都收视口大小影响,但是影响具体不知,就好像800x800是最小一样(其他的没测了,只测了400x400、600x600不行,看比例800x800是最小正好满窗口了)。
又怀疑投过去的区域小了,将区域放大,其位置反倒缩小,所以跟理解不一样:
- 一种是理解直接投射过去,投射过去区域变大所以变大(不是的);
- 一种是投射过去区域不变,那么区域变大视图区域可见空间范围变大(实际是这样,但是视口对屏幕的大小未变);
综合以上,又测试了加大视口,也正常,所以怀疑有可能是qt和osg结合的时候这个地方设置了一个最小值,而可能吧,欢迎探讨,这里深究暂时也没结果,且解决了,所以不继续深究了。
解决
修改相机视口大小为最小800x800。
后续补充
后续查看做的这个qtosg兼容类,做的时候,自己设置的800x800,就是这个原因了:
入坑二:相机视口区域不透明
问题
当作最前面的文本hud,是可以透明,但是这里进行调整之后,无法透明。
尝试
修改了语句,可以透明了部分,但是没了。
解决
相机是一个投影矩阵,没有透明,但是文字hud为什么透明呢?。
入坑三:内置几何体关闭光照后纯白色
问题
关闭光照后,几何体白色
原理
光照关闭要设置颜色,不想设置颜色,就单独给体开放关照。
解决
入坑四:文本看不到
问题
文本看不到,旋转后发现是太大了。
位置较大。
尝试
测试下是把整个坐标区域的展示范围扩大,那么实际看起来就是缩小。
解决
入坑五:hud旋转中心不对
问题
Hud旋转中心不对
这时旋转中心还不对,可能需要调整旋转中心
原理
开始去修改矩阵,发现都不对,其实中间点一直是0,0,0,其就是中心,那么我们设置文本的显示点不是从0,0开始即可。
下面将四边形的角点改为0,0,0来标识,然后修改文本的中心点:
解决
入坑六:hud旋转反向了
问题
旋转是反的,然后光照也反了,变换矩阵有问题
原理
数据几何变换算来算去很费劲,直接测试的结论:
代码写反了,让上下反向了,应该是-50~50
解决
还剩下光照问题,这个不好咋弄了,反正是关闭光照,或者是自己手动添加光源,用系统的可能有点问题。
这个暂时没解决,实际使用就是用一个,可以从长度单独给这个相机设置一个光源,这里因为需求本身不需要投影,不做测试了。
OSG开发笔记(三十二):深入理解相机视口、制作支持与主视图同步变换旋转的相机HUD的更多相关文章
- Django笔记三十二之session登录验证操作
本文首发于公众号:Hunter后端 原文链接:Django笔记三十二之session登录验证操作 这一篇笔记将介绍 session 相关的内容,包括如何在系统中使用 session,以及利用 sess ...
- 树莓派开发笔记(十二):入手研华ADVANTECH工控树莓派UNO-220套件(一):介绍和运行系统
前言 树莓派也可以做商业应用,工业控制,其稳定性和可靠性已经得到了验证,故而工业控制,一些停车场等场景也有采用树莓派作为主控的,本片介绍了研华ADVANTECH的树莓派套件组UNO-220-P4N ...
- Java开发学习(三十二)----Maven多环境配置切换与跳过测试的三种方式
一.多环境开发 我们平常都是在自己的开发环境进行开发, 当开发完成后,需要把开发的功能部署到测试环境供测试人员进行测试使用, 等测试人员测试通过后,我们会将项目部署到生成环境上线使用. 这个时候就有一 ...
- BizTalk开发系列(三十二)浅谈BizTalk主机性能优化
很多BizTalk的项目都要考虑到性能优化的问题,虽然BizTalk采用多线程处理消息的,大大提高了程序效率.但默认情况下 BizTalk的主机有很多阻止参数会控制BizTalk对服务器的资源使用率, ...
- Android UI开发第三十二篇——Creating a Navigation Drawer
Navigation Drawer是从屏幕的左侧滑出,显示应用导航的视图.官方是这样定义的: The navigation drawer is a panel that displays the ap ...
- 安卓开发笔记(十二):SQLite数据库储存(上)
SQLite数据库存储(上) 创建数据库 Android专门提供了一个 SQLiteOpenHelper帮助类对数据库进行创建和升级 SQLiteOpenHelper需要创建一个自己的帮助类去继承它并 ...
- .net开发笔记(十二) 设计时与运行时的区别(续)
上一篇博客详细讲到了设计时(DesignTime)和运行时(RunTime)的概念与区别,不过没有给出实际的Demo,今天整理了一下,做了一个例子,贴出来分享一下,巩固前一篇博客讲到的内容. 简单回顾 ...
- 网站开发进阶(三十二)HTML5之FileReader的使用
HTML5之FileReader的使用 HTML5定义了FileReader作为文件API的重要成员用于读取文件,根据W3C的定义,FileReader接口提供了读取文件的方法和包含读取结果的事件模型 ...
- Java开发笔记(十二)布尔变量论道与或非
在编程语言的设计之初,它们除了可以进行数学计算,还常常用于逻辑推理和条件判断.为了实现逻辑判断的功能,Java引入了一种布尔类型boolean,用来表示“真”和“假”.该类型的变量只允许两个取值,即t ...
- PID控制器开发笔记之十二:模糊PID控制器的实现
在现实控制中,被控系统并非是线性时不变的,往往需要动态调整PID的参数,而模糊控制正好能够满足这一需求,所以在接下来的这一节我们将讨论模糊PID控制器的相关问题.模糊PID控制器是将模糊算法与PID控 ...
随机推荐
- MFC连接Access2007数据库
// TODO: 在此添加额外的初始化代码 //初始化ADO环境 if (!AfxOleInit()) { AfxMessageBox(L"OLE初始化失败"); return F ...
- 什么?!90%的ThreadLocal都在滥用或错用!
最近在看一个系统代码时,发现系统里面在使用到了 ThreadLocal,乍一看,好像很高级的样子.我再仔细一看,这个场景并不会存在线程安全问题,完全只是在一个方法中传参使用的啊!(震惊) 难道是我水平 ...
- Vue状态管理库Pinia详解
Pinia 是 Vue 的状态管理库,它提供了一种更简单.更不规范的 API 来管理应用的状态.Pinia 的设计哲学是简单性和易用性,它避免了 Vuex 中的许多复杂概念,如 mutations 和 ...
- 常用 DNS 查询速度测试
测试工具 DNS-Benchmark | GitHub 总结 进过多轮测试,我认为: 223.5.5.5 和 223.6.6.6 平均响应时间最短(223.5.5.5 在教育网内疑似无法连通) 114 ...
- [Panzura] identify user operations(copy, open, read ... ) in audit log
应该属于sequence classificagtion 问题 https://monkeylearn.com/text-classification/ https://machinelearning ...
- 【LLM训练系列】从零开始训练大模型之Phi2-mini-Chinese项目解读
一.前言 本文主要是在复现和实践Phi2-mini-Chinese后,简要分析下Phi2-mini-Chinese这个项目,做一个学习实战总结. 原文发布于知乎:https://zhuanlan.zh ...
- 【合合TextIn】OCR身份证 / 银行卡识别功能适配鸿蒙系统
一.鸿蒙系统与信创国产化的背景 自鸿蒙系统推出以来,其不仅成为了华为在软件领域的重要里程碑,更是国产操作系统的一面旗帜,也是国产移动平台几乎唯一的选择,标志着中国在构建独立自主的软件生态体系上迈 ...
- C++ 模板(函数模板与类模板)
模板 模板介绍 C++提供了函数模板(function template).所谓函数模板.实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表.这个通用函数就称为函数模板. ...
- namenode rpc 请求队列调优
当 datanode 上新写完一个块,默认会立即汇报给 namenode.在一个大规模 Hadoop 集群上,每时每刻都在写数据,datanode 上随时都会有写完数据块然后汇报给 namenode ...
- 参与 2023 第一季度官方 Flutter 开发者调查
Flutter 3.7 已经正式发布,每个季度一次的 Flutter 开发者调查也如约而至,邀请社区的各位成员们填写! 调查表链接: https://flutter.cn/urls/2023q1wx ...