OSG编程指南<九>:坐标系统及坐标系变换

news2024/12/23 6:31:42

1、坐标系统

  坐标系是一个精确定位对象位置的框架,所有的图形变换都是基于一定的坐标系进行的。对于从事计算机图形学的研究者,掌握图形变换是不可或缺的,因此,理解坐标系非常重要。一个三维图形工作者可以认为自己站在一定的坐标系中,通过自己的想法来操控模拟的虚拟环境。无论何时何地,只要确定原点和坐标轴的方向,都可以非常方便地建立坐标系。通常来说,任何一个三维坐标系都能表示空间中的所有点,因为坐标轴可以无限延伸。但为了方便处理各种问题,习惯上常定义多种坐标系。这些坐标系在本质上没有好坏之分,只是在不同的情况下处理问题的简化程度不同。
  三维坐标系总体上可以分为两大类,即左手坐标系和右手坐标系,相信读者对这两种坐标系都有所了解,这里就不再介绍。常用的坐标系有世界坐标系、物体坐标系和摄像机坐标系,下面分别进行介绍。

1.1 世界坐标系

  世界坐标系是一个特殊的坐标系,它建立了描述其他坐标系所需要的参考框架。从另一方面说,能够用世界坐标系来描述其他坐标系的位置,而不能用更大的、外部的坐标系来描述世界坐标系。世界坐标系也被广泛地称为全局坐标系或宇宙坐标系。世界坐标系描述的是整个场景中的所有对象,可以理解为绝对坐标系,所有对象的位置都是绝对坐标。
  从整体上考虑,它为所有对象的位置提供一个绝对的参考标准,从而避免了物体之间由于各自独立的物体坐标系而导致的坐标系混乱。世界坐标系通常描述的问题是一些对象的初始位置及场景中对象的变换过程,对象主要包括摄像机和绘制的物体。

1.2 物体坐标系

  物体坐标系是针对某一特定的物体而建立的独立坐标系。每一个物体都有自己的坐标系,当物体发生变换时,实际上是它本身的坐标系相对于世界坐标系发生变换的过程。物体坐标系对于描述特定的物体非常方便,假设所有的物体都用一个世界坐标系来描述,则一个物体进行任何变换的计算量都非常大,包括顶点坐标变换等。定义物体坐标系之后,非常容易即可实现物体的变换,只需要对物体坐标系相对世界坐标系发生变换即可。
  如果用世界坐标系来描述一个人,很难确定人的各个器官的精确坐标,从而人体模型的建立就非常困难,这时可以充分体现物体坐标系描述特定物体的优势。物体坐标系通常描述的问题是特定物体的内部对象,主要包括物体的顶点、物体的法向量和物体的方向。

1.3 摄像机坐标系

  摄像机坐标系是和观察者密切相关的坐标系。摄像机坐标系和屏幕坐标系相似,差别在于摄像机坐标系处于 3D 空间中,而屏幕坐标系在 2D 平面里。摄像机坐标系可以被看作是一种特殊的物体坐标系,该物体坐标系就定义在摄像机的屏幕可视区域。
  摄像机坐标系描述的是物体是否绘制渲染,并且在屏幕上显示出来。可以这样认为,即使视野再宽广,也无法一眼看到整个世界,这样读者就会发现摄像机坐标系的意义所在。
  摄像机坐标系描述的问题是哪些物体应该绘制渲染并且显示在屏幕上,主要包括物体是否在摄像机坐标系区域内、物体的渲染顺序和物体的遮挡绘制。因此,摄像机坐标系描述的问题是非常复杂的,一个好的虚拟引擎在这方面的表现肯定会非常突出,如区域裁剪、物体的遮挡渲染等。
  对于坐标系基本了解以后,读者可能会发现其中并没有涉及 OSG,这是因为前面讲解的都是一些基本的概念,只有了解了这些基础知识后才能够轻松理解后面的内容。OSG 对于上面所描述的 3 种坐标系都有比较好的应用,具体实现可参见源代码。OSG 采用的世界坐标系是左手坐标系,这点与 OpenGL 是保持一致的。值得注意的是它们之间的坐标轴方向不一样。

