热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

PlacingaCamera:theLookAtFunction(翻译)

链接Keywords:Matrix,LookAt,camera,crossproduct,transformationmatrix,transform,camera-to-worl

目录


Moving the Camera

The Method

Step 1: compute the forward axis

 Step 2: compute the right vector. 

Step 4: compute the up vector

Step 4: set the 4x4 matrix using the right, up and forward vector as from point.

The Look-At Method Limitation

Keywords: Matrix, LookAt, camera, cross product, transformation matrix, transform, camera-to-world, look-at, Gimbal lock.

在这个简短的课程中,我们将学习一种简单但有用的方法来移动 3D 摄像机。 如果您不熟悉变换矩阵和向量之间的叉积的概念,您将不会轻易理解本课。 希望如果情况还不是这样,我们建议您阅读名为几何的课程(完整)。


Moving the Camera

能够在 3D 场景中移动相机非常重要。 然而,在 Scratchapixel 的大部分课程中,我们通常使用一个 4x4 矩阵,通常标记为 camToWorld 来设置相机位置和空间旋转(记住相机不应该缩放)请记住,假定处于默认位置的相机以原点为中心并沿负 z 轴对齐。 这在光线追踪:生成相机光线一课中有详细解释。但是,使用 4x4 矩阵来设置场景中的相机位置并不是很友好,除非我们可以访问 3D 动画系统(例如 Maya 或 Blender)来设置相机并导出其变换矩阵。

希望我们可以使用另一种比直接设置矩阵更好的方法,并且不需要editor (尽管这当然总是更好)。 这种技术并没有真正的名称,但程序员通常将其称为 Look-At 方法。 该方法的思想很简单。 为了设置相机位置和方向,您真正需要的是一个来设置空间中的相机位置,我们将其称为起点,以及一个定义相机正在观察的点。 我们将把这个点称为 to 点。

 有趣的是,从这对点,我们可以创建一个相机 4x4 矩阵,我们将在本课中演示。

相机沿负 z 轴对齐。 这是否意味着我需要将相机沿 y 轴旋转 180 度或沿 z 轴放大 -1? 一点也不。 变换相机与变换场景中的任何其他对象没有什么不同。 请记住,在光线追踪中,我们构建主光线就好像相机位于其默认位置一样。 这在光线追踪:生成相机光线课程中进行了解释。 这是我们实际反转光线方向的时候。 换句话说,此时光线方向的 z 坐标始终为负:处于默认位置的相机沿负 z 轴向下看。 这些主要光线然后由相机到世界矩阵进行转换。 因此,在构建 4x4 相机到世界矩阵时无需考虑相机的默认方向。


The Method

请记住,4x4 矩阵对笛卡尔坐标系的 3 轴进行编码。 同样,如果这对您来说不是很明显,请阅读几何课程。 请记住,在处理矩阵和坐标系时需要注意两个约定。 对于矩阵,您需要在行优先和列优先表示之间进行选择。 在 Scratchapixel,我们使用行优先符号。 至于坐标系,您需要在右手坐标系和左手坐标系之间进行选择。 我们使用右手坐标系。 4x4 矩阵的第四行(在行主矩阵中)对平移值进行编码。

 您如何命名笛卡尔坐标系的轴取决于您的偏好,您可以将它们命名为 x、y 和 z,但在本课中,为了清楚起见,我们将它们命名为 right(对于 x 轴)、up(对于 y 轴) ) 并向前 (z 轴)。 这在下图进行了说明。

根据 from-to 点对构建 4x4 矩阵的方法可以分为四个步骤:

Step 1: compute the forward axis

在图 1 和图 2 中,很容易看出相机局部坐标系的前轴沿着由 from 和 to 点定义的线段对齐。 一点点几何就足以计算这个向量。 你只需要归一化向量 To-From(注意这个向量的方向:它是 To-From 而不是 From-To)。 这可以通过以下代码片段来完成:

Vec3f forward = Normalize(from - to);

We found one vector. Two left!

 Step 2: compute the right vector. 

回忆一下几何课,笛卡尔坐标是由三个相互垂直的单位向量定义的。 我们也知道,如果我们取两个向量 A 和 B,它们可以被看作是在一个平面上,这两个向量的叉积创建了第三个向量 C 垂直于该平面,因此也明显垂直于 A 和 B . 我们可以使用这个属性来创建我们的右向量。 这里的想法是使用一些任意向量并计算前向向量和这个任意向量之间的交叉向量。 结果是一个向量,它必须垂直于前向向量,并且可以在我们的笛卡尔坐标系的构建中用作右向量。 计算这个向量的代码很简单,因为它只意味着前向向量和这个任意向量之间的叉积:

Vec3f right = crossProduct(randomVec, forward);

现在的问题是,我们如何选择这个任意向量? 嗯,这个向量不能完全随意,这就是我们用斜体写这个词的原因。 想一想:如果前向向量是 (0,0,1),那么正确的向量应该是 (1,0,0)。 只有当我们选择向量 (0,1,0) 作为我们的任意向量时,才能做到这一点。 事实上: (0,1,0) x (0,0,1) = (1,0,0) 这里的符号 x 说明了叉积。 请记住,计算叉积的方程式是:

其中 a 和 b 是两个向量,c 是 a 和 b 的叉积的结果。 当您查看图 3 时,您还可以注意到,无论前向矢量的方向如何,与前向矢量和矢量 (0,1,0) 定义的平面垂直的矢量始终是相机笛卡尔坐标的右矢量 系统。 那是因为该坐标系的向上向量位于图 4 所示的同一平面内。这很好,因为向量 (0,1,0) 可以明显地代替我们之前所说的任意向量。

 另请注意,从该观察中,正确的向量始终位于 xz 平面内。 你怎么会问? 如果相机有一个滚动,正确的向量不会在不同的平面上吗? 这实际上是正确的,但是将滚动应用于相机并不是您可以直接使用观察方法完成的事情。 要添加相机胶卷,您首先需要创建一个矩阵来滚动相机(围绕 z 轴旋转相机),然后将该矩阵乘以使用观察方法构建的相机到世界矩阵。

Finally, here is the code to compute the right vector:

Vec3f tmp(0, 1, 0);
Vec3f right = crossProduct(Normalize(tmp), forward);

请注意,我们对任意向量进行归一化,以防您实际使用不同于 (0,1,0) 的向量。 因此,为了安全起见,我们将使其正常化。 还要注意叉积中向量的顺序。 请记住,叉积不是可交换的(它实际上是反交换的,有关详细信息,请查看几何课程)。 记住正确顺序的最佳助记方法是考虑正向向量 (0,0,1) 与向上向量 (0,1,0) 的叉积,我们知道它应该给出 (1,0,0 ) 而不是 (-1,0,0)。 如果你知道叉积的方程,你应该很容易发现顺序是向 up×forward 而不是相反。 太好了,我们有前向和右向向量。 现在如何找到向上向量?

Step 4: compute the up vector

嗯,这很简单,我们有两个正交向量,前向向量和右向向量,因此计算这两个向量之间的叉积只会给我们缺少的第三个向量,向上向量。 请注意,如果前向和右向向量被归一化,那么从叉积计算出的向上向量也将被归一化:

Vec3f up = crossProduct(forward, right);

同样,您需要注意叉积中涉及的向量的顺序。 太好了,我们现在有了定义相机坐标系的三个向量。 现在让我们构建最终的 4x4 相机到世界矩阵。

Step 4: set the 4x4 matrix using the right, up and forward vector as from point.

完成这个过程所需要做的就是构建相机到世界矩阵本身。 为此,我们只需用正确的数据替换矩阵的每一行:


  • Row 1: replace the first three coefficients of the row with the coordinates of the right vector,

  • Row 2: replace the first three coefficients of the row with the coordinates of the up vector,

  • Row 3: replace the first three coefficients of the row with the coordinates of the forward vector,

  • Row 4: replace the first three coefficients of the row with the coordinates of the from point.

同样,如果您不确定我们为什么要这样做,请查看几何课程。 最后这里是完整功能的源代码。 它从两个参数(from 和 to 点)计算并返回相机到世界的矩阵。 请注意,函数第三个参数(在以下代码中称为 tmp)是用于计算右向量的任意向量。 它使用默认值 (0,1,0) 设置,但可以根据需要进行更改(因此需要在使用时对其进行标准化)。

Matrix44f lookAt(const Vec3f& from, const Vec3f& to, const Vec3f& tmp = Vec3f(0, 1, 0))
{
Vec3f forward = normalize(from - to);
Vec3f right = crossProduct(normalize(tmp), forward);
Vec3f up = crossProduct(forward, right);

Matrix44f camToWorld;

camToWorld[0][0] = right.x;
camToWorld[0][1] = right.y;
camToWorld[0][2] = right.z;
camToWorld[1][0] = up.x;
camToWorld[1][1] = up.y;
camToWorld[1][2] = up.z;
camToWorld[2][0] = forward.x;
camToWorld[2][1] = forward.y;
camToWorld[2][2] = forward.z;

camToWorld[3][0] = from.x;
camToWorld[3][1] = from.y;
camToWorld[3][2] = from.z;

return camToWorld;
}

The Look-At Method Limitation

该方法非常简单并且通常效果很好。 虽然它有一个Achilles heels (弱点)。 当相机垂直向下或向上看时,前轴非常接近用于计算右轴的任意轴。 极端情况当然是当向前轴和这个任意轴完全平行时,例如 当前向向量是 (0,1,0) 或 (0,-1,0) 时。 不幸的是,在这种特殊情况下,叉积无法为正确的向量生成结果。 这个问题实际上没有真正的解决方案。 您可以检测这种情况,并选择手动设置向量(因为您知道向量的配置应该是什么)。 可以使用四元数插值开发更优雅的解决方案。




推荐阅读
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • 本文介绍了设计师伊振华受邀参与沈阳市智慧城市运行管理中心项目的整体设计,并以数字赋能和创新驱动高质量发展的理念,建设了集成、智慧、高效的一体化城市综合管理平台,促进了城市的数字化转型。该中心被称为当代城市的智能心脏,为沈阳市的智慧城市建设做出了重要贡献。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • 电话号码的字母组合解题思路和代码示例
    本文介绍了力扣题目《电话号码的字母组合》的解题思路和代码示例。通过使用哈希表和递归求解的方法,可以将给定的电话号码转换为对应的字母组合。详细的解题思路和代码示例可以帮助读者更好地理解和实现该题目。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Linux环境变量函数getenv、putenv、setenv和unsetenv详解
    本文详细解释了Linux中的环境变量函数getenv、putenv、setenv和unsetenv的用法和功能。通过使用这些函数,可以获取、设置和删除环境变量的值。同时给出了相应的函数原型、参数说明和返回值。通过示例代码演示了如何使用getenv函数获取环境变量的值,并打印出来。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • 本文介绍了在iOS开发中使用UITextField实现字符限制的方法,包括利用代理方法和使用BNTextField-Limit库的实现策略。通过这些方法,开发者可以方便地限制UITextField的字符个数和输入规则。 ... [详细]
author-avatar
杭ai君浩
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有