草庐IT

Cesium中的相机—setView&lookAtTransform

云上飞47636962 2023-04-13 原文

作为相机系列,此处先温习一下前期涉及到Cesium中Camera的两个概念(系列文章可参考我前面发的“Cesium中的相机–”系列)。

回顾

相机的空间位置

Cesium中,世界坐标系就是地球的WGS84系,也即地球固连坐标系(Earth Fiexed),在此坐标中定义相机的位置与观测方位。

相机坐标系见下图(使用Hubble望远镜示意相机),在Camera对象中,通常用三个矢量来表示:Up、Right和Direction,这三个方向确定了相机的观测方位。Up、Right、Direction与相机坐标系(视图坐标系)XYZ三轴的关系为:
X=Direction
Y=Left
Z=Up

Heading/Pitch/Roll的定义

在Cesium中,常使用Heading/Pitch/Roll三个参数来表示相机相对某坐标系的姿态:

  • Heading,表示相机绕Up轴旋转,Up轴为+Z轴,且定义绕-Z轴旋转为正。
  • Pitch,表示相机绕Right轴旋转,Right轴为-Y轴,且定义绕-Y轴旋转为正。
  • Roll,表示相机绕Direction轴(视线方向)旋转,Direction轴为+X轴,且绕+X轴旋转为正。
    相机的三个旋转方向见下图示意,同时给出了Cesium中相机的Up/Right/Direction三个轴与X/Y/Z轴的关系。

SetView

温顾了上述两个知识点,下面直接给出SetView的定义

Camera中的SetView函数用来设置相机的位置和方位的。

相机位置由destination指定,通常使用三维笛卡尔坐标(Cartesian3)来确定,此坐标默认为世界坐标系(即WGS84系)下,显然,destination距离地球越远,则视场内地球越小。

相机的方位由orientaiton参数指定,有两种方式:1)HeadingPitchRoll三个参数;2)DirectionUp两个参数。

下图左图给出了Cesium中SetView函数时,相机的初始方位:

  • 相机的视线(Direction)方向与当地北向重合
  • 相机的上方向(Up)与当地天顶方向(地心-相机位置矢量方向)

因此,基于相机的初始方位,可以使用HeadingPitchRoll三个参数来表示相机的具体方位,具体转换过程见上面有关HeadingPitchRoll的阐述。

简单举个例子,如果我们想要相机看向地心,那么为上图右图所示。可知,只要将相机从初始方位沿着Y轴旋转90°(对应的Pitch角度为-90°)即可。

当然,使用Direction和Up两个参数也可以指定相机的方位,简介明了,但是需要给出两个具体的矢量方向。

下面直接以代码给出几个示例:

// 1. Top-Down视角。此处仅设置相机位置,函数内部将Pitch设置为-90°,实现向地心看的效果
viewer.camera.setView({
    destination : Cesium.Cartesian3.fromDegrees(-117.16, 32.71, 15000.0)
});

// 2 采用Heading,Pitch,roll参数 设置相机方位
viewer.camera.setView({
    destination :  Cesium.Cartesian3.fromDegrees(0, 0, 10000000),	//赤道上空1000km高度
    orientation: {
        heading : Cesium.Math.toRadians(90.0), // 相机方向指向当地东向
        pitch : Cesium.Math.toRadians(-90),    // 再将相机方向转向地心,此时Up方向指向当地东向
        roll : 0.0                             
    }
});

// 3. 采用Heading,Pitch,roll参数设置相机方位,相机位置没有设置,则采用原来的位置!
viewer.camera.setView({
   orientation: {
        heading : Cesium.Math.toRadians(90.0), // 相机方向指向当地东向
        pitch : Cesium.Math.toRadians(-90),    // 再将相机方向转向地心,此时Up方向指向当地东向
        roll : 0.0                             
    }
});


// 4. 相机位置使用矩形定义,相机方位为缺省设置(内部pitch=-90°),实现看地心效果
viewer.camera.setView({
    destination : Cesium.Rectangle.fromDegrees(west, south, east, north)
});