OSG:X 正方向向右,Y 正方向朝里,Z 正方向朝上。
OpenGL:X 正方向向右,Y 正方向朝上,Z 正方向朝外。

  OSG 的坐标系统可以理解为 OpenGL 坐标系统绕 X 轴顺时针旋转 90°。读者需牢记并理解坐标系,以后经常会遇到按一定的要求进行图形变换的情况。图 2-1 形象地描述了两个坐标系,其中,左侧的为 OSG 的坐标系,右侧的为 OpenGL 的坐标系。
在这里插入图片描述

2、坐标系变换

  坐标系变换是计算机图形处理的一个基础研究方向。三维实体对象需要经过一系列的坐标变换才能正确、真实地显示在屏幕上。在一个场景中,当读者对场景中的物体进行各种变换及相关操作时,坐标系变换是非常频繁的。坐标系变换通常包括世界坐标系-物体坐标系变换、物体坐标系-世界坐标系变换和世界坐标系-屏幕坐标系变换。屏幕坐标系是一个二维平面坐标系,即显示器平面,是非常标准的笛卡儿坐标系的第一象限区域。使用 OSG 开发时,经常会用到坐标系变换。下面介绍在 OSG 中坐标系变换的具体实现,可能不能面面俱到,但具有普遍应用性。

2.1 世界坐标系-物体坐标系变换

  世界坐标系-物体坐标系变换相对比较容易,它描述的问题主要是关于物体本身的。假设在世界坐标系中,一个人正准备走向一栋建筑,那么他就面临世界坐标系到物体坐标系的变换过程,变换过程中面临的问题是人相对建筑物的朝向、人相对建筑物的距离及人的移动方向等一系列的问题。上面的假设读者或许会觉得非常熟悉,它基本等同于基本变换的过程。在 OSG 中,已有如下相关的类实现了基本变换过程:

osg::PositionAttitudeTransform //位置变换类
osg::MatrixTransform //矩阵变换类

  这两个类实现的效果基本是相同的,只是数据的表达方式有区别,这样有利于处理各种数据的变换。后面的章节还会详细介绍,这里暂且留一个如何使用这两个类的疑问。或许学习这一章读者会有很多实现方面的疑问,不过没关系,通读全书后将不再疑惑。通过上面的两个类可以很方便地实现世界坐标系-物体坐标系的变换过程应该是容易理解的。世界坐标系-物体坐标系变换的意义在于简化了世界坐标系下变换的运算,当面对非常大的场景时,这种变换在一定程度上可以减少数据的运算量和提高场景的渲染效率。

2.2 物体坐标系-世界坐标系变换

  物体坐标系-世界坐标系变换不能简单地理解为世界坐标系-物体坐标系变换的逆过程。物体坐标系-世界坐标系变换描述的问题是处于世界坐标系中的物体。假设在物体坐标系中,一栋建筑物中有一个人,如何确定人在世界坐标系中的位置信息就是物体坐标系-世界坐标系变换所面对的问题。物体坐标系-世界坐标系变换是有一定难度的。
  对于场景图形中某一个 OSG 节点,它和根节点之间可能存在一些变换节点,那么如何获取该节点在世界坐标系中的位置就显得非常困难。但是,在场景图形中,每一个节点都有自己的父节点且有自己的变换矩阵,这些变换矩阵包含了相对坐标数据。那么,计算某一特定节点在世界坐标系下的坐标,只需要将该节点的根节点和该节点之间的所有变换矩阵相乘即可。
  在 OSG 中有多种方式来实现物体坐标系-世界坐标系的变换,如回调、访问器等,用访问器实现是一种方便可操控的方式,相对而言,回调在一定程度上不具备可操控行,且会因为增加额外开销而影响渲染效率,但每帧都会自动计算矩阵变换。访问器通过遍历的方式记录场景中节点的路径,并计算路径上矩阵变换的世界坐标,最终返回一个矩形式表示的世界坐标。下面具体实现该访问器,读者不必理解每一行代码,但需要理解实现的基本思路和原理,在以后的学习中,读者将逐渐熟悉代码,这些代码会经常用到。

//该访问器类用于返回某个节点的世界坐标
//从起始节点开始向根节点遍历,并将遍历的节点记录到 nodePath 中
//第一次到达根节点之后,记录起始点到根节点的节点路径
//获取所有世界坐标矩阵之后,即获得节点的世界坐标
class GetWorldCoordinateOfNodeVisitor : public osg::NodeVisitor
{
public:
	GetWorldCoordinateOfNodeVisitor() :
		osg::NodeVisitor(NodeVisitor::TRAVERSE_PARENTS), done(false)
	{
		wcMatrix = new osg::Matrixd();
	}

