UnityShader入门精要C4-学习Shader所需的数学基础

本文记录《UnityShader入门精要》第4章的读书笔记。

第4章是介绍编写Shader所需要具备的数学知识,坐标系、矢量、矩阵、变换…都是很常用很重要的东西。但这种概念性的东西,不能实操顶多做做题,难免晦涩和枯燥。为了避免知其然而不知其所以然,一定要掌握这些知识,至少也得有个印象,免得后面用到了都不知去哪找公式。

本章内容很多,作者介绍的也十分细致,只是偶尔有些地方排版不好,让我看起来有些难受。但感觉这是知乎电子书的锅,错别字一堆…

希望能在之后的实际运用中,弄清各种酷炫效果的原理,加深对图形学基本数学运算的熟练度。近期应该不会算不来点积叉积了吧~

4. 学习Shader所需的数学基础

最常用的是矢量和矩阵(线代)(为什么不好好学数学)。

4.1 背景:农场游戏

假定在开发农场游戏,主角是壮牛妞妞。

4.2 笛卡儿坐标系

Cartesian Coordinate System。

4.2.1 二维笛卡儿坐标系

4.2.2 三维笛卡儿坐标系

在三维笛卡儿坐标系重,我们要定义3个坐标轴和一个原点。

这3个坐标轴也叫基矢量(basis vector)。如果3个坐标轴互相垂直且长度为1,称为标准正交基。如果它们互相垂直但长度不为1,称为正交基。默认情况使用的坐标轴指标准正交基。

因坐标轴方向不同,分为左手坐标系(left-handed coordinate space)和右手坐标系(right-handed coordinate space)

4.2.3 左手坐标系和右手坐标系

二维坐标系,总可以通过一些旋转操作使它们的坐标轴指向相同。三维有时候不行。

判断左手还是右手坐标系的方法略。两种坐标系的旋向性(handedness)不同。

判断绕坐标系旋转的正方向,作者描述的是在左手坐标系使用左手法则:举起左手握拳,大拇指指向旋转轴正方向,那么旋转的正方向就是剩下4个手指弯曲的方向。这个存疑,在UE4使用的左手坐标系中,X/Y轴右手法则是正方向,Z轴是左手法则。

4.2.4 Unity使用的坐标系

Unity使用的是左手坐标系,右X上Y前Z。

观察空间,是右手坐标系。观察空间指以相机为原点,相机前向是z轴负方向的坐标系。在这个坐标系里,z坐标减少意味着场景深度的增加,和模型空间定义相反。

4.2.5 练习题

解答如下:

1:右手坐标系

2:

  • 在左手坐标系中,绕y轴正方向旋转90°,落在了x轴正方向上,坐标是(1,0,0)
  • 在右手坐标系中,绕y轴正方向旋转90°,依然是落在了x轴正方向,坐标是(1,0,0)
  • 虽然坐标相同,位置不同。如果y轴z轴相同时,两个坐标系x轴正方向刚好相反,所以变换后的点刚好yz平面对称

3:在相机的观察空间下,小球在相机前面10的位置,因为观察空间前是z的负方向,所以球体的z值是-10。在相机的模型空间中,z值是10,和观察空间刚好相反。

4.3 点和矢量

  • 点 point
  • 矢量/向量 vector
    • 模 magnitude
    • 方向 direction
  • 标量 scalar

4.3.1 点和矢量的区别

点是一个没有大小之分的空间中的位置,矢量是一个有模和方向但没有位置的量。矢量通常用于描述偏移量,任何一个点都可以表示成一个从原点出发的矢量。

4.3.2 矢量运算

1. 矢量和标量乘法/除法

kv=(kvx,kvy,kvz)

2. 矢量的加法和减法

a±b=(ax±bx,ay±by,az±bz)

几何意义:

  • 加法:把矢量a的头连接到矢量b的尾的矢量
  • 减法:其实就是矢量a加矢量-b

3. 矢量的模

|v|=sqrt(vx²+vy²+vz²)

4. 单位矢量

单位矢量(unit vector):模为1的矢量,很多情况下只关心方向而不是模。也被称为被归一化的矢量(normalized vector),转换成单位矢量的过程称为归一化(normalization)。

v单=v/|v|

