UnityShaderGraph

Youtube教程UnityShaderGraphBasics

创建

首先我们创建一个universal render pipeline的项目

进入项目,我们可以在package Manager当中查看Shader Graph插件是否已经正常安装

之后

image-20250326173931136

我们在资源管理面板当中选择符合自己项目渲染管线的shader Graph

image-20250326174447831

双击之后,unity就会打开这个shader graph

之后你可以调整fragment片元着色器输出的颜色

image-20250326174730722

获取挂载了这个shadergraph的材质

image-20250326174930174

在shadergraph里面创建属性

image-20250326175035393

image-20250326175223926

image-20250326175625340

调整颜色之后赋值到场景中的物体上

image-20250326181453712

纹理

右键或者空格快速创建节点

image-20250326180609272

image-20250326180657196

创建了之后,我们把我们创建的texture属性连接上sampleTexture2D,一般来说,unity会自动在内存空间当中帮我们申明一段uv(也就是uv0)

之后我们将颜色和纹理的rgb值进行相乘

image-20250326180921267

注意:
image-20250326183803781

image-20250326184109623

制作简单的uv动画

image-20250326181103954
我们创建了一个scrollvelocity属性

image-20250326184454269
之后创建time节点和multiply节点

image-20250326184554948
这个结点可以计算uv的偏移

image-20250326184846935

透明

image-20250326185831125

queue值更低的物体绘制顺序更加靠前。一般来说透明物体距离摄像机越远它的queue值会越大,也就是越远的物体越先进行绘制,这样会牺牲一定的性能

要把透明物体的深度写入关闭,并且保证不透明物体是先进行绘制的,防止有物体读取到透明物体的深度值然后发生剔除

Shadergraph会自动替我们为透明物体设置渲染队列

image-20250326190816753

在graphsetting里面将物体的surfaceType设置成透明

image-20250326191156630

之后我们就可以得到alpha混合的效果

image-20250326191442699

alpha clipping可以将不透明度低于某一个值的片元直接进行裁剪

首先我们要在Graph Setting调整Surface Type为Opaque,并且勾选alpha clipping

image-20250326191756744

image-20250326191828841
image-20250326191912852

如图我们将一个png图片(这个图片中间透明度最低(alpha高),两边透明度最高(alpha低),以圆的方向扩散开),可以看到alpha值小于0.5的都被剔除了

image-20250326192137685

之后我们创建一个threshold属性然后连接alpha clip threshold,之后将这个属性mode设置成slider滑块,然后我们就可以在inspector当中调整透明剪切的程度

使用alpha裁剪的不透明物体有剔除像素的能力但是太暴力了

我们可以使用一种叫做dithering的技术来伪造透明度

image-20250326192509823

shadergraph中创建dither

image-20250326192759659

将x设置成1
image-20250326192836903

image-20250326192850264

之后我们就可以在界面来调整了

image-20250326192945336

深度

image-20250326193624193

Auto代表对不透明物体进行深度写入,对透明物体不进行深度写入

ForceEnabled代表强行对所有的物体进行深度写入

ForceDisabled代表不对透明物体进行深度写入

Depth Test

image-20250326193924111

它决定了深度值的比较方式和是否写入像素值

LEqual代表了如果物体更近那么就pass

image-20250326194150451

always代表不论深度缓冲区中的值是多少这个物体总是会被绘制

image-20250326194136441

Never代表任何时候深度缓冲区的比较都会失败

image-20250326194257935

Greater代表只有当这个物体前面有更近的物体(更小的深度值)这个物体才会被绘制

image-20250326194527810

image-20250326194613415

这个按钮可以让你在inspector窗口当中modify这些depth setting

我们的shader一般不能直接对depth buffer中的值进行读取,而是在所有的物体都被绘制之后

image-20250326194841115

unity会将这个这一帧的深度值buffer的状态保存为一个摄像机深度纹理

我们shader可以获取这个深度纹理

注意

这个纹理只会包含有用的信息如果我们在写一个透明的shader

这个纹理不会包含有关透明物体的深度值

在URP当中使用这个纹理

我们需要激活它

第一步:找到你的项目使用的URP assets。如果你使用的URp模板创建的项目,那么这个资源在Assets/Settings

image-20250326195538515

选择其中的每一个并且将其的DepthTexture勾选上

之后我们创建一个新的ShaderGraph

第一步我们需要将Graph Settings里面的Surface Type选择上Transparent

之后我们需要添加新的颜色属性:前色和背景色

image-20250326195828907

创建一个叫做scenedepth的node

image-20250326195900168

image-20250326195959354

之后将模式选择成raw,这个时候我们就可以获取depth values了

image-20250326200155296

如果我们调整成Linear01

那么depth value就会线性化

他和raw value一样都在0和1之间,但是通过映射得到了相反的效果

