0x00 前言


首先要说明的是,本文的标题事实上来自于知乎上的一个同名问题:为什么directX里表示三维坐标要建一个4*4的矩阵? - 编程 。因此,正如Milo Yip大神所说的这个标题事实上是存在问题的:矩阵是用于表示变换而不是坐标的。再了解了矩阵的作用之后,我们就要继续思考为什么变换要使用一个4×4的矩阵而不是3×3的矩阵呢?是不是多此一举呢?下面我们就来聊聊这个话题。

0x01 怎么平移一个三维空间中的点?

我们应该怎么平移一个三维空间中的点呢?答案很简单,我们只需要对这个点的坐标中的每个分量(x,y,z)和对应轴上的平移距离相加即可。

例如,点p1(x1,y1,z1)在X轴Y轴以及Z轴上分别平移Δx,Δy,Δz到新的点p2(x2,y2,z2),那么我们只需在坐标对应的分量上加上这些增量就可以确定点p2的坐标了。

x2 = x1 + Δx

y2 = y1 + Δy

z2 = z1 + Δz

很简单是吗?那么接下来再让我们来看一看另一种变换:旋转。

0x02 再来旋转一个点

旋转相比较平移来说,会略显复杂一些。因为我们需要指明以下几个方面来描述一个旋转:

  1. 旋转轴
  2. 旋转方向
  3. 旋转角度

在这里,我们假设点p需要绕Z轴顺时针旋转β度。

如这个很难看的图所示,我们的点P1(x1,y1,z1)以Z轴位轴顺时针旋转β度之后来到了点P2(x2,y2,z2)。接下来,让我们假设原点到P1的距离位L,且P1和Y轴之间的夹角位α,那么根据三角函数公式我们就可以计算出P1点的具体坐标了:

x1 = L·sinα

y1 = L·cosα

由于是绕Z轴旋转,因此z坐标不变,因此此处不考虑。

同样根据三角函数公式,我们可以继续计算出P2的具体坐标:

x2 = L·sin(α + β)

y2 = L·cos(α + β)

再让我们回忆一下中学时代的几何数学的内容,对青春的回忆又把我们带回了课堂上老师声嘶力竭向我们传授的内容——两角和与差:

cos(α+β)=cosα·cosβ-sinα·sinβ
cos(α-β)=cosα·cosβ+sinα·sinβ
sin(α+β)=sinα·cosβ+cosα·sinβ
sin(α-β)=sinα·cosβ-cosα·sinβ

回忆起老师传授给我们的知识之后,接下来结合P1的坐标表示形式我们就可以把P2的坐标换一个表示形式了。

x2 = L·(sinα·cosβ+cosα·sinβ) = cosβ·x1 + sinβ·y1

y2 = L·(cosα·cosβ-sinα·sinβ) = cosβ·y1 - sinβ·x1

z2 = z1

因此,在已知P1点坐标以及旋转角度β的情况下,我们就可以根据以上公式分别求出P2点坐标的各个分量。如果再结合上一小节中平移一个点的公式来看,我们可以发现似乎并不需要矩阵,而仅仅通过两组表达式就能实现坐标的变换。但是......

0x03 带来便捷的矩阵

当然,从理论上讲我们的确可以只通过数学公式就能实现变换,但实际的情况却是在变换十分复杂时,直接使用数学表达式来进行运算也是相当繁复的。因此,在现实中常常使用矩阵(由m × n个标量组成的长方形数组)来表示诸如平移、旋转以及缩放等线性变换。而另一个更有趣的事实是,当两个变换矩阵A和B的积为P=AB时,则变换矩阵P相当于A和B所代表的变换。举一个例子,若A为旋转矩阵,B为平移矩阵,则矩阵P就能够实现旋转和平移变换。不过需要注意的是,矩阵乘法不符合交换律,因此AB和BA并不相等。
说了这么多,我们似乎还是没有回答为什么三维空间需要一个4×4矩阵来实现变换呢?下面我们就带着这个问题,分别看看3×3矩阵和4×4矩阵的使用吧。

3×3矩阵和旋转

首先,我们先来看一个矩阵乘以一个三维矢量的算式:

可以看到该矩阵为一个3×3的矩阵,矩阵的右侧是点P1的坐标,而矩阵的左侧则是点P2的坐标。根据这个表达式,我们可以求出x2、y2、z2的值:

x2 = a·x1 + b·y1 + c·z1

y2 = d·x1 + e·y1 + f·z1

z2 = g·x1 + h·y1 + i·z1

为了将矩阵等式和之前小节的数学表达式联系起来,下面我们就将旋转表达式和该矩阵等式做一个对比。

x2 = a·x1 + b·y1 + c·z1
x2 = cosβ·x1 + sinβ·y1

y2 = d·x1 + e·y1 + f·z1
y2 = cosβ·y1 - sinβ·x1

z2 = g·x1 + h·y1 + i·z1
z2 = z1

