参考

1、Shadow Map 原理和改进

2、【OpenGL】02 - OpenGL中的坐标系

3、矩阵理论 (这个是京东地址)

4、维基百科(文中的数学概念出处)

5、msdn mul

6、msdn matrix

7、unity shader lab built-in

8、nvidia bump map tutorial

9、Computing Tangent Space Basis Vectors for an Arbitrary Mesh

一、基础

坐标系的定义是:对于一个n维系统,能够使每一个点和一组(n个)标量构成一一对应的系统。

我们的出发点是三维欧几里得空间,或三维实内积空间。对于这样的三维空间,最常见的坐标系就是笛卡尔坐标系,指定了三个互相垂直的向量x、y、z,这样每个空间中的点就可以表示成

考虑原点的存在,则对点V有:

这里的x、y、z,就是三维实数空间的一组标准正交基。

原点和基,就唯一的定义了一个坐标系。

如果要展开讨论数学概念实在是太难懂,我们下面直接看unity中的各种坐标系来讨论各种情况。本文中讨论的unity坐标系有以下几个:

  • 本地坐标系。local space
  • 世界坐标系。world space
  • 相机坐标系(观察坐标系)。view space
  • 投影坐标系。projection space
  • 切线坐标系。tangent space

这几个坐标系将在下面逐个讨论。

二、本地坐标系

1、shader中的本地坐标系,与 Editor中的本地坐标系

在unity中,有一个肉眼可见的本地坐标系,当你选中一个物体时,就会显示出其本地坐标系。

    图1、Editor中的本地坐标系

那么问题来了,标这个坐标系,是shader中的本地坐系吗?

产生这样的疑问是自然的,3ds max中建立模型使用的坐标系是右手坐标系,而unity editor中显示的本地坐标系是左手坐标系,显然是不一样的:

  图2、3ds Max 中的本地坐标系

我们这里不对3dmax中的轴做任何修改,并在导出时选择z向上(即和在3dmax中看到的一样),则导入到unity中,并将旋转等reset,看到的是这样的:

        图3、导入unity后的本地坐标系

注意此时方向已经和在3dsmax中不一样了。

那么在shader中的坐标系到底是哪个坐标系呢?我们使用下面这样的shader来观察以下:

Shader "Custom/TestCoordShader" {SubShader{Tags{ "RenderType" = "Opaque" }LOD 200Pass{CGPROGRAM#pragma vertex   vert#pragma fragment frag#include "UnityCG.cginc"struct v2f {float4 pos : SV_POSITION;float4 col : COLOR;};v2f vert(appdata_base v) {v2f o;o.pos = mul(UNITY_MATRIX_MVP, v.vertex);o.col = v.vertex;return o;}float4 frag(v2f i) : COLOR{return i.col.x; // i.col.y; //i.col.z;}ENDCG}}//FallBack "Diffuse"
}

将 fragment 中的颜色值分别修改为 x y z,就可以得到下面的结果:


                                                                      图4、shader中本地坐标系的x、y、z方向

可以看到vertex的x、y、z方向和editor中显示的坐标轴向一致,且更仔细的观察的话,会发现原点位置也一致。因此我们可以得出结论:shader中的本地坐标系,就是在editor中看到的本地坐标系

2、本地坐标系:左手坐标系

这里需要注意的是,本地坐标系是一个左手坐标系系统。

图5、左手坐标系和右手坐标系

这里不展开讨论左右手坐标系的问题,在后面会再探讨这个东西。

三、世界坐标系
1、世界坐标系的形态


                                                                             图6、位于原点的正方体

和各自为政的本地坐标系不同,在unity中,每个scene都有唯一存在的一个世界坐标系。同样的,世界坐标系也是一个左手坐标系。一般常见的用法,是使得x正方向为正右方,y正方向为正上方,z正方向为正前方(常用而已),如图6中所示。

2、从本地坐标系转换到世界坐标系

现在要将本地坐标系的点、向量等转换到世界坐标系中。
1)不移动原点的坐标系变换
        考虑我们上面对于坐标系的要素描述:原点和基。这里的本地坐标系和世界坐标系的不同就在于这两点。
        首先不考虑原点的变化,先假定本地坐标系和世界坐标系共原点O。
        而在基的方面,本地坐标系和世界坐标系是同一个三维线性空间中选取两组不同的基构成的坐标系,分为称为 local 基 和 world 基。
        容易看出,从 local 基到 world 基是一个线性变换(反之亦然),参考《矩阵理论》中对过渡矩阵和坐标系一节的讨论,可以知道,任意点 p 在世界坐标系下的位置,可以用其在本地坐标系下的位置,左乘  local 基 到 world 基的过渡矩阵的逆矩阵得到。而这个逆矩阵,实际上就是 world  基 到 local 基的过渡矩阵。因此有:

公式1、不移动原点时,本地坐标系到世界坐标系的坐标轴变换和点变换
        注意到 world 基的正交特性,不失一般性的,令 X_world = (1, 0, 0),Y_world = (0, 1, 0),Z_world = (0, 0, 1),带入到上面的坐标轴变换公式,可以求出M_local->world,则(其中 X_local_in_world 是指该local 基在世界坐标系下的表示):

公式2、坐标系变换与坐标轴的关系
        这个结论是可以推广的,即:点P在坐标系V下的坐标,相当于其在坐标系U下的坐标,左乘矩阵T,其中T为 坐标系U的三个基(列向量)在坐标系V下的坐标构成的矩阵。
        将这个矩阵记做 p 从本地坐标系变换到世界坐标系的变换矩阵 Trans:

公式3、本地坐标系变换到世界坐标系的变换矩阵 Trans
2)不移动原点的坐标系变换——举例

