利用Shader,我们可以实现很多有趣的效果,比如这样的胶片颗粒滤镜。今天让我们来看看如何搭配RenderTexture把它搬到Unity中,搬到我们的屏幕上,借用屏幕的后期处理,赋予游戏老电影一般的质感。

整个过程分为两大步骤,首先生成一张噪点纹理,然后将噪点纹理和输入的屏幕贴图进行颜色混合,这些过程都需要在OnRenderImage中执行,因为OnRenderImage会在渲染完所有图像后执行,方便我们进行屏幕后期处理。话不多说,先上效果图。

生成噪点纹理

好吧,场景是有点。。。但那不是重点。重点在于这些颗粒状的噪点是如何实现的呢?以下是基于https://www.shadertoy.com/view/4sSXDW中Shader实现的Unity ShaderLab版本,让我们先啃掉最关键的部分吧。

    //从外部获取随机数,让画面达到随机变换的效果float _Random;//噪点像素值生成方法float Noise(float2 n, float x){n += x;return frac(sin(dot(n.xy, float2(12.9898, 78.233))) * 43758.5453);}//对每一个像素进行卷积操作,取其周围及自身的像素值生成噪点,再以一定权重相加float Step1(float2 uv, float n){float a = 1.0, b = 2.0, c = -12.0, t = 1.0;   return (1.0 / (a * 4.0 + b * 4.0 + abs(c))) * (Noise(uv + float2(-1.0,-1.0) * t, n) * a +Noise(uv + float2( 0.0,-1.0) * t, n) * b +Noise(uv + float2( 1.0,-1.0) * t, n) * a +Noise(uv + float2(-1.0, 0.0) * t, n) * b +Noise(uv + float2( 0.0, 0.0) * t, n) * c +Noise(uv + float2( 1.0, 0.0) * t, n) * b +Noise(uv + float2(-1.0, 1.0) * t, n) * a +Noise(uv + float2( 0.0, 1.0) * t, n) * b +Noise(uv + float2( 1.0, 1.0) * t, n) * a +0.0);}//再对每一个像素进行卷积操作,取的是上面Step1的结果,这样可以让噪点分布更加均匀float Step2(float2 uv, float n){float a = 1.0, b = 2.0, c = 4.0, t = 1.0;   return (1.0 / (a * 4.0 + b * 4.0 + abs(c))) * (Step1(uv + float2(-1.0,-1.0) * t, n) * a +Step1(uv + float2( 0.0,-1.0) * t, n) * b +Step1(uv + float2( 1.0,-1.0) * t, n) * a +Step1(uv + float2(-1.0, 0.0) * t, n) * b +Step1(uv + float2( 0.0, 0.0) * t, n) * c +Step1(uv + float2( 1.0, 0.0) * t, n) * b +Step1(uv + float2(-1.0, 1.0) * t, n) * a +Step1(uv + float2( 0.0, 1.0) * t, n) * b +Step1(uv + float2( 1.0, 1.0) * t, n) * a +0.0);}//对三个颜色通道赋值,值来自Step2,这里会将外部传入的随机数传到Step2中,让画面达到随机变换的效果float3 Step3(float2 uv){float a = Step2(uv, 0.07 * frac(_Random));float b = Step2(uv, 0.11 * frac(_Random));float c = Step2(uv, 0.13 * frac(_Random));return float3(a, b, c);}

在片元着色器中调用Step3,通过以上算法对输入的纹理进行处理。

    float3 frag(v2f i) : SV_Target{return Step3(i.uv);}

对于顶点着色器倒没有什么特殊的要求,因为这里没有涉及到其他的顶点变换,用最普通的屏幕投射就行,通过UnityObjectToClipPos方法将顶点从对象空间转换为齐次坐标中的相机剪辑空间。

    v2f vert(a2f v){v2f o;o.pos = UnityObjectToClipPos(v.vert);o.uv = v.texcoord.xy;return o;}

这样我们就完成了第一步生成一张噪点纹理的Shader,那么在C#中要如何使用呢?

首先,生成一张RenderTexture,顾名思义就是可以渲染的纹理,这样我们才可以通过上面的Shader渲染出噪点纹理,在这里宽高是自定的,并且如果宽高变化需要重新创建,因为更大的尺寸意味着更多的像素和更多的颗粒。

    if (grainRT == null || !grainRT.IsCreated() || grainRT.width != width || grainRT.height != height){Destroy(grainRT);grainRT = new RenderTexture(width, height, 0);grainRT.Create();}