	virtual void apply(osg::Node& node)
	{
		if (!done)
		{
			//到达根节点,此时节点路径也已记录完整
			if (0 == node.getNumParents())
			{
				wcMatrix->set(osg::computeLocalToWorld(this->getNodePath()));
				done = true;
			}
			//继续遍历
			traverse(node);
		}
	}
	//返回世界坐标矩阵
	osg::Matrixd* giveUpDaMat()
	{
		return wcMatrix;
	}
private:
	bool done;

	osg::Matrix* wcMatrix;
};

//计算场景中某个节点的世界坐标,返回 osg::Matrix 格式的世界坐标
//创建用于更新世界坐标矩阵的访问器之后,即获取该矩阵
osg::Matrixd* getWorldCoords(osg::Node* node)
{
	GetWorldCoordinateOfNodeVisitor* ncv = new GetWorldCoordinateOfNodeVisitor();

	if (node && ncv)
	{
		//启用访问器
		node->accept(*ncv);

		return ncv->giveUpDaMat();
	}
	else
	{
		return NULL;
	}
}

2.3 世界坐标系-屏幕坐标系变换

  在场景中,所有的实体对象需要经过一系列的坐标变换才能正确显示在屏幕上,这些变换主要包括模型变换(将实体对象正确地放置在场景中)、投影变换(将场景中的实体对象投影到垂直于视线方向的二维成像平面上)和视口变换(投影变换之后得到的顶点需要经过视区变换才能得到最后的窗口坐标)。屏幕坐标是二维坐标,世界坐标是三维坐标,因此,正确的世界坐标系-屏幕坐标系变换就非常必要。
  通过前面的介绍可知,世界坐标系-屏幕坐标系变换主要有 3 个步骤,即模型变换、投影变换和视口变换,由模型变换和投影变换得到归一化的设备坐标,最后由视口变换得到屏幕窗口坐标。

//返回三维点在二维屏幕上的投影点
osg::Vec3d WorldToScreen(osgViewer::View* view,osg::Vec3 worldpoint)
{
double in[4], out[4];

in[0] = worldpoint._v[0];
in[1] = worldpoint._v[1];
in[2] = worldpoint._v[2];
in[3] = 1.0;
//获得当前的投影矩阵和模型视图矩阵
osg::Matrix projectMatrix= view->getCamera()->getProjectionMatrix();
osg::Matrix viewprojectMatrix = view->getCamera()->getViewMatrix();
//变换模型视图矩阵
double modelViewMatrix[16];
memcpy(modelViewMatrix,viewprojectMatrix.ptr(),sizeof(GLdouble) * 16);
Transform_Point(out, modelViewMatrix, in);
//变换投影矩阵
double myprojectMatrix[16];
memcpy(myprojectMatrix,projectMatrix.ptr(),sizeof(GLdouble) * 16);
Transform_Point(in, myprojectMatrix, out);

//变换视口变换矩阵
if (in[3] == 0.0)
return osg::Vec3d(0,0,0);

in[0] /= in[3];
in[1] /= in[3];
in[2] /= in[3];

int viewPort[4];
osg::Viewport* myviewPort = view->getCamera()->getViewport();
viewPort[0] = myviewPort->x();
viewPort[1] = myviewPort->y();
viewPort[2] = myviewPort->width();
viewPort[3] = myviewPort->height();
//计算二维屏幕投影点
osg::Vec3d sceenPoint;
sceenPoint._v[0] = (int)(viewPort[0] + (1 + in[0]) * viewPort[2] / 2 + 0.5);
sceenPoint._v[1] = (int)(viewPort[1] + (1 + in[1]) * viewPort[3] / 2 + 0.5);
sceenPoint._v[2] = 0;

return sceenPoint;
}