通过对比x2,我们可以发现a=cosβ,b=sinβ,c=0;
对比y2,也可以发现d=-sinβ,e=cosβ,f=0;
最后对比z2,可以确定g=0,h=0,i=1;
将这个结果带入到之前的矩阵中,我们的等式就可以变成下面这个样子:

也就是说,通过这个3×3的变换矩阵,我们就已经实现了三维空间的旋转变换。那么为什么还需要使用4×4矩阵呢?

搞不定的平移,4×4矩阵来救场

我们已经通过一个3×3矩阵搞定了旋转变换,显然如果这个3×3矩阵真的是完美的解决变换的方案的话,那么它显然也必须要适合于其他的变换,例如平移。但是它到底能否满足平移的需求呢?下面我们还是通过对比矩阵等式和数学表达式的方式,来寻找答案。

x2 = a·x1 + b·y1 + c·z1
x2 = x1 + Δx

y2 = d·x1 + e·y1 + f·z1
y2 = y1 + Δy

z2 = g·x1 + h·y1 + i·z1
z2 = z1 + Δz

通过对比,我们发现平移和旋转之间很有趣的一个区别,那就是平移的表达式中带有常量Δx,而无论是旋转的表达式还是矩阵等式中都不存在这样一个常量能够与之对应。那么问题就来,我们没有办法使用3×3的矩阵来表示平移。这个问题该如何解决呢?答案其实很简单,那就是使用4×4矩阵来实现。但是随之而来的一个问题就是如何让一个三维坐标和一个4×4的矩阵相乘呢?

齐次坐标

为了解决三维矢量和4×4矩阵相乘的问题,我们机智的为三维矢量添加了第四个分量,这样之前的三维矢量(x,y,z)就变成了四维的(x,y,z,w),这样由4个分量组成的矢量便被称为齐次坐标。需要说明的是,齐次坐标(x,y,z,w)等价于三维坐标(x/w,y/w,z/w),因此只要w分量的值是1,那么这个齐次坐标就可以被当作三维坐标来使用,而且所表示的坐标就是以x,y,z这3个值为坐标值的点。
因此,为了和4×4矩阵相乘,我们的P1点坐标就变成了(x1,y1,z1,1)。而矩阵等式也变成了下面这个样子:

我们再将这个新的矩阵等式和平移的数学表达式做一番对比:

x2 = a·x1 + b·y1 + c·z1 + d
x2 = x1 + Δx

y2 = e·x1 + f·y1 + g·z1 + h
y2 = y1 + Δy

z2 = i·x1 + j·y1 + k·z1 + l
z2 = z1 + Δz

1 = m·x1 + n·y1 + o·z1 + p

通过对比x2,我们可以发现a=1,b=0,c=0,d=Δx;
对比y2,也可以发现e=0,f=1,g=0,h=Δy;
再对比z2,可以确定i=0,j=0,k=1,l=Δz;
最后还可以根据表达式求出m=0,n=0,o=0,p=1;
这样,我们就求出了我们的4×4的平移矩阵:

0x04 总结

写到这里,不知各位是否还记得之前在介绍矩阵乘法的时候我有提到过两个变换矩阵A和B的积P=AB,相当于A和B所代表的变换。事实上在游戏编程中,常常需要把一连串的变换预先通过计算成为单一矩阵,所以就不能即存在3×3的矩阵又存在4×4的矩阵。而将3×3矩阵拓展成4×4矩阵还是相对更加容易的。这样,就通过一个4×4矩阵整合了平移矩阵、旋转矩阵。

