《UnityShader入门精要》学习3

笛卡尔坐标系(Cartesian Coordinate System)

二维笛卡儿坐标系

一个二维的笛卡儿坐标系包含了两个部分的信息:

  • ·一个特殊的位置,即原点,它是整个坐标系的中心。
  • ·两条过原点的互相垂直的矢量,即x轴和y轴。这些坐标轴也被称为是该坐标系的基矢量。

 三维笛卡儿坐标系

左手坐标系和右手坐标系

在模型空间中,一个物体的右侧(right)、上侧(up)和前侧(forward)分别对应了x轴、y轴和z轴的正方向。

点和矢量

矢量的乘法有两种最常用的种类:点积(dot product,也被称为内积,inner product)叉积(cross product,也被称为外积,outer product)

点积:  a ·b=(ax,ay,az)·(bx,by,bz)=axbx+ayby+azbz        a·b=|a||b|cosθ

矢量的点积满足交换律,即a·b=b·a

需要注意的是,叉积不满足交换律,即a×b≠b×a。实际上,叉积是满足反交换律的,即a×b=-(b×a)。而且叉积也不满足结合律,即 (a×b) ×c≠a×(b×c)

|a×b|=|a||b|sinθ  注意,是绝对值的公式,方向还需要另外考虑

矩阵

矢量可以看成是n×1的列矩阵(column matrix)或1×n的行矩阵(row matrix),其中n对应了矢量的维度。例如,矢量v=(3,8,6)可以写成行矩阵   [3 8 6]  或者列矩阵

矩阵和标量的乘法

矩阵与矩阵的乘法

一个r×n的矩阵A和一个n×c的矩阵B相乘,它们的结果AB将会是一个r×c大小的矩阵

C中的每一个元素cij等于A的第i行所对应的矢量和B的第j列所对应的矢量进行矢量点乘的结果,即

对于每个元素cij,我们找到A中的第i行和B中的第j列,然后把它们的对应元素相乘后再加起来,这个和就是cij。

AB≠BA                (AB)C=A(BC)       ABCDE=((A(BC))D)E=(AB)(CD)E

特殊的矩阵

方块矩阵(square matrix),简称方阵,是指那些行和列数目相等的矩阵。

如果一个矩阵除了对角元素外的所有元素都为0,那么这个矩阵就叫做对角矩阵(diagonal matrix)

单位矩阵(identity matrix),用In来表示 

任何矩阵和它相乘的结果还是原来的矩阵。

转置矩阵(transposed matrix)实际是对原矩阵的一种运算,即转置运算。给定一个r×c的矩阵M,它的转置可以表示成MT,这是一个c×r的矩阵。转置矩阵的计算非常简单,我们只需要把原矩阵翻转一下即可。也就是说,原矩阵的第i行变成了第i列,而第j列变成了第j行。数学公式是:

矩阵串接的转置,等于反向串接各个矩阵的转置

逆矩阵(inverse matrix)

不是所有的矩阵都有逆矩阵,第一个前提就是,该矩阵必须是一个方阵。

给定一个方阵M,它的逆矩阵用M-1来表示。逆矩阵最重要的性质就是,如果我们把M和M-1相乘,那么它们的结果将会是一个单位矩阵。也就是说

如果一个矩阵有对应的逆矩阵,我们就说这个矩阵是可逆的(invertible)或者说是非奇异的(nonsingular);相反的,如果一个矩阵没有对应的逆矩阵,我们就说它是不可逆的(noninvertible)或者说是奇异的(singular)

如果一个矩阵的行列式(determinant)不为0,那么它就是可逆的