// 5. 使用direction和up矢量方向直接定义相机的方位
viewer.camera.setView({
    destination : Cesium.Cartesian3.fromDegrees(-122.19, 46.25, 5000.0),  // 相机的位置
    orientation : {
        direction : new Cesium.Cartesian3(-0.042312, -0.201232, -0.978629), //相机视线方向矢量(WGS84系下)
        up : new Cesium.Cartesian3(-0.479345, -0.855321, 0.1966022)         //相机的up方向矢量(WGS84系下)
    }
});

lookAtTransform

setView函数直接确定了相机在世界坐标系的位置,而且相机的方向可以任意指定。但是在Cesium中,我们常常有一种跟随对象的视角,也就是相机始终跟随运动的对象(如下图的飞行中的龙飞船)。鼠标无论怎么调整视角,相机的方向始终对着龙飞船,也就是说相机的参考系不再是世界坐标系了,而是飞船局部坐标系。

lookAtTransform函数的定义见下图,它的主要功能是:设定一个局部的参考系,让相机始终朝着局部参考系的原点。

在上面定义中,两个参数的说明如下,可结合下图来看。

  • transform是相机参考的局部坐标系到世界坐标系的齐次坐标转换矩阵(Matrix4),通过此4×4的矩阵,可以把局部坐标系的位置直接转换到世界坐标系中(包含旋转和平移,详细参考另一篇文章:Cesium中的相机—齐次坐标与坐标变换)。此局部坐标系一般为地面某点的“east-north-up”坐标系或者运行的卫星轨道坐标系,见下图中的o-XYZ坐标系。
  • offset有两种类型,此处只讲一种:Cartesian3,即笛卡尔坐标,表示相机在局部坐标系中的位置,如下图中的OP矢量即表示相机的offset参数。
  • 相机的位置有了,那么相机的方位如何确定?见下图:相机的方向,即Direction始终指向局部坐标系的中心;相机的Up方向约束在局部坐标系的Z方向。

    lookAtTransform的用途非常广泛,可以将相机设置为任何局部坐标系,而局部坐标是通过Matrix4矩阵表示。

下面直接以代码给出示例:

//  在赤道上零经度上空一点,局部坐标系为:east-north-up坐标系,X:当地东向,Y:当地北向,Z:当地天顶方向,即世界坐标系的x方向
//	transform为局部坐标系到世界坐标系的齐次转换矩阵(包含旋转和平移)
const transform = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(0, 0, 10000000));
//	相机设置在局部坐标系的(0,0,1000)处,相机的视线方向指向局部坐标系零点,本例中,也就是从局部坐标系的Z轴向原点看,在世界坐标系下看就是向地心看
//  注意,由于此时视线方向与Z轴重合,所以程序内部将up方向设置为局部坐标系的Y方向,本例中也就是世界坐标系的Z方向。见下图示意。
var offset = new Cesium.Cartesian3(0, 0, 1000);
viewer.camera.lookAtTransform(transform, offset);

下图为上个例子的代码对应的示意图

另一个例子就是地球惯性系的视角,也即地球在自转的效果,参见下面代码,这个也是Cesium官方给出的例子

//  根据当前场景时间实时计算视角
//  此函数被注册到scene.preRender事件中
function icrf(scene, time) {
  // 不是3D窗口,直接返回
  if (scene.mode !== Cesium.SceneMode.SCENE3D) {
    return;
  }
  //  本例中,相机的局部坐标系就是地球惯性系,原点和世界坐标系重合,因此只有旋转,没有平移
  //  计算当前时刻,地球惯性系(icrf)到固定系(Fixed,也就是世界坐标系WGS84)的转换矩阵
  var icrfToFixed = Cesium.Transforms.computeIcrfToFixedMatrix(time);

  //  此处判断icrfToFixed是因为有可能上面代码返回为undefined
  if (Cesium.defined(icrfToFixed)) {
    // Maxtrix4形式,注意,此处没有平移,因为原点重合
    var transform = Cesium.Matrix4.fromRotationTranslation(icrfToFixed);
    // 当前相机的位置(相对局部坐标系的位置)
    var offset = Cesium.Cartesian3.clone(scene.camera.position);
    scene.camera.lookAtTransform(transform, offset);
  }
}

