资源
课程
最简明的图形渲染流程解说——别被图形学吓到,入门
图形渲染流程:
几何阶段
模型
模型顶点 $XYZ$ 坐标
模型矩阵 $M$
世界(游戏场景)
- 世界空间坐标
- 模型空间到世界空间的转换过程: 世界空间坐标 $= M\times$ 顶点坐标
- 世界空间坐标
相机
- 观察矩阵 $V$
- 观察空间坐标
- 世界空间到观察空间的转换过程:观察空间坐标 $= V\times M\times$ 顶点坐标
投影
- 投影矩阵 $P$
- 投影坐标
- 观察空间到投影的转换过程:投影坐标 $= P\times V\times M\times$ 顶点坐标
由于矩阵乘法满足结合律,因此我们将引入的三个矩阵结合称为 $MVP$ 矩阵。($MVP$ 矩阵乘以坐标,就完成了坐标的完整变化,这个过程被称为几何阶段)
要对顶点坐标做额外的处理,称之为顶点着色器 Vertex Shader。
光栅化
几何阶段结束后,还需要进行光栅化,才可以一个个像素点地显示。
在光栅化阶段,计算机需要知道这些屏幕像素需要涂上什么样的颜色,是片元着色器 Fragment Shader需要完成的工作。
通用着色器又叫顶点-片元着色器,因此两种着色器加起来才是完整的 Shader。
01. 第一个光照shader
URP / HDRP 支持 ShaderGraph。
Lit Shader Graph,光照模板,相当于以前的 PBR 节点。
Unlit Shader Graph,无光照模板
新建一个 Lit Shader Graph,打开之。
左边的面板可以控制最终显示在 Material 面板上的参数。
中间的节点控制 Shader 逻辑。
右边的 Graph Inspector 用于设置节点属性(Node Settings)和全局属性(Graph )。
课程的示例 Shader Graph。
- MainTex 是可设置的 Texture2D 类。
- Sample Texture 2D 节点将 Texture 坐标转成 RGBA 颜色信息输出。
- MainColor 是可设置的 Color 类。
- Multiply 节点将两种颜色 A 和 B 相乘成一种颜色 Out 输出。
- ColorTemp 和 ColorTint 都是可设置的 Float 数,在 Node Settings 以 Slider 形式,范围在 0 到 1 之间。
- ColorTemp 色温越小越蓝,越大越黄。
- ColorTint 色调越小越绿,越大越粉。
- White Balance 节点用于白平衡处理。
- 最后将输出的颜色应用在 Fragment 的 BaseColor 上。
在 GameObject 上的效果:
01-1 光照补充与SubGraph
新建一个 Shader Graph,绑定 Material,再绑定到模型上。
一阵操作 Shader Graph,使得 Shader Graph 可以支持:
- MainTex 贴图
- Normal 法线贴图
- Matallic 金属度贴图
- Smoothness 光滑度
- Emission 自发光贴图
- EmissionOn 控制自发光是否打开
- EmissionColor 自发光颜色
- AO 环境光屏蔽贴图
选中这些节点,右键 Covert To Sub Graph。
这些节点就以一个 SubGraph 的形式展示。
02. 轮廓光/边缘光
根据原模型提供的漫反射贴图 MainTex、法线贴图 NormalTex、金属度贴图 MetalTex、自发光贴图 EmissionTex 创建 Shader Graph 节点。
创建轮廓光效果:
- Fresnel Effect 节点,创建轮廓光
- RimPower,Float 属性,控制轮廓光强度,值越接近 0,轮廓光越发散
- RimColor,Color 属性,控制轮廓光颜色
- Multiply 节点,将 Fresnel Effect 节点输出的颜色值和 RimColor 相乘,获得带颜色的轮廓光
- Add 节点,将轮廓光与轮廓光贴图相结合
- 为什么要用 Add 而不是 Multiply?因为轮廓光贴图几乎全黑,使用 Multiply 会导致最后输出也几乎全黑
最终效果。
03. 溶解
溶解效果需要在 Graph Inspector 里打开 Alpha Cliping。
Simple Noise 节点控制溶解形态,连接至 Alpha。
Noise Scale 控制溶解图案大小。
- ClipRate 控制 Alpha Clip Threshold,Alpha 值低于 ClipRate 的点将不会被渲染。
添加溶解自发光边缘。
- EdgeWidth 控制边缘宽度。
- ClipRate 和 EdgeWitdh 经过 Add 节点叠加,输出值和 Simple Noise 的输出作为 Step 节点的输入,得到一张二值化图。
- 二值化图和 EdgeColor 节点作为 Multiply 节点的输入,给溶解边缘染色,最后连上 Emission。
演示效果。
如果要利用模型自带的 Emission 贴图,还要再整个 Add 节点,最后再连 Emission 节点。
可以使用代码控制 ClipRate 的值来控制消融状态。
如果要让物体自动消融,则可以使用 Time 节点连着 Remap 替换 ClipRate 来让物体自动消融。
04. 水面(上)
水面是透明的,Surface Type 设成 Transparent。
水面没有阴影,关闭 Cast Shadows。
检测与岸边的相交边缘,不透明的物体与半透明的物体之间的边缘检测,需要依靠屏幕深度来完成,打开它。
场景深度节点深度(Scene Depth,Sampling 为 Eye,包含透明像素)减去屏幕空间的 $W$ 向量(Screen Position,Mode 为 Raw,不包含透明像素)就是不透明物体与半透明物体相交的边缘。
预览结果。靠近水面边缘的地方呈现黑色,表明场景深度有效。
增加一个 Depth,控制深浅强度。
Strength 连 Multiply 调整修改力度。
- Clamp 将值钳制在 0 到 1 之间。
- ShallowColor 和 Deep Color 控制水波颜色。
浏览效果,此时水已经被染色。
Lerp 分理出 Alpha 通道,以显示水的透明效果。
设定好 ShallowColor 和 Deep Color 的 Alpha 值才有效。
浏览效果,此时水呈现透明色。
给水增加法线贴图。
共有两种法线:
- FirstNormal
- SecondNormal
SampleTexture2D 的 Type 记得选 Normal。两个法线贴图用 Add 节点相加。
- Tiling And Offset 控制法线贴图的缩放和平移。
- Normal Strength 调整法线强度,最后连片元着色器的 Normal。
浏览效果。可以正确渲染法线了。
将 NormalStrength 与 边缘信息再进行 Lerp 运算,使得法线贴图考虑边缘信息。
给法线贴图的 Tiling And Offset 用 Time 控制,以达到水波流动的动态效果。
在 Scene 里打开 Always Refresh,便可浏览到水流实时流动的效果。
接下来修改顶点着色器,只需要修改模型的 Y 坐标即可。
用 Gradient Noise 给模型的 Y 坐标做一个扰动,后面用 Time、Multiply、Tiling And Offset 控制扰动属性。
浏览效果,此时物体发生了形变。
增加 Displacement 控制扰动强度。
预览结果。通过修改 Displacement 的值以修改扰动强度。
05. 水面(下)
为了给水面增加波光粼粼的效果,使用一个 Float 类型,范围 $[0, 1]$ 的 Smoothness 来控制片元着色器的 Smoothness。
给水面增加物体折射扭曲的效果,需要操作场景像素点的颜色 Scene Color,渲染器设置的 Opaque Texture 需要打开。
增加 Gradient Noise,Normal From Weight 将其转成法线贴图来调整 Scene Color,最后与之前渲染的颜色作一个 Lerp,输出到片元着色器的 Base Color。
RefractionStrength 可以调整折射的强度。
预览效果。
使用 Time 操作 Gradient Noise 的 Tiling And Offset 让折射效果随时间而变化。
RefractionSpeed 和 RefractionScale 分别控制折射效果的改变速度和扭曲大小。
最终 Shader Graph 如上图所示。
06. 积雪
使用之前 pbr Sub Graph 创建一个基本的 Shader Graph 模板。
通过物体表面法线方向 Normal Vector 与世界空间的夹角来判断这个表面是否应该要有积雪。
- SnowDirection 雪的方向,作一个 Normalize 归一化。
- DotProduction,计算物体表面法线方向和雪的法线之间的夹角。
- Remap,将 $[-1, 1]$ 的范围映射到 $[0, 1]$。
- SnowDepth,控制雪的深度。
- OneMinus,输出值 = 1 - 输出值,让 SnowDepth 符合值越大深度越深的效果。
- Step,阈值处理。
- 与之前的 Emission 做相加,最后的 Emission 即为所求。
预览效果。
07. 自定义光照
漫反射:
兰伯特光照模型:$diffuse=I\cdot (L\cdot N)$
- $I$ 是光照强度。
- $L$ 为入射光线的反向量。
- $N$ 为当前表面的法向量方向。
这么放置节点。
MainLight 是一个自定义函数类,设置好它的 Outputs:
- Color 漫反射颜色
- Direction 漫反射方向
|
漫反射预览效果。
|
这个 HLSL 文件中定义了两个函数,
MainLight_half
和DirectSpecular_half
。这两个函数分别用于计算主光源的信息和直接镜面反射的信息。
MainLight_half
函数接收一个世界坐标位置(WorldPos
)作为输入,输出主光源的方向(Direction
)、颜色(Color
)、距离衰减(DistanceAtten
)和阴影衰减(ShadowAtten
)。函数中首先判断是否在 ShaderGraph 预览模式下,如果是则直接赋值,否则根据是否使用屏幕空间阴影来计算阴影坐标(shadowCoord
),然后根据阴影坐标获取主光源信息。
DirectSpecular_half
函数接收镜面反射系数(Specular
)、光滑度(Smoothness
)、方向(Direction
)、颜色(Color
)、世界法线(WorldNormal
)和世界视线(WorldView
)作为输入,输出镜面反射的颜色(Out
)。函数中首先判断是否在 ShaderGraph 预览模式下,如果是则直接赋值,否则对输入参数进行处理(例如计算光滑度,规范化法线和视线等),然后调用LightingSpecular
函数计算镜面反射的颜色。这两个函数主要用于在 Unity 的 Shader 中计算光照和反射,以实现更真实的渲染效果。
把上面这些代码放在一个 hlsl 文件中。
MainLight_half 函数,精度为 Half,读取 Postion 作为世界坐标,输出:
- 主光源的方向 Direction
- 颜色 Color
- 距离衰减 DistanceAtten
- 阴影衰减 ShadowAtten
镜面反射:光照强度是光源向量与顶点法线的反射向量的点积。
DirectSpecular_half 函数,精度为 Half。
输入:
- 镜面反射系数 Specular
- 光滑度 Smoothness
- 方向 Direction
- 颜色 Color
- 世界法线 WorldNormal
- 世界视线 WorldView
输出:
- 镜面反射的颜色 Out
- 镜面反射系数 Specular
- 光滑度 Smoothness
- 方向 Direction
- 颜色 Color
由物体给出;
- 世界法线 WorldNormal
- 世界视线 WorldView
从游戏世界中获取。
emmmm 乱七八糟的节点。
最终效果。
08. 四方线框
教程里的四方线框对模型的 UV 贴图有要求,Blender 新建一个立方体模型,做细分,添加一个 UVMaps 并重置,导出至 Unity。
使用 Alpha Clip 隐去非边框的像素来实现四方线框的效果。
预览效果。
制作 U 另一方向。
预览效果。
同理,制作 V 方向的效果。
预览效果。
增加 Emission 颜色,颜色模式设为 HDR。
预览效果。
如果需要辉光效果,在 Camera 里打开 Post Processing。
Volume 调整 Bloom 的 Tint 和 Intensity。
制作线框消失的效果,将顶点低于 Threshold 的隐去。
预览效果。
用 Time 代替 Threshold,由于 Time 是一个随时间不断增加的数,对其进行 Modulo 求余就可以实现周期一样的效果。
09. Shader 帧动画
使用 Shader Graph 来实现用一张 UV 贴图的帧动画。
使用 Tiling And Offset 将 UV 贴图裁切。
一阵数学运算使得可以根据坐标来裁切 UV 贴图。
使用 Time 使得自动播放 UV 动画。
全家福。