然后,用Shader取渲染刚才创建的RenderTexture,在这里将随机数传入。那么如何完成渲染呢?说到这个就不得不提到Graphics.Blit方法了,这个方法会从源纹理通过材质渲染到目标纹理,在这将源纹理赋值为grainRT自身或者null即可,因为上面Shader中的计算仅涉及到纹理坐标而不涉及采样。

    Material grainMat = GetMaterial("Custom/Grain");grainMat.SetFloat(Shader.PropertyToID("_Random"), Random.value);Graphics.Blit(grainRT, grainRT, grainMat);

在这里,由于需要经常取Material,所以用一个方法把New出来的Material放到Cache中,需要的时候从Cache中取出,防止反复创建和销毁材质。

   private Dictionary<string, Material> matCache = new Dictionary<string, Material>();Material GetMaterial(string shaderName){Material mat;if (!matCache.TryGetValue(shaderName, out mat)){Shader shd = Shader.Find(shaderName);mat = new Material(shd);matCache.Add(shaderName, mat);}return mat;}

好,现在我们已经有一张噪点纹理了,接下去就应该把这张纹理和摄像机得到源纹理进行颜色混合。为此我们还需要再写一个Shader。

对噪点纹理和相机得到的纹理进行颜色混合

在片元着色器中,要先对摄像机得到的源纹理(将会作为_MainTex被传入)进行采样,为了防止过曝,可以进行一定的灰度处理,这里使用了color * = 0.5,其实可以考虑将这个参数作为一个可变参数,会有不同的观感。同样的,也对噪点纹理进行采样,然后进行颜色混合color += color * grain,但这样处理的话颗粒会很不清晰,所以要对噪点进行放大,乘上强度参数 _Intensity。

    half3 frag(v2f i) : SV_Target{half3 color = tex2D(_MainTex, i.uv).rgb;color *= 0.5;float3 grain = tex2D(_GrainTex, i.uv).rgb;color += color * grain * _Intensity;return color;}

顶点着色器与前一个Shader相同,不需要特殊处理,所以可以把这个公有的vert方法放到Public.cginc中,将代码模块化。

    #ifndef __PUBLIC__#define __PUBLIC__#include "UnityCG.cginc"struct a2f{float4 vert : POSITION;float4 texcoord : TEXCOORD0;};struct v2f{float4 pos : SV_POSITION;float2 uv : TEXCOORD0;};v2f vert(a2f v){v2f o;o.pos = UnityObjectToClipPos(v.vert);o.uv = v.texcoord.xy;return o;}#endif

