书接上篇,所以标题数字也跟着,直接从 1.8 开始计数。
本篇主要是 shader graph 的使用。
1.8 Shader Graph 起步
之前我们只是使用 Unity 提供的 Shaders。但是实际上我们完全可以通过写代码创建自己的 Shaders。不过就像该系列第一篇文章里开头就说的一样,本系列教程不会用到任何代码。因此我们使用的是 Unity 提供的一个可视化创建 Shaders 的工具--Shader Graph。
Shader Graph 提供了无需代码来创建 Shader 的方式。我们可以组合 textures,在 fragment shader 里移动纹理,或者在 vertex shader 里改变 vertices 的位置。专业的 TA 会创建 shader 来完成各种特殊的艺术风格,比如岩浆,风暴云等等。
本教程将会教你像一个 TA 一样创建 shader,来让其他艺术家用在 materials 上。
1.8.1 查看 shader graph
示例项目中已经有两个创建好的 shder graph。双击打开,或者点击 material 右边的 edit 按钮。
不过示例项目的 shader 看起来有些可怕,我们还是从一个简单的 shader 开始慢慢 build。
- 在 project window 里创建一个 shader graph -> URP -> Lit Shader Graph。便可见如下内容。
- 左边的框框里将会是以后暴露出来的属性,TA 会在这里添加想要暴露给艺术家的属性(如果有的话)。艺术家给 materials 更换该 Shader 的时候,就可以调整这些属性。
- 右下角则是一个实时监测 shader 效果的 preview。
- 右上角的 graph inspector 正如其名,就是个 graph 里的 inspector,用于显示选中东西的设置。
- 中央的这两个版块比较特殊。叫做 master stack。定义了这个 graph 的最终效果,可以看到分别叫做 vertex 和 fragment,分别包含着一些节点,这些节点是最终的节点,经过前面的处理之后,在这里显现效果。
操作
一些常见的有用操作:
- 鼠标中键移动。滚轮缩放,
- 选中某个节点,按 F 会聚焦过去(双击没用)。
- 按 A 会显示适应屏幕的布局。
1.8.2 添加程序生成贴图
本教程将会教你制作一个自发光,透明,闪烁的效果。
为了制作闪烁效果,我们需要使用程序生成噪声贴图(procedural noise map)。使用 procedural 来修饰,是为了区分于其他 texture,表示该贴图是通过算法生成的,而不是来源于某个图片或者什么实际来源。
很幸运地,unity shader graph 提供了一些 noise 效果。
添加 node
如果你喜欢乱按,那么肯定已经发现鼠标右键可以创建节点。但实际上,有更方便的快捷键,按空格键即可快速唤出创建菜单。
这里我们先添加一个 gradient noise。
1.8.3 随时间移动
想要创建闪烁效果,我们需要让 noise 移动。但怎么移动呢?我们之前在讲 texture 的时候,就已经提到过 offset 的使用。那么我们只要在 shader graph 中让其改变就行了。
- 创建一个 tiling and offset 的 node。
- 把该 node 的 output 连到 noise map 节点的 UV input 中。之前我们也是提到过 UV 只是为了区分于 XYZ 的二维坐标系。
- 调整一些 tiling 和 offset,可以很直观地看到变化。
- 为了让这个变化能够自主持续地变化,我们创建一个 time 节点。
- 自己拖动下分别到 tiling 和 offset input 上试试,可以看到已经可以自动变化了。
- 将 grdient noise 的 output 连到 fragment 的 base map 节点上。现在就可以在右下角的 preview 中看到效果了。
- 点左上角的 save asset 来保存,ctrl+s 没用
1.8.4 添加 material 输入参数
为了让艺术家来调节 shader 效果,比如说闪烁速度,我们会想把时间参数暴露出来。
为了达到这一效果,我们需要使用 input nodes。而 input nodes 则是由刚刚提到过的,左边的框框来创建。
- 因此我们创建一个 float 类型的节点来控制速度。
- 将该节点拖到图上,这时我们遇到了一个情况。我们想让这个节点和 time 一起使用。但是 time 节点根本没有输入点。
- 因此,我们创建一个 multiply 节点,将速度节点和time连在输入上。multiply 的输出节点连在 offset input 上。
- 但是,发现 noise 突然停下来了。这是因为我们的速度节点初始化后为 0。
- 在 inspector 里便可以轻松调整。
1.8.5 组合贴图
为了让结果看起来闪烁效果更强,我们这次尝试组合两个贴图。
- 比较简单的是,第二张图我们就用刚刚的图,只不过将速度降为一半。复制一下刚才创建的几个节点,然后稍加调整,比如说 timer 和速度节点可以复用。
- noise map 正如所看到的一样,就是个灰度图。因此我们再创建一个 multiply 节点,将这两个 noise map 连在一起,最后输出到 base map 中。
1.8.6 分组
就是单纯地分组,让整个 graph 看起来 well organized。以及添加一些注释,让自己知道自己在干什么。
把想要放在同一组的节点选中,在任意节点上右键选择 group selection 或者 ctrl+G 即可。
1.8.7 允许在 material 中调整 texture 的 scale
现在我们要允许调整 scale。因为我们的 noise map 在一些大型物体上会显得像是一些小噪点(虽然本来就是噪点),因此我们需要让 scale 能够在 material 中进行调整。
1.8.8 调整对比度
现在的 map,对比度偏高,黑色和白色很明显,没什么灰色。因此我们希望能够自主调整对比度。
而为了达到这种效果,我们可以使用 remap 节点,这个节点可以缩小灰度值的范围,让对比度更小一些。(不是说 remap 只能缩小范围,而是我们要用 remap 来缩小范围)
- 添加一个 Vector2 类型的 material property。
- 新建一个 remap 节点,把之前的 map 拖到 In 里。刚刚新建的 Vector2 类型拖到 out min max 里。把 In min max 改成 0 和 1。Vector2 的值改成 0 和 1,结果如下图:
解释几个问题:
- 这是在干嘛?简而言之,remap 把 In min max 范围重新扩展(或缩小)到 Out min max 的范围。然后对 In 的输入进行处理。
- 这里我们将 In min max 改成 0 和 1,正好对应我们殊途灰度图的最小值和最大值。因此 remap 能够对整个灰度图进行扩展(或缩小)。
- 然而,因为我们的 Vector2 类型值也刚好被我们设置成了 0 和 1,因此现在这个 remap 没有任何效果。
因此,我们可以调整 vector2 的值,让其大于 0-1 的范围,则是增加对比度。小于 0-1 的范围,则是减少对比度。
1.8.9 调整颜色和透明度
调整颜色
- 添加一个 color 类型的 material property,和上一步的结果 multiply 一下。
- 现在就已经可以调整颜色了,但我们希望颜色更时髦一些,使用 HDR 效果。
- 其实在刚才创建的属性中,可以在 inspector 里,调整为 HDR mode。
- 但是还不够,我们还可以使用 emission map(准确来说,是 procedural emission map)。因此我们除了把输出给 base color,还要给 emission。
调整透明度
现在所有的地方都是同一个透明度,但是如果我们让不同亮度下透明度不同的话,很明显,闪烁效果会更好。
在 graph inspector 中,这次我们切换到 graph settings。将 surface 调整 opaque 为 transparent。(仔细看看 graph settings,这其实就是在 material 里设置的那些东西)
回到 graph workspace,可以看到 fragment 里多了个 alpha 节点,我们把和颜色相乘前的 map 拖到 Alpha 节点上。
现在,我们就拥有了一个自发光的,闪烁的 shader 了。
1.8.10 最后调整
fragment 里还有一些别的参数,来都来了,就都暴露出来呗。
比如说 metallic 和 smoothness。
1.8.11 创建 material
创建一个 material,切换 shader。shader graph 创建的 shader,会在 shader graph->你起的名字里找到。
2. 使用 shadergraph 制作旗帜飘扬动画
2.1 介绍一些其他 nodes
2.1.1 position
position 控制对 mesh 的节点或者片段的访问。比如说把位置设置成 view,那么着色器将会从摄像机的角度来渲染。
输入仅有一个 space 参数:输出的位置的坐标空间。有 object,view,world,tangent,absolute world 五种模式。
- object:着色器渲染位置和 object 位置相关的时候使用。
- view:着色器渲染位置和摄像机位置相关的时候使用。
- world:着色器渲染位置和整个场景位置相关的时候使用。
- tangent:着色器渲染位置和 object 的切线位置相关的时候使用。
- absolute world:返回 object 在世界中的绝对位置。
试着用一用:
赋予一个 material 用在某个 object 上,移动摄像头,发现物体会跟着移动,纹理的正面始终朝向摄像机。
2.1.2 time
之前已经用过,直接讲每个参数的含义:
- time:提供平滑的时间值。
- sine timme:提供时间的正弦值。
- cosine time:提供时间的余弦值。
- delta time:提供当前帧的时间。
- smooth delta:提供平滑的当前帧时间。
2.1.3 gradient
先看图:
在 gradient 里定义渐变颜色,在 sample gradient 里使用:
- time 指的是渐变的位置,从 0 到 1 就是渐变颜色从左到右。
2.1.4 Texture 2D
没什么好说的,要使用 texture 的时候就会用到。相关的节点有 Texture 2D 和 Sample Texture 2D。
2.2 旗帜飘扬效果
这个教程讲的极其敷衍,这里给出我自己的制作心路历程:
2.2.1 位置的变化
首先我们应该这么想,我想让旗帜进行形变,因此我需要获取 object 每一点的位置。如下图是一个简单的图表:
- position 的 space 参数设置成 object。
- time 和 multiply 只是为了现在看看效果。
2.2.2 形变
然后,我们就要仔细想一想旗帜飘扬形变的基本原理是什么。
把旗帜看成一个立体,其实要做的是,让 object 的 x,y 轴保持不变,z 轴的位移会根据时间和位置进行变化。
那么,既然 x,y,z 轴的变化不一样,那么,理所应当,我们要把 x,y,z 轴分离出来,然后再合并。如下图:
- split 里有 4 个参数,rgba,这是颜色通道,不过对于 position 来说,这其实就是 xyz。A 通道我们用不上。
这里只是简单的,把 3 轴的参数分离出来,再合并,此时的效果就和什么都没有是一样的。
(这里需要说一声,右下角的 preview 中,x 轴是左右,y 轴是上下,z 轴是里外)
因此接下来,我们要对 z 轴进行操作,因为仔细想想形变的方向其实就是是 z 轴,也就是面对屏幕的方向。
因此,一个基本想法就是,利用时间和 x 轴的位置,来改变 z 轴的位置,如下图:
这个图很复杂,我们一一细讲:
- 首先 position,split,combine 就不讲了,名字表示含义。
- 这里我们让 x 轴的值和 time 相加,然后再经过一个 sin() 函数。
- sin 很好理解,毕竟飘扬效果就是一个正弦波。但是为什么是 x 轴和 time 相加呢?
- 其实如果我们不使用 time 和 add,直接把 x 轴的值经过 sin 也可以。此时的效果就是 object 会变成一个曲线形状,但是不会随时间变化。
- 因此我们要引入时间值,想象一个 sin 函数,随着横轴不断增加,纵轴就会在 -1 和 1 之间变化。而 time 节点就是为了让横轴不断增加。
2.2.3 固定移动
虽然刚才我们已经可以让旗帜飘扬了,但是其实并不是我们想要的样子。因为旗帜应该有一边保持不动(因为被固定在柱子上)。因此这一小节就来解决这个问题:
这里只展示改动的部分:
- 从 multiply 里实时的效果可以看到,中间有一个地方始终保持不变。这个地方就是 0 的位置,也就是 object 的边缘。
- 主要效果由 uv + split 实现,one minus 只是用于改变固定的边缘。
- 这里的 uv 是四维的,我们选取其中的 U 轴的值,与 sin 函数的结果相乘。
但是现在的效果依然不够好,因为旗帜飘扬的幅度太大了,频率也不够快:
2.2.4 调整
这一步不会细讲,毕竟都是一些数值调整,用各种 add,multiply,divide 节点来调整数值即可。比如说把 time 值翻倍就可以提高飘扬频率,把 sine 节点后的值除下去就可以让幅度变小。
效果如下:
至此,红旗飘扬效果制作完成