零矢量不可以被归一化。

5. 矢量的点积

点积(dot product,也叫内积,inner product)。

  • a·b=(ax,ay,az)·(bx,by,bz)=axbx+ayby+azbz
  • a·b=b·a

几何意义:a·b表示矢量b在单位矢量a上的投影。a·b=|a||b|cosθ。

如果矢量a不是单位矢量,那么a·b表示b在a方向上的投影值,再乘以a的长度。

点积具有一些很重要的性质,有助于Shader的计算:

  1. 点积可结合标量乘法。意义是对点积其中一个矢量进行缩放,相当于对最后的点积结果进行缩放:(ka)·b=a·(kb)=k(a·b)
  2. 点积可结合矢量加法和减法。点积的操作数可以是矢量相加减后的结果:a·(b±c)=a·b±a·c
  3. 一个矢量和本身进行点积的结果,是该矢量的模的平方:v·v=vxvx+vyvy+vzvz=|v|²

6. 矢量的叉积

叉积(cross produce,也叫外积,outer produce)。叉积的结果仍然是一个矢量。

axb=(ax,ay,az)x(bx,by,bz)=(aybz-azby,azbx-axbz,axby-aybx)

  • 叉积不满足交换律:axb≠bxa
  • 叉积满足反交换律:axb=-bxa(模相同方向刚好相反的两个向量)
  • 叉积不满足结合律:(axb)xc≠ax(bxc)

几何意义:axb的结果是一个同时垂直于这两个矢量的新矢量。

叉积的模:|axb|=|a||b|sinθ。结果是a和b构成的平行四边形的面积。

叉积的方向:在左手坐标系中,使用左手法则。手心放在a和b的交点处,让手掌方向和a的方向重合,弯曲四指向b的方向靠拢,最后伸出大拇指,大拇指指向的方向就是左手坐标系中的axb的方向。在右手坐标系中使用右手法则,方向应该恰好和左手坐标系相反。

叉积的应用:最常见的一个应用就是计算垂直于一个平面、三角形的矢量。还可以判断三角面片的朝向。

4.3.3 练习题

解答如下:

1:

  1. 否。矢量的位置不重要,它可以没有固定的位置,但矢量有大小(模)和方向
  2. 否。选择左手还是右手坐标系是很重要,但这并不影响叉积的计算

2:

  1. sqrt(62)。|(2,7,3)|=sqrt(2²+7²+3²)=sqrt(4+49+9)=sqrt(62)
  2. (12.5,10,25)。2.5(5,4,10)=(2.5x5,2.5x4,2.5x10)=(12.5,10,25)
  3. (1.5,2)。(3,4)/2=0.5(3,4)=(1.5,2)
  4. (5/13,12/13)。a=(5,12),|a|=13,归一化a=a/|a|=(5/12,12/13)
  5. (1,1,1)/sqrt(3)
  6. (10,9)
  7. (-6,1,2)

3:sqrt(308)。两点的距离矢量为(10,13,11)-(2,1,1)=(8,12,10),距离为这个矢量的模sqrt(64+144+100)=sqrt(308)

4:

  1. 75。(4,7)·(3,9)=4x3+7x9=12+63=75
  2. 13。(2,5,6)·(3,1,2)-10=2x3+5x1+6x2-10=6+5+12-10=13
  3. 13。0.5(-3,4)·(-2,5)=0.5(6+20)=13
  4. (-9,-13,7)。(3,-1,2)x(-5,4,1)=(-1-8,-10-3,12-5)=(-9,-13,7)
  5. (9,13,-7)。(-5,4,1)x(3,-1,2)=(8+1,3+10,5-12)=(9,13,-7)。和上面结果方向相反

5:

  1. 12。a·b=|a||b|cosθ=4x6xcos(60)=12
  2. |axb|=|a||b|sinθ=4x6xsin(60)=20.784

6:

  1. a=x-p,θ=acos(v·a/(|v||a|)),θ<90即在前方,否则在后方。看v·a的符号也行,大于0即在前方,否则后方
  2. 后方。a=(10-4,6-2)=(6,4),v·a=-3x6+4x4=-2<0,后方
  3. 还是计算出角度a=x-p,θ=acos(v·a/(|v||a|)),θ<φ/2就能看到,否则不行。
  4. 距离=|x-p|,即连接两点的向量的模,距离小于限制就可以观察到

