一篇文章弄懂three.js中的各种矩阵关系的方法_js矩阵运算

一篇文章弄懂three.js中的各种矩阵关系的方法_js矩阵运算这两天正在重新实现maptalks.js的三维变换逻辑, 需要从底层重新实现一遍三维投影转换的算法。 好在三维投影算法已有很成熟的实现范式, 我选择了THREE.js作为参考对象, 这篇文章也是我对THREE.js中矩阵转换关系的总结。 三维投影算法就是将上诉因素抽象为数学算法…

这两天正在重新实现maptalks.js三维变换逻辑, 需要从底层重新实现一遍三维投影转换的算法。 好在三维投影算法已有很成熟的实现范式, 我选择了THREE.js作为参考对象, 这篇文章也是我对THREE.js中矩阵转换关系的总结。

本文面向有一定webgl开发基础的读者,THREE的版本为写作时的最新版本r88

三维投影算法

什么是三维投影转换? 简而言之,就是将三维空间中的物体,投射在相机视平面的转换算法, 如图:

(图片截取自webglfundamentals):

在这个场景中,如图摆放5个F字母和相机

一篇文章弄懂three.js中的各种矩阵关系的方法_js矩阵运算

相机看到的景象如下:

一篇文章弄懂three.js中的各种矩阵关系的方法_js矩阵运算

根据实际生活经验,相机看到的景象和以下因素有关,任何改变都会让相机眼中的世界发生变化:

  • 相机投影类型(正射投影还是透视投影), 透视投影最符合现实世界, 也是最常用的投影方式
  • 相机的位置和方向
  • 物体的位置和形变(旋转/缩放/平移)

三维投影算法就是将上诉因素抽象为数学算法,用来计算三维物体在相机视平面上的位置

实际应用中我们是通过矩阵计算来实现的。简而言之,我们将相机的位置方向, 相机的类型, 物体的位置和形变能转换为 矩阵, 将这些矩阵进行一系列计算后, 最终得到三维投影矩阵:

u_matrix

基于它, 任意给定三维坐标[x, y, z], 我们都能算出相机视平面上的位置:


[x, y] = u_matrix * [x, y, z, 1]

当然,实际应用中情况会更复杂一些,例如三维图形引擎为了简化计算,一般将三维物体组织为层级结构,通过物体的本地位置和对上层的相对位置来计算出其在世界中的绝对位置,但归根到底,我们需要的只是最终的位置矩阵。

THREE中的矩阵

让我们回到THREE.js,来看看THREE是怎么组织定义组织投影矩阵的。

我们知道,THREE定义了场景(Scene)和相机(Camera), Scene用来添加管理三维物体, Camera用来控制相机的位置, 角度等,代码大概如下:

const scene = new THREE.Scene();
const mesh = new THREE.Mesh(new THREE.Cube());
scene.add(mesh);
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
scene.add(camera);
renderer.render(scene, camera);