在 Unity 的渲染管线(如 URP、HDRP)中,Z-Buffer 存储的深度值通常是 非线性的(透视投影下)。使用 Linear01 可以将这些深度值转换为 线性 0~1 之间的数值,使其更适合用于后处理计算(如景深、雾效)。

image-20250326200558400

image-20250326200627299

相机深度值代表的是从相机到物体的真实距离,以世界单位来衡量(Unity的米),而不是0~1归一化的深度值

在eye模式下,近平面near->深度值接近0

远平面深度值变大(单位是米)

image-20250326201117203

image-20250326201134353

Lerp节点与math中的lerp类似,在两个值之间依靠一个0~1的比例值进行线性变化
image-20250326201302719

image-20250326201428536

之后我们可以在场景当中调整深度图的颜色,获得一些渐变效果

之后可以把材质拖拽到物体上

image-20250326201512011

之后让摄像机对着这个物体,我们可以看到这个物体后面显示了深度值(被设置成了线性的粉色)

image-20250326201557380

顶点

我们创建两个值来模拟顶点动画

image-20250326202936764

我们将会让顶点根据sin值上下浮动,其中speed代表速度(频率),strength代表幅度

但是如果仅仅是这样,我们只会让全部的顶点根据正弦函数上下移动,而模拟不出我们需要的波浪效果

我们可以将顶点在世界空间当中的坐标作为offset偏移值来模拟波浪效果

image-20250326203210457

image-20250326203239883

你也可以使用模型空间,但是在世界空间下,它们可以更好地合并在一起,如上图所示

image-20250326210131340

image-20250326210204342

tessellation(曲面细分)

URP Shader Graph貌似不支持tessellation

image-20250326210531810

image-20250326210545296

image-20250326210629878

image-20250326210656911
hdrp是camera relative rendering,所以是绝对世界坐标

image-20250326210820654

image-20250326210836794

image-20250326210926307

之后通过更改x的值,附上材质的物体将会添加曲面细分

image-20250326211017867

与之前创建unlit shader graph不一样,这次我们创建了一个lit shader graph

image-20250326211536605

我们找到一个具有许多附加纹理的纹理

image-20250326212447980

依次是反射颜色贴图,粗糙度贴图,环境光遮罩贴图,位移贴图,法线贴图

高度贴图

image-20250326213045530

连接到一起

image-20250326213121386

法线贴图

image-20250326213509127

金属

image-20250326213605871

可以看到在Graph inspector当中,这里有两种工作流,Specular和Metallic

image-20250326213919035

两者的区别在于

image-20250326214203858

创建一个名为Metallic的属性

image-20250326214311959
image-20250326214325954

连接到fragment上面

粗糙度贴图

image-20250326214551723

image-20250326215039822

自发光

添加一个叫做emmision color的vector4

image-20250326215121510

将其设置成HDR

image-20250326215135885

这允许我们使用更高密度超出通常选色范围的颜色

image-20250326215259322

一般来说你需要通过bloom后处理效果才能得到自发光物体的泛光现象

image-20250326215433240

但是拥有URP的项目可以直接调用bloom

image-20250326215510722

之后我们会在Hierarchy面板当中看到新建了一个Global Volume物体

image-20250326215618708

之后我们可以添加override来得到更多的后处理效果

image-20250326215644853

image-20250326215816574

环境光遮蔽

有时候我们不需要整个物体都拥有高光效果,比如说这个物体上有一些泥巴或者坑坑洼洼的小洞,那么相对于金属表面来说这些地方的反射强度就会弱一些

image-20250326220101087

image-20250326220439038

最终结果

image-20250326220928800

更多灯

菲涅耳反射

它事实上是一种类型的镜面反射

image-20250326221312021

主要是这个power可以控制菲涅耳反射的力度

使用菲涅耳反射模拟物体高亮

添加属性,用来控制菲涅耳反射强度

image-20250326222220127

添加菲涅耳颜色。并且设置成HDR

image-20250326222316930

image-20250326222330341

注意这个intensity实际上是指数,pow(color,intensity),所以当intensity为0的时候即没有HDR

image-20250326222742176

有时候这下面只是一些非常相似的颜色,这事实上并不是bug,而是intensity太高的缘故

image-20250326222453227

image-20250326222955109

image-20250326223151784

因为硬边立方体的法线布置比较失真,所以效果不太好

image-20250326223258883

球体的效果

NPR

一般来说,不使用lit shadergraph来模拟非真实感的效果,因为lit的可操作性太低

一般使用unlit来模拟npr

image-20250326230513524

接下来是对卡通渲染来说至关重要的阈值阶段

有两种方法来处理

第一种涉及到Step节点

image-20250326230657262

否则输出1或者白色,在数学中这被称为阶跃函数

而smoothStep,中间会有一层缓冲区

image-20250326230822397

image-20250326232428043