7:v=(p2-p1)x(p3-p2),因为p1p2p3都在xy平面上,所以叉积向量v一定垂直于xy平面,即(0,0,a)的形式。如果v的z分量a<0,则3个点是顺时针,否则逆时针。

4.4 矩阵

Matrix

4.4.1 矩阵的定义

由mxn个标量组成的长方形数组。

行(row)列(column),3x4的矩阵有3行4列。3x3的矩阵可以写成:

4.4.2 和矢量联系起来

矢量可以看成是nx1的列矩阵(column matirx),或1xn的行矩阵(row matirx)。如[3 8 6]。

4.4.3 矩阵运算

1. 矩阵和标量的乘法

就是每个元素和该标量相乘。

2. 矩阵和矩阵的乘法
一个rxn的矩阵A和一个nxc的矩阵B相乘,结果C=AB是一个rxc大小的矩阵。相乘的矩阵,第一个矩阵的列数必须和第二个矩阵的行数相同。

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

先找到对应的行矩阵和列矩阵,把它们进行矢量点积后就可以得到结果值:

在Shader计算中,我们更多的是用4x4矩阵来运算的。矩阵乘法满足一些性质:

  1. 矩阵乘法不满足交换律:AB≠BA
  2. 矩阵乘法满足结合律:(AB)C=A(BC)

4.4.4 特殊的矩阵

1. 方块矩阵

square matrix,简称方阵,指行和列数目相等的矩阵。对角元素(diagonal elements),指行号和列号相等的元素,例如m11、m22、m33。如果一个矩阵除了对角元素外的所有元素都为0,那么这个矩阵叫做对角矩阵(diagonal matrix)。如:

2. 单位矩阵

identity matrix,用In来表示,对角元素都是1的对角矩阵,一个3x3的单位矩阵如下:

任何矩阵和单位矩阵相乘的结果都是原来的矩阵:MI=IM=M

3. 转置矩阵

transposed matrix,对原矩阵进行转置运算。给定一个rxc的矩阵M,它的转置可以表示成MT,这是一个cxr的矩阵。把行列翻转一下,第i行变成了第i列,第j列变成了第j行:

转置矩阵的常用性质:

  1. 矩阵转置的转置等于原矩阵:(MT)T=M
  2. 矩阵串接的转置,等于反向串接各个矩阵的转置:(AB)T=BTAT

4. 逆矩阵

inverse matrix,方阵才可能有逆矩阵。用M-1表示,如果把M和M-1相乘,那么它们的结果会是一个单位矩阵:MM-1=M-1M=I

所有元素都是0的矩阵没有逆矩阵。如果一个矩阵有对应的逆矩阵,就是可逆的(invertible)或说是非奇异的(nonsingular)。反之,就是不可逆的(noninvertible)或说是奇异的(singular)

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

逆矩阵的重要性质:

  1. 逆矩阵的逆矩阵是原矩阵本身:(M-1)-1=M
  2. 单位矩阵的逆矩阵是它本身:I-1=I
  3. 转置矩阵的逆矩阵是逆矩阵的转置:(MT)-1=(M-1)T
  4. 矩阵串接相乘后的逆矩阵等于反向串接各个矩阵的逆矩阵:(AB)-1=B-1A-1

几何意义:可以使用逆矩阵还原变换。例如用变换矩阵M对矢量v进行了一次变换,再使用它的逆矩阵M-1进行一次变换就可以得到原来的矢量:M-1(Mv)=(M-1M)v=Iv=v

5. 正交矩阵

orthogonal matrix。如果一个方阵M和它的转置矩阵的乘积是单位矩阵的话,这个矩阵是正交的:MMT=MTM=I。

如果一个矩阵是正交的,那么它的转置矩阵和逆矩阵是一样的:MT=M-1。在三维变换中,经常用逆矩阵求反向的变换,但计算量很大,如果用转置矩阵代替,就容易求解。

以上演算可以得到结论:

  1. c1、c2、c3的单位矢量,和自己的点积是1
  2. c1、c2、c3之间互相垂直,之间的点积是0