图7、本地坐标系到世界坐标系变换举例
        以这个cube的本地坐标系为例,其坐标轴在世界坐标系下的表示分别为:

这样就可以得到过渡矩阵

3)仿射变换——考虑原点的移动
        前面的推导都是基于原点不移动得到的,这样假设的原因在于线性变换并不能够表示原点的移动。那么如果现在需要考虑这一变化,则需要引入仿射变换。
        所谓仿射变换,就是线性变换加平移,其实就是在刚才的线性变换的基础上再加上原点的移动。
        由于已经不是线性变换,那么使用线性变换矩阵就无法表示了,也就是说,现在已经无法写成 p' = T * p 的形式。因此,引入齐次线性空间的概念,即增加第4维w,用来辅助表示移动。
        考虑这样的仿射变换,首先保持原点不变,进行线性变换,其线性变换矩阵为A,再将原点平移b,则在该仿射变换下,任意点P有:

公式4、仿射变换
        关于齐次变换的更多细节,可以参考之前的博客(这篇文章较老,冲突的概念以本文为准)。
        仔细考虑这个表示平移的b向量,会发现他是转换后的线性空间V的原点所在位置,在原线性空间U下的坐标

图8、仿射变换的原点变换
        现在回到本地坐标系向世界坐标系的变换过程中,则可以得到齐次变换下的变换矩阵:

公式4、仿射变换矩阵与坐标轴和原点
        考虑到齐次空间的特性,点的w分量为1,向量的w分量为0,则可以进一步写为:

4)在unity中获得这个转换矩阵
        Unity中已经提供了获得这个矩阵的方便形式,在script中使用
gameObject.transform.localToWorldMatrix
        在shader中使用如下的矩阵:
_Object2World
5)转换法线
        将点或者普通的向量从本地坐标系转换到世界坐标系,只要按照上面说的,用该转换矩阵左乘原坐标值就可以了,但是对于法线,还需要一些特殊的处理。
        为什么?
        回答这个问题,就需要搬出法线的定义:三维平面的法线是垂直于该平面的三维向量。曲面在某点P处的法线为垂直于该点切平面的向量。所以,在转换之后,还需要保持原有的垂直关系。
        按照上面举例中的变换矩阵,假定某条法线为(1,1,0),与之垂直的一个向量(1,-1,0)。如果左乘变换矩阵,则法线变为(3.5,3.5,-0.7),向量变为(0.7,-3.5,-3.5)。变换之后的内积为 -7,即已经不再垂直。
        其实从数学上看这是显然的:

公式5、使用变换矩阵变换法线——不再垂直
        而从直观上,原因是这样的:

图9、使用变换矩阵变换法线——不再垂直
        因此要正确的进行法线的变换,要使得保持 V 和 N 的内积为0,因此要让 N 左乘 Trans的逆转置矩阵,这样有:

公式6、用逆转置矩阵转换法线
6)获取逆转置矩阵的捷径——mul的秘密
        上面提到要转换法线,需要使用从本地坐标系到世界坐标系的仿射变换矩阵 (即 _Object2World) 的逆转置矩阵,即应该使用 _World2Object 的转置矩阵。
        那么在shader中完成这个变换,应有:

float4 worldPos  = mul(_Object2World, v.vertex);
float4 worldNorm = mul(transpose(_World2Object), v.normal);

但在实际使用中,常常见到这样的写法:

float4 worldPos  = mul(_Object2World, v.vertex);
float4 worldNorm = mul(v.normal, _World2Object);

这两种写法的结果是一样的吗?是的,这里就必须说明 mul 的用法了(官方文档)。
        简单来说,如果 mul 的第一个参数是矩阵 M,第二个参数是向量 V,则结果 Out 为(以vector4为例):

如果第一个参数是向量 V,第二个参数是矩阵 M,则结果 Out 为

则可以进行如下推导:

公式7、mul与转置矩阵
        注意到输出的vector4,在数据格式上,转置与不转置实际上并没有区别,(shader中,转置实际上只对矩阵有效果)。因此,mul(V, M) 就相当于 mul( tranpose(M), V )更进一步的,对于正交矩阵,其转置矩阵等于逆矩阵,那么通过这个方法,还可以得到他的逆矩阵,这个特性我们后面还会看到。

四、观察坐标系
1、观察坐标系的形态
        View space是观察者眼中的世界,可以认为是观察者的本地坐标系,以观察者的位置为原点。但在《Shadow Map 原理和改进》中可以看到,观察坐标系又与 editor 中看到的 camera 的本地坐标系有本质的区别:它是右手坐标系
        其形态如下所示:

图10、观察坐标系

2、左手坐标系和右手坐标系的互相转换——数学运算的独立性
        在考虑如何从世界坐标系转换到观察坐标系之前,我们要先思考一个问题:上一节的转换公式是在世界坐标系和本地坐标系——两个都是左手坐标系——的情况下推导的,那么现在要在左手坐标系和右手坐标系之间进行转换了,那么之前的推导是否仍然有效?
        这里就要展开来谈谈矩阵运算与左右手规则的关系了。
1)线性变换定理与左右手无关
        上一节讨论的线性变换,是从一组基变换到另一组基,对基本身没有任何要求,他们甚至可以不满足互相正交的特性,到底构成的是左手坐标系还是右手坐标系,容易看到,对定理本身是完全没有影响的。
2)矩阵乘法 mul 与左右手无关
        矩阵乘法的本质实际上就是对点或向量进行变换,即更换基向量,从线性变换定理与左右手无关也容易看出 mul 也不挑剔他所在的线性空间。
3)点积与左右手无关
        点积需要在实内积空间中进行,要使得下面的点积计算方式生效,需要 a 和 b 在同一个线性空间内,且基向量两两正交。

公式8、点积
        注意到unity中使用的左手坐标系和右手坐标系中的基向量都是满足正交条件的,因此点积都可以正常进行。
4)叉乘与左右手规则的关系
        叉乘是唯一需要注意的。其数学公式如下:

公式8、叉乘的数学计算方法
        可以看到,其本身也是与基向量的形态独立的,因此计算公式本身(例如,cross函数本身)并不因左右手坐标系的变化而变化。
        但是,计算叉乘还有一个方法(以下这段出自wiki):

公式9、叉乘
        这里方向向量 n 的确定,通常会介绍右手规则。但实际上,右手规则仅适用于右手坐标系下的情况,在左手坐标系中,需要使用左手规则。总结来说,就是叉乘的数学表达式是独立于左右手坐标系的。但如果使用X手判定准则来进行方向的判断,则在左手坐标系中应使用左手准则,在右手坐标系中使用右手准则。