smoothstep模拟npr光照

image-20250326232618690

这个ambient light strength是一个浮点值

之后我们就可以在inspector面板里面进行光照调整了

场景交叉的环境光遮蔽

更多的环境光遮蔽

我们在现实生活当中,会发现当两个物体相交的时候,这两个物体之间存在阴影

如何检测交叉点

当shader正在运行的时候,它只能访问有关当前正在渲染的像素的信息。

包括其在世界空间当中的位置,我们希望将该位置与其后面渲染的下一个对象的位置进行比较

image-20250326233542875

如果两点之间的距离小于我们给定的阈值,那么就说明我们检测到了交点

image-20250326233616013

这确实带来了一些限制

首先我们的交叉着色器必须是透明的,因为unity仅在渲染所有不透明物体之后和渲染所有透明物体之前,将深度缓冲区的状态保存到深度纹理当中,其次,出于同样的原因,我们的着色器将会无法检测任何两个透明物体之间的交点

image-20250326234123851

获取相机到地板的距离

由于地板是有深度值的,我们将会使用场景深度节点来获取相机与之前在此处渲染的对象之间的距离

image-20250326235214700

注意要将Samplin设置成Eye模式,精确地获取到这个距离

获取相机到物体表面的距离

由于物体设置的是Transparent模式,它的深度没有进行写入,所以我们需要使用一些特殊的处理方法

回顾一下裁剪空间的概念

在裁剪空间当中这个w值的意义是相机和正在渲染的顶点之间的距离

image-20250326234623859

透视除法之后的z/w才是深度值

image-20250326234806230

image-20250326234956399

之后我们得到了照射到floor的距离(蓝色),以及到物体表面的距离(橙色)

image-20250326235511523

之后我们将其详见获取器紫色部分的值

shader使用技巧复制粘贴

使用子图来进行重用节点

image-20250326235819924

选中这些节点右键

image-20250326235859785

之后我们可以将它保存在任何我们想要的地方

之后我们可以在project view当中双击进入subgraph进行编辑

image-20250327000251565

可以看到它必须要一个输出不然就会报错

这个子图默认不需要输入,但是我们仍然可以像常规图添加属性一样来添加输入

image-20250327000447213
点击此处将新的输出添加到列表当中

该子图的唯一输出僵尸表示交叉点长度的距离值

该子图的唯一输出将会是表示交叉点长度的距离值,于是我们可以添加一个float类型的输出

回到我们的shadergraph

注意要将Surface Type选择为Transparent

image-20250327000851304

如果我们将这个函数连接到basecolor,球体网格看起来边缘会是黑色的

image-20250327001000225

因为在靠近地面的地方,距离值几乎为0,这个时候差不多就是黑色,上面的部分没有交点,几乎就是白色

相反。我们想将其转换为一个值,其中1代表交叉点的全部强度,并且当我们距离交叉点越来越远的时候,它会变得越来越低

这个时候我们就会使用One minus,但是有时候这会变成负数,因为白色部分有很多地方都是与背面物体的距离大于1的。

所以我们会使用到saturate

image-20250327001641851

接下来我们需要控制交叉口的宽度

有很多的方法可以做到这一点,但是目前我们处理的是0 到 1之间的值

所以最简单的方法可能是将之提升到可以配置的Power Value(指数值)

image-20250327002016623

image-20250327002041603

其中0会将完全遮挡应用于整个网格,而25是一个任意值,会导致非常薄的遮挡

image-20250327002207129

image-20250327002232833

这个浮点属性来添加使遮挡整体变亮或者变暗的功能

这个可以是0到1之间的滑块

它将会作为我们迄今为止计算的值的全局乘数

image-20250327002441670

到目前为止,我们仍然有一个0到1之间遮挡强度的值,其中0代表没有遮挡,1代表遮挡程度最高

由于各种浮点运算,这个值将会难以达到1

image-20250327002739893

之后我们可以在inspector窗口当中调整边缘环境光遮蔽的强度

image-20250327002805172

我们可以通过这种效果来软化岩石与地板之间的边界

image-20250327002955264

这是一个非常基础的屏幕空间的环境光遮蔽

image-20250327093254520

更加elegant的解决方法是使用被渲染像素周围几个像素的深度值以更加准确地了解像素周围物体的形状

场景交叉2:制作波浪泡沫和边缘GLA

注意blender

注意blender当中使用的坐标系与unity不同,unity是左手,blender是右手,在导出高精度平面的时候要应用变换

image-20250327100122876

复制一份之前的wave shadergraph

引入之前的子图

image-20250327100417430

我们需要在深度差异较小的地方应用浮沫

这将不同于交叉遮挡着色器

创建一个滑动条属性控制浮沫,数值越大浮沫的长度越大

image-20250327100632399

image-20250327100757922

当泡沫距离变小的时候divide节点的输出会变大

