通过 Shader Graph 在 Unity 中简单实现 CRT 显示器效果
一冰在北
编辑于 2026年01月30日 15:59
收录于文集
共1篇
游戏开发教程

前几天实现了一个CRT的后处理效果,今天来把教程写一下

本来想出一期视频的,不过感觉视频的信息密度比较小(还有就是我个人比较懒),所以只打算写一篇专栏。

CRT效果演示​


我这里的CRT主要有以下几个效果

  • 扫描线

  • 屏保扫描线

  • 凸面效果

  • 屏幕撕裂效果

  • RGB通道分离效果

    这些效果本身的实现并不复杂,接下来我会详细解释以下每个效果的实现方法

    因为CRT涉及到了对屏幕的后处理,所以我们需要在 shader graph 中获取屏幕的纹理,我主要知道有两种方法可以实现2D抓屏

    通过在 URP 资产文件中创建新的 Feature

    这种方法对性能的消耗较少,但是缺少灵活性,不太适用多后处理效果的情况

    之前演示视频中的效果就是通过该方法来实现的,但是由于游戏中还通过 Pixel Prefect Camera 对画面进行了像素化处理,CRT本身的效果也被像素化处理了

    如果要采用这种方法的话需要创建一个 Fullscreen shader ,之后把对应材质拖拽给新的 Feature

之后就可以在 shader graph 中通过 URP Sample Buffer 节点来获取屏幕颜色

通过摄像机输出屏幕纹理

这种方法比较灵活,但是由于需要额外的相机对画面进行渲染,性能上可能不如前者

通过 Pixel Prefect Camera 将像素化后的画面输出为纹理,再对该纹理进行后处理来避免后处理的结果被像素化

这个方法需要我们先在项目文件中创建一个新的渲染纹理,纹理的尺寸需要和游戏的像素分辨率相同,之后将该渲染纹理拖拽到一个额外的渲染相机中

之后就可以通过采样该纹理来获取屏幕颜色(因为是纹理采样,所以 shader graph 的种类可以根据具体情况来自由选择,这里的种类 是Lit)

扫描线

实现扫描最常用的方法是通过sin函数来影响纹理采样的结果

首先将UV乘上一个圆周率,再对UV坐标进行分离来分别获取在x轴和y轴上0-π的渐变值

将x轴和y轴的分量分别连入 sine 节点中,就能在两个方向分别得到 sin 的半周期值

一个半周期值对应的是一个亮像素,而一个周期的值则会对应一组亮像素与暗像素

一般我们希望扫描线的行数与屏幕中的像素数量相对应,所以我们还需要获取渲染纹理的像素分辨率来把0-π的值扩展到0-nπ

对于采用创建 Feature 的同学无法直接获取像素分辨率,也可以直接添加一个Vector2来进行计算

将x轴和y轴结果相乘后再和屏幕颜色相乘

放大一些

扫描线被直接相乘而导致只有sin后在x轴和y轴上值同时为1像素才能被正常显示,因此呈现了这样的效果

如果不把x轴和y轴相乘,而是把他们分别拿出,画面效果则是这样

虽然实现了扫描线的效果,但是观感却并不好,因此我们需要去调整扫描线的强度

这里创建了一个Vector4的值用于调整扫描线的效果,Vector4的前两个值分别用来在x轴上控制扫描线的强度以及对最后的亮度进行补值,后两个值则对y轴进行相同操作

我们还可以对扫描线进行进行额外的处理,比如把 sine 的传参与 Time 节点进行相加,让扫描线具有缓慢移动的效果

让我们看一下效果(gif展示出来比较卡顿)

屏保扫描线

屏保扫描线的效果在实现时用到了一张额外的贴图(需要将纹理的 Warp 模式改为 Repeat)

对这张贴图进行采样前,我们需要让他的UV坐标在y轴上与时间相加

还可以增加一个偏移对进行采样进行叠加

之后将结果与采样后的值进行混合,创建一个新的变量用来控制混合强度

效果如下

凸面效果

这里通过对屏幕周围进行变暗处理形成了一种伪凸面的效果

对UV分离后相乘可以得到这样的结果

通过 One Minus 将x轴和y轴反转后再次进行相乘得到相反的结果

再将两个结果再次进行相乘

可以看到相乘后的结果并不明显,所以我们声明一个变量作为强度与其相乘

但是可以看到屏幕周围的黑色边缘太大了,如果直接将强度拉高又会导致中间区域过曝,所以我们添加一个pow节点用于控制边缘大小

因为冥函数在指数处于(0,1)时会将底数小于1的值升高,而将大于1的值降低,所以创建一个(0,1)的滑块变量作为指数

之后将结果与前面几步的结果进行相乘

效果如下

屏幕撕裂效果

屏幕撕裂的原理是将噪声在水平方向上的采样结果直接与UV坐标相加

但显然我们不希望屏幕一直保持被撕裂的状态

所以我们可以添加一个随机数,并通过Step来作为阀门筛选出大于一定值的随机数,并将其与UV的水平偏移值相乘

这只是一种简单的方法,除此之外还可以采用其他的方法,比如我采用的是随机阈值+周期输入的方法

可以让撕裂效果同时具有周期性与随机性

除此之外,也可以对噪声的 Scale 值进行随机处理,来让屏幕撕裂的形状也具有随机性

下图是将阈值调低的结果

RGB通道分离效果

通过将屏幕的采样结果在三个通道上分离偏移就可以实现该效果

该效果实现比较简单,唯一需要注意的是用于采样的UV坐标应该是上一步撕裂效果的UV,因为实现比较简单,这里只放上一张截图

其他后处理

除了上述的这些效果,CRT还有一些其他效果是该 shader 没有实现出来的,不过幸运的是 URP 中拥有现成的后处理效果可以实现这些没有实现的效果

下面列举几例

Bloom

可以用于实现电子投射到屏幕时产生的泛光效果

同时还可以实现屏幕油污效果

屏幕的扭曲

模拟真实屏幕的扭曲

画面噪点

以上就是专栏的全部内容了

项目已上传GIthub:https://github.com/IceinNorth/IceTool

3.15号更新

最近项目遇到了性能上的一些问题,初步判断是因为摄像机过多的原因,之前有提到用额外的摄像机渲染是为了分离像素化和后处理,不过实际上可以直接在 shader中加入像素化

一开始倒是尝试过 UV 分阶的方法来做像素化,不过会导致玩家和地面之间一直相差一行像素,直到昨天晚上突然意识到是 UV 分阶后的采样点在像素角上的问题,所以尝试对分阶后的 UV 进行了偏移,结果符合预期。

如下图

需要注意的是这里需要讲分阶后的 UV 连给撕裂屏幕(也可以把撕裂屏幕的 UV 连给像素化处理),并且新加一个 Vector2 作为目标分辨率,其他的可以去参考前面提到的后处理的做法,在URP的设置里新加一个 Feature 然后把采样 ScreenTex 换成 URP Sample Buffer。


注:部分内容实现思路来自于《多洛可小镇》制作人王子饼干大佬,大家可以支持一下他们的游戏,这是他们在B站上的官号@虹视游戏工作室