3、从世界坐标系变换到观察坐标系
        经过了上面的讨论,我们可以放心大胆的说,可以按照本地坐标系变换到世界坐标系的思路,再从世界坐标系变换到观察坐标系。
        在 Script 中获得这个变换矩阵的方法是:

Camera.worldToCameraMatrix

在shader中,这一步的单独的矩阵是:

UNITY_MATRIX_V

综合从本地坐标系到观察坐标系的变换矩阵:

UNITY_MATRIX_MV

4、值得注意的 z 分量和 w 分量
        到目前为止,无论是本地坐标系到世界坐标系的变换,还是世界坐标系到观察坐标系的变换,都不影响点(或向量)的 w 分量,仍然保持为 1 (或0,对于向量)。
另外,可以注意下 z 分量,可以看到,由于 z 的正方向的缘故,所以观察者可见的所有点的 z 分量都小于0,且距离越远,负的值越大。

五、投影坐标系
1、投影坐标系的形态
        投影坐标系是将观察者眼中的世界进行截取和归一得到的(比较详细的讨论可以参考《【OpenGL】02 - OpenGL中的坐标系》),首先截取世界中,camera的平截体包含的部分,然后又再次变为一个左手坐标系(对透视投影来说,坐标系的概念可能已经拓展)

图11、投影坐标系:正投影和透视投影
        这就产生了问题,为什么前面也是左手坐标系(本地坐标系,世界坐标系),后面也是左手坐标系(投影坐标系),中间为什么要费力的插一个右手坐标系呢(观察坐标系)?实际上,在Opengl中,本地坐标系和世界坐标系都是右手坐标系,也就是说,直到投影空间中才变换为左手。而unity中的本地坐标系和世界坐标系是左手系统,所以显得观察坐标系比较的特别。

2、正投影
        正投影见第一张图,在从观察坐标系变换到投影坐标系之后,点 (x,y,z)的取值范围有:

其变换矩阵可以参考我的坐标系那篇博客:

公式10、正投影的投影变换矩阵

3、透视投影
        透视投影的情况要复杂的多,在这一步,w 值开始正式发挥作用。
        先直接来看变换矩阵(关于求法仍建议阅读坐标系一文)

公式11、透视投影的投影变换矩阵
        对原观察坐标系下点P,变换到投影坐标系下坐标P’为:

公式12、透视投影变换下点的变换情况
        如果只考虑(x,y,z),那么和正投影是相同的,但是多了第四维w,使得 z 坐标轴的指向产生了弯曲

图12、透视坐标系

4、从观察坐标系到投影坐标系——获取变换矩阵
        自己想要计算的话,可以用上面提供的公式计算,unity本身也为我们提供了获取这个变换矩阵的方式。
        在shader中,这一步单独的变换矩阵:
UNITY_MATRIX_P
        在script中,如果使用 camera.projectionMatrix ,可能会发现与 shader 中的这个矩阵有些差异,这是由于,在我的电脑上(目前不清楚是否和显卡有关),实际使用的变换矩阵,最终会使得 z 的取值范围为 [0, 1],和上面公式略有不同。
        因此在script中,需要使用下面的代码来获取投影变换矩阵:
GL.GetGPUProjectionMatrix(c.projectionMatrix, false)

5、值得注意的w分量
        之前的 w 分量一直为1,现在终于有了作用。在正投影变换后,w的值仍然是1;但是在透视投影变换之后,w的值等于观察坐标系下的 -z。另外,在透视坐标系中,在 shader 中实际应该使用的 x、y、z分量应当是 P 在变换后的坐标值的 Px、Py、Pz分量除以 w 来获取。
        一说到除以就必须当心0除问题,w有可能是0吗?答案是不可能,这是由于透视投影的camera的视锥体中的点,其z的取值范围是 [-ZFar, -ZNear],不会取值到0。

六、切线空间
1、切线空间的形态
        在做 bump map的时候,会提到切线空间 tangent space。切线空间的具体意义这里不展开讨论(关于这个问题,解释的最清楚的是《OpenGL的法线贴图教程》),这里主要讨论坐标系变换问题。
        切线空间的坐标轴分别为:
X轴——切线 tangent
Y轴——副切线 biTangent
Z轴——法线 normal
        其中,法线即我们平时所说的法线:


        切线选取的是,与法线垂直的,沿着贴图uv的u变量增长方向的向量:


        副切线则选取与这两个向量都垂直的,一般通过叉乘得到。从而构成坐标系:


        需要注意的是,unity中,实际获得的 B 向量,可能与这张图中的 B 向量反向。

2、在unity中获得和使用切空间变换矩阵
        在unity中,有这样一个宏 TANGENT_SPACE_ROTATION,可以获得变换到切线空间
#define TANGENT_SPACE_ROTATION \
    float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; \
    float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal )
        使用的时候,可以这样用:
TANGENT_SPACE_ROTATION;
o.viewDirForParallax = mul (rotation, ObjSpaceViewDir(v.vertex));
        这个变换矩阵有很多细节值得说明,我们下面一个一个来看。

3、这是个什么样的矩阵?
        这个问题换个问法就是,float3x3如何构造?
        实际上,float3x3是行优先填充元素的,可以看这里。所以,这个矩阵是:

注意,这个矩阵作为变换矩阵是非常奇怪的。回忆我们在 第三节世界坐标系 中,第2小节的结论:
点P在坐标系V下的坐标,相当于其在坐标系U下的坐标,左乘矩阵T,其中T为 坐标系U的三个基(列向量)在坐标系V下的坐标构成的矩阵。
        因此变换矩阵应当是一个由三个列向量构成的矩阵,而现在却是三个行向量,这是为什么?

4、转置与逆矩阵
        现在回答前面的问题:原因在于,实际上这是一个逆矩阵。
        首先,可以注意到,三个基向量(T,B,N)都是单位向量,且两两正交。则其构成的矩阵是一个正交矩阵。对于正交矩阵,其转置矩阵就是他的逆矩阵,因此有:

注意看后面的这个矩阵,他显然满足前面的结论,T、B、N都是在本地坐标系下的值,因此这个列向量构成的 [T B N] 矩阵,是将点从切线坐标系变换到本地坐标系的变换矩阵。 那么这个矩阵的逆矩阵,就是将点从本地坐标系变换到切线坐标系的变换矩阵。而由于正交,其逆矩阵就是其转置矩阵,所以从本地坐标系变换到切线坐标系的变换矩阵,就是我们在第2小节里面看到的情况。

5、左手坐标系?右手坐标系?
        由于 B 向量是叉乘得到的,那么一个值得关注的问题就是,这个坐标系到底是左手规则还是右手规则?这就牵涉到之前的另外一个结论:对于叉乘,在左手坐标系中使用左手规则,在右手坐标系中使用右手规则,但是无论使用哪个规则,其数学计算表达式并不会有任何不同。
        而现在,N、T都是在本地坐标系中的向量,而本地坐标系是一个左手坐标系,所以 N 和 T 的叉乘使用左手规则,那么在没有其他变数的情况下,T、B、N应该如下,构成一个左手坐标系:


        但是现在还有一个变量 tangent.w,他的取值为 1 或 -1。如果为1,则上面的结论不变,如果为-1,则B则会反向,从而构成一个右手坐标系。
这个变量的存在是由于B 向量通常还代表着 uv 中 v 增长的方向,所以需要调整 B 轴使得他和 v 增长的方向一致。(这里可参考这篇文章)

至此,对于坐标系的讨论基本就告一段落了。
————————————————
版权声明:本文为CSDN博主「ronintao」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ronintao/java/article/details/52136673