我们可以使用一个step节点,如果in小于edge则输出0,否则输出1

image-20250327100954185

只有当深度差小于浮沫距离的时候,也就是相除结果小于1的时候,这个节点输出1,这个step才会被启用

image-20250327101426688

image-20250327101454327

image-20250327102554784

回到shader graph当中,添加一个名为simple noise的节点

之后再添加一个float的foam scale,velocity属性。

image-20250327102619138

image-20250327104014837

image-20250327104144326

image-20250327104241890

image-20250327105034612

image-20250327105024347

解决直线边缘

回到子图当中我们添加offset属性在屏幕空间上进行偏移模拟折射

image-20250327105400593

image-20250327105544017

保存子图

我们可以发现其它使用了这个子图的shadergraph出现了这个输入

image-20250327105711060

添加属性

image-20250327105747174

image-20250327105757975

使用0到0.1的原因是他代表的是屏幕的比例

image-20250327105905868

image-20250327105917962

调整offset之后

image-20250327105938221

但是仍然有bug

image-20250327110032192

修正

image-20250327112623587

image-20250327110202457

因为shader会把你屏幕上显示的所有物体片元表面与物体遮挡的水面相减并且得到结果,由于我们是小于某个值(这个值在0到1之间)就会绘制浮沫,这个时候就会发生一些问题,所以我们要将其取反,并且根据这个距离重新进行计算。

image-20250327110226456

之后

image-20250327110240373

边缘发光

效果

image-20250327124632215

这种效果在我们创建类似能量盾的物品的时候非常有用

思路,我们将任何轴上接近于0或者1的uv值都将被注册为物体的发光部分

image-20250327125437155

image-20250327130312669

之后这样做,把rg单独分离出来,并且相加(得到一个新的vector2)

image-20250327130543729

之后获取另一边

image-20250327130744888

image-20250327130804496

image-20250327130823594

事实上,这四个角落会凸显出来,因为发生了两个通道数值的相加

image-20250327131028100

注意要保证shader的模式设置的是透明

image-20250327131055643

现在的效果

image-20250327131139198

image-20250327132903009

image-20250327132909635

现在的效果

image-20250327133139576

image-20250327133208847

这种方法的缺点所有的uv接缝都会发光,只能用于一些简单uv的物品

Custom Functions

之前我们使用MainLight来模拟了NPR的照射,但是有时候我们会需要多个灯光照射的结果

image-20250327133629064

我们可以在shader graph里面使用自定义函数节点

image-20250327134736588

我们需要在asset menu当中创建一个hlsl文件

image-20250327134828496

image-20250327134918259

其中ADDITIONAL_LIGHT_INCLUDED预处理指令可以防止Unity多次将你的函数包含在生成的代码当中

在里面我们可以自定义任何我们想要的函数,而自定义函数节点可以通过名称调用其中的任何一个函数

image-20250327135032534

每一个函数都需要以特殊的方式进行编写

image-20250327135426550

我们可以在nodesetting当precision来设置它

image-20250327135516875

image-20250327135633101

attenuation为1代表这个光源离物体很近被光源完全照亮

而0代表这个光源离物体很远不会被光源照亮

image-20250327135919912

image-20250327140004286

image-20250327140103780

之后我们设定好这个函数

image-20250327140151276

image-20250327140238279

image-20250327140339153

image-20250327140413588

然后我们可以将其连接到diffuse上去

image-20250327142130998

image-20250327142204735

之后我们可以获得和之前一样的漫反射非真实感光照

image-20250327142236939

我们将衰减和颜色混入光照当中

image-20250327142424643

image-20250327142625291

image-20250327142652064

进行点光源的限制

image-20250327140801311

image-20250327141004600

ShaderGraph的限制

在着色器代码当中,我们可以进行循环
image-20250327141129391

但是在shadergraph当中,则不能进行循环

image-20250327141215458

我们为点光添加照明

image-20250327141451630

image-20250327141332920

image-20250327141428327

将这些部分合并成子图

image-20250327141602280

进入子图

image-20250327141643513

image-20250327141738128

image-20250327141823958

image-20250327142823119

我们复制出总共四个点光源

image-20250327142856119

之后将这些光照叠加起来

image-20250327142941425

效果

image-20250327142955721

如果这时候我们加入第五个光源,由于shadergraph实际上只处理了4个点光源,移动这个光源可能会出现这个光源替换其他光源的bug

image-20250327143203245

image-20250327143212869

如果灯光少于4个,事实上这个shadergraph仍然会计算4个光源(shader会创造一个黑光灯来计算颜色)这会造成性能上的浪费

制作另一个计算点光源的函数

image-20250327143459035

与之前的代码进行比较

image-20250327140801311

这个代码致力于计算场景当中的所有点光源

image-20250327143815886

image-20250327143800643

image-20250327143904044