//输入点,进行矩阵变换
void Transform_Point(double out[4], const double m[16], const double in[4])
{
#define M(row,col) m[col*4+row]
out[0] =M(0, 0) * in[0] + M(0, 1) * in[1] + M(0, 2) * in[2] + M(0,
) * in[3];
out[1] =M(1, 0) * in[0] + M(1, 1) * in[1] + M(1, 2) * in[2] + M(1,
) * in[3];
out[2] = M(2, 0) * in[0] + M(2, 1) * in[1] + M(2, 2) * in[2] + M(2,
) * in[3];
out[3] = M(3, 0) * in[0] + M(3, 1) * in[1] + M(3, 2) * in[2] + M(3,
) * in[3];
#undef M
}

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

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

相关文章

【初始前后端交互+原生Ajax+Fetch+axios+同源策略+解决跨域】

初始前后端交互原生AjaxFetchaxios同源策略解决跨域 1 初识前后端交互2 原生Ajax2.1 Ajax基础2.2 Ajax案例2.3 ajax请求方式 3 Fetch3.1 fetch基础3.2 fetch案例 4 axios4.1 axios基础4.2 axios使用4.2.1 axios拦截器4.2.2 axios中断器 5 同源策略6 解决跨域6.1 jsonp6.2 其他技…

【每日一题】2824. 统计和小于目标的下标对数目-2023.11.24

题目&#xff1a; 2824. 统计和小于目标的下标对数目 给你一个下标从 0 开始长度为 n 的整数数组 nums 和一个整数 target &#xff0c;请你返回满足 0 < i < j < n 且 nums[i] nums[j] < target 的下标对 (i, j) 的数目。 示例 1&#xff1a; 输入&#xff1…

Digicert通配符证书:满足你的所有需求

通配符证书是一种可以保护一个主域名及其所有子域名的SSL证书。这意味着&#xff0c;如果你有一个主域名&#xff0c;比如www.example.com&#xff0c;并且你有多个子域名&#xff0c;比如blog.example.com、store.example.com等&#xff0c;那么只需要一个通配符证书&#xff…

ubuntu22.04系统下载程序和依赖,并拷贝到指定路径下

脚本1 apt install aptitude apt-get -d install xxx #xxx是待下载的安装包 mv /var/cache/apt/archives/* /home/tuners/1apt install aptitude apt-get -d install xxx mv /var/cache/apt/archives/*.deb /home/tuners/1 xxx 为程序包名称 /home/tuners/1为保存程序包的…

阿里云windwos 安装oracle数据库,外部用工具连接不上,只能在服务器本机通过127.0.0.1 连接

1. 首先检查阿里云服务器安全组端口是否开放 oracle 数据库端口 2. 其次找到oracle 安装的目录&#xff0c;打开这俩个文件&#xff0c;将localhost 修改为 服务器本机名称 3.重启oracle 监听服务&#xff0c;就可以连接了

IBM SPSS Statistics 27 Mac(统计分析软件)

IBM SPSS Statistics是一款数据分析和统计建模软件&#xff0c;它专为研究人员、分析师和商业用户设计。该软件能够帮助用户对大量数据进行分析和预测&#xff0c;帮助用户制定决策和解决问题。 IBM SPSS Statistics提供了丰富的统计分析功能&#xff0c;包括描述性统计、相关性…

【鸿蒙应用ArkTS开发系列】- 云开发入门实战二 实现省市地区三级联动地址选择器组件(下)

文章目录 概述端云调用流程端侧集成AGC SDK端侧省市地区联动的地址选择器组件开发创建省市数据模型创建省市地区视图UI子组件创建页面UI视图Page文件 打包测试总结 概述 我们在前面的课程&#xff0c;对云开发的入门做了介绍&#xff0c;以及使用一个省市地区联动的地址选择器…

IDM(Internet Download Manager)PC版提升下载速度与效率的利器

你是否曾经因为下载速度慢而感到烦恼&#xff1f;或者在下载大型文件时&#xff0c;经历了长时间的等待&#xff1f;如果你有这样的困扰&#xff0c;那么IDM&#xff08;Internet Download Manager&#xff09;就是你的救星&#xff01; IDM是一款高效、实用的下载管理器&…

PS 计数工具 基础使用方式讲解

上文PS 注释工具 基础使用方法讲解 中 我们讲了注释工具 解析来 我们来看这个计数工具 这里 我们换一张图像 如果 我要你数清楚 这个图上有几个咖啡豆 你能数清楚吗&#xff1f; 哈哈 其实也不难 不是特别大 但是 例如很多 且无规则物品时 我们可能就会数乱 左上角属性的话 我…

