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控 ...
随机推荐
- MSYS2、MinGW、Cygwin 关系梳理
还记得大一刚开始写 C 代码时,经常看到 MSYS2.MinGW.Cygwin 等名词.对于第一次接触编程的我来说这些名词让我眼花缭乱.当时查阅了一些资料,但是对于这些名词的解释始终让我云里雾里.现在 ...
- Python if __name__ == "__main__" 解释
一种机制,允许脚本以不同的方式运行,这取决于作为独立的程序执行还是作为模块被其他脚本导入.这种机制就是 if __name == "__main__" 其作用是控制某些代码块只在该 ...
- 移除 Parallels Access Sound 虚拟设备
在安装了 Parallels 之后,发现 Mac 没声音,打开声音设置一看音频输出设备被设为了 Parallels Access Sound.把输出设备调回 MacBook 扬声器就有声音了. 但是音 ...
- C语言中的数据类型及其转换
目录 计算机中的数据类型 整型数据之间的转换 相同字长之间的转换 小字长转大字长 大字长转小字长 int.float.double之间的转换 float->double double->f ...
- c++ 命名的强制类型转换
显式转换:显式将一种类型转换为另一种类型. References: C++中的显示数据类型转换 与命名的强制类型转换相比,旧式的强制类型转换从表现形式上来说不那么清晰明了,容易被看漏,所以一旦转换过程 ...
- 小tips:tomcat下JSP页面引用css、js等资源路径404问题
在JSP页面头部添加如下代码: <% String path = request.getContextPath(); String basePath = request.getScheme() ...
- 实战合集 | I/O 2021 Flutter 研讨会
2021 年的 Google I/O 大会已圆满闭幕,本次大会带来了诸多关于各项谷歌开发技术产品的最新更新.在此次 I/O,Flutter 发布了 2.2 版本,包括 Web 版的增强.更多 Mate ...
- C语言位域的内存布局
本文将先粗略介绍大小端,和大小端的测试方法,最后介绍位域的内存布局. 1. 大小端 大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中. 小端模式,是指数据的高字节保存 ...
- cf1632 Codeforces Round #769 (Div. 2)题解
A ABC 水题0.0 B Roof Construction 构造题,经过实验构造出了0的右边是不超过\(n\)的最大的\(2^k\rightarrow n\),左边是\(2^k-1,2^k-2,. ...
- 014 Python 的数据类型(数字、字符串、列表、字典)
#!/usr/bin/env python # -*- coding:utf-8 -*- # Datatime:2022/7/24 20:31 # Filename:014 Python 的数据类型( ...