正交矩阵(orthogonal matrix)。正交是矩阵的一种属性。如果一个方阵M和它的转置矩阵的乘积是单位矩阵的话,我们就说这个矩阵是正交的(orthogonal

我们可以得到以下结论:

  • ·矩阵的每一行,即c1、c2和c3是单位矢量,因为只有这样它们与自己的点积才能是1;
  • ·矩阵的每一行,即c1、c2和c3之间互相垂直,因为只有这样它们之间的点积才能是0。
  • ·上述两条结论对矩阵的每一列同样适用,因为如果M是正交矩阵的话,MT也会是正交矩阵。

一个坐标空间需要指定一组基矢量,也就是我们理解的坐标轴。如果这些基矢量之间是互相垂直的,那么我们就把它们称为是一组正交基(orthogonal basis)。但是,它们的长度并不要求一定是1。如果它们的长度的确是1的话,我们就说它们是一组标准正交基(orthonormal basis)

在Unity中,常规做法是把矢量放在矩阵的右侧,即把矢量转换成列矩阵来进行运算,我们的矩阵乘法通常都是右乘

矩阵的几何意义:变换

什么是变换

变换(transform),指的是我们把一些数据,如点、方向矢量甚至是颜色等,通过某种方式进行转换的过程。

线性变换(linear transform)

线性变换指的是那些可以保留矢量加和标量乘的变换。用数学公式来表示这两个条件就是:

缩放(scale)就是一种线性变换。例如,f(x)=2x,可以表示一个大小为2的统一缩放,即经过变换后矢量x的模将被放大两倍

旋转(rotation)也是一种线性变换。对于线性变换来说,如果我们要对一个三维的矢量进行变换,那么仅仅使用3×3的矩阵就可以表示所有的线性变换。

仿射变换(affine transform)。仿射变换就是合并线性变换和平移变换的变换类型。仿射变换可以使用一个4×4的矩阵来表示,为此,我们需要把矢量扩展到四维空间下,这就是齐次坐标空间(homogeneous space)。

齐次坐标

那么,我们如何把三维矢量转换成齐次坐标呢?

对于一个点,从三维坐标转换成齐次坐标是把其w分量设为1,而对于方向矢量来说,需要把其w分量设为0。这样的设置会导致,当用一个4×4矩阵对一个点进行变换时,平移、旋转、缩放都会施加于该点。但是如果是用于变换一个方向矢量,平移的效果就会被忽略。

分解基础变换矩阵

可以使用一个4×4的矩阵来表示平移、旋转和缩放。我们把表示纯平移、纯旋转和纯缩放的变换矩阵叫做基础变换矩阵。这些矩阵具有一些共同点,我们可以把一个基础变换矩阵分解成4个组成部分:

其中,左上角的矩阵M3×3用于表示旋转和缩放,t3×1用于表示平移,01×3是零矩阵,即01×3=[00 0],右下角的元素就是标量1。

平移矩阵

缩放矩阵

旋转矩阵

复合变换

我们可以把平移、旋转和缩放组合起来,来形成一个复杂的变换过程

变换的结果是依赖于变换顺序的,由于矩阵乘法不满足交换律,因此矩阵的乘法顺序很重要

在绝大多数情况下,我们约定变换的顺序就是先缩放,再旋转,最后平移

坐标空间

坐标空间的变换

每个空间都有一个父(parent)坐标空间。对坐标空间的变换实际上就是在父空间和子空间之间对点和矢量进行变换。

假设,现在有父坐标空间 以及一个子坐标空间 。我们知道在父坐标空间中子坐标空间的原点位置以及3个单位坐标轴。我们一般会有两种需求:一种需求是把子坐标空间下表示的点或矢量Ac转换到父坐标空间下的表示Ap,另一个需求是反过来,即把父坐标空间下表示的点或矢量Bp转换到子坐标空间下的表示Bc。我们可以使用下面的公式来表示这两种需求:

其中,Mc→p表示的是从子坐标空间变换到父坐标空间的变换矩阵,而Mp→c是其逆矩阵(即反向变换)

如何求出从子坐标空间到父坐标空间的变换矩阵Mc→p。

现在,我们已知子坐标空间C的3个坐标轴在父坐标空间P下的表示xc、yc、zc,以及其原点位置Oc。当给定一个子坐标空间中的一点Ac=(a, b,c),我们同样可以依照上面4个步骤来确定其在父坐标空间下的位置Ap:

 其中的“ | ”符号表示按列展开

我们把上面的式子扩展到齐次坐标空间中,得到:

当我们已知从模型空间到世界空间的一个4×4的变换矩阵,可以提取它的第一列再进行归一化后(为了消除缩放的影响)来得到模型空间的x轴在世界空间下的单位矢量表示。同样的方法可以提取y轴和z轴。我们可以从另一个角度来理解这个提取过程

而现在,我们不仅可以根据变换矩阵Mc→p反推出子坐标空间的坐标轴方向在父坐标空间中的表示xc、yc 和 zc,还可以反推出父坐标空间的坐标轴方向在子坐标空间中的表示xp、yp 和 zp,这些坐标轴对应的就是Mc→p的每一行!也就是说,如果我们知道坐标空间变换矩阵MA→B是一个正交矩阵,那么我们可以提取它的第一列来得到坐标空间 的x轴在坐标空间 下的表示,还可以提取它的第一行来得到坐标空间 的x轴在坐标空间 下的表示。反过来,如果我们知道坐标空间B的x轴、y轴和z轴(必须是单位矢量,否则构建出来的就不是正交矩阵了)在坐标空间 下的表示,就可以把它们依次放在矩阵的每一行就可以得到从 到 的变换矩阵了。

顶点的坐标空间变换过程

模型空间

模型空间(model space),是和某个模型或者说是对象有关的。有时模型空间也被称为对象空间(object space)或局部空间(local space)。每个模型都有自己独立的坐标空间,当它移动或旋转的时候,模型空间也会跟着它移动和旋转。

由于顶点变换中往往包含了平移变换,因此需要把其扩展到齐次坐标系下,得到顶点坐标是(0, 2, 4, 1)

世界空间

世界空间(world space)

世界空间可以被用于描述绝对位置

在Unity中,世界空间同样使用了左手坐标系。但它的x轴、y轴、z轴是固定不变的

顶点变换的第一步,就是将顶点坐标从模型空间变换到世界空间中。这个变换通常叫做模型变换(model transform)。

观察空间

观察空间(view space)也被称为摄像机空间(camera space)

观察空间可以认为是模型空间的一个特例

Unity中观察空间的坐标轴选择是:+x轴指向右方,+y轴指向上方,而+z轴指向的是摄像机的后方

观察空间是一个三维空间,而屏幕空间是一个二维空间。从观察空间到屏幕空间的转换需要经过一个操作,那就是投影(projection)

顶点变换的第二步,就是将顶点坐标从世界空间变换到观察空间中。这个变换通常叫做观察变换(view transform)

裁剪空间

顶点接下来要从观察空间转换到裁剪空间(clip space,也被称为齐次裁剪空间)中,这个用于变换的矩阵叫做裁剪矩阵(clip matrix),也被称为投影矩阵(projection matrix)

视锥体指的是空间中的一块区域,这块区域决定了摄像机可以看到的空间。视锥体由六个平面包围而成,这些平面也被称为裁剪平面(clip planes)。视锥体有两种类型,这涉及两种投影类型:一种是正交投影(orthographic projection),一种是透视投影(perspective projection)

在视锥体的6块裁剪平面中,有两块裁剪平面比较特殊,它们分别被称为近剪裁平面(near clip plane)远剪裁平面(far clip plane)。它们决定了摄像机可以看到的深度范围。

投影矩阵有两个目的。

  • ·首先是为投影做准备。这是个迷惑点,虽然投影矩阵的名称包含了投影二字,但是它并没有进行真正的投影工作,而是在为投影做准备。真正的投影发生在后面的齐次除法(homogeneous division)过程中。而经过投影矩阵的变换后,顶点的w分量将会具有特殊的意义。
  • ·其次是对x、y、z分量进行缩放。我们上面讲过直接使用视锥体的6个裁剪平面来进行裁剪会比较麻烦。而经过投影矩阵的缩放后,我们可以直接使用w分量作为一个范围值,如果x、y、z分量都位于这个范围内,就说明该顶点位于裁剪空间内。

透视投影

视锥体的意义在于定义了场景中的一块三维空间。所有位于这块空间内的物体将会被渲染,否则就会被剔除或裁剪。我们已经知道,这块区域由6个裁剪平面定义,那么这6个裁剪平面又是怎么决定的呢?在Unity中,它们由Camera组件中的参数和Game视图的横纵比共同决定

我们可以通过Camera组件的Field of View(简称FOV)属性来改变视锥体竖直方向的张开角度,而Clipping Planes中的Near和Far参数可以控制视锥体的近裁剪平面和远裁剪平面距离摄像机的远近。这样,我们可以求出视锥体近裁剪平面和远裁剪平面的高度

正交投影

屏幕空间

经过投影矩阵的变换后,我们可以进行裁剪操作。当完成了所有的裁剪工作后,就需要进行真正的投影了,也就是说,我们需要把视锥体投影到屏幕空间(screen space)

总结

法线变换

法线(normal),也被称为法矢量(normal vector)

切线(tangent),也被称为切矢量(tangent vector)

由于切线是由两个顶点之间的差值计算得到的,因此我们可以直接使用用于变换顶点的变换矩阵来变换切线。因为切线和法线都是方向矢量,不会受平移的影响

其中 TA和TB 分别表示在坐标空间A下和坐标空间B下的切线方向

 Unity Shader的内置变量(数学篇)

变换矩阵

这里的函数都是Unity 5.2版本的,可能会与现在使用的不同,此处仅供参考

摄像机和屏幕参数

答疑解惑

使用3×3还是4×4的变换矩阵

对于线性变换(例如旋转和缩放)来说,仅使用3×3的矩阵就足够表示所有的变换了。但如果存在平移变换,我们就需要使用4×4的矩阵。因此,在对顶点的变换中,我们通常使用4×4的变换矩阵。当然,在变换前我们需要把点坐标转换成齐次坐标的表示,即把顶点的w分量设为1。而在对方向矢量的变换中,我们通常使用3×3的矩阵就足够了,这是因为平移变换对方向矢量是没有影响的。

CG中的矢量和矩阵类型

在CG中,矩阵类型是由float3x3、float4x4等关键词进行声明和定义的。而对于float3、float4等类型的变量,我们既可以把它当成一个矢量,也可以把它当成是一个1 x n的行矩阵或者一个n x 1的列矩阵。这取决于运算的种类和它们在运算中的位置。例如,当我们进行点积操作时,两个操作数就被当成矢量类型

        float4  a  =  float4(1.0,  2.0,  3.0,  4.0);
        float4  b  =  float4(1.0,  2.0,  3.0,  4.0);
        // 对两个矢量迚行点积操作
        float  result  =  dot(a,  b);

但在进行矩阵乘法时,参数的位置将决定是按列矩阵还是行矩阵进行乘法

        float4  v  =  float4(1.0,  2.0,  3.0,  4.0);
        float4x4  M  =  float4x4(1.0,  0.0,  0.0,  0.0,
                          0.0,  1.0,  0.0,  0.0,
                          0.0,  0.0,  1.0,  0.0,
                          0.0,  0.0,  0.0,  1.0);
        // 把v当成列矩阵和矩阵M迚行右乘
        float4  column_mul_result  =  mul(M,  v);
        // 把v当成行矩阵和矩阵M迚行左乘
        float4  row_mul_result  =  mul(v,  M);
        // 注意:column_mul_result不等于row_mul_result,而是:
        //  mul(M, v)  ==  mul(v,  tranpose(M))
        //  mul(v, M)  ==  mul(tranpose(M),  v)

在CG中,对float4x4等类型的变量是按行优先的方式进行填充的

Unity中的屏幕坐标:ComputeScreenPos/VPOS/WPOS

在顶点/片元着色器中,有两种方式来获得片元的屏幕坐标。

一种是在片元着色器的输入中声明VPOS或WPOS语义(关于什么是语义,可参见5.4节)。VPOS是HLSL中对屏幕坐标的语义,而WPOS是CG中对屏幕坐标的语义。两者在Unity Shader中是等价的。我们可以在HLSL/CG中通过语义的方式来定义顶点/片元着色器的默认输入,而不需要自己定义输入输出的数据结构

另一种方式是通过Unity提供的 ComputeScreenPos 函数。这个函数在UnityCG.cginc里被定义。通常的用法需要两个步骤,首先在顶点着色器中将ComputeScreenPos的结果保存在输出结构体中,然后在片元着色器中进行一个齐次除法运算后得到视口空间下的坐标

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Unity Shader是一种用于渲染图形的程序,它可以控制对象的表面颜色、纹理、透明度、反射等属性,从而实现特殊的视觉效果。对于游戏开发者来说,掌握Shader编写技巧是非常重要的。 以下是关于Unity Shader入门精要: 1. ShaderLab语言 ShaderLab是Unity中用于编写Shader的语言,它是一种基于标记的语言,类似于HTML。ShaderLab可以用于定义Shader的属性、子着色器、渲染状态等信息。 2. CG语言 CG语言是Unity中用于编写Shader的主要语言,它是一种类似于C语言的语言,可以进行数学运算、向量计算、流程控制等操作。CG语言可以在ShaderLab中嵌入,用于实现Shader的具体逻辑。 3. Unity的渲染管线 Unity的渲染管线包括顶点着色器、片元着色器、几何着色器等组件,每个组件都有不同的作用。顶点着色器用于对对象的顶点进行变换,片元着色器用于计算每个像素的颜色,几何着色器用于处理几何图形的变形和细节等。 4. 模板和纹理 在Shader中,我们可以使用纹理来给对象添加图案或者贴图,也可以使用模板来控制对象的透明度、反射等属性。纹理可以通过内置函数tex2D()来获取,模板可以通过内置函数clip()来实现裁剪。 5. Shader的实现 Shader的实现需要注意以下几点: - 在ShaderLab中定义Shader的属性、子着色器、渲染状态等信息。 - 在CG语言中实现Shader的具体逻辑,包括顶点着色器、片元着色器等内容。 - 使用纹理和模板来实现特定的视觉效果。 - 在对象上应用Shader,通过调整Shader的属性来达到不同的效果。 以上是关于Unity Shader入门精要,希望对你有所帮助。如果你想更深入地了解Shader的编写技巧,可以参考官方文档或者相关教程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值