《Unity Shader入门精要》笔记(三)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《Unity Shader入门精要》笔记(三)相关的知识,希望对你有一定的参考价值。
参考技术A二维笛卡尔坐标系:
x轴、y轴朝向并非固定,如:OpenGL和DirectX使用了不同的二维笛卡尔坐标系。
三维笛卡尔坐标系:
标准基矢量:互相垂直,且长度为1的基矢量。
正交基:互相垂直,但长度不为1的基矢量。
以手的大拇指作为 +x 轴,食指作为 +y 轴,中指作为 +z 轴,将3根手指互相垂直,可以用左手示意的坐标系,为左手坐标系:
可以用右手示意的坐标系,为右手坐标系:
左手坐标系和右手坐标系无法通过旋转实现坐标轴指向重合。
左手坐标系和右手坐标系分别对应 左手法则 和 右手法则 ,用来在坐标系中定义旋转的正方向,下图4个手指指向的方向即为正方向:
Unity的模型空间和世界空间使用的是左手坐标系,注意观看下图红、绿、蓝轴在右上角分别对应x轴、y轴、z轴:
Unity的观察空间使用的是右手坐标系。观察空间,就是以摄像机作为原点的坐标系,在这个坐标系中,摄像机的前向是z轴的负方向,与模型/世界空间的定义相反。即:z轴坐标的减少意味着场景深度的增加。
点是n维空间(游戏中主要是用二维、三维空间)中的一个位置,没有大小、宽度的概念。
二维空间点的表示: p = (x, y)
三维空间点的表示: p = (x, y, z)
矢量是n为空间中包含模和方向的有向线段,没有位置的概念。
矢量的模:矢量的长度,非负数。
矢量的方向:矢量在空间中的指向。
矢量的表示与点类似, v = (x, y),v = (x, y, z),v = (x, y, z, w) 。
为区分点和矢量,在变量书写上,标量用小写字母表示,如:a, b, x, y, z等;矢量用小写的粗体字母表示,如: a , b , u , v 等。
矢量通常有一个箭头表示:
标量是只有模,没有方向的量,比如:距离、速度等。
矢量无法与标量进行加减运算,但是可以进行乘法或除法运算。
矢量与标量的乘法:
kv = (kv x , kv y , kv z )
矢量可以被非0的标量除,但是矢量无法作为除数:
从几何意义上看,一个矢量 v 和一个标量k相乘,意味着对矢量 v 进行一个大小为|k|的缩放。若k<0,则矢量方向取反,如下图:
两个矢量加减,即:两个矢量的对应分量进行加减,公式如下:
a + b = (a x +b x , a y +b y , a z +b z )
a - b = (a x -b x , a y -b y , a z -b z )
从几何意义上看,矢量加法,即:把矢量 a 的头连接到矢量 b 的尾,然后画一条从 a 的尾到 b 的头的矢量,来得到 a 和 b 相加后的矢量,如下图所示:
也可以理解为:一个点从 a 的尾进行位置偏移 a ,在进行位置偏移 b ,就等同于进行了 a + b 的位置偏移,这被称为矢量加法的 三角形定则 。
矢量的减法类似:
在图形学中,矢量通常用于描述位置偏移(简称位移)。我们可以利用矢量的加法和减法来计算一点相对于另一点的位移。
矢量的模是一个标量,可以理解为矢量在空间中的长度。表示符号通常是在矢量的两边加上竖线,比如:| v |。
三维矢量的模的计算公式:
其他维度的矢量的模计算类似,都是对每个分量平方相加后开根号。几何意义,可用下图解释:
单位矢量指模为1的矢量,也被称为被归一化的矢量(normalized vector)。通常用在只关心方向,不关心模的矢量,比如:模型的发现方向、光源方向等。
把非零矢量转换成单位矢量的过程叫 归一化 。
单位矢量的表示为:
单位矢量的公式:
零矢量:每个分量的值都为0的矢量,如: v = (0, 0, 0)。零矢量不能被归一化,因为除法运算时,分母不能为0。
从几何意义上看,对于二维空间,单位矢量就是从圆心出发、到圆边界的矢量:
对于三维空间,单位矢量就是从圆心出发、到球面的矢量。
在Unity Shader中,会经常遇到法线方向、光源方向,这些矢量不一定是归一化后的矢量,计算的时候需要将这些矢量归一化成单位矢量。
矢量的乘法有两种类型:点积(dot product)、叉积(cross product)。
矢量的点积,也叫内积。点积的运算表示: a · b ,中间的点不能省略。
点积公式一 :
a · b = (ax, ay, az) · (bx, by, by) = axby + ayby + azbz
点积满足交换律:
a · b = b · a
点积的几何意义: 投影 。
投影的值可能是负数,投影结果的正负号与 a 、 b 两个矢量的方向有关:方向相反,结果小于0;方向相同,结果大于0;方向垂直,结果等于0。
性质一:
点积可结合标量乘法
(k a )· b = a ·(k b )=k( a · b )
k的几何意义是:对矢量进行缩放。
性质二:
点积可结合矢量加减法
a ·( b + c ) = a · b + a · c
将 c 换成- c 就是减法的版本。
性质三:
一个矢量与自身点积的结果是该矢量模的平方
v · v = v x v x + v y v y + v z v z = | v | 2
可以用矢量点积的形式来求矢量的模,Shader中常用模的平方来直接做比较或运算,目的是减少开放带来的性能消耗。
点积公式二 :
a · b = | a || b |cosθ
公式二的证明:
假设对两个单位矢量进行点积
如下图所示:
由上图可知,cosθ对应的直角边是: a · b 的点积( b 矢量在 a 矢量的投影),且 cosθ = 直角边 / 斜边 ,则 a · b 的点积 = cosθ * 斜边 ,因为单位矢量 b 的模是1(斜边长度为1),所以: a · b 的点积 = cosθ,也就是两个单位矢量的点积为夹角的cos值。
再由之前性质一,可得推导公式二:
由公式二可知,点积可用于求两个矢量的夹角:
叉积,也叫外积。与点积不同,叉积的结果仍然是矢量,而非标量。
叉积的表示: a x b ,叉号不能省略。叉积的计算公式如下:
a x b = (a x , a y , a z ) x (b x , b y , b z ) = (a y b z -a z b y , a z b x -a x b z , a x b y -a y b x )
具体的记法,可以这样:
叉积不满足交换律,即: a x b ≠ b x a ;但是叉积满足反交换律,即: a x b = - ( b x a )。
叉积不满足结合律,即:( a x b ) x c ≠ a x ( b x c )。
叉积的几何意义:
对两个矢量进行叉积的结果,会得到同时垂直于这两个矢量的新矢量。
叉积的模
公式:
| a x b | = | a || b |sinθ
这容易联想到平行四边形求面积:
面积A = | b | h = | b | (| a | sinθ) = | a || b |sinθ
叉积的方向
从几何意义可知,两个矢量的叉积,会得到垂直于两个矢量的新矢量,但是与其垂直的有两个向量。这时前面学到的 左/右手坐标系 就派上用场了,它用来确定叉积得到新矢量的方向朝哪边。
将大拇指与a同向,食指与b同向,中指指向的方向就是叉积结果的方向,所以使用左、右手就会得到不同的朝向,如下图:
同理,左右手法则也通用可以用来判断,如下图:
矩阵(Matrix),就是有m x n个标量组成的长方形数组,通常用方括号在左右两侧围住这些数字,大概像这样:
有些资料也会用圆括号或花括号,其实都一样的。
矩阵有行、列之分,上图的数组就是三行四列。以3x3矩阵为例,它可以写成:
mij表示这个元素在矩阵M的第i行、第j列。
矢量,我们通常写成: a = (x, y, z),可以看出矢量与矩阵一样,也是个数组。将矢量按照矩阵的写法,可以看成是 n x 1 的列矩阵或 1 x n 的行矩阵,n对应矢量的维度。
以矢量 v = (3, 8, 6)举例,写成行矩阵:
[3, 8, 6]
写成列矩阵:
为什么要和矢量联系起来?因为Shader中经常会将法线(矢量)进行坐标变换,而坐标变换是矩阵的几何意义,所以需要运用矩阵的运算来将法线从模型空间转变成世界空间。(后续会学到)
与矢量类似,矩阵和标量相乘后,结果仍然是一个矩阵。公式如下:
矩阵和矩阵相乘后,结果也是矩阵。新的矩阵的维度与两个原矩阵的维度有关。一个 rxn 的矩阵A和一个 nxc 的矩阵B相乘后,得到的结果AB是一个 rxc 大小的矩阵。需要注意, 第一个矩阵的列数必须和第二个矩阵的行数相等,才能相乘 。
比如:矩阵A的维度是 4x3 ,矩阵B的维度是 3x6 ,则AB的维度是 4x6 。
矩阵乘法的表达式:
假设有 rxn 的矩阵A和 nxc 的矩阵B,相乘后得到一个 rxc 的矩阵C = AB,那么C中的每个元素Cij等于A的第i行所对应的矢量和B的第j列所对应的矢量进行点乘的结果,即:
简单解释为:
对于每个元素c ij ,找到A中的第 i 行和B中的第 j 列,把他们对应的元素相乘后再加起来,这个和就是c ij 。
性质一:
矩阵乘法不满足交换律: AB ≠ BA
性质二:
矩阵乘法满足结合律: (AB)C = A(BC) 、 ABCDE = ((A(BC))D)E = (AB)(CD)E
方块矩阵,简称方阵。指行数和列数相等的矩阵,比如: 3x3 、 4x4 的矩阵。
方块矩阵独有的: 对角元素 ——行号和列号相等的元素。只有对角元素非0的矩阵叫 对角矩阵 。
对角元素都为1的对角矩阵,叫做单位矩阵,用I n 表示,比如:
单位矩阵特性:任何矩阵和它相乘的结果还是原来的矩阵。相当于标量中1的地位。
MI = IM = M
转置矩阵实际是对原矩阵的一种运算,即转置运算。一个 rxc 的矩阵M,其转置表示成M T ,是一个 cxr 的矩阵,本质是原来的矩阵行、列对换。
性质一:
矩阵转置的转置等于原矩阵。
(M T ) T = M
性质二:
矩阵串联的转置,等于反向串联各个矩阵的转置。
(AB) T = B T A T
只有方阵才有逆矩阵,逆矩阵表示为M -1 。一个矩阵与它的逆矩阵相乘,结果是一个单位矩阵:
MM -1 = M -1 M = I
有点标量里面倒数的味道。
不是所有方阵都有对应逆矩阵,比如:所有元素都为0的矩阵。
如果一个矩阵有对应的逆矩阵,则它是 可逆的 或 非奇异性的 ;
相反,则它是 不可逆的 或 奇异性的 。
判断矩阵是否可逆:
矩阵的 行列式 不为0,则它是可逆的。
参考视频链接: https://www.bilibili.com/video/BV1aW411Q7x1?p=2
性质一:
逆矩阵的逆矩阵是原矩阵本身。
(M -1 ) -1 = M
性质二:
单位矩阵的逆矩阵是它本身。
I -1 = I
性质三:
转置矩阵的逆矩阵是逆矩阵的转置。
(M T ) -1 = (M -1 ) T
性质四:
矩阵串联相乘后的逆矩阵等于反串联各个矩阵的逆矩阵。
(AB) -1 = B -1 A -1
(ABCD) -1 = D -1 C -1 B -1 A -1
矩阵的几何意义是 变换 ,逆矩阵表示还原这个变换,或这个变换的反向变换。
使用变化矩阵M对矢量 v 进行一次变换,然后再使用逆矩阵M -1 进行一次变换,会得到原来的矢量v。
M -1 (M v ) = (M -1 M) v = I v = v
正交矩阵是特殊的方阵。一个方阵M和它的转置矩阵的乘积是单位矩阵,则这个矩阵是正交的。
MM T = M T M = I
有逆矩阵的性质MM -1 = M -1 M = I可以得出正交矩阵的逆矩阵是它的转置矩阵:
M T = M -1
正交矩阵可以用转置矩阵的运算代替逆矩阵的运算,因为逆矩阵计算更复杂。
怎样判定一个矩阵是正交矩阵?来看一下它有哪些定义。
因为:
所以:
于是可以得到以下结论:
一个矢量(比如:平行光的方向、表面发现方向),既可以写成行矩阵的形式,也可以写成列矩阵的形式,但是当它和矩阵相乘时,使用行矩阵还是列矩阵对其乘法的书写次序和结果值是有影响的。
假设有一个矢量 v = (x, y, z),写成行矩阵是: v = [x y z],写成列矩阵是: v = [x y z] T (这里使用转置符号表示列矩阵的写法,纯粹为了排版)。另外有一个矩阵M:
当M和行矩阵相乘时,写法为:
v M = [xm 11 +ym 21 +zm 31 xm 12 +ym 22 +zm 32 xm 13 +ym 23 +zm 33 ]
当M和列矩阵相乘时,写法为:
可以看到两者相乘的书写次序和结果里面元素也是不一样的。
Unity中通常把矢量当做列矩阵,所以相乘时,矢量是放在矩阵的右侧的,且阅读顺序也是从右到左。例如:
CBA v = (C(B(A v )))
表示先对 v 进行A矩阵变换,再进行B矩阵变换,最后进行C矩阵变换。
Unity Shader入门精要读书笔记序章
本系列的博文是笔者读《Unity Shader入门精要》的读书笔记,这本书的章节框架是:
第一章:着手准备。
第二章:GPU流水线。
第三章:Shader基本语法。
第四章:Shader数学基础。
第五章:利用简单的顶点/片元着色器来实现辅助技巧。
第六章:基本光照模型。
第七章:法线纹理、遮罩纹理等基础纹理。
第八章:透明度测试和透明度混合。
第九章:复杂光照实现。
第十章:高级纹理(立方体纹理等)。
第十一章:纹理动画、顶点动画。
第十二章:屏幕特效。
第十三章:深度纹理。
第十四章:非真实感的渲染算法。
第十五章:噪声在游戏渲染中的应用。
第十六章:优化技巧。
第十七章:表面着色器实现渲染。
第十八章:物理渲染技术。
第十九章:Unity5中的可能出现的问题。
第二十章:更深入的大门。
本书的主旨:原理+使用技巧。
第一篇的笔记:
第二篇到第四篇没有动手编程,都是理论知识。
第二篇《渲染流水线》的笔记:
1.渲染流水线是为了在屏幕上渲染二维图像;
2.渲染流水线的输入是虚拟摄像机、光源、shader和纹理;
3.渲染流程的三个阶段:应用阶段、几何阶段、光栅化阶段;
4.应用阶段是CPU负责实现的,开发者主导此阶段;
5.开发者在应用阶段下的任务有:a准备好场景如摄像机的位置和朝向、光源等,b粗粒度剔除工作,是为了将不可见的物体剔除,不让它们传到几何阶段,c设置好每一个模型的渲染状态如材质、纹理、shader等;
6.渲染图元包括点、线、三角面等需要被绘制的几何信息;
7.几何阶段在GPU上进行,它决定图元以何种方式绘制,在哪里绘制等,和每一个渲染图元打交道,输入的渲染图元经过多步处理后,输出屏幕空间中的而为顶点坐标、深度值、着色细节等信息;
8.光栅化阶段是为了利用上一个阶段产生的信息产生屏幕上的像素,渲染出最终图像的,在GPU上进行本阶段。对顶点的数据进行插值,得到最终的像素。
9.三个阶段都会有更加细的流水线划分;
10.CPU是渲染流水线的起点;
11.应用阶段a将数据加载到显存中,b设置渲染状态(也就是shader、纹理等),c调用drawcall;
12.渲染所需数据从硬盘HDD加载到内存RAM中,再被加载到显存VRAM中;
13.显卡对显存的访问更加快,比起对内存,更不用说比起HDD;
14.渲染状态规定了场景中的网格是如何被渲染的,例如使用了何种顶点着色器(VertexShader)片元着色器(FragmentShader)、光源属性和材质等;
15.CPU将渲染状态准备好后,通过调用DrawCall让GPU进行渲染,如果一直不改动此渲染状态,那么GPU将会一直以这个渲染状态来进行绘制;
16.DrawCall的调用不需要指定任何材质信息(原因上一点也说了),一次DrawCall命令针对一个图元Primitives的列表;
17.顶点数据由CPU加载到显存上去,然后DrawCall交由顶点着色器处理;
18.顶点着色器完全可编程,通常为顶点做空间变换,顶点着色等功能;
19.曲面细分着色器用于细分图元;
20.几何着色器用于逐图元着色,或者用于产生更多的图元;
21.裁剪将不再摄像机内的顶点裁掉,剔除某些三角形面;
22.屏幕映射将图元的坐标切换到屏幕坐标系下;
23.几何阶段的流水线为:顶点数据》顶点着色器》曲面细分着色器(可选)》几何着色器(可选)》裁剪》屏幕映射;
24.片元着色器可以编程,负责逐片元操作,逐片元操作为了执行颜色修改、深度缓冲、混合等,逐片元操作是可配置而不可编程的;
25.光栅化阶段的流水线是:三角形设置》三角形遍历》片元着色器》逐片元操作;
26.顶点着色器处理单元是顶点,每进来一个顶点就会调用一次顶点着色;
27.顶点着色器本身不知道顶点之间的拓扑关系,也不销毁或创建顶点;
28.顶点着色器能够并行处理顶点,因为顶点之间相互独立;
29.顶点着色器主要完成坐标变换和逐顶点光照计算;
30.顶点着色器必须要完成的一个工作:把顶点坐标从模型空间转换到齐次裁剪空间,这个空间是视野空间,这个空间也是为了做裁剪运算的,比如把不在(-1,-1,-1)到(1,1,1)里的点裁剪掉;
31.术语:归一化设备坐标NDC;
32.OpenGL和Unity的NDC中的z分量是-1到1的,而DX是0到1的;
33.在NDC单位立方体内进行裁剪时,在视野内的部分保留,因而可能产生新顶点,效果如下:
34.裁剪是硬件决定的,无法编程;
35.屏幕映射将图元的x和y转换到屏幕坐标系下,屏幕映射不处理z,z和映射后的xy合称窗口坐标系;
36.DX的屏幕坐标系原点在左上角(和UE一样),OpenGL的原点在左下角;
37.经过了几何阶段的屏幕映射的处理,将会传来关于顶点的额外信息如深度值,法线,视角方向等;
38.光栅化的两个重要目标:a图元覆盖了哪些像素,b计算这些像素的颜色;
39.三角形设置是为了计算三角形三条边处在哪些像素上的;
40.三角形遍历是为了计算某一个像素是否被一个三角形覆盖,如果是的话,这些像素称为“片元”fragment,这个遍历过程也称为扫描变换;注意片元和像素并不完全一致;
41.在所有的量上,比如深度、颜色等,为片元进行插值计算;
42.片元着色器接受三角形遍历得到的对顶点插值的结果,输出是一个或者多个颜色值;
43.片元着色器还能够完成重要的渲染技术如纹理采样;
44.真正对像素产生影响的阶段是逐片元操作;
45.逐片元操作PerFragmentOperations是OpenGL的说法,在DX中称为OutputMerger输出合并阶段;
46.逐片元操作a决定每个片元的可见性,如深度测试等,b将片元和已经储存在颜色缓冲区的颜色进行合并,或者说混合;
47.逐片元操作是可配置的;
48.逐片元操作的流程:片元》模板测试》深度测试》混合》颜色缓冲区;
49.模板测试是自定义的测试,当一个片元的某个指标值和缓冲区中的此指标值做比较,通过测试的话,就可以进入下一个测试,没有通过的话就被舍弃;不管有没有通过,都可以设置缓冲区中的指标值;
50.深度测试常常用来做前后遮挡的测试,如当前片元的深度比缓冲区中的深度小(靠近屏幕),那么它就通过测试,并有权利修改缓冲区中的深度指标;
51.通过了所有测试的进入混合操作,如果没有开启混合(也就是完全遮挡的),那么直接使用该片元的颜色,更新到缓冲区中的颜色值;如果开启了的话,将缓冲区和本片元的颜色做一个数学运算得到新值写入给缓冲区;
52.逐片元操作中的测试往往会被移到更加早的步骤上进行测试,为了节省计算;深度测试提早做,EarlyZ技术;
53.避免我们看到光栅化中的图像,将会进行双重缓冲,即当下一帧画面的内容准备好后,才登上屏幕;
54.OpenGL和DX是计算机图像的API,由APP调用,GL和DX则会去调用显卡驱动,显卡驱动真正去调用硬件设备;
55.SL着色语言是用来编写着色器代码的,从而避免写汇编语句,HLSL是DX的着色语言,GLSL是OpenGL的着色语言,CG(C for Graphic)是NVIDIA的着色语言;
56.UnityShader语言和CG、HLSL和GLSL不完全一样是着色器语言;
57.CPU和GPU是如何一起工作的?有一个命令缓冲队列,CPU向其中塞入命令如DrawCall或者改变渲染状态,而GPU则是从中获取命令并执行;
58.渲染速度往往快于提交命令的速度,也就是说CPU提交DrawCall数目太多将会导致效率低(复制10000个1kb文件和复制一个10M文件的比喻,即提交大量很小的DrawCall会导致效率低下);
59.如何减少DrawCall?尽量让一次DrawCall做很多活,也就是批处理思想;
60.固定函数流水线也称为固定管线,通常指在较旧的GPU上实现的渲染流水线;(只可以配置而不可以完全控制,就好像只能使用开关控制而不能修改布局的电路);注:管线是管道线的简称,也就是封闭的流水线,无法完全操控内部;
61.Shader是GPU流水线上一些课高度编程的阶段,最终代码在GPU上运行,有特定类型的着色器(顶点着色器、片元着色器),利用着色器控制流水线中的渲染细节如顶点着色器进行变换等;
以上是关于《Unity Shader入门精要》笔记(三)的主要内容,如果未能解决你的问题,请参考以下文章
Unity Shader入门精要学习笔记 - 第17章 Unity的表面着色器探秘
Unity Shader入门精要学习笔记 - 第5章 开始 Unity Shader 学习之旅
Unity Shader入门精要学习笔记 - 第3章 Unity Shader 基础