Unity
后处理shader—以图片作为基础元素去渲染视野中的内容
概:本篇主要内容是如何在Unity中实现用图片作为基础元素去对相机最后拍到的内容做后处理渲染。(讲也不是很明白,建议直接移步效果预览那部分看看效果就明白了)
前期学习参照:这个效果的实现原理很大一部分参照于知乎罗老师的字符后处理渲染那篇文章,这里贴个链接Unity3D后期Shader特效-马赛克13-文字图像(灰度转ID|图像块坐标偏移)
(比较草率的)最终效果预览
- 首先关于这个最终效果有几点需要强调下,第一,由于我只是临时兴起摸鱼写了这个东西,所以并没有做更加精细的优化处理,导致利用差不多30张图片就是极限了,如果将一张纹理的横向纵向限度都利用起来,约莫可以利用近千张图片。精细度直接幂次增长。第二,还是由于我比较懒且仅仅是摸鱼写的东西,所以我并没有对用的图片进行筛选,简单抓起来自己收藏文件夹里的“老婆”们,批量拖入就用了,导致很多图片色调相近,色阶并不是很丰富。第三,啊,这个不是我懒了,这个确实技术力有限,实现逻辑仅仅是用平均灰度来进行替换的,而并没有涉及到具体的颜色辨识问题,比方说一个很亮的红色,可能会用一个很亮的绿色去替代(因为灰度相近,直接按照最终效果相近就放一起了)。
- 好,接下来是真正的最终效果预览
- 首先是相机拍摄到的内容原图
- 然后是精度较高的后处理后的效果(不知道为啥截图放到csdn里效果有点小差别)
- 接着是精度下调的效果(为了能看明白这确实是用一张张图片拼出来的dio)
啊,如上就是最终实现的一个效果,如果有兴趣就继续往下看,我会简介如何实现这样的东西,如果感觉很拉跨就跑路(如果有好的建议不妨给一手评论欸嘿嘿)
总体思路
- 首先是关于我为啥会想到做这么个玩意,早些天的时候在学习罗老师的那个后处理效果,感觉很好玩,实现起来也费了点劲,根本闹不清这UV怎么算的(虽然闹清后感觉也就那样了)。然后前俩天在写(抄)作业的时候突然想到了(对,细节凭空想起来,我就是容易走神)以前看到过的别人发的那种用小图片拼接成一个大图片的图,以前好奇过这种东西怎么实现的,当时根本没学过图形方面内容,觉得应该就是一堆图乱拼起来,然后反手原图盖一层上去,就有这个效果了。然后现在想起来,草率了。人家可能还真是实打实的按照图片本有的颜色拼起来的效果。于是脑洞大开决定在Unity中做一个这样的后处理效果,实时处理每一帧的镜头(对GPU开销还是比较大的玩玩就好)。(想迫害自己做过的游戏项目了,如果用自己游戏的截图去渲染自己的游戏,想想就觉得刺激又鬼畜
- 然后梳理下要实现这个东西需要做些什么,首先后处理需要用后处理shader来做,这个shader中我们需要拿到这些图片如果一个一个去定义就无法实现足够的动态性,所以我们需要在外界将这些要用到的纹理用一个脚本拼接成一张传个这个shader,然后只需要告诉它我给你的这张纹理里包含了多少的子纹理。在shader中按照自己编写的逻辑去解开拼接使用就行了。
除了上面提到的这个c#脚本和shader以外,由于我是在之前专门做后处理学习的项目里做的,这个项目里我搭了个小架子,所以后面我会简述下这个架子的内容。
综上所述,我们要实现这个效果主要需要俩个核心东西:根据纹理去做最终后处理的shader,将要用的纹理打包成一张的c#脚本。
架子简介
- 这个架子大概从上到下依次是,相机脚本,对应不同渲染风格的c#脚本,c#脚本所调用的一系列shader。
- 相机脚本:主要负责在属性检查器中公开当前选择的滤镜类型,所以我给其定义了一个枚举类型来作为滤镜类型。并且相机脚本中得有各种渲染风格的具体对象,以便在属性检查器中更换了渲染模式后能及时的切换其使用的渲染脚本。(代码如下)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class Pixlate : MonoBehaviour
{
public enum EDirType
{
彩铅风格,
像素风格,
字符风格,
图片填充
}
RenderTexture re1;
RenderTexture re2;
public EDirType 滤镜类型 = EDirType.彩铅风格;
public Filter 滤镜;
static private List<Filter> col;
public Material[] effectMaterial;
private void Start()
{
col = new List<Filter>();
col.Add(new Color_Pencil_Filter());
col.Add(new PixelFilter());
col.Add(new Char_Filter());
col.Add(new Picture_Filter());
}
private void Update()
{
switch(滤镜类型)
{
case EDirType.彩铅风格:
if (滤镜 == col[0]) break;
滤镜 = col[0];
break;
case EDirType.像素风格:
if (滤镜 == col[1]) break;
滤镜 = col[1];
break;
case EDirType.字符风格:
if (滤镜 == col[2]) break;
滤镜 = col[2];
break;
case EDirType.图片填充:
if (滤镜 == col[3]) break;
滤镜 = col[3];
break;
default:
break;
}
}
private void OnRenderImage(RenderTexture source, RenderTexture destination)
{
if(滤镜 == null)
{
return;
}
effectMaterial = 滤镜.material;
if(re1 == null)
{
re1 = new RenderTexture(source);
}
if (re2 == null)
{
re2 = new RenderTexture(source);
}
Graphics.Blit(source, re1);
foreach (Material m in effectMaterial)
{
Graphics.Blit(re1, re2, m);
Graphics.Blit(re2, re1);
}
//滤镜.Random_Parameter();
Graphics.Blit(re1, destination);
}
}
- 不同渲染风格的c#脚本:这些脚本需要负责将自身对应的渲染shader载入并实例化成材质数组,传给相机脚本,让其做后处理渲染。所以我为它们定义了一个抽象父类,规定了这一类脚本都需要有的一些元素:当前滤镜的名字,shader对应的路径,载入好的shader对象,实例化好的材质数组,初始化方法,材质所需参数随机载入方法(这个方法与本篇所涉及的后处理效果没很大关系)。(父类代码如下)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[System.Serializable]
public abstract class Filter
{
public string