基础和初级 1-8
一轮:2021年6月16日17:19:23
二轮:2021年10月28日20:24:37
作者:聪头
本书Unity版本:v5.2.1
Github:https://github.com/candycat1992/Unity_Shaders_Book
本文档为独自整理的笔记,故章节的主题和书本不一致!
—————– 基础篇 —————–
Ch1.欢迎来到Shader的世界
介绍各章节内容,本文档为独自整理的笔记,故章节的主题和书本不一致!
Ch2.渲染流水线
![image-20211027112031079](基础和初级 1-8.assets/image-20211027112031079.png)
2.1 应用阶段
输入:无
执行者:CPU
输出结果:渲染图元(点、线三角面等)
过程:
把数据加载到显存中:硬盘 –> 内存 –> 显存
- 内存数据加载到显存后,可以移除。但对于一些数据,如用于碰撞检测的网格数据,则不希望移除
- 场景数据:摄像机、视锥体、场景中包含模型、光源信息等。粗粒度剔除,把那些不可见的物体剔除出去。
设置渲染状态:定义场景中网格是怎样被渲染的
简单理解为Unity给Material绑定Shader,给模型绑定Material并设置参数
例如,指定顶点/片元着色器,光源属性,材质等
调用DrawCall
- 由CPU发给GPU的命令,指向一个需要被渲染的图元(primitives)列表。给定一个DrawCall,GPU会根据渲染状态(如材质、纹理、着色器等)和所有输入的顶点数据进行计算,最终输出成屏幕上显示的像素。该计算过程,就是GPU流水线
2.2 几何阶段
输入:CPU
执行者:GPU
输出结果:屏幕空间的顶点信息
过程:
- 顶点着色器:完全可编程控制,必须实现
- 用于实现顶点的空间变换、顶点着色等
- 坐标变换:对顶点的坐标(即位置)进行某种变换。必须完成:把顶点从模型空间转换到齐次裁剪空间。
- 曲面细分着色器:完全可编程控制,可选
- 细分图元
- 几何着色器:完全可编程控制,可选
- 用于执行逐图元的着色操作或产生更多图元
- 裁剪:可配置,不可编程
- 裁剪不在相机范围内的顶点,并剔除三角图元的面片
- ![image-20210616181056609](基础和初级 1-8.assets/image-20210616181056609.png)
- 屏幕映射:GPU固定实现
- 负责把每个图元的坐标转换到屏幕坐标系中
2.3 光栅化阶段
输入:屏幕坐标系下的顶点位置以及和它们相关的额外信息,如深度值(z坐标)、法线方向、视角方向等
执行者:GPU
输出结果:计算每个图元覆盖了哪些像素,以及为这些像素计算它们的颜色
过程:
三角形设置:GPU固定实现
- 计算光栅化一个三角形网格所需的信息
- 上一阶段输出的都是三角网格的顶点,即每条边的两个端点。我们需要计算每条边上的像素坐标,得到整个三角网格对像素的覆盖情况。
- 计算三角网格表示数据的过程就叫做三角形设置
三角形遍历:GPU固定实现
- 片元:像素被一个三角网格覆盖,就会生成一个片元(fragment)。一个片元并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色(利用上一阶段的三角网格的3个顶点信息对整个覆盖区域的像素进行插值)。这些状态包括(但不限于)它的屏幕坐标、深度信息,以及其他从几何阶段输出的顶点颜色,例如法线、纹理坐标等
- 找到哪些像素被三角网格覆盖的过程就是三角形遍历,这个阶段也叫做扫描变换(Scan Conversion)
片元着色器:完全可编程控制,可选
- 输入的是上一个阶段对顶点信息插值得到的结果,输出的是一个或多个颜色值。这一阶段可以完成很多重要的渲染技术,如纹理采样
解释: 上一阶段不会影响屏幕上每个像素的颜色值,而是会产生一系列的数据,用来表述一个三角网格是怎样覆盖每个像素的。一句话说,每个片元只负责存储这样一系列数据,真正会对像素产生影响的是下一阶段,逐片元操作
逐片元操作:可配置,不可编程
很多重要操作,如修改颜色、深度缓冲、混合等
![image-20210616181608813](基础和初级 1-8.assets/image-20210616181608813.png)
逐片元操作阶段所做的操作。只有通过了所有的测试后,新生成的片元才能和颜色缓冲区中已经存在的像素颜色进行混合,最后再写入颜色缓冲区中
区别:
1.模板测试和写入一起开和关;深度测试和写入可分别开启
2.模板测试未通过也可更新值;深度测试没通过不可更新值
源颜色:片元着色器的颜色值
目标颜色:已经存在于颜色缓冲区中颜色值
补充:
- 屏幕显示的是颜色缓冲区中的颜色值。GPU会使用双重缓冲的策略,对场景的渲染是在幕后发生的,即在后置缓冲区中。一旦场景已经被渲染到了后置缓冲区中,GPU就会交换前置缓冲区和后置缓冲区中的内容,前置缓冲区就是显示在屏幕上的图像。由此,保证我们看到的图像总是连续的
2.4 课后答疑
DrawCall
性能瓶颈:CPU(而非GPU),每次调用DrawCall,CPU需要向GPU发送很多内容,包括数据、状态和命令等。在这一阶段,CPU需要完成很多工作,例如检查渲染状态等。而GPU的渲染能力很强,因此渲染速度往往快于CPU提交命令的速度。如果DrawCall数量太多,CPU就会把大量时间花费在提交DrawCall上,造成CPU过载
减少DrawCall的办法:
- 避免使用大量很小的网格,当不可避免地使用很小的网格时,考虑是否可以合并它们
- 避免使用过多的材质。尽量在不同的网格之间共有同一个材质
什么是Shader
- GPU流水线上一些可高度编程的阶段,而由着色器编译出来的最终代码是会在GPU上运行的(对于固定管线的渲染来说,着色器有时等同于一些特定的渲染设置)
Ch3.UnityShader基础
3.1 UnityShader概述
- Unity Material和Shader
把材质(Material)想象成组件,把UnityShader想象成源文件。
组件的目的:个性化
源文件的目的:通用性
- ShaderLab:为Unity开发者提供的高层级的渲染抽象层
3.2 Shader结构:名称
- 使用”/“控制位置
![img](基础和初级 1-8.assets/_CopyPix_1610467306_2.png)
3.3 Shader结构:Properties
- 材质和UnityShader的桥梁,通过材质面板传递Shader属性
![img](基础和初级 1-8.assets/_CopyPix_1610467284_1.png)
解释:
- Name:Shader访问的名字
- display name:材质面板上的名字
- Propertype:类型
- DefaultValue:默认值
Properties属性!!!
三个叹号:需要掌握的知识点,方便日常开发中通过目录快速定位
![img](基础和初级 1-8.assets/_CopyPix_1610467420_3.png)
说明:
- 2D、Cube、3D的内置纹理默认值包括:white、black、gray、bump
例子
为了在Shader中可以访问到这些属性,我们需要在Cg代码片中定义和这些属性类型相匹配的变量。
需要说明的是,即使我们不在Properties语义块中声明这些属性,也可以直接在Cg代码片中定义变量。此时,我们可以通过过脚本向Shader中传递这些属性
3.4 Shader结构:SubShader
- 每个UnityShader可以包含多个SubShader语义块(至少一个),加载时Unity选择第一个可在目标平台运行的SubShader。如果都不支持,使用Fallback
解释:不同显卡具有不同的能力,写SubShader就是为了在旧显卡使用计算复杂度较低的着色器,在高级显卡使用计算复杂度较高的着色器,以便提供更出色的画面
重要组成
- SubShader:定义了一系列Pass以及可选的状态([RenderSetup])和标签([Tags])设置
- Pass:定义了一次完整的渲染流程
语法:
状态设置!!!
- SubShader和Pass语法相同
![img](基础和初级 1-8.assets/_CopyPix_1610468312_6.png)
SubShader专属标签!!!
概念:SubShader标签是一个键值对,它的键和值都是字符串类型,用来告诉Unity渲染引擎:我希望怎样以及何时渲染这个对象
语法:![img](基础和初级 1-8.assets/_CopyPix_1610468434_7.png)
类型:
Pass语义块
语法:
![img](基础和初级 1-8.assets/_CopyPix_1610470346_15.png)
Pass名称
- Name “MyPassName”
- 通过Pass名称,我们可以使用ShaderLab的UsePass命令来直接使用其他Unity Shader中的Pass
- 例如:UsePass “MyShader/MYPASSNAME”
Unity内部会把所有Pass名称转换成大写字母
Pass专属标签!!!
概念:Pass标签也是告诉渲染引擎我们希望怎样来渲染物体
语法:![img](基础和初级 1-8.assets/_CopyPix_1610468434_7.png)
类型:
![img](基础和初级 1-8.assets/_CopyPix_1610469466_13.png)
特殊的Pass
- UsePass:复用其他UnityShader中的Pass
- GrabPass:抓取屏幕并将结果存储在一张纹理中,以用于后续的Pass处理
3.5 Shader结构:Fallback
- 所有SubShader都不能运行,使用该Shader
- Fallback还会影响阴影的投射,在渲染阴影纹理中,Unity会在每个UnityShader中寻找一个阴影投射的Pass。Fallback使用的内置Shader中包含了这样一个通用的Pass
语法:![img](基础和初级 1-8.assets/_CopyPix_1610469581_14.png)
例子:Fallback "VertexLit"
其他语义:有时间自行完善
- 扩展编辑界面:CustomEditor
- 命令分组:Category
3.6 着色器分类
表面着色器
Surface Shader
概念:Unity自己创造的一种着色器代码类型,是对顶点/片元着色器更高层的抽象,本质仍是顶点/片元着色器,定义在SubShader块(而非Pass块)中的CGPROGRAM和ENDCG之间。可以使用Cg/HLSL几乎所有语法。
![image-20211027141818806](基础和初级 1-8.assets/image-20211027141818806.png)
顶点/片元着色器
Vertex/Fragment Shader
概念:本书使用。使用Cg/HLSL语言来编写,更复杂但更灵活。定义在Pass内
3.7 课后答疑
UnityShader != 真正的Shader
Ch4.学习Unity所需的数学基础
推荐视频:
4.1 笛卡尔坐标系
二维笛卡尔坐标系:
![image-20210616200043474](基础和初级 1-8.assets/image-20210616200043474.png)
三维笛卡尔坐标系:
- 基矢量:3个坐标轴
- 标准正交基:3个坐标轴之间互相垂直,且长度为1
- 正交基:坐标轴之间互相垂直但长度不为1
判断坐标系:哪只手满足就是哪个坐标系
- 大拇指:+x
- 食指:+y
- 中指:+z
判断旋转正方向:伸出坐标系对应的手,大拇指对准旋转轴正方向,握拳方向就是旋转正方向(左手顺时针,右手逆时针)
4.2 点和矢量
点:n维空间中的一个位置,没有大小、宽度的概念
矢量(向量):n维空间中一种包含了模和方向的有向线段
标量:只有模,没有方向
规定:
![image-20211027144859926](基础和初级 1-8.assets/image-20211027144859926.png)
矢量运算:
矢量和标量乘法/除法:
矢量的加法和减法:
矢量的模:![image-20210616200632856](基础和初级 1-8.assets/image-20210616200632856.png)
单位矢量:![image-20210616200645116](基础和初级 1-8.assets/image-20210616200645116.png)
点积
- 公式1:![img](基础和初级 1-8.assets/_CopyPix_1610596172_1.png)
- 公式2:![img](基础和初级 1-8.assets/_CopyPix_1610596190_2.png)
叉积
- 公式:![img](基础和初级 1-8.assets/_CopyPix_1610596306_4.png)
- 模:![img](基础和初级 1-8.assets/_CopyPix_1610596321_5.png)
- 方向:确定坐标系,根据左(右)手法则,从a到b握拳,拇指方向为叉积结果方向
4.3 矩阵
定义
- mxn个标量组成的长方形数组
行向量和列向量:
矩阵运算
矩阵和标量相乘:
矩阵和矩阵相乘:
矩阵乘法性质
- 矩阵乘法不满足交换律:![image-20210616201311692](基础和初级 1-8.assets/image-20210616201311692.png)
- 矩阵乘法满足结合律:![image-20210616201304986](基础和初级 1-8.assets/image-20210616201304986.png)
特殊矩阵
1.方块矩阵:简称方阵,行和列数目相等
对角元素:行和列号相等的元素。如m
11对角矩阵:除对角元素外均为0的矩阵。非常适合次方计算。
![image-20210616201636339](基础和初级 1-8.assets/image-20210616201636339.png)
2.单位矩阵:任何矩阵和它相乘的结果还是原来的矩阵
![image-20210616201644840](基础和初级 1-8.assets/image-20210616201644840.png)
3.转置矩阵:实际是对原矩阵的一种运算。行列对调
性质一:![image-20211027152547410](基础和初级 1-8.assets/image-20211027152547410.png)
性质二:![image-20211027152553314](基础和初级 1-8.assets/image-20211027152553314.png)
4.逆矩阵:满足
![image-20210616201827625](基础和初级 1-8.assets/image-20210616201827625.png)
逆矩阵判断:
- 必须是方阵
- 行列式不为0
逆矩阵性质:
逆矩阵的逆矩阵是原矩阵本身(设M可逆)
![image-20210616202250777](基础和初级 1-8.assets/image-20210616202250777.png)
单位矩阵的逆矩阵是它本身
![image-20210616202301981](基础和初级 1-8.assets/image-20210616202301981.png)
转置矩阵的逆矩阵是逆矩阵的转置
![image-20210616202310929](基础和初级 1-8.assets/image-20210616202310929.png)
矩阵串接相乘后的逆矩阵等于反向串接各个矩阵的逆矩阵。可推广到多维
![image-20210616202317611](基础和初级 1-8.assets/image-20210616202317611.png)
![image-20210616202331557](基础和初级 1-8.assets/image-20210616202331557.png)
5.正交矩阵
- 如果一个方阵M和它的转置矩阵的乘积是单位矩阵的话,我们就说这个矩阵是正交的
![image-20210616202452202](基础和初级 1-8.assets/image-20210616202452202.png)
等价于
![image-20210616202515644](基础和初级 1-8.assets/image-20210616202515644.png)
推论
- c1 -
:表示一个三维向量c1的行展开式
4.4 变换
变换:指的是我们把一些数据,如点、方向矢量甚至颜色等,经过某种方式进行转换的过程
线性变换:指可以保留矢量加和标量乘的变换
![image-20210616203541827](基础和初级 1-8.assets/image-20210616203541827.png)
仿射变换:合并线性变换和平移变换。仿射变换可以使用一个4x4的矩阵表示,为此,我们需要把矢量扩展到四维空间下,就是齐次坐标空间
齐次坐标:四维向量
- 点:w=1
- 向量:w=0
1.平移矩阵
![img](基础和初级 1-8.assets/_CopyPix_1610607136_6.png)
2.缩放矩阵
![img](基础和初级 1-8.assets/_CopyPix_1610607147_7.png)
3.旋转矩阵(正交矩阵)
无论坐标轴如何变换,两两之间总是互相垂直,且长度为1,满足上述推论。故旋转矩阵是正交矩阵
- 绕x轴旋转θ
![img](基础和初级 1-8.assets/_CopyPix_1610607178_8.png)
- 绕y轴旋转θ
![img](基础和初级 1-8.assets/_CopyPix_1610607202_9.png)
- 绕z轴旋转θ
![img](基础和初级 1-8.assets/_CopyPix_1610607212_10.png)
约定:先缩放,再旋转,最后平移
4.5 坐标空间变换
- 变谁就按列展开乘谁,详见P69
- 第一个”谁”:目标坐标空间,即变换到的空间
- 第二个”谁”:自己的坐标轴在目标空间下的表示
如果矩阵正交,那么只需转置即可得到逆变换
已知子坐标空间C的3个坐标轴在父坐标空间P下的表示Xc,Yc,Zc,以及原点位置Oc。给定一个子坐标空间一点Ac=(a,b,c),确定其在父空间下的位置Ap。
![img](基础和初级 1-8.assets/_CopyPix_1610608729_11.png)
个人心得
记录于:2021.10.27
根据看的角度不同,分为两种变换类型。二者的区别在于前后是否有其他坐标系参与:
- 坐标变换:我们坐标系下的点经过一个矩阵变换到我们坐标系下另一个点
- 如果不好想象变换过程,可以看变换是如何对基坐标产生影响的,应用于所有的点即可
- 基变换:它们坐标系下的点,在我们坐标系下的表示
- 基变换的逆:我们坐标系下的点,在它们坐标系下的表示(这就说明MVP矩阵是如何实现点在不同空间转换的)
举例说明1:以二维旋转变换为例
完善:基变换的逆–2022年7月26日22:20:35
举例说明2:基变换的逆的结论的证明
- 同时解决了一个问题:为什么从前一个空间,到下一个空间的变换,有按列向量展开为矩阵的说法?
- 这里没有引入平移维度,由[4.5 坐标空间变换](#4.5 坐标空间变换)暂且认为多引入一列O
c即可得到正确结果,时间原因,暂不举例说明 - 王炸结论:变换到哪个空间,就用那个空间表示自身的基,按列向量展开基坐标成矩阵。这个矩阵就可以实现坐标空间的变换
顶点的坐标变换过程
难点:投影矩阵推导
参考链接:https://blog.csdn.net/zhanghua1816/article/details/23121735
并非真正从3D投影到2D,而是投影到NDC (Normalized Device Coordinates) 中,但是投影变换不会进行齐次除法,所以结果并非真正意义上的点,而是一个待处理的四维向量(w分量非0非1,而是和z有关)。所以四维向量需经过齐次除法,才算真正成为NDC中的点。
个人小结:
- 观察空间 - 投影变换 -> 裁剪空间(手动实现)
- 裁剪空间 - 齐次除法 -> NDC空间(底层实现)
- NDC空间 - 屏幕映射 -> 屏幕空间(底层实现)
NDC和裁剪空间并非同一空间!
正交投影矩阵
透视投影矩阵
4.6 法线变换
详见P86
结论:用变换矩阵的逆转置矩阵变换法线即可得到正确的结果
4.7 Shader内置变量!!!
变换矩阵
摄像机和屏幕参数
4.8 答疑解惑(了解)
Cg中的矢量和矩阵类型
矢量初始化:
- float4 a = float4(1.0, 2.0, 3.0, 4.0);
矩阵初始化(行优先填充):
- float3x3 M = float3x3(1.0, 2.0, 3.0, 4.0, 5.0, 6.0,7.0, 8.0, 9.0);
$$
\begin{matrix}
1 & 2 & 3 \
4 & 5 & 6 \
7 & 8 & 9 \
\end{matrix}
$$
注意:脚本的Matrix4x4(列优先填充)
获取Unity中的屏幕坐标
法1.frag函数直接声明语义
在片元着色器的输入中声明VPOS(HLSL)或WPOS(Cg)语义
- 设屏幕分辨率400x300,VPOS、WPOS各分量取值范围如下
- xy值代表在屏幕空间中的像素坐标
- x∈[0.5,400.5]
- y∈[0.5,300.5]
- z∈[0,1],近平面z为0,远平面z为1
- 透视:w∈[1/Near,1/Far](正交:w=1。这些值是通过投影矩阵变换后的w分量取倒数得到的)
原点在(0.5,0.5),即像素中心对应的是浮点值
视口坐标:屏幕空间除以屏幕分辨率得到的。左下角(0,0),右上角(1,1)
- 例子.求视口空间坐标
![img](基础和初级 1-8.assets/_CopyPix_1610615065_15.png)
法2.ComputeScreenPos函数
原理:
ComputeScreenPos内部实现
核心思想:手动模拟屏幕映射,得到未经齐次除法的视口坐标
例子.
不能在顶点着色器中先做齐次除法,具体详见P92,但是写得也不是很明白,只能隐约感觉到其中的奥妙
解释如下:
- 总之,不能在投影空间(齐次裁剪空间)中直接进行插值,因为这并不是一个线性的空间,而插值往往是线性的
—————– 初级篇 —————–
一轮整理:2021年6月16日17:19:23
二轮整理:2021年10月28日20:24:37
作者:聪头
Ch5.开始Unity Shader学习之旅
渲染纹理:
- OpenGL:左下角(0,0)
- DirectX:左上角(0,0)
5.1 一个最简单的顶点/片元着色器
基本结构
ShaderLab和Cg变量匹配关系!!!
![img](基础和初级 1-8.assets/_CopyPix_1610638213_2.png)
也可以在Cg变量前加uniform
- uniform关键词是Cg中修饰变量和参数的一种修饰词,仅仅用于提供一些关于该变量初始值是如何指定和存储的相关信息(这和其他图像编程接口的uniform不太一样,可省略)
5.2 内置文件和变量
内置包含文件后缀:.cginc
CGIncludes文件夹
- 包含了所有的内置包含文件
DefaultResources文件夹
- 包含了一些内置组件或功能所需要的Unity Shader,例如一些GUI元素使用的Shader
DefaultResourcesExtra文件夹
- 包含了所有Unity中内置的Unity Shader
Editor文件夹
- 包含了一个脚本文件,用于定义Unity5引入的Standard Shader所用的材质面板
常用包含文件
常用顶点输入结构体!!!
1 | //例如 |
常用帮助函数
帮助函数整理详见:[6.2 Unity内置函数!!!](#6.2 Unity内置函数!!!)
5.3 语义!!!
语义实际上就是一个赋给Shader输入和输出的字符串,这个字符串表达了这个参数的含义。通俗来讲,就是让Shader知道从哪里读取数据,并把数据输出到哪里
应用->顶点
![img](基础和初级 1-8.assets/_CopyPix_1610639854_6.png)
顶点->片元
![img](基础和初级 1-8.assets/_CopyPix_1610639919_7.png)
片元输出
![img](基础和初级 1-8.assets/_CopyPix_1610639970_8.png)
5.4 课后答疑
渲染平台差异
当使用多张渲染图像并开启抗锯齿后,需要手动处理翻转问题(单张屏幕图像并开启抗锯齿,Unity也会帮我们处理)
1 |
|
精度选择
float
高精度类型,32位,通常用于世界坐标下的位置,纹理UV,或涉及复杂函数的标量计算,如三角函数、幂运算等。
half
中精度类型,16位,数值范围为[-60000,+60000],通常用于本地坐标下的位置、方向向量、HDR颜色等。
fixed
低精度类型,11位,数值范围为[-2,+2],通常用于常规的颜色与贴图,以及低精度间的一些运算变量等。
在PC平台不管你Shader中写的是half还是fixed,统统都会被当作float来处理。half与fixed仅在一些移动设备上有效。
比较常用的一个规则是,==除了位置和坐标用float以外,其余的全部用half==。主要原因也是因为大部分的现代GPU只支持32位与16位,也就是说只支持float和half,不支持fixed。
Ch6.Unity中的基础光照
6.1 标准光照模型
环境光
用于描述其他所有的间接光照
![img](基础和初级 1-8.assets/_CopyPix_1611156059_1.png)
自发光
这个部分描述当给定一个方向时,一个表面本身会向该方向发射多少辐射量
![img](基础和初级 1-8.assets/_CopyPix_1611156067_2.png)
漫反射
用于描述当光源照射到模型表面时,该表面会向每个方向散射多少辐射量
![img](基础和初级 1-8.assets/_CopyPix_1611156086_3.png)
Half Lambert
- 使暗部不那么暗
![img](基础和初级 1-8.assets/_CopyPix_1611157347_6.png)
高光反射
用于描述当光源照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量
Phong
反射向量推导:https://blog.csdn.net/yinhun2012/article/details/79466517
Blinn
着色
- 逐顶点:Gouraud着色
- 逐像素:Phong着色
局限性:无法表现菲涅尔反射;Blinn-Phong模型是各向同性(isotropic)的,当我们固定视角方向和光源方向旋转这个表面时,反射不会发生任何改变。但有些表面时具有各向异性反射性质的,如拉丝金属,毛发等
6.2 Unity内置函数!!!
书P137
观察者
光源
- 以下仅可用于前向渲染,而且结果没有被归一化!
其他
内置函数得到的方向往往是没有归一化的,我们需要使用normalize函数对结果归一化
实战6-1:基本光照模型
1 | // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld' |
Ch7.基础纹理
7.1 单张纹理
shader参数解析
- _MainTex_ST:在Unity中需要使用
纹理名_ST
的方式声明某个纹理的属性。其中ST是缩放(scale)是平移(translation)的缩写- xy:存储缩放值
- zw:存储偏移值
Inspector参数解析
- Alpha from Grayscale:勾选后,透明通道的值将会由每个像素的灰度值生成
- Wrap Mode:决定了纹理坐标超过[0,1]范围后将会如何被平铺
- Repeat:纹理坐标超过1,整数部分被舍弃,而直接使用小数部分采样
- Clamp:纹理坐标超过1,那么将会截取到1,如果小于0,将会截取到0
- Filter Mode:它决定了当纹理由于变换而产生拉伸时将会采用哪种滤波模式。说人话就是一张64x64的纹理放大至512x512时,需要对纹理进行处理。以下三种,效果和性能耗费逐步增大
- Point:最近邻滤波。在放大或缩小时,采样的像素数目通常只有一个,因此图像看起来会有一种像素风格的效果
- Bilinear:线性滤波,找4个邻近像素插值混合
- Trilinear:在Bilinear基础上,对多级渐远纹理之间进行混合
- Generate Mip Maps:多级渐远纹理技术。将原纹理提前用滤波处理来得到很多更小的图像,像金字塔一般。每层是对上一层图像降采样的结果。根据摄像机远近,选择相应纹理进行采样,确保结果的合理性
7.2 凹凸映射
法线纹理的存储
- 模型空间:对于模型顶点自带的法线,它们是定义在模型空间中的,因此将修改后的模型空间中的表面法线存储在一张纹理中,这种纹理被称为是模型空间的法线纹理。(不常用)
- 切线空间:对于模型的每个顶点,它都有一个属于自己的切线空间,这个切线空间的原点就是该顶点本身,而z轴是顶点的法线方向(n),x轴是顶点的切线方向(t),而y轴可由法线和切线叉积而得,也被称为副切线(bitangent)或副法线
切线空间满足右手坐标系
切线空间法线纹理呈现蓝紫色的原因:
- 切线纹理就是存储了每个点在各自切线空间中的法线扰动方向。如果一个点的法线方向不变,那么就在它的切线空间中,新的法线方向就是z轴方向,即值为(0,0,1),经过映射得到像素颜色为(0.5,0.5,1),就是蓝紫色
两种法线纹理的比较
两种光照计算的比较
最常用的就是在切线空间和世界空间进行光照计算
效率上:
- 切线空间优于世界空间,因为前者可以在顶点着色器中就完成对光照方向和视角方向的变换。而第二种方法由于要先对法线纹理进行采样,所以变换过程必须在片元着色器中实现,这意味着我们需要额外进行一次矩阵操作,将法线从切线空变换到世界空间
通用性上:
- 世界空间由于切线空间。因为我们有时需要在世界空间下进行一些计算,例如使用Cubemap进行环境映射时,我们需要使用世界空间下的反射方向对Cubemap进行采样
采样法线核心代码
- 未设置成Normal Map,则手动采样
1 | fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw); //利用uv采样法线贴图 [0~1] |
- 设置成Normal Map,自动采样
1 | fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw); |
设置成NormalMap好处:
- 可以让Unity根据不同平台对纹理进行压缩,再通过UnpackNormal函数来针对不同的压缩格式对法线进行正确的采样。压缩原理:只有两个通道必不可少,第三个通道可以推导出来(法线是单位向量,并且切线空间下的法线方向的z分量始终为正)
利用高度图生成法线:白色相对更高,黑色相对更低
- 勾选Create From Grayscale
- Bumpiness:控制凹凸程度
- Filtering:
- Smooth:使得生成后的法线纹理会比较平滑
- Sharp:使用Sobel滤波来生成法线。实现很简单,只需要在一个3x3的滤波器中计算x和y方向上的导数,然后从中得到法线即可
求逆矩阵的核心代码!!!
矩阵元素下划线访问方式
1 | float3x3 test = float3x3(0,0,0, |
求逆函数
1 | //求4x4矩阵的逆矩阵:核心思想 伴随矩阵/行列式 |
实战7-1:凹凸映射–切线空间
模型空间–>切线空间变换矩阵的推导
1 | Shader "Unity Shaders Book/Chapter 7/CT_NormalMapInTangentSpace" |
实战7-2:凹凸映射–世界空间
1 | // Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld' |
实战7-3:渐变纹理(核心)
- 使用Half-Lambert采样渐变纹理
1 | float halfLambert = 0.5 * dot(lightDir, worldNormal) + 0.5; //[-1,1] -> [0,1],去采样 |
渐变纹理的WrapMode设为Clamp,防止因为精度问题,导致出现脏点
Ch8.透明效果
8.1 透明度测试
透明度不满足条件就会被舍弃。代码详见P165
1 | //参数x:裁剪时使用的标量或矢量条件,类型为float4/float3/float2/float1/float |
被舍弃的片元不会进行任何处理,也不会对颜色缓冲产生任何影响(说明在片元着色器就已经完成舍弃操作了);否则,就会按照普通不透明物体的处理方式来处理它,即进行深度测试和深度写入
故透明度测试不需要关闭深度写入
8.2 透明度混合
需要关闭深度写入,会与颜色缓冲区的颜色混合
排序!!!
①先渲染所有不透明物体,并开启它们的深度测试和深度写入
②把半透明物体按它们距离摄像机的远近进行排序,然后从后往前的顺序渲染这些半透明物体,并开启它们的深度测试,但关闭深度写入
Unity在内部使用一系列整数索引来表示每个渲染队列,且索引号越小表示越早被渲染
双Pass渲染
解决重叠问题
1 | Pass |
解决正反面渲染顺序问题
- 先Cull Front正常渲染,再Cull Back正常渲染
混合命令
- 源颜色:用S表示,指的是由片元着色器产生的颜色值
- 目标颜色:用D表示,指的是从颜色缓冲区中读取到的颜色值
混合因子 !!!
本质:就是设置四个因子,即源和目标各分量的权重。源rgb因子,源a因子;目标rgb因子,目标a,得到输出O
混合操作!!!
本质:本质就是设置源和目标的混合关系
- 混合操作命令通常是与混合因子一起工作的。但需要注意的是,当使用Min和Max混合操作时,混合因子实际上是不起任何作用的,它们仅仅会判断原始的源颜色和目的颜色之间的比较结果
常见混合类型!!!
实战8-1:透明度混合之双Pass渲染
如果模型自身存在重叠,采用双Pass渲染;如果模型本身没有重叠,可以使用单Pass渲染
- 原理:
- 第一个Pass:开启深度写入,但不输出颜色。它的目的仅仅是为了把该模型的深度值写入深度缓冲中
- 第二个Pass:进行正常的透明度混合,由于上一个Pass已经得到了逐像素的正确的深度信息,该Pass就可以按照像素级别的深度排序结果进行透明渲染
1 | Shader "Unity Shaders Book/Chapter 8/CT_AlphaBlendingWithZWrite" { |
左图为单Pass渲染,右图为双Pass渲染
标签解释:通常透明度混合或测试都应该在SubShader中设置这3个标签
Queue:混合渲染队列为Transparent,测试为AlphaTest
RenderType:可以让Unity把这个shader归入到提前定义的组(混合就是Transparent,测试就是TransparentCutout),以指明该Shader是一个使用了透明度混合的shader。该标签常用于着色器替换功能
IngnoreProjector:设置为true,意味着这个Shader不会受到投影器(Projectors)的影响
补充:双面渲染的透明效果
采用双Pass渲染
第一个Pass:只渲染背面
第二个Pass:只渲染正面
Unity会顺序执行SubShader中的各个Pass,因此我们可以保证背面总是在正面之前渲染,从而可以保证正确的深度渲染关系
在Pass里面调用Cull Off | Front | Back
相应指令进行透明度混合