如果一个矩阵满足上面的条件,那么它就是一个正交矩阵。一组标准正交基可以精确的满足上述条件(3个坐标轴互相垂直且长度为1)。这表示,使用标准正交基来构建坐标系的话,可以直接使用转置矩阵来求变换的逆变换。

4.4.5 行矩阵还是列矩阵

把矢量转换成行矩阵或列矩阵,会影响和矩阵相乘的结果。

在Unity中,常规做法是把矢量放在矩阵的右侧(作为列矩阵),矩阵乘法通常是右乘:CBAv=(C(B(Av))),等价于vATBTCT=(((vAT)BT)CT)

4.4.6 练习题

1:

  1. 存在
  2. 不存在
  3. 存在

2:通过正交矩阵的特性来判断。每个行向量应该是单位向量,并且互相垂直。

  1. 否。三个行向量不互相垂直
  2. 是。满足上述特性
  3. 是。满足上述特性

3:1x3行矩阵乘3x3矩阵得到的是1x3行矩阵,3x3矩阵乘3x1列矩阵得到的是3x1列矩阵:

  1. 相同。乘单位矩阵还是自己
  2. 不同。Av=vAT
  3. 相同。这个矩阵的转置还是自己(对称矩阵)

4.5 矩阵的几何意义:变换

在三维渲染中,矩阵的可视化结果就是变换,包含旋转、缩放和平移。

4.5.1 什么是变换

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

线性变换(linear transform),指那些可以保留矢量加和标量乘的变换。

  • f(x)+f(y)=f(x+y)
  • kf(x)=f(kx)

缩放(scale),旋转(rotation)是线性变换,还有错切(shear)、镜像(mirroring/reflection)、正交投影(orthographic projection)。

平移变换不是线性变换,不能用一个3x3矩阵来表示。仿射变换(affine transform),是合并线性变换和平移变换的变换类型,可以用4x4的矩阵来表示,这就是齐次坐标空间(homogeneous space)。

4.5.2 齐次坐标

为了表示平移操作,需要把原来的三维矢量转换为四维矢量,即齐次坐标(homogeneous coordinate)。

如何把三维矢量转换为齐次坐标:三维坐标转换是把w分量设为1,方向矢量把w设为0。

4.5.3 分解基础变换矩阵

基础变换矩阵:纯平移、纯旋转、纯缩放。

4.5.4 平移矩阵

平移矩阵:基础变换矩阵中的t3x1矢量对应了平移矢量,左上角的矩阵M3x3为单位矩阵I3。

平移变换不会对方向矢量产生任何影响。平移矩阵的逆矩阵就是反向平移得到的矩阵。平移矩阵不是正交矩阵。

4.5.5 缩放矩阵

如果缩放系数kx=ky=kz,称为统一缩放(uniform scale),否则成为非统一缩放(nonuniform scale)。缩放矩阵的逆矩阵是使用原缩放系数的倒数来对点或方向矢量进行缩放。缩放矩阵一般不是正交矩阵。

如果在任意方向上进行缩放,一种方法是先将缩放轴变换成标准坐标轴,然后进行沿坐标轴的缩放,再使用逆变换得到原来的缩放轴朝向。

4.5.6 旋转矩阵

绕坐标轴旋转:

旋转矩阵的逆矩阵是旋转相反角度得到的变换矩阵。旋转矩阵是正交矩阵,而且多个旋转矩阵之间的串联同样是正交的。

4.5.7 复合变换

通过矩阵的串联,把平移、旋转、缩放组合起来:P=MtMrMs。先进行缩放,再旋转,再平移。如果顺序变了,因为矩阵的乘法不满足交换律,结果不同。

TODO:旋转顺序没看明白。

4.6 坐标空间

4.6.1 为什么要使用这么多不同的坐标空间

我们需要在不同的情况下使用不同的坐标空间,因为一些概念只有在特定的坐标空间下才有意义,才更容易理解。

4.6.2 坐标空间的变换

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

4.6.3 顶点的坐标空间变换过程

在渲染流水线中,一个顶点要经过多个坐标空间的变换才能最终被画在屏幕上。一个顶点最开始是在模型空间中定义的,最后它将会变换到屏幕空间中。