“为什么DirectX里表示三维坐标要建一个4*4的矩阵?”的更多相关文章

  1. ACM1174_爆头解题思路_空间三维坐标求点到直线的距离

    /* 爆头 Description gameboy是一个CS高手,他最喜欢的就是扮演警察, 手持M4爆土匪的头.也许这里有人没玩过CS,有必 要介绍一下“爆头”这个术语:所谓爆头,就是子 弹直接命中对 ...

  2. OpenGL 获取当前屏幕坐标的三维坐标(gluUnProject使用例子 Qt)

    之前使用VS+glut实现了gluUnProject使用例子,用于渲染管道的逆过程,将屏幕坐标转换为opengl三维坐标,本文将尝试使用QT来实现. 代码如下:  main.cpp  12345678 ...

  3. MATLAB在三维坐标中显示图片 并 使得图片部分透明

    要画一个光路图,本来可以用proe,但是鼠标不好用,有些操作也忘了,用MATLAB画了个.下面是用到的图片. 但是三维坐标中显示彩色图片的目标没有搞定,做了个灰度图,然后用仿射程序将彩色图片贴到了二维 ...

  4. OpenGL 获取当前屏幕坐标对应的三维坐标

    转自原文 OpenGL 获取当前屏幕坐标对应的三维坐标,使用很简单glu库中的一个函数 #include <GL/glut.h> #include <stdlib.h> #in ...

  5. VTK根据三维坐标点集生成点云

    一个简单的利用VTK根据三维坐标点集生成点云的例子,仅供参考. 一.环境:vtk-8.1 & vs2013(需自行配置vtk的环境) 二.我所读取的三维坐标点集为txt格式文件,每个点的x,y ...

  6. Direct-X学习笔记--三维摄像机

    一.介绍 哇! 到了传说中的3D摄像机啦! 之前我们写的东东,都是观察点不动,通过世界变换让东西动,今天,通过三维摄像机我们就能够改变我们的观察点,观察方向,任意在三维空间中驰骋.之前我们所设定的视角 ...

  7. 基于gulp编写的一个简单实用的前端开发环境好了,安装完Gulp后,接下来是你大展身手的时候了,在你自己的电脑上面随便哪个地方建一个目录,打开命令行,然后进入创建好的目录里面,开始撸代码,关于生成的json文件请点击这里https://docs.npmjs.com/files/package.json,打开的速度看你的网速了注意:以下是为了演示 ,我建的一个目录结构,你自己可以根据项目需求自己建目

    自从Node.js出现以来,基于其的前端开发的工具框架也越来越多了,从Grunt到Gulp再到现在很火的WebPack,所有的这些新的东西的出现都极大的解放了我们在前端领域的开发,作为一个在前端领域里 ...

  8. 转载 STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发

    STM32 使用Cubemx 建一个USB(HID)设备下位机,实现数据收发  本文转载自 https://www.cnblogs.com/xingboy/p/9913963.html 这里我主要说一 ...

  9. 给你的Kubernetes集群建一个只读账户(防止高管。。。后)

    给你的Kubernetes集群建一个只读账户 需求:我们知道搭完k8s集群会创建一个默认的管理员kubernetes-admin用户该用户拥有所以权限,有一天开发或测试的同学需要登录到k8s集群了解业 ...

随机推荐

  1. 一次修改闭源 Entity Provider 程序集以兼容新 EntityFramework 的过程

    读完本文你会知道,如何在没有源码的情况下,直接修改一个 DLL 以去除 DLL 上的强命名限制,并在该程序集上直接添加你的“友元程序集(一种特殊的 Attribute,将它应用在程序集上,使得程序集内 ...

  2. Centos 下 mysql root 密码重置

    重置mysql密码的方法有很多,官网也提供了很方便的快捷操作办法,可参考资料 resetting permissions .本文重置密码的具体步骤如下: 一.停止MySQL(如果处于运行状态) #se ...

  3. 【原】FMDB源码阅读(二)

    [原]FMDB源码阅读(二) 本文转载请注明出处 -- polobymulberry-博客园 1. 前言 上一篇只是简单地过了一下FMDB一个简单例子的基本流程,并没有涉及到FMDB的所有方方面面,比 ...

  4. ASP.NET Core框架揭秘(持续更新中…)

    之前写了一系列关于.NET Core/ASP.NET Core的文章,但是大都是针对RC版本.到了正式的RTM,很多地方都发生了改变,所以我会将之前发布的文章针对正式版本的.NET Core 1.0进 ...

  5. 简记用ArcGIS处理某项目需求中数据的步骤

    文章版权由作者李晓晖和博客园共有,若转载请于明显处标明出处:http://www.cnblogs.com/naaoveGIS/ 1. 背景 项目需求涉及如下几个步骤: a.矢量化 b.获取范围内要素 ...

  6. 【微信小程序开发•系列文章六】生命周期和路由

    这篇文章理论的知识比较多一些,都是个人观点,描述有失妥当的地方希望读者指出. [微信小程序开发•系列文章一]入门 [微信小程序开发•系列文章二]视图层 [微信小程序开发•系列文章三]数据层 [微信小程 ...

  7. 【repost】JS中的异常处理方法分享

    我们在编写js过程中,难免会遇到一些代码错误问题,需要找出来,有些时候怕因为js问题导致用户体验差,这里给出一些解决方法 js容错语句,就是js出错也不提示错误(防止浏览器右下角有个黄色的三角符号,要 ...

  8. ASP.NET Core 在 JSON 文件中配置依赖注入

    前言 在上一篇文章中写了如何在MVC中配置全局路由前缀,今天给大家介绍一下如何在在 json 文件中配置依赖注入. 在以前的 ASP.NET 4+ (MVC,Web Api,Owin,SingalR等 ...

  9. 兼容Mono的下一代云环境Web开发框架ASP.NET vNext

    微软在2014年5月12日的TechEd大会上宣布将会发布下一代ASP.NET框架ASP.NET vNext的预览.此次发布的ASP.NET框架与以前相比发生了根本性的变化,凸显了微软“云优先”(cl ...

  10. RavenDB官网文档翻译系列第二

    索引>查询>处理文档关联 处理文档关联 RavenDB坚持的一个设计原则就是文档是独立的,这就是说处理一个文档所需的所有数据都存储在文档本身之中.然而,这不是说对象之间不应存在联系. 在许 ...