与第一步类似,在C#中,我们会通过Graphics.Blit来进行纹理渲染。从摄像机获得源纹理,这时候源纹理会被作为_MainTex传入Shader中,然后将噪点纹理作为_GrainTex传入,同样还有刚才提到的强度_Intensity,到这里对接成功。

    void OnRenderImage(RenderTexture src, RenderTexture des){//...Material outputMat = GetMaterial("Custom/Output");outputMat.SetTexture(Shader.PropertyToID("_GrainTex"), grainRT);outputMat.SetFloat(Shader.PropertyToID("_Intensity"), intensity);Graphics.Blit(src, des, outputMat, 0);}

OnRenderImage是每一帧都调用的,如果嫌太快了,也可以加上一个时间间隔

    if ((frame++) % interval != 0){return;}frame = 1;

最后,把写好的C#脚本附到主摄像机上,大功告成!

当然,我们可以通过参数调节想要的效果,比如时间旅行画风。。

或者坏掉的电视机画风。。


随文附上代码一份

Grain.shader

Shader "Custom/Grain"
{CGINCLUDE#pragma exclude_renderers d3d11_9x#pragma target 3.0#include "UnityCG.cginc"#include "Public.cginc"//从外部获取随机数,让画面达到随机变换的效果float _Random;//噪点像素值生成方法float Noise(float2 n, float x){n += x;return frac(sin(dot(n.xy, float2(12.9898, 78.233))) * 43758.5453);}//对每一个像素进行卷积操作,取其周围及自身的像素值生成噪点,再以一定权重相加float Step1(float2 uv, float n){float a = 1.0, b = 2.0, c = -12.0, t = 1.0;   return (1.0 / (a * 4.0 + b * 4.0 + abs(c))) * (Noise(uv + float2(-1.0,-1.0) * t, n) * a +Noise(uv + float2( 0.0,-1.0) * t, n) * b +Noise(uv + float2( 1.0,-1.0) * t, n) * a +Noise(uv + float2(-1.0, 0.0) * t, n) * b +Noise(uv + float2( 0.0, 0.0) * t, n) * c +Noise(uv + float2( 1.0, 0.0) * t, n) * b +Noise(uv + float2(-1.0, 1.0) * t, n) * a +Noise(uv + float2( 0.0, 1.0) * t, n) * b +Noise(uv + float2( 1.0, 1.0) * t, n) * a +0.0);}//再对每一个像素进行卷积操作,取的是上面Step1的结果,这样可以让噪点分布更加均匀float Step2(float2 uv, float n){float a = 1.0, b = 2.0, c = 4.0, t = 1.0;   return (1.0 / (a * 4.0 + b * 4.0 + abs(c))) * (Step1(uv + float2(-1.0,-1.0) * t, n) * a +Step1(uv + float2( 0.0,-1.0) * t, n) * b +Step1(uv + float2( 1.0,-1.0) * t, n) * a +Step1(uv + float2(-1.0, 0.0) * t, n) * b +Step1(uv + float2( 0.0, 0.0) * t, n) * c +Step1(uv + float2( 1.0, 0.0) * t, n) * b +Step1(uv + float2(-1.0, 1.0) * t, n) * a +Step1(uv + float2( 0.0, 1.0) * t, n) * b +Step1(uv + float2( 1.0, 1.0) * t, n) * a +0.0);}//对三个颜色通道赋值,值来自Step2,这里会将外部传入的随机数传到Step2中,让画面达到随机变换的效果float3 Step3(float2 uv){float a = Step2(uv, 0.07 * frac(_Random));float b = Step2(uv, 0.11 * frac(_Random));float c = Step2(uv, 0.13 * frac(_Random));return float3(a, b, c);}float3 frag(v2f i) : SV_Target{return Step3(i.uv);}ENDCGSubShader{//由于是屏幕渲染,所以剔除和深度都可以关Cull Back ZWrite Off ZTest AlwaysPass{CGPROGRAM#pragma vertex vert#pragma fragment fragENDCG}}
}

Output.shader

Shader "Custom/Output"
{Properties{_MainTex ("Texture", 2D) = "white" {}}CGINCLUDE#pragma target 3.0#include "UnityCG.cginc"#include "Public.cginc"sampler2D _MainTex;float _Intensity;sampler2D _GrainTex;half3 frag(v2f i) : SV_Target{half3 color = tex2D(_MainTex, i.uv).rgb;color *= 0.5;float3 grain = tex2D(_GrainTex, i.uv).rgb;color += color * grain * _Intensity;return color;}ENDCGSubShader{Cull Off ZWrite Off ZTest AlwaysPass{CGPROGRAM#pragma vertex vert#pragma fragment fragENDCG}}
}

Public.cginc

#ifndef __PUBLIC__
#define __PUBLIC__#include "UnityCG.cginc"struct a2f
{float4 vert : POSITION;float4 texcoord : TEXCOORD0;
};struct v2f
{float4 pos : SV_POSITION;float2 uv : TEXCOORD0;
};v2f vert(a2f v)
{v2f o;o.pos = UnityObjectToClipPos(v.vert);o.uv = v.texcoord.xy;return o;
}#endif

CameraRenderer.cs

using System.Collections.Generic;
using UnityEngine;public class CameraRenderer : MonoBehaviour
{RenderTexture grainRT;[Range(0f, 20f)]public float intensity = 10f;[Range(1, 1000)]public int width = 600;[Range(1, 1000)]public int height = 600;[Range(1, 100)]public int interval = 1;private int frame = 1;void OnRenderImage(RenderTexture src, RenderTexture des){if ((frame++) % interval != 0){return;}frame = 1;if (grainRT == null || !grainRT.IsCreated() || grainRT.width != width || grainRT.height != height){Destroy(grainRT);grainRT = new RenderTexture(width, height, 0);grainRT.Create();}Material grainMat = GetMaterial("Custom/Grain");grainMat.SetFloat(Shader.PropertyToID("_Random"), Random.value);Graphics.Blit(grainRT, grainRT, grainMat);Material outputMat = GetMaterial("Custom/Output");outputMat.SetTexture(Shader.PropertyToID("_GrainTex"), grainRT);outputMat.SetFloat(Shader.PropertyToID("_Intensity"), intensity);Graphics.Blit(src, des, outputMat, 0);}private Dictionary<string, Material> matCache = new Dictionary<string, Material>();Material GetMaterial(string shaderName){Material mat;if (!matCache.TryGetValue(shaderName, out mat)){Shader shd = Shader.Find(shaderName);mat = new Material(shd);matCache.Add(shaderName, mat);}return mat;}
}

Unity:用Shader和RenderTexture实现胶片颗粒滤镜相关推荐

  1. Unity编写Shader内置各种矩阵和方法介绍

    返回目录 大家好,我是阿赵. 这里记录一下Unity编写Shader内置各种矩阵和方法 一.Unity内置转换矩阵 1.MVP类矩阵 UNITY_MATRIX_MVP:Current model * ...

  2. Unity初学者Shader Graph教程

    Unity初学者Shader Graph教程 了解面向非程序员的 Unity 引擎可视化着色器编程工具的来龙去脉 课程英文名:Your Ultimate Guide to Shader Graph f ...

  3. 1.Unity之Shader新手入门

    Unity Shader着色器的基本概念 如何使用Unity Shader着色器 示例:如何使用Unity Shader着色器创建复杂的效果 总结 什么是Unity中的Shader着色器? Shade ...

  4. Unity学习shader笔记[一百]简单焦散Caustic效果

    焦散是模仿光透过水底的一个投影景象 有两个版本,改版最后效果如下 这里是简单的基于物体的焦散,基于水体的焦散思路是水面物体的shader中拿到ColorBuffer,然后用水体的屏幕空间坐标取采集Co ...

  5. Unity之Shader基础探索

    Unity之Shader基础探索 一.什么是Shader? 1.Shader的开发语言 2.着色器用途 3.着色器的编辑 4.着色器性能分析工具 5.着色器编译 6.异步着色器的编译工作原理 7.内置 ...

  6. Unity使用Shader快速制作一个圆形遮罩

    Unity使用Shader快速制作一个圆形遮罩 如何用Shader来做出圆形.切倒角和边缘虚化 Unity使用Shader快速制作一个圆形遮罩 橙子前言 一.创建Shader 二.创建Material ...

  7. Unity官方Shader介绍: TRANSFORM_TEX

    Unity官方Shader介绍: TRANSFORM_TEX 文章目录 Unity官方Shader介绍: TRANSFORM_TEX 纹理(Texture) 纹理坐标(UVW, STR) 纹理映射(T ...

  8. Unity 流光shader 记录分享

    Unity 流光shader 转载自:链接 Shader "Custom/DataFlowEffect" {Properties{_MainColor("Main Col ...

  9. Unity中用shader graph制作一个简单的传送门效果

    Unity中用shader graph制作一个简单的传送门效果 一.配置渲染管线 1.通过菜单"Asset" --> "Create" --> &q ...

最新文章

  1. Linux的su命令,sudo命令和限制root远程登录
  2. 数据库设计(概念、步骤)
  3. python3 字典添加_python3字典删除元素和添加元素的几种方法
  4. 用python庆祝生日_生日到底该过阴历还是阳历好呢?不是迷信,都怪我们大意!...
  5. js 实现简单的轮询
  6. java五星好评点评器_亲,麻烦给个五星好评!—RatingBar
  7. linux python开发环境sql数据迁移到mysql_运用Python语言编写获取Linux基本系统信息(三):Python与数据库编程,把获取的信息存入数据库...
  8. j2EE+mysql的一点总结
  9. 36 《魔鬼数学 : 大数据时代,数学思维的力量》 -豆瓣评分8.3
  10. 雷达基础系列文章之四:雷达专业国内期刊
  11. 传统方法VS深度学习方法
  12. (附源码)基于PHP二手服装网站 毕业设计 201711
  13. ztree通过ajax获取json并勾选checkbook
  14. 面试可能遇到java基础知识
  15. 云效平台性能测试功能:一个基于Jmeter的性能压测平台
  16. 个人网站真能转成商业网站,你能么?
  17. git 常见问题汇总(更新中)
  18. 关于兼容光模块的那些事,看这篇就够了
  19. ShaderGraph——流光
  20. Phalcon之教程 2:INVO 项目讲解(Tutorial 2: Explaining INVO)

热门文章

  1. 3、EasyExcel介绍
  2. 面试官:说说你对操作系统的理解?核心概念有哪些?
  3. 席慕蓉笔下,那些我们读过的人生哲理
  4. html 网页跳转动画效果,jQuery实现切换页面过渡动画效果_jquery
  5. RacingGame学习笔记1——辅助类
  6. 递归使用案例:输出对称图形
  7. 硬盘虚拟化设置被禁用/Intel VT-x可能被禁用。
  8. oracle apex服务安装
  9. 微软SQL服务器被黑客入侵以窃取代理服务的带宽
  10. 在使用CAA添加工具条时,是否遇到过这样的乱码情况