4.6.4 模型空间

model space,也被称为对象空间(object space)或局部空间(local space)。

在模型空间中,我们经常使用一些方向概念,如前后左右上下。

4.6.5 世界空间

world space,它建立了我们所关心的最大的空间,可以被用于描述绝对位置。

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

先构建出模型变换的变换矩阵(顺序是缩放、旋转、平移),再将模型空间的坐标变换到世界空间。

4.6.6 观察空间

view space,也被称为摄像机空间(camera space)。观察空间x右y上z后,和其他Unity坐标系的z轴相反,这是要符合OpenGL传统,弄成右手坐标系。相机正前方指向的是-z方向。

顶点变换的第二步,就是将顶点坐标从世界空间变换到观察空间中。

4.6.7 裁剪空间

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

视锥体(view frustum)决定被剔除的部分,由六个平面包围形成,这些平面被称为裁剪平面(clip planes)。视锥体有两种类型,涉及两种投影类型:

  • 正交投影(orthographic projection)
  • 透视投影(perspective projection)

在视锥体中,两块裁剪平面决定了相机可以看到的深度范围:

  • 近裁剪平面(near clip plane)
  • 远裁剪平面(far clip plane)

如果直接使用视锥体定义的空间裁剪,比较麻烦。所以要用一种更加通用、方便和整洁的方式来进行裁剪,这种方式就是通过一个投影矩阵把顶点转换到一个裁剪空间中。投影矩阵的两个目的:

  • 为投影做准备。投影矩阵没有做真正的投影工作,而是在为投影做准备。真正的投影是发生在后面的齐次除法(homogeneous division),屏幕映射
  • 对xyz分量进行缩放。经过投影矩阵的缩放后,可以直接使用w分量作为范围值,如果xyz分量都位于这个范围内,说明该顶点位于裁剪空间内

下面看一下两种投影类型使用的投影矩阵具体是什么:

1. 透视投影

Camera组件的FieldOfView改变视锥体竖直方向的张开角度,ClippingPlanes中的Near和Far控制视锥体近裁剪平面和远裁剪平面距离摄像机的远近:

横向信息通过摄像机的横纵比得到,定义横纵比为:Aspect=nearClipPlaneWidth/nearClipPlaneHeight=farClipPlaneWidth/farClipPlaneHeight。

根据已知的Near、Far、FOV和Aspect来确定透视投影的投影矩阵(推导过程本节略):

将一个顶点和上述投影矩阵相乘,可以从观察空间变换到裁剪空间:

w分量不是1了,而是-z(正的)。如果顶点在视锥体内,那么它变换后的坐标必须满足:

  • -w≤x≤w
  • -w≤y≤w
  • -w≤z≤w

另外,裁剪矩阵会改变空间的旋向性,空间从右手坐标系变换到了左手坐标系。

2. 正交投影

Camera组件的Size来改变视锥体竖直方向上高度的一半,Near和Far还是控制近远裁剪平面:

正交投影的裁剪矩阵如下:

一个顶点和上述投影矩阵相乘后的结果如下:

判断一个变换后的顶点是否位于视锥体内使用的方法和透视投影一样。

4.6.8 屏幕空间

screen space,最后把视锥体投影到屏幕空间。屏幕空间是二维空间,从裁剪空间投影有两个步骤:

一是进行标准齐次除法,也叫做透视除法(perspective division)。用齐次坐标系的w分量去除xyz分量,在OpenGL中,这一步得到的坐标叫做归一化的设备坐标(Normalized Device Coordinates, NDC)。


二是经过齐次除法后,透视投影和正交投影的视锥体都变换到相同的立方体内,可以通过变换后的xy坐标来映射输出窗口对应的像素坐标:

4.6.9 总结

本节介绍了一个顶点从模型空间变换到屏幕坐标的过程。顶点着色器的基本任务就是把顶点坐标从模型空间转换到裁剪空间,片元着色器我们可以得到该片元在屏幕空间的像素位置。

4.7 法线变换

  • 法线(normal),也叫法矢量(normal vector)。如果直接用变换矩阵,无法确保法线的垂直性
  • 切线(tangent),切矢量(tangent vector)。与纹理空间对齐,与法线方向垂直