Unity 中的坐标系相关推荐

  1. Unity 中的 3D 数学学习笔记——认识坐标系及坐标系之间的转换

    一 :3d空间坐标系  x,y,z 轴 z轴方向确定有两种方式 左手坐标系和右手坐标系 左手坐标系:伸开左手,大拇指指向X轴正方向,食指指向Y轴正方向,其他三个手指指向Z轴正方向. 右手坐标系:伸开右 ...

  2. 左手坐标系和右手坐标系以及Unity中的世界坐标系和本地坐标系

    一.左手坐标系和右手坐标系 1.左手坐标系: 伸开我们的左手, 掌心向外, 大拇指与食指成90度, 中指.无名指和小指弯曲, 大拇指指向的方向就是X轴正方向, 食指指向的方向就是Y轴正方向, 中指.无 ...

  3. unity怎么显示骨骼_骨骼动画的原理及在Unity中的使用

    制作骨骼动画 我们看看这几步操作后,我们得到了那些数据: 1.每个皮肤顶点的初始世界坐标. 2.每个骨骼关节顶点的初始世界坐标. 3.每个顶点被骨骼顶点的影响信息. 4.骨骼如何移动. 骨骼动画原理 ...

  4. Unity Shader入门精要学习笔记 - 第6章 开始 Unity 中的基础光照

    转自冯乐乐的<Unity Shader入门精要> 通常来讲,我们要模拟真实的光照环境来生成一张图像,需要考虑3种物理现象. 首先,光线从光源中被发射出来. 然后,光线和场景中的一些物体相交 ...

  5. 【转】UNITY中相机空间,投影空间的正向问题

    原文链接1:https://www.cnblogs.com/wantnon/p/4570188.html 原文链接2:https://www.cnblogs.com/hefee/p/3820610.h ...

  6. [转]解读Unity中的CG编写Shader系列3——表面剔除与剪裁模式

    在上一个例子中,我们得到了由mesh组件传递的信息经过数学转换至合适的颜色区间以颜色的形式着色到物体上.这篇文章将要在此基础上研究片段的擦除(discarding fragments)和前面剪裁.后面 ...

  7. Unity Shader:雾的数学运算以及在Unity中使用Fog

    文章目录 1,Unity Fog效果图 2,uniform fog均匀雾的数学公式推导 3,Fog在Unity中的应用 4,Unity Fog的源码分析 5,Unity exp fog VS unif ...

  8. 浅谈Unity中的rotation和Quaternion的乘法

    动手写游戏以后一个比较切身的体会,就是实际操作能检验很多语言的细节,也许平时看API文档,或者看一些教程的时候并没有深刻的体会,因为大多情况下你只知道了该怎么做,却不知道为什么要这么做,或者怎么想到这 ...

  9. Unity中的矩阵含义

    Unity中的矩阵含义 本人第一次写博文,不足的地方请大家指出来,我不会详解它的计算是什么样的,因为同类文章有很多,这个如果读者感兴趣可以自己学习一下相关的手册和知识. 什么是矩阵 简单的说,矩阵就是 ...

最新文章

  1. Wireshark的入门使用
  2. 如何让奇异值分解(SVD)变得不“奇异”?
  3. 在linux下也能进行51单片机开发吗?送你一份教程。
  4. 微软第二财季营收达 517 亿美元,净利润同比增长 21%
  5. stopwatch_在Java中衡量执行时间– Spring StopWatch示例
  6. php 获取当天到23 59,js 获取当天23点59分59秒 时间戳 (最简单的方法)
  7. .NET平台依赖注入机制及IoC的设计与实现
  8. 300张小抄表搞定机器学习知识点:学习根本停不下来!
  9. 网站颜色变黑白的CSS代码,Chrome、火狐、IE
  10. weka连接mysql数据库
  11. Cannot load module file xxx.iml Intellij
  12. 人工智能离我们很遥远?专家:美图软件其实也是
  13. vim配置——MA6174
  14. Windows10删除hiberfil.sys
  15. Wing IDE中文乱码问题
  16. 基于微信小程序的小说阅读系统(小程序+Nodejs)
  17. java coverage_Java Coverage(Cobertura)工具
  18. problem-1654B. Prefix Removals-codeforces
  19. charts中各种图演示
  20. android AudioRecord 音频录制 噪音消除

热门文章

  1. json大文件打开与美化
  2. CRC校验笔记 C语言代码分析
  3. 华为屏幕锁如何解除强制恢复出厂解开华为手机忘记的密码线刷解决方法
  4. table表格数据无缝循环向上滚动,hover后暂停滚动
  5. 通话记录分析 --- 通话详情 之一
  6. Markowitz投资组合优化——寻找有效前沿策略分享
  7. SAP纺织行业解决方案
  8. heic图片格式转换工具
  9. 程序员必备:炫酷樱桃轴机械键盘,免费包邮送到家!
  10. Anti-Rootkit(ARK)内核级系统防护软件KsBinSword的设计与实现