vue2【组件的构成】

目录 1&#xff1a;什么是组件化开发 2&#xff1a;vue中的组件化开发 3&#xff1a;vue组件的三个组成部分 4&#xff1a;组件中定义方法&#xff0c;监听器&#xff0c;过滤器&#xff0c;计算属性节点。 5&#xff1a;template中只允许唯一根节点&#xff0c;style默认…

arp报文及使用go实现

一、ARP协议报文格式及ARP表 ARP&#xff08;Address Resolution Protocal&#xff0c;地址解析协议&#xff09;是将IP地址解析为以太网的MAC地址&#xff08;或者称为物理地址&#xff09;的协议。在局域网中&#xff0c;当主机或其他网络设备有数据要发送给另一个主机或设备…

【2023年APMCM亚太杯C题】完整数据与解题思路

2023年亚太杯C题 数据下载与搜集重点数据其余数据第一问第二问第三问第四问第五问第六问 数据与思路获取 数据下载与搜集 该题并没有提供数据集&#xff0c;对所需数据进行收集整理是对题目进行求解的基础。在本题中&#xff0c;主要需要以下数据&#xff1a;新能源汽车历史销…

(免费领源码)java#springboot#mysql流浪动物救助系统78174-计算机毕业设计项目选题推荐

摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存…

知行之桥EDI系统HTTP签名验证

本文简要概述如何在知行之桥EDI系统中使用 HTTP 签名身份验证&#xff0c;并将使用 CyberSource 作为该集成的示例。 API 概述 首字母缩略词 API 代表“应用程序编程接口”。这听起来可能很复杂&#xff0c;但真正归结为 API 是一种允许两个不同实体相互通信的软件。自开发以…

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《基于外部性理论的网侧储能成本疏导机制研究》

这个标题涉及到一个关于储能的研究&#xff0c;主要聚焦在基于外部性理论的网侧&#xff08;电网侧&#xff09;储能成本疏导机制上。 基于外部性理论&#xff1a; 这表明研究的框架或者理论基础是"外部性理论"。外部性是指某个经济活动的影响不仅限于直接参与者&…

Adobe的组织工具程序Bridge 2024 版本下载与安装

目录 前言一、Bridge 2024安装二、使用配置总结 前言 Adobe Bridge是由 Adobe 公司开发的一款用于管理和组织创意资产的工具。它是Adobe Creative Cloud 套件的一部分&#xff0c;为设计师、摄影师和其他创意专业人员提供了一个集中管理和浏览其多媒体文件的平台。注&#xff…

JVM类加载的过程和JVM垃圾回收机制

文章目录 一、JVM类加载的过程1.1类加载的基本流程1.1.1加载1.1.2验证1.1.3准备1.1.4解析1.1.5初始化 1.2双亲委派模型 二、JVM垃圾回收机制2.1找到垃圾2.1.1引用计数(比如Python&#xff0c;PHP中用到)2.1.2可达性分析(比如Java中用到) 2.2释放垃圾2.2.1标记清除2.2.2复制算法…

小程序中的大道理--综述

前言 以下将用一个小程序来探讨一些大道理, 这些大道理包括可扩展性, 抽象与封装, 可维护性, 健壮性, 团队合作, 工具的利用, 可测试性, 自顶向下, 分而治之, 分层, 可读性, 模块化, 松耦合, MVC, 领域模型, 甚至对称性, 香农的信息论等等. 为什么不用大程序来说大道理呢? …

python写文件

output_file open(E:/XD_transfer/代码/CNN_new/try.csv, w) output_file.write(Sample, \n) for j in range(5):output_file.write(str(j) \n)

1-docker安装和配置、虚拟化、配置国内源、镜像操作、容器基本操作(run运行容器、-v目录映射、-p端口映射、容器其他操作)

1 docker和虚拟化 2 docker安装和配置 2.0 docker 中的一些概念 2.1 配置镜像加速器&#xff08;国内源&#xff09; 3 镜像操作 4 容器操作 4.1 容器基本操作 4.2 run运行容器 4.3 -v目录映射 4.4 -p端口映射 4.5 容器其他操作 1 docker和虚拟化 ## 什么是虚拟化在计算机中&…