我们根据上面的总结按图索骥,THREE中定义了下述三个矩阵:

  • 相机投影类型:投影矩阵(ProjectMatrix
  • 相机的位置和方向: 视图矩阵 (CameraMatrixWorldInverseViewMatrix)
  • 物体的位置和形变: 物体位置矩阵(ObjectWorldMatrix)

三维投影矩阵(u_matrix)计算公式

三维投影矩阵计算公式如下:

const uMatrix = ProjectMatrix * CameraMatrixWorldInverse* ObjectMatrixWorld

是不是很简单?

如果你有兴趣,可以写一段最简单的THREE程序,跟踪一下THREE的绘制逻辑,看看THREE是怎么生成和运用这些矩阵的。

接下来我们来解释一下怎么在THREE中得到上述三个矩阵:

相机投影矩阵(ProjectMatrix)

相机投影矩阵决定了相机是透视投影相机还是正射投影相机,现实世界都是透视投影,所以透视投影也是最常用的。
在THREE中,通过用不同的相机类实例化,得到不同类型的相机,例如定义一个透视投影相机:

const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  • 获得ProjectionMatrix
    camera.projectMatrix

相机视图矩阵(CameraMatrixWorldInverse)

有的三维引擎或教程,会把视图矩阵称为ViewMatrix(例如webglfundamentals)
视图矩阵的含义是,固定其他因素,我们改变了相机的位置和角度后,它眼中的世界也会发生变化,这种变化就是视图矩阵。

前面提到,相机在三维空间中的位置是camera.matrixWorld,而它的视图矩阵是相机位置矩阵的逆矩阵CameraMatrixWorldInverse,它也符合了我们的生活经验:

  • 固定相机,人向左移动
  • 固定人,向右移动相机

这两种情况在相机眼中是一样的。

在THREE中,我们一般通过设置camera的position和up,调用lookAt来改变相机的视图矩阵

camera.position.set(x, y, z);
camera.up.set(x, y, z);
camera.lookAt(x, y, z);
  • 获得CameraMatrixInverse
    camera.matrixWorldInverse

我们知道,最终的投影是在GLSL顶点着色器中计算的。在一次绘制中,ProjectionMatrixCameraMatrixWorldInverse一般不会发生变化,而ObjectMatrixWorld每个物体都可能不同, 所以为了减少顶点着色器中的计算量,有些三维引擎会在javascript程序中提前计算出ProjectionMatrix * CameraMatrixWorldInverse的值传递给顶点着色器,这个矩阵一般称为ViewProjectionMatrix

物体位置矩阵(ObjectWorldMatrix)

ObjectWorldMatrix描述了物体在三维场景中的位置。

  • 获得ObjectWorldMatrix
    object.matrixWorld

前面提到,THREE中的物体是有层级关系的,所以THREE中物体的matrixWorld是通过local matrix(object.matrix)与父亲的matrixWorld递归相乘得到的, 其中的原理可以查阅webglfundamentals中的这篇教程

一些应用

获取屏幕二维坐标

给定三维坐标[x, y, z],怎么获取它在屏幕上的二维坐标呢?计算公式如下:

const [x, y] = ProjectionMatrix * CameraWorldMatrixInverse * [x, y, z]

THREE在Vector3上封装了方法:

const v = new THREE.Vector3(x, y, z);
const xy = v.project(camera);

源代码如下:

project: function () {

    var matrix = new Matrix4();

    return function project(camera) {

        matrix.multiplyMatrices(camera.projectionMatrix, matrix.getInverse(camera.matrixWorld));
        return this.applyMatrix4(matrix);

    };
}(),

屏幕坐标转化为三维坐标

给定屏幕二维坐标[x, y],怎么获取它在三维空间中三维坐标呢?计算公式如下:

const [x, y, z] = CameraWorldMatrix * ProjectionMatrixInverse * [x, y, z]

THREE在Vector3上封装了方法:

const v = new THREE.Vector3(x, y, z);
const xyz = v.unproject(camera);

源代码如下:

unproject: function () {

    var matrix = new Matrix4();

    return function unproject(camera) {

        matrix.multiplyMatrices(camera.matrixWorld, matrix.getInverse(camera.projectionMatrix));
        return this.applyMatrix4(matrix);

    };

}(),

不过屏幕坐标转化为三维坐标不是这么简单,因为屏幕上的二维坐标在三维空间中其实对应的是一条射线,其可以对应了无限个三维坐标点,更深入的原理可以阅读这篇stackoverflow上的问题, THREE的作者mroob和一位网友给了精彩的回答。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13263.html

(0)

相关推荐

  • macbook窗口快捷键_Mysql排序

    macbook窗口快捷键_Mysql排序1.4、怎么避免笛卡尔积现象?当然是加条件进行过滤;思考:避免了笛卡尔积现象,会减少记录的匹配次数吗?不会,次数还是56次。只不过显示的是有效记录……

    2023-05-01
    113
  • 组复制背景 | 全方位认识 MySQL 8.0 Group Replication「建议收藏」

    组复制背景 | 全方位认识 MySQL 8.0 Group Replication「建议收藏」作者 罗小波 · 沃趣科技高级数据库技术专家 转自 沃趣科技(woqutech) MySQL Group Replication(MGR)自问世以来,一直是大家技术分享、技术讨论的热点,虽然在MyS…

    2023-01-25
    135
  • 多线程之线程池「建议收藏」

    多线程之线程池「建议收藏」小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。 创建线程会产生系统开销,并且每个线程会占用一定的内存等资源,同时线程的销毁也需要带来一定的压力。过多的线程还会带来由于上下文切换等等的性能损

    2023-07-27
    122
  • Python搜索关键词的实现方法

    Python搜索关键词的实现方法在我们的生活和工作中,我们经常需要搜索特定的信息,这时搜索引擎就会帮助我们去实现这一目标。像Google和百度这样的搜索引擎,为用户提供了高度精准的搜索结果。而像Python这样的程序设计语言,也提供了一些强大的搜索工具,可以帮助我们实现关键词搜索。本文将介绍Python搜索关键词的实现方法,帮助读者们更好的利用Python进行信息搜索。

    2024-05-06
    58
  • 一文带你读懂 Hbase 的架构组成[通俗易懂]

    一文带你读懂 Hbase 的架构组成[通俗易懂]hi,大家好,我是大D。今天咱们继续深挖一下 HBase 的架构组成。 Hbase 作为 NoSQL 数据库的代表,属于三驾马车之一 BigTable 的对应实现,HBase 的出现很好地弥补了大数据

    2023-05-16
    147
  • MySQL实战45讲 14「终于解决」

    MySQL实战45讲 14「终于解决」MySQL实战45讲 14 count(*) 的实现方式 执行 count(*) 操作时的优化 自己计数的方法 不同的 count 用法

    2023-05-28
    132
  • MySQL 多表关联一对多查询取最新的一条数据「终于解决」

    MySQL 多表关联一对多查询取最新的一条数据「终于解决」SQL语句 SELECT SQL_CALC_FOUND_ROWS * FROM tableA a LEFT JOIN ( SELECT BC.* FROM ( SELECT MAX( id ) AS…

    2023-03-08
    150
  • 如何用Python写爬虫?

    如何用Python写爬虫?近年来,随着互联网的飞速发展,数据已经成为了一种非常重要的资源。而爬虫作为一种获取数据的手段,已经成为了程序员们日常工作中不可或缺的一部分。而Python作为一种功能强大的编程语言,在爬虫领域也非常受欢迎。本文将会介绍如何使用Python写爬虫,帮助读者了解爬虫的基本原理以及如何使用Python实现爬虫。

    2024-05-08
    59

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注