切线是两个顶点之间的差值计算的,可以直接用用于变换顶点的变换矩阵来变换切线,方向向量不受平移影响:Tb=Ma->bTa

同一个顶点的切线Ta和法线Na必须垂直,Ta·Na=0,假设用矩阵G来变换法线,那么变换后的法线会依然和变换后的切线垂直:Tb·Nb=(Ma->bTa)·(GNa)=0。

  • 使用原变换矩阵的逆转置矩阵来变换法线,就可以得到正确的结果(M-1a->b)T
  • 如果变换矩阵是正交的(只有旋转),可以直接用原变换矩阵Ma->b
  • 如果变换没有非统一缩放(包含旋转和统一缩放),用远变换矩阵的逆转置(1/k)Ma->b

4.8 UnityShader的内置变量(数学篇)

UnityShader提供的内置参数,使我们不需要手动计算一些值。UnityShaderVariables.cginc文件有说明。

4.8.1 变换矩阵

下面所有矩阵都是float4x4类型(Unity5.2):

  • UNITY_MATRIX_MVP:当前的模型观察投影矩阵,用于将顶点/方向矢量从模型空间->裁剪空间
  • UNITY_MATRIX_MV:当前的模型观察矩阵,用于将顶点/方向矢量从模型空间->观察空间
  • UNITY_MATRIX_V:当前的观察矩阵,用于将顶点/方向矢量从世界空间->观察空间
  • UNITY_MATRIX_P:当前的投影矩阵,用于将顶点/方向矢量从观察空间->裁剪空间
  • UNITY_MATRIX_VP:当前的观察投影矩阵,用于将顶点/方向矢量从世界空间->裁剪空间
  • UNITY_MATRIX_T_MV:UNITY_MATRIX_MV的转置矩阵
  • UNITY_MATRIX_IT_MV:UNITY_MATRIX_MV的逆转置矩阵,用于将法线从模型空间变换到观察空间,也可用于得到UNITY_MATRIX_MV的逆矩阵
  • _Object2World:当前的模型矩阵,用于将顶点/方向矢量从模型空间变换到世界空间
  • _World2Object:_Object2World的逆矩阵,用于将顶点/方向矢量从世界空间变换到模型空间

UNITY_MATRIX_MV,在正交时(仅旋转),UNITY_MATRIX_T_MV就是它的逆矩阵。如果考虑统一缩放k,逆矩阵乘1/k。如果只变换方向矢量,取UNITY_MATRIX_T_MV的前3行前3列把方向矢量从观察空间变换到模型空间。

4.8.2 摄像机和屏幕参数

Unity提供了一些内置变量,让我们访问当前正在渲染的相机的参数信息(Unity5.2):

  • _WorldSpaceCameraPos:float3,该相机在世界空间中的位置
  • _ProjectionParams:float4,x=1.0,y=Near,z=Far,w=1.0+1.0/Far。Far和Near为远近裁剪平面和相机的距离
  • _ScreenParams:float4,x=width,y=height,z=1.0+1.0/width,w=1.0+1.0/height。width和height为相机渲染目标的像素宽度和高度
  • _ZBufferParams:float,x=1-Far/Near,y=Far/Near,z=x/Far,w=y/Far,用于线性化Z缓存中的深度值
  • unity_OrthoParams:float4,x=width,y=height,z没有定义,w=1.0(正交相机)或w=0.0(透视相机)。width和height是正交投影相机的宽度和高度
  • unity_CameraProjection:float4x4,该相机的投影矩阵
  • unity_CameraInvProjection:float4x4,该相机的投影矩阵的逆矩阵
  • unity_CameraWorldClipPlanes[6]:float4,该相机的6个裁剪平面在世界空间下的等式,顺序:左、右、下、上、近、远裁剪平面

4.9 答疑解惑

4.9.1 使用3x3还是4x4的变换矩阵

  • 线性变换(旋转、缩放等),用3x3就够了
  • 有平移,要用4x4。对顶点的变换通常用4x4,w分量设为1
  • 对方向矢量的变换用3x3

4.9.2 Cg中的矢量和矩阵类型

对于float3、float4等变量,既可以当成矢量,也可以当成1xn行矩阵或nx1列矩阵:

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)

