文章目录
入门三维开发必须得了解的基础知识
-
三维基础知识:https://developer.mozilla.org/zh-CN/docs/Games/Techniques/3D_on_the_web/Basic_theory
-
如有错误,欢迎指出
世界坐标到屏幕坐标的过程
-
首先让我们用文字来描述一下空间的一个点是经过怎样的蹂躏最后才变成屏幕上的一个点的,感受下这中间经历过的可怕历程
-
总的来说,空间中的一个点,先要转换成以相机为原点的相机坐标,再转换成齐次裁剪空间的投影坐标,然后变换为范围在[-1,1]的归一化坐标,这个归一化坐标再转成屏幕的坐标
-
在这个过程中涉及到了很多坐标系和矩阵变换,我们只需要认准一点,就是一个坐标系变成另一个坐标系都可以用一个变换矩阵来表示
-
现在第一步,由世界坐标变为相机坐标,它的变换矩阵为mc,这个矩阵的意义就在于世界坐标的任何一点,经过mc矩阵变换后,就能变成相机坐标
-
那么相机坐标是啥呢,相机坐标就是以相机为原点的坐标系
-
第二步,由相机坐标变为齐次裁剪空间坐标,它的变换矩阵为mp,经常称之为投影矩阵(这个矩阵主要根据相机的参数fov,near,far,aspect等等计算得出),齐次裁剪空间坐标的意义在于有了它就能非常快速地计算物体是否在视锥体内,因为不在视锥体内的东西由于看不到,没必要浪费性能去计算它们
-
第三步,由齐次裁剪空间坐标变为归一化的设备坐标(NDC),这个变化的特点就是值域变为[-1,1]了,这样的好处就是屏蔽了各种空间的大小带来的不便,方便后续的计算
-
第四步,由归一化的设备坐标变为屏幕坐标,同样地这样的变换也有一个矩阵来表示,我们得到了点的屏幕坐标,就可以开始渲染了
-
上面的过程我们可以数一数总共出现了世界坐标系,相机坐标系,齐次裁剪空间坐标系,归一化设备坐标,屏幕坐标系,这几个坐标系的xyz轴方向都得仔细去了解下,其实主要就是z轴,它的正方向有不同习惯,xy轴的正方向大家都比较统一,屏幕坐标也有以左上角和以左下角为原点的区别
-
因为出现了这么多坐标系,各个轴的方向不同、原点不同,就会导致我们需要进行一大堆的换算,因此要学习三维,没有一定的数学基础真的玩不了,这时多么怀念当初的数学知识,特别是线性代数啊
-
实际上因为我举例的是一个点,如果这个点在某个模型上呢,那么最开始还要多加一个模型坐标系和由模型坐标到世界坐标的变换过程,然后渲染时需要纹理,纹理又有一个uv坐标系,想想实在太可怕了
-
最后总结为
- 上面好像有好多变换矩阵啊,这些变换矩阵怎么来的,其实这就涉及到线性代数和齐次坐标了,简单点说线性代数可以帮助我们完成各种变换(位移,旋转,缩放),而齐次坐标就是将这三种变换都统一为矩阵乘法
- 要哭了,又多了线性代数和齐次坐标,这辈子最大的过错就是没好好学数学,那么不妨再让你奔溃一下,三角函数你也必须得杠杆的才行
- 说了这么多,就是建议你先复习下三角函数,然后好好学习线性代数(线性变换,矩阵乘法等),再去百度下齐次坐标,接下来才开始进入三维世界,进行各种坐标变换(别走啊,快回来)
坐标系
- 左手坐标系(z轴正方向为屏幕外射向屏幕内,与右手坐标系相反),右手坐标系(与我们平常在课本中看到的三维坐标系一致,z轴正方向为屏幕内射向屏幕外)
- 在unity中除了相机坐标系是使用右手坐标系外,其余都是左手坐标系
- 而webgl则基本是右手坐标系
模型坐标系(本地坐标系)
- 一般以模型的中心为原点,模型上的各点坐标是相对于模型坐标系的
- 在其他资料中,模型坐标也会被称为本地坐标,这里只要知道它们说的都是同一个东西即可
- 通过模型转换,可以将模型坐标系上的点的坐标转换为世界坐标系的坐标
世界坐标系
- 世界坐标系分为左右手坐标系,unity采用左手坐标系,opengl采用右手坐标系
- 一般情况下,世界坐标系的原点(0,0,0)转换后会在屏幕的中点
- 通过视图转换,可以将世界坐标系上的点的坐标转换为相机坐标系的坐标
相机坐标系
- 一般以相机位置为原点,也有分为左右手坐标系,不过在unity中相机空间(也叫观察空间)采用的是右手坐标系(坑吧,unity世界空间中使用左手坐标系,为啥就不能搞成一致的呢)
- 通过投影转换,可以将相机坐标系上的点的坐标转换为齐次裁剪空间坐标系的坐标
齐次裁剪空间坐标系
- 齐次裁剪空间,又叫规则观察体(Canonical View Volume),之所以要引入这个空间,就是为了把不在这个空间的物体裁剪剔除
- 那么怎么知道物体在不在这个空间呢,就是将相机空间中的一点(x,y,z,1)经过投影矩阵(又叫裁剪矩阵)变换后,判断新坐标(x’,y’,z’,-z)各分量(x’,y’,z’)是否在[-w,w]中(w指的是(x’,y’,z’,-z)的第四个分量-z),以及w是否在[near,far]中,如果是这个点就是在齐次裁剪空间内
- 如果对上面的(x’,y’,z’,-z)各分量都除以w分量,就得到(x’/-z,y’/-z,z’/-z,1),这个空间就称为NDC(归一化设备空间)
- 你可以理解NDC为一个单位立方体,这是个左手坐标系(unity和opengl是一样的),即z轴射向屏幕的为正方向,在unity和opengl下z分量为[-1,1],而在DirectX中是[0,1],xy轴的值域都在[-1,1]
归一化设备空间坐标系
- 如果对齐次裁剪空间的(x’,y’,z’,-z)各分量都除以w分量,就得到(x’/-z,y’/-z,z’/-z,1),这个空间就称为NDC(归一化设备空间)
- 你可以理解NDC为一个单位立方体,这是个左手坐标系(unity和opengl都是一样的,为什么呢,因为投影矩阵会改变空间的自旋性,而unity和opengl在裁剪空间都是右手坐标系,自旋后就都是左手坐标系了),即z轴射向屏幕的为正方向,在unity和opengl下z分量为[-1,1],而在DirectX中是[0,1],xy轴的值域都在[-1,1]
屏幕坐标系
- 屏幕坐标系中DirectX以屏幕左上角为原点,向右为x轴正方向,向下为y轴正方向,而opengl则以屏幕左下角为原点,向右为x轴正方向,向上为y轴正方向
uv坐标系
- uv坐标系主要是用于纹理图,纹理图说白了其实就是一张2d图
- uv坐标系以纹理图片的左下角为原点,向右为u,向上为v
- 由于纹理图的尺寸可能千奇百怪,因此uv坐标范围通常都被归一化到[0,1]范围内
视锥体
- 相机主要有两种模式,正交投影和透视投影,其中透视投影就跟我们眼睛看到的一样,会有近大远小的效果,而正交投影则无论远近,看到的都是一样大小
- 不同的视锥体需要不同的处理过程,对于透视投影的视锥体来说,判断一个顶点是否处于一个金字塔内部(透视投影的视椎体看起来就是一个金字塔)是比较麻烦的,因此需要一种更加通用、方便和整洁的方式来进行裁剪,这就需要将顶点转换到一个裁剪空间中
矩阵
投影矩阵(裁剪矩阵)
- 裁剪矩阵会改变空间的旋向性,空间从右手坐标系变换到了左手坐标系,如下图(其中lrtbn是left right top bottom near的简写)
- 投影矩阵会把所有顶点坐标从eye coordinates(观察空间/相机空间,eye space或view space)变换到裁剪坐标(clip coordinated,属于裁剪空间,clip space)。然后,这些裁剪坐标被变换到标准化设备坐标(normalized device coordinates, NDC,即坐标范围在-1到1之间),这一步是通过用用裁剪坐标的w分量除裁剪坐标实现的
- 透视投影矩阵,其中r表示为截面宽度的一半,t表示截面高度的一半,n表示near,f表示far,都为正数
- 正交投影矩阵
- 详细可以看这篇文章:OpenGL投影矩阵(Projection Matrix)构造方法 - lxycg - 博客园
mvp矩阵
-
mvp矩阵其实就是模型矩阵,视图矩阵,投影矩阵三个矩阵相乘,注意顺序:mvp矩阵=投影矩阵*视图矩阵*模型矩阵
-
顶点在空间中的变换:
- 模型空间 → 模型矩阵 → 世界空间→ 视图矩阵 → 视图空间→ 投影矩阵 → 裁剪空间
-
我们拿到点在模型坐标上的位置后,通过mvp矩阵就能得到此点的齐次裁剪空间坐标
-
视图矩阵实际上是记录了相机的变换的逆矩阵,在实际生活中我们是认为物体不动,相机在动,但相对来说,也可以认为相机不动,物体在相反的动,视图矩阵就描述了这种相反的过程
-
这里有篇文章详细介绍了这块的内容: WebGL model view projection - Web API 接口参考 | MDN
线性代数
- 以前学线代也就是根据公式计算计算,根本不知道线代究竟有什么用,等接触到了图像变换的时候,才知道原来图片的旋转,缩放,平移,错切,翻转都是通过线代实现的,这个时候后悔当初没好好学也不要紧,从现在开始找百度先生好好学习吧
- 矩阵相乘举例
- 注意矩阵乘法不满足交换律,即AB!=BA;也不满足消去律,即AB=AC,A!=O(O表示零向量)时,B!=C
- 还有不是任意两个矩阵都可以相乘的,比如m*n矩阵只能和n*x矩阵相乘(m*n表示m行n列),例如2*3矩阵可以和3*1矩阵乘,但不能和2*2矩阵相乘
线性运算
- 矩阵的加减法和矩阵的数乘合称矩阵的线性运算
- 矩阵加法满足如下运算律
- A+B=B+A
- (A+B)+C=A+(B+C)
- 矩阵数乘满足如下运算律
转置矩阵
- 矩阵的转置满足以下运算律
齐次坐标
- 齐次坐标是用来升维的,比如二维的(x,y)可以用三维的(x/w,y/w,w)来表示
- 在我们的空间计算中w通常为1,于是我们的二维(x,y)就可以用三维的(x,y,1)表示
- 齐次坐标的一个典型例子就是统一了平移,缩放,旋转,都可以用矩阵乘来表示
- 我们知道旋转和缩放都是通过矩阵乘,而平移却是矩阵加,这使得对于平面的变换无法用统一的表示方法,于是引入了齐次坐标,这个齐次坐标可以用三维来表示二维,例如二维中的(X,Y),可以变成齐次坐标(X/w,Y/w,w),当我们选择w等于1时,就有(X,Y)转成齐次坐标(X,Y,1),有人可能会问,这么做有啥用,实际上这么做就是为了解决上面说到的变换(位移,缩放,旋转)用矩阵表示方式的不统一,试想一下,如果我们可以用矩阵乘就表示位移,缩放,旋转那该有多好,这样就不用再考虑它是平移就用矩阵加,是缩放旋转就用矩阵乘了,都统一为用矩阵乘,至于为何可以这样请查看下文的矩阵变换
- 这里也有篇文章可以详看: 写给大家看的“透视除法” —— 齐次坐标和投影 - 简书
仿射变换
矩阵变换
-
这部分的内容跟我们大学学习的线性代数就有很大关系了
-
先以二维为例来说明吧,我们知道,在二维平面中的任何向量(用a表示)都可以由两个基本的单位向量(用i和j来表示)来表示,因此向量的变换(平移,缩放,旋转)自然也都可以转化为这两个单位向量的变换,比如我们想要向量a顺时针旋转90度,那么就可以看成是单位向量顺时针旋转90度,而向量a是可以通过单位向量得到的,这样子我们便可以通过单位向量得到顺时针旋转90度的需要的矩阵
-
我们假定通过矩阵v变换后就能得到顺时针旋转90的效果
-
那么 单位向量*矩阵v=向量a*矩阵v
-
单位向量我们一般都用i(1,0),j(0,1)表示,我们可以将i,j表示为矩阵
-
矩阵乘法只有结合律没有交换律,因此顺序很重要,比如abc=a(bc),ab!=ba
-
如果一个物体要先通过矩阵v1变换,再通过矩阵v2变换,那么我们可以把v1和v2相乘得到的矩阵作为物体的总的变换矩阵
-
我们可以想象一下,一个物体先平移再绕原点旋转和物体绕原点旋转再平移所得到的效果是不一样的
二维矩阵变换
在这里声明一下,由于各自采取的坐标系不同(左右手坐标系)和归一化([-1,1],[0,1])不同,矩阵变换公式并不是一模一样的,但原理上大致一样,因此使用矩阵公式时请不要盲目套用,一定要先清楚自己所处的坐标系等相关信息
- 上面的齐次坐标肯定有人会疑惑,这是来干嘛的,其实引入这个主要是因为缩放和旋转都可以用矩阵乘得到,而位移却是矩阵加,为了让这三种变换都统一为矩阵乘,于是才需要齐次坐标,接下来你看看下面的推导过程公式应该就会理解了
- 因为我喜欢用列来表示点坐标矩阵,如
- 而有些人可能喜欢行(x,y,1)表示
- 这两种表示都可以,只是在矩阵乘法上有点小区别,用列的话需要右乘,而行的话需要左乘
- 下面的例子用的都是右乘
- AB叫做A左乘B,或者叫做B右乘A
缩放
旋转
位移
三维矩阵变换
缩放
位移
旋转
- 三维的旋转矩阵就比较复杂一点了,因为可以分别绕x轴y轴z轴旋转,不过通过上面的二维的旋转已经可以类比得到规律:绕x轴旋转说明x坐标没变化,把yoz当成一个平面,绕x轴旋转也相当于在平面绕原点o旋转,这里面有个特殊情况是绕y轴旋转的时候
透视投影矩阵变换
- 这个的矩阵变化计算公式有点复杂
- 具体可以查看这篇文章:透视投影矩阵的推导 - bluebean - 博客园
平移、缩放和旋转的顺序
- 世界坐标变换里对平移缩放旋转的顺序非常重要,要先缩放、后旋转、再平移
- 这三种变换按各种顺序执行,结果是不同的
- 当一个物体刚刚放入世界坐标系中,它的本地坐标系原点和坐标轴和世界坐标系的原点和坐标轴都是重合的(这里先不考虑左右手坐标系,暂定世界坐标和本地坐标都是一样的标准)
- 而我们知道
- 缩放并不会改变坐标轴的方向和原点,因此经过缩放后两个坐标系还是重合的
- 旋转会导致两个坐标系的坐标轴不再重合,但原点还是重合的
- 平移会导致两个坐标系的原点不再重合,但坐标轴方向仍然一致
- 看了上面,你应该知道为何要先缩放了吧,因为缩放后两个坐标系还是重合的,之所以平移要在旋转之后,是因为平移会改变原点的位置,而旋转却能保持原点不变,任何保持原点不变的变换都需要放在平移之前
坐标变换
NDC坐标转为屏幕坐标
-
我们知道投影坐标系的原点在中心,值域为[-1,1],而屏幕坐标原点在左上角,y轴方向向下,假设屏幕分辨率为w*h,于是我们可以得到投影坐标系统的点(x,y)转为屏幕坐标后是 (w/2*(x+1),h/2*(1-y)),注意这是屏幕坐标以左上角为原点的情况
-
这个计算原理也很简单,就是由于坐标系的不同,首先为了确保正方向一致,我们要绕x轴进行翻转,于是原来点(x,y)就变成了(x,-y),接着由于值域[-1,1]映射的是屏幕坐标值域的[0,w]和[0,h],于是(x,-y)变成(w/2*x,-h/2*y),然后由于屏幕坐标原点与投影坐标原点的相对位置(即屏幕坐标在x轴y轴上分别位移w/2,h/2得到投影坐标原点),于是坐标分别相加w/2,h/2,即(w/2*x+w/2,-h/2*y+h/2),最后化简一下为(w/2*(x+1),h/2*(1-y))
-
假设(X,Y)为屏幕坐标,(x,y)为投影坐标,运用到我们上面说到的矩阵运算就是如下公式,这时你会发现矩阵运算有多好用了吧,这里的结果跟我们口述推导的(w/2*(x+1),h/2*(1-y))结论是一致的
-
-
当我们把左边的矩阵合成一个矩阵,就得到了ndc转为以左上角为原点的屏幕坐标的转换矩阵(3*3),如下
//屏幕左上角为原点的变换矩阵,第二行第二列为负号主要是因为两个坐标系的y轴方向刚好相反 w/2 0.0 w/2 0.0 -h/2 h/2 0.0 0.0 1.0
-
如果有些非要以屏幕左下角为原点,那么转为屏幕坐标后是(w/2*(x+1),h/2*(1+y))
-
其对应的转换矩阵如下
//屏幕左下角为原点的变换矩阵 w/2 0.0 w/2 0.0 h/2 h/2 0.0 0.0 1.0
-
这里提醒一下,由于NDC坐标是归一化的,因此在相机的aspect不为1的情况下,x轴和y轴方向上的单位向量的大小不是一样的
NDC坐标转为UV坐标
-
这个转换在投影时有妙用,可用于将一张图片投影在模型物体上的计算
-
设备归一化坐标的值域是[-1,1],而uv坐标的值域是[0,1],也就是从设备归一化坐标到uv坐标是先绕原点缩小1/2,然后再向上向右平移1/2,于是可得到变换矩阵,如下
//uv左下角为原点的变换矩阵 0.5 0.0 0.5 0.0 0.5 0.5 0.0 0.0 1.0
-
上面的uv坐标是以左下角为原点的,假如是以左上角为原点的,那么就相当于绕x轴翻转了一下,即y变为-y,x保持不变,于是可得如下矩阵
//uv左上角为原点的变换矩阵 0.5 0.0 0.5 0.0 -0.5 0.5 0.0 0.0 1.0
参考
(1条消息)D3D中的世界矩阵,视图矩阵,投影矩阵_diaoqu4574的博客-CSDN博客
https://blog.csdn.net/diaoqu4574/article/details/102223247
关于Opengl各种矩阵变换(MVPW)的自我理解
http://www.360doc.com/content/14/0826/17/13726687_404788406.shtml
旋转变换(一)旋转矩阵 - 莫水千流 - 博客园
https://www.cnblogs.com/zhoug2020/p/7842808.html
(1条消息)“为什么DirectX里表示三维坐标要建一个4*4的矩阵?”_weixin_34311757的博客-CSDN博客
https://blog.csdn.net/weixin_34311757/article/details/86322479?depth_1-