【Unity Shader入门精要】Shader数学基础
原作者博客链接:http://blog.csdn.net/candycat1992/article/
书籍链接:http://product.dangdang.com/23972910.html
目录
特殊矩阵
转置矩阵(Transposed Matrix)
转置矩阵的计算非常简单,我们只需要把原矩阵翻转一下即可。也就是说,原矩阵的第i行变成了第i列,而第j列变成了第j行。数学公式是:
- 性质1:转置矩阵的转置为原矩阵
- 性质2:串接矩阵的转置等于反向串接各个矩阵的转置
逆矩阵(Inversed Matrix)
原矩阵和其逆矩阵相乘为一个单位矩阵。
- 性质1:逆矩阵的逆矩阵是原矩阵本身。
- 性质2:单位矩阵的逆矩阵是其本身。
- 性质3:转置矩阵的逆矩阵是逆矩阵的转置。
- 性质4:矩阵串接相乘后的逆矩阵为反向串接各个矩阵的逆矩阵。
正交矩阵(Orthogonal Matrix)
原矩阵和其转置矩阵相乘的结果为单位矩阵。一个正交矩阵的行和列之间分别构成了一组标准正交基。
Unity使用列矩阵 右乘
在Unity中,常规做法是把矢量放在矩阵的右侧,即把矢量转换成列矩阵来进行运算。因此,在本书后面的内容中,如无特殊情况,我们都将使用列矩阵。这意味着,我们的矩阵乘法通常都是右乘, 例如
如果v是行矩阵,其与下面的式子是等价的
矩阵的几何意义:变换
齐次坐标空间(homogeneous space)
仿射变换(affinetransform)就是合并线性变换和平移变换的变换类型。仿射变换可以使用一个4x4的矩阵来表示,为此,我们需要把矢量扩展到四维空间下,这就是齐次坐标空间(homogeneous space)。
平移矩阵
缩放矩阵
旋转矩阵
复合变换
Scale ->Rotation ->Transform
为什么要约定这样的顺序,而不是其他顺序呢?
如果我们按先平移、再缩放的顺序进行变换,假设初始情况下妞妞位于原点,我们先按(0,0,5)平移它,现在它距离原点5个单位。然后再将它放大2倍,这样所有的坐标都变成了原来的2倍,而这意味着妞妞现在的位置是(0,0,10),这不是我们希望的。正确的做法是,先缩放再平移。也就是说,我们先在原点对妞妞进行2倍的缩放,再进行平移,这样姐妞的大小正确了,位置也正确了。
Rz ->Rx ->Ry
上面的公式书写顺序是不是反了?
实际上,有一个非常重要的东西我们没有说明白,那就是旋转时使用的坐标系。给定一个旋转顺序(例如这里的zxy),以及它们对应的旋转角度,有两种坐标系可以选择。
-
绕坐标系E下的z轴旋转,绕坐标系E下的y轴旋转,绕坐标系E下的x轴旋转ex,即进行一次旋转时不一起旋转当前坐标系。
-
绕坐标系E下的z轴旋转,在坐标系E下再绕z轴旋转后的新坐标系E'下的y轴旋转,在坐标系E'下再绕y轴旋转后的新坐标系E"下的x轴旋转,即在旋转时把坐标系一起转动。
很容易知道,这两种选择的结果是不一样的。但如果把它们的旋转顺序颠倒一下,它们得到的结果就会是一样的!说得明白点,在第一种情况下,按zxy顺序旋转和在第二种情况下,按yxz顺序旋转是一样的。
而Unity文档中说明的旋转顺序指的是在第一种情况下的顺序。
坐标空间
父子坐标空间的变换
每个空间都有一个父(parent)坐标空间。对坐标空间的变换实际上就是在父空间和子空间之间对点和矢量进行变换。
假设,现在有父坐标空间P以及一个子坐标空间C。
我们知道在父坐标空间中子坐标空间的原点位置以及3个单位坐标轴。
我们一般会有两种需求:
- 一种需求是把子坐标空间下表示的点(a,b,c)或矢量A。转换到父坐标空间下的表示A
M(c->p)
- 一个需求是反过来,即把父坐标空间下表示的点或矢量B。转换到子坐标空间下的表示B
M(p->c)
顶点、向量坐标空间
L模型空间
L世界空间
R观察空间
为了得到顶点在观察空间中的位置,我们可以有两种方法。
-
计算观察空间的3个坐标轴在世界空间下的表示,然后构建出从观察空间(C)变换到世界空间(P)的变换矩阵,再对该矩阵求逆来得到从世界空间变换到观察空间的变换矩阵。 -
想象平移整个观察空间,让摄像机原点位于世界坐标的原点,坐标轴与世界空间中的坐标轴重合即可。
这两种方法得到的变换矩阵都是一样的,不同的只是我们思考的方式。这里我们使用第二种方法。
由Transform组件可以知道,摄像机在世界空间中的变换是先按(30,0,0)进行旋转,然后按(0,10,-10)进行了平移。
那么,为了把摄像机重新移回到初始状态(这里指摄像机原点位于世界坐标的原点、坐标轴与世界空间中的坐标轴重合),我们需要进行逆向变换,即先按(0,-10,10)平移,以便将摄像机移回到原点,再按(-30,0,0)进行旋转,以便让坐标轴重合。因此,变换矩阵就是:
但是,由于观察空间使用的是右手坐标系,因此需要对z分量进行取反操作。我们可以通过乘以另一个特殊的矩阵来得到最终的观察变换矩阵:
L裁剪空间(透视/正交投影、视椎体、裁剪)
- 透视投影
高:
宽:
相关证明:
https://www.docin.com/p-99795905.html?docfrom=rrela
https://www.docin.com/p-1007337368.html
- 正交投影
高:2*Size
宽:Aspect * Height
L屏幕空间(屏幕映射)
- 归一化设备坐标(NDC)
在Unity中,屏幕空间左下角的像素坐标是(0,0),右上角的像素坐标是(pixelWidth,pixelHeight)。由于当前x和y坐标都是[-1,1],因此这个映射的过程就是一个缩放的过程。齐次除法和屏幕映射的过程可以使用下面的公式来总结:
- scrPosX = (x/w * 0.5 + 0.5) * pixelWidth
- srcPosY = (y/w * 0.5 + 0.5) * pixelHeight
- zBuffer: z/w
- z:可以后续用来做透视校正差值
法线向量坐标空间
切线可以用上述的变换矩阵
与法线类似,切线往往也是模型顶点携带的一种信息。它通常与纹理空间对齐,而且与法线方向垂直。由于切线是由两个顶点之间的差值计算得到的,因此我们可以直接使用用于变换顶点的变换矩阵来变换切线。
法线不能用上述的变换矩阵
已知:
Unity常见空间变换
UNITY_MATRIX_MVP
模型到世界 | UNITY_MATRIX_M |
mul(unity_ObjectToWorld, v.vertex) |
float3 UnityObjectToWorldDir(float3 dir) float3 UnityWorldToObjectDir(float3 dir) |
||
float3 UnityObjectToWorldNormal(float3 normal) | ||
模型到观察 | UNITY_MATRIX_MV | |
模型到裁剪 | UNITY_MATRIX_MVP | UnityObjectToClipPos(v.vertex) |
世界到观察 | UNITY_MATRIX_V | |
世界到裁剪 | UNITY_MATRIX_VP | |
观察到裁剪 | UNITY_MATRIX_P |
到屏幕空间
- ComputeSreenPos(float4 clipPos);//计算结果xy规范到0~1,zw不变
到视口空间
- scrPos.xy / scrPos.w
viewDir和lightDir
viewDir | float3 WorldSpaceViewDir(float4 oSpacePos) |
float3 ObjSpaceViewDir(float4 oSpacePos) | |
float3 UnityWorldSpaceViewDir(float4 wSpacePos) | |
lightDir | float3 WorldSpaceLightDir(float4 oSpacePos) |
float3 ObjSpaceLightDir(float4 oSpacePos) | |
float3 UnityWorldSpaceLightDir(float4 wSpacePos) |