矩阵乘法,参数的位置决定是看作列矩阵还是行矩阵,在Cg中矩阵乘法是mul函数:

float4 v=float4(1.0,2.0,3.0,4.0);
float4x4 M=float4x4(1.0,0.0,0.0,0.0,
                    0.0,2.0,0.0,0.0,
                    0.0,0.0,3.0,0.0,
                    0.0,0.0,0.0,4.0);

// 把v当成列矩阵和M右乘
float4 column_mul_result=mul(M,v);
// 把v当成行矩阵和M左乘
float4 row_mul_result=mul(v,M);

// colume_mul_result不等于row_mul_result,而是等于...转置
mul(M,v)==mul(v,tranpose(M))
mul(v,M)==mul(tranpose(M),v)

注意Cg对矩阵类型中元素的初始化和访问顺序:

  • 矩阵类型的变量是按行优先来进行填充的
  • 访问元素时,也是按行来索引的
    ```
    float3x3 M=float3x3(1.0,2.0,3.0,
                  4.0,5.0,6.0,
                  7.0,8.0,9.0);
    

// 第1行,(1.0,2.0,3.0)
float3 row=M[0];

// 第2行第1列的元素,4.0
float ele=M[1][0];


Unity脚本中的矩阵类型Matrix4x4是按列优先,区别。

### 4.9.3 Unity中的屏幕坐标:ComputeScreenPos/VPOS/WPOS
在顶点/片元着色器中,有两种方式来获得片元的屏幕坐标。

一种是在片元着色器的输入中声明VPOS或WPOS语义:

fixed4 frag(float4 sp:VPOS):SV_Target{
// 用屏幕坐标除以屏幕分辨率_ScreenParams.xy,得到视口空间中的坐标
return fixed4(sp.xy/_ScreenParams.xy,0.0,1.0);
}


![](4.43.png)

xy值代表了在屏幕空间中的像素坐标,如果屏幕分辨率是400x300,那么x的范围是[0.5,400.5],y的范围是[0.5,300.5],这里的像素坐标不是整数值,因为OpenGL和DX10以后的版本认为像素中心对应的是浮点值中的0.5。z值范围为[0,1],从近裁剪平面,到远裁剪平面。w分量和相机投影类型相关,透视投影是[1/Near,1/Far],正交投影是1。

视口空间(viewport space),就是把屏幕坐标归一化,让屏幕左下角是(0,0),右上角是(1,1)。

另一种是通过Unity提供的ComputeScreenPos函数。首先在顶点着色器中将ComputeScreenPos的结果保存在输出结构体中,然后在片元着色器中进行一个齐次除法运算后得到视口空间下的坐标:

struct vertOut{
float4 pos:SV_POSITION;
float scrPos:TEXCOORD0;
}

verOut vert(appdata_base v){
verOut o;
o.pos=mul(UNITY_MATRIX_MVP,v.vertex);

// 第一步:把ComputeScreenPos的结果保存在scrPos中
o.scrPos=ComputeScreenPos(o.pos);
return o;
}

fixed4 frag(vertOut i):SV_Target{
// 第二步:用scrPos.xy除以scrPos.w得到视口空间的坐标
float2 wcoord=(i.scrPos.xy/i.scrPos.w);
return fixed4(wcoord,0.0,1.0);
}

```

这种方法实际上是手动实现了屏幕映射的过程。透视时,z的范围是[-Near,Far],w的范围是[Near,far];正交时z的范围是[-1,1],w恒为1。

4.10 扩展阅读

  1. 图形学数学学习资料:Fletcher Dunn / Ian Parberry 《3D Math Primer for Graphics and Game Development》
  2. 图形学数学学习资料:Eric Lengyel 《Mathematics for 3D Game Programming and Computer Graphics, 3rd Edition》
  3. 如何从左手坐标系转换到右手坐标系同时又保持视觉效果一样:David Eberly 《Conversion of Left-Handed Coordinates to Right-Handed Coordinates》
  4. 如何得到线性的深度值:http://www.humus.name/temp/Linearize%20depth.txt

4.11 练习题答案

略,我全对啦。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 cdd@ahucd.cn

×

喜欢就点赞,疼爱就打赏

B站 cdd的庇护之地 github itch