//  每次场景更新时都调用函数icrf调整视角
viewer.scene.preRender.addEventListener(icrf);

通过以上两个例子可知,我们只要提供了想要的局部坐标系到世界坐标系的齐次转换矩阵(Matrix4形式),那么就可以将相机局限在我们想要的局部坐标系中!!

有关Cesium中的相机—setView&lookAtTransform的更多相关文章

  1. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  2. ruby - 其他文件中的 Rake 任务 - 2

    我试图在一个项目中使用rake,如果我把所有东西都放到Rakefile中,它会很大并且很难读取/找到东西,所以我试着将每个命名空间放在lib/rake中它自己的文件中,我添加了这个到我的rake文件的顶部:Dir['#{File.dirname(__FILE__)}/lib/rake/*.rake'].map{|f|requiref}它加载文件没问题,但没有任务。我现在只有一个.rake文件作为测试,名为“servers.rake”,它看起来像这样:namespace:serverdotask:testdoputs"test"endend所以当我运行rakeserver:testid时

  3. ruby-on-rails - Ruby net/ldap 模块中的内存泄漏 - 2

    作为我的Rails应用程序的一部分,我编写了一个小导入程序,它从我们的LDAP系统中吸取数据并将其塞入一个用户表中。不幸的是,与LDAP相关的代码在遍历我们的32K用户时泄漏了大量内存,我一直无法弄清楚如何解决这个问题。这个问题似乎在某种程度上与LDAP库有关,因为当我删除对LDAP内容的调用时,内存使用情况会很好地稳定下来。此外,不断增加的对象是Net::BER::BerIdentifiedString和Net::BER::BerIdentifiedArray,它们都是LDAP库的一部分。当我运行导入时,内存使用量最终达到超过1GB的峰值。如果问题存在,我需要找到一些方法来更正我的代

  4. ruby-on-rails - rails : "missing partial" when calling 'render' in RSpec test - 2

    我正在尝试测试是否存在表单。我是Rails新手。我的new.html.erb_spec.rb文件的内容是:require'spec_helper'describe"messages/new.html.erb"doit"shouldrendertheform"dorender'/messages/new.html.erb'reponse.shouldhave_form_putting_to(@message)with_submit_buttonendendView本身,new.html.erb,有代码:当我运行rspec时,它失败了:1)messages/new.html.erbshou

  5. ruby-on-rails - 由于 "wkhtmltopdf",PDFKIT 显然无法正常工作 - 2

    我在从html页面生成PDF时遇到问题。我正在使用PDFkit。在安装它的过程中,我注意到我需要wkhtmltopdf。所以我也安装了它。我做了PDFkit的文档所说的一切......现在我在尝试加载PDF时遇到了这个错误。这里是错误:commandfailed:"/usr/local/bin/wkhtmltopdf""--margin-right""0.75in""--page-size""Letter""--margin-top""0.75in""--margin-bottom""0.75in""--encoding""UTF-8""--margin-left""0.75in""-

  6. ruby-on-rails - Rails 3 中的多个路由文件 - 2

    Rails2.3可以选择随时使用RouteSet#add_configuration_file添加更多路由。是否可以在Rails3项目中做同样的事情? 最佳答案 在config/application.rb中:config.paths.config.routes在Rails3.2(也可能是Rails3.1)中,使用:config.paths["config/routes"] 关于ruby-on-rails-Rails3中的多个路由文件,我们在StackOverflow上找到一个类似的问题

  7. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  8. ruby-on-rails - Rails - 一个 View 中的多个模型 - 2

    我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何

  9. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  10. ruby - 检查 "command"的输出应该包含 NilClass 的意外崩溃 - 2

    为了将Cucumber用于命令行脚本,我按照提供的说明安装了arubagem。它在我的Gemfile中,我可以验证是否安装了正确的版本并且我已经包含了require'aruba/cucumber'在'features/env.rb'中为了确保它能正常工作,我写了以下场景:@announceScenario:Testingcucumber/arubaGivenablankslateThentheoutputfrom"ls-la"shouldcontain"drw"假设事情应该失败。它确实失败了,但失败的原因是错误的:@announceScenario:Testingcucumber/ar

随机推荐