本系列主要参考《Unity Shaders and Effects Cookbook》一书(感谢原书作者),同时会加上一点个人理解或拓展。

这里是本书所有的插图。这里是本书所需的代码和资源(当然你也可以从官网下载)。

========================================== 分割线 ==========================================

写在前面

呜呼,这本书最后一篇了有木有!!!想想就有点小激动呢。。。(可是还有很多隔过去了。)技术文章里就不说太多废话了,之后会写一篇总结的,坑也会慢慢补上的(我希望吧。)。

好啦!我们本节要学习的特效是一个更加常见的画面特效。在市场上很多的第一人称射击游戏(FPS)中,我们都可以看到夜视效果的身影,比如《使命召唤》、《光晕》等等。这种效果使屏幕蒙上一层非常独特的黄绿色。下面是《使命召唤》中的夜视效果:

 

为了实现这种夜视效果,我们需要使用Photoshop来分解上述效果的实现。我们可以从网上找一些参考图片,然后和已有图层合并,看看我们需要哪种混合效果,以及我们要怎样组合不同的图层。下面的图像显示了在Photoshop中进行上述过程的最后结果:

而最后Shader实现的结果像下面这样:

下面,我们开始将Photoshop中的最终图像分解成几个部分,来更好的理解这些资源是如何被整合在一起的。而后,我们会深入讲解实现过程。

准备工作

之前说过,使用Photoshop可以让我们方便地构分层图像,以便我们可以更好地理解如何得到一个夜视效果。现在,我们就把夜视效果分解成几个组成图层。

  • 偏绿色的画面(Tinted green):夜视效果的第一个图层就是它标志性的绿色,几乎所有的夜视图像都是这种色调的。这可以让我们的特效看起来就是明显的夜视效果。
  • 扫描线(Scan lines):为了增加夜视效果的真实性,我们在上述着色层的上面添加了一层扫描线。在这里,我们使用了一张用Photoshop创建的纹理,然后让用户选择平铺系数来控制扫描线的宽窄和大小。
  • 噪点(Noise):下一个图层是一个简单的噪点图层,它平铺在上述两种图层的上方,用于分解图像为我们的特效添加更多的细节效果。
  • 晕影(Vignette):夜视效果的最后一层就是晕影。从上面《使命召唤》的游戏截图可以看出,它使用一个晕影来模拟一个视野效果。我们使用下面的图层来作为晕影纹理。
现在,我们把上述各个纹理结合在一起,开始正式创建夜视效果的画面特效。
  1. 准备好一张晕影纹理、一张噪声纹理和一张扫描线纹理,你可以在本书资源(见本文最上方)中找到对应的纹理。
  2. 创建一个新的脚本,命名为NightVisionEffect.cs,以及一个新的Shader,命名为NightVisionEffectShader.shader。
  3. 使用前一章第一篇里的代码填充上述新的脚本和Shader。
  4. 把OldFilmEffect脚本添加到Camera上,并使用OldFilmEffectShader给OldFilmEffect脚本中的Cur Shader赋值。

实现

现在你的画面特效脚本系统应该已经建立好了,现在我们来实现具体的脚本和Shader。
首先,我们来填写NightVisionEffect.cs脚本的主要代码。
  1. 第一步我们要定义一些需要在面板中显示的变量,以便让用户进行调整。在脚本中添加如下代码:
    	#region Variables
    public Shader nightVisionShader; public float contrast = 2.0f;
    public float brightness = 1.0f;
    public Color nightVisionColor = Color.white; public Texture2D vignetteTexture; public Texture2D scanLineTexture;
    public float scanLineTileAmount = 4.0f; public Texture2D nightVisionNoise;
    public float noiseXSpeed = 100.0f;
    public float noiseYSpeed = 100.0f; public float distortion = 0.2f;
    public float scale = 0.8f; private Material curMaterial;
    private float randomValue = 0.0f;
    #endregion
  2. 下面,我们需要填充OnRenderImage函数。在这个函数里,我们将要把上述变量传递给Shader,使得Shader可以使用这些数据来处理render texture:
    	void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture){
    if (nightVisionShader != null) {
    material.SetFloat("_Contrast", contrast);
    material.SetFloat("_Brightness", brightness);
    material.SetColor("_NightVisionColor", nightVisionColor);
    material.SetFloat("_RandomValue", randomValue);
    material.SetFloat("_Distortion", distortion);
    material.SetFloat("_Scale", scale); if (vignetteTexture) {
    material.SetTexture("_VignetteTex", vignetteTexture);
    } if (scanLineTexture) {
    material.SetTexture("_ScanLineTex", scanLineTexture);
    material.SetFloat("_ScanLineTileAmount", scanLineTileAmount);
    } if (nightVisionNoise) {
    material.SetTexture("_NoiseTex", nightVisionNoise);
    material.SetFloat("_NoiseXSpeed", noiseXSpeed);
    material.SetFloat("_NoiseYSpeed", noiseYSpeed);
    } Graphics.Blit(sourceTexture, destTexture, material);
    } else {
    Graphics.Blit(sourceTexture, destTexture);
    }
    }
  3. 最后,我们需要保证一些变量的范围。这些范围可以根据需要后续更改:
    	void Update () {
    contrast = Mathf.Clamp(contrast, 0.0f, 4.0f);
    brightness = Mathf.Clamp(brightness, 0.0f, 2.0f);
    distortion = Mathf.Clamp(distortion, -1.0f, 1.0f);
    scale = Mathf.Clamp(scale, 0.0f, 3.0f);
    randomValue = Random.Range(-1.0f, 1.0f);
    }
接下来,我们来实现关键的Shader部分。
  1. 首先,还是添加一些新的Properties:
    	Properties {
    _MainTex ("Base (RGB)", 2D) = "white" {}
    _VignetteTex ("Vignette Texture", 2D) = "white" {}
    _ScanLineTex ("Scan Line Texture", 2D) = "white" {}
    _ScanLineTileAmount ("Scale Line Tile Amount", Float) = 4.0
    _NoiseTex ("Noise Texture", 2D) = "white" {}
    _NoiseXSpeed ("Noise X Speed", Float) = 100.0
    _NoiseYSpeed ("Noise Y Speed", Float) = 100.0
    _NightVisionColor ("Night Vision Color", Color) = (1, 1, 1, 1)
    _Contrast ("Contrast", Range(0, 4)) = 2
    _Brightness ("Brightness", Range(0, 2)) = 1
    _RandomValue ("Random Value", Float) = 0
    _Distortion ("Distortion", Float) = 0.2
    _Scale ("Scale (Zoom)", Float) = 0.8
    }
  2. 我们还需要在CGPROGRAM块中添加对应的变量,以便Properties块可以和CGPROGRAM块通信:
    	SubShader {
    Pass {
    CGPROGRAM #pragma vertex vert_img
    #pragma fragment frag #include "UnityCG.cginc" uniform sampler2D _MainTex;
    uniform sampler2D _VignetteTex;
    uniform sampler2D _ScanLineTex;
    fixed _ScanLineTileAmount;
    uniform sampler2D _NoiseTex;
    fixed _NoiseXSpeed;
    fixed _NoiseYSpeed;
    fixed4 _NightVisionColor;
    fixed _Contrast;
    fixed _Brightness;
    fixed _RandomValue;
    fixed _Distortion;
    fixed _Scale;
  3. 我们的特效还需要一个透镜变形(lens distortion)效果,来模拟从透镜中观察、图像边界由于透镜度数而发生变形的效果。在变量声明的下方添加如下代码:
    			float2 barrelDistortion(float2 coord) {
    // Lens distortion algorithm
    // See http://www.ssontech.com/content/lensalg.htm float2 h = coord.xy - float2(0.5, 0.5);
    float r2 = h.x * h.x + h.y * h.y;
    float f = 1.0 + r2 * (_Distortion * sqrt(r2)); return f * _Scale * h + 0.5;
    }

    解释:夜视效果实际和之前的老电影效果很像,都是把一些图层组合、模块化起来。最大的不同就是这里使用了一个透镜变形效果。

    上述算法是由SynthEyes的成员提供的,免费使用哦~第一行代码首先找到纹理的中心——float(0.5, 0.5)。一旦得到了图像中心,我们可以根据像素距离中心的远近对像素应用一个拉伸。具体分析可见本文最下方的相关链接。

  4. 现在,我们到了整个Shader的关键部分。首先,在frag函数里添加如下代码来得到render texture和晕影纹理:
    			fixed4 frag(v2f_img i) : COLOR {
    // Get the colors from the Render Texture and the uv's
    // from the v2f_img struct
    half2 distortedUV = barrelDistortion(i.uv);
    fixed4 renderTex = tex2D(_MainTex, distortedUV);
    fixed4 vignetteTex = tex2D(_VignetteTex, i.uv);

    解释:这三行代码很简单。在得到了变形后的UV坐标后,采样得到render texture中对应的像素,再按正常的UV得到晕影纹理的对应像素。

    最后,得到了底层图像像素renderTex和晕影图层的像素vignetteTex。

  5. 下一步,我们需要处理扫描线和噪点纹理,通过UV坐标为它们添加合适的动画:
    				// Process  scan lines and noise
    half2 scanLinesUV = half2(i.uv.x * _ScanLineTileAmount, i.uv.y * _ScanLineTileAmount);
    fixed4 scanLineTex = tex2D(_ScanLineTex, scanLinesUV); half2 noiseUV = half2(i.uv.x + (_RandomValue * _SinTime.z * _NoiseXSpeed),
    i.uv.y + (_Time.x * _NoiseYSpeed));
    fixed4 noiseTex = tex2D(_NoiseTex, noiseUV);

    解释:这里跟之前老电影的纹理动画处理方式类似。

    对于扫描线纹理,之前我们说过,通过_ScanLineTileAmount可以调整画面上扫描线的宽窄和多少。_ScanLineTileAmount越大,条纹越密越细,反之越宽。

    对于噪点纹理,我们需要添加一个动态效果。在XY方向上处理方式稍有不同,这是为什么呢?我自己的理解是这样的。。。对于X方向,我们想要模拟的是一个小幅度的类似抖动的效果,因此用_SinTime作为因子。而对于Y方向,我们想要模拟向一个方向不断滚动的效果,因此使用_Time作为因子。

    最后,根据上述UV坐标可以得到扫描线纹理的像素scanLineTex和噪点纹理的像素noiseTex。

  6. 最后一层图层是偏绿的颜色效果。我们仅仅需要处理render texture的光度值(Luminance),然后给它添加一个夜视颜色,来得到最终形象的夜视效果:
    				// Get the luminosity values from the render texture using the YIQ values
    fixed lum = dot(fixed3(0.299, 0.587, 0.114), renderTex.rgb);
    lum += _Brightness;
    fixed4 finalColor = (lum * 2) + _NightVisionColor;

    解释:最后一层图层是为整个画面添加绿色色调。首先也是通过YIQ得到当前的光度值lum,再加上_Brightness来调整亮度。最后,再加上_NightVisionColor(也就是绿色)。这里lum*2是为了不至于让整个画面太暗。

  7. 最后,我们把所有的图层结合在一起,返回最终的像素值:
    				// Final output
    finalColor = pow(finalColor, _Contrast);
    finalColor *= vignetteTex;
    finalColor *= scanLineTex * noiseTex; return finalColor;
    }
完整的脚本和Shader如下:
NightVisionEffect脚本:
using UnityEngine;
using System.Collections; [ExecuteInEditMode]
public class NightVisionEffect : MonoBehaviour { #region Variables
public Shader nightVisionShader; public float contrast = 2.0f;
public float brightness = 1.0f;
public Color nightVisionColor = Color.white; public Texture2D vignetteTexture; public Texture2D scanLineTexture;
public float scanLineTileAmount = 4.0f; public Texture2D nightVisionNoise;
public float noiseXSpeed = 100.0f;
public float noiseYSpeed = 100.0f; public float distortion = 0.2f;
public float scale = 0.8f; private Material curMaterial;
private float randomValue = 0.0f;
#endregion #region Properties
public Material material {
get {
if (curMaterial == null) {
curMaterial = new Material(nightVisionShader);
curMaterial.hideFlags = HideFlags.HideAndDontSave;
}
return curMaterial;
}
}
#endregion // Use this for initialization
void Start () {
if (SystemInfo.supportsImageEffects == false) {
enabled = false;
return;
} if (nightVisionShader != null && nightVisionShader.isSupported == false) {
enabled = false;
}
} void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture){
if (nightVisionShader != null) {
material.SetFloat("_Contrast", contrast);
material.SetFloat("_Brightness", brightness);
material.SetColor("_NightVisionColor", nightVisionColor);
material.SetFloat("_RandomValue", randomValue);
material.SetFloat("_Distortion", distortion);
material.SetFloat("_Scale", scale); if (vignetteTexture) {
material.SetTexture("_VignetteTex", vignetteTexture);
} if (scanLineTexture) {
material.SetTexture("_ScanLineTex", scanLineTexture);
material.SetFloat("_ScanLineTileAmount", scanLineTileAmount);
} if (nightVisionNoise) {
material.SetTexture("_NoiseTex", nightVisionNoise);
material.SetFloat("_NoiseXSpeed", noiseXSpeed);
material.SetFloat("_NoiseYSpeed", noiseYSpeed);
} Graphics.Blit(sourceTexture, destTexture, material);
} else {
Graphics.Blit(sourceTexture, destTexture);
}
} // Update is called once per frame
void Update () {
contrast = Mathf.Clamp(contrast, 0.0f, 4.0f);
brightness = Mathf.Clamp(brightness, 0.0f, 2.0f);
distortion = Mathf.Clamp(distortion, -1.0f, 1.0f);
scale = Mathf.Clamp(scale, 0.0f, 3.0f);
randomValue = Random.Range(-1.0f, 1.0f);
} void OnDisable () {
if (curMaterial != null) {
DestroyImmediate(curMaterial);
}
}
}

NightVisionEffectShader如下:

Shader "Custom/NightVisionEffectShader" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_VignetteTex ("Vignette Texture", 2D) = "white" {}
_ScanLineTex ("Scan Line Texture", 2D) = "white" {}
_ScanLineTileAmount ("Scale Line Tile Amount", Float) = 4.0
_NoiseTex ("Noise Texture", 2D) = "white" {}
_NoiseXSpeed ("Noise X Speed", Float) = 100.0
_NoiseYSpeed ("Noise Y Speed", Float) = 100.0
_NightVisionColor ("Night Vision Color", Color) = (1, 1, 1, 1)
_Contrast ("Contrast", Range(0, 4)) = 2
_Brightness ("Brightness", Range(0, 2)) = 1
_RandomValue ("Random Value", Float) = 0
_Distortion ("Distortion", Float) = 0.2
_Scale ("Scale (Zoom)", Float) = 0.8
}
SubShader {
Pass {
CGPROGRAM #pragma vertex vert_img
#pragma fragment frag #include "UnityCG.cginc" uniform sampler2D _MainTex;
uniform sampler2D _VignetteTex;
uniform sampler2D _ScanLineTex;
fixed _ScanLineTileAmount;
uniform sampler2D _NoiseTex;
fixed _NoiseXSpeed;
fixed _NoiseYSpeed;
fixed4 _NightVisionColor;
fixed _Contrast;
fixed _Brightness;
fixed _RandomValue;
fixed _Distortion;
fixed _Scale; float2 barrelDistortion(float2 coord) {
// Lens distortion algorithm
// See http://www.ssontech.com/content/lensalg.htm float2 h = coord.xy - float2(0.5, 0.5);
float r2 = h.x * h.x + h.y * h.y;
float f = 1.0 + r2 * (_Distortion * sqrt(r2)); return f * _Scale * h + 0.5;
} fixed4 frag(v2f_img i) : COLOR {
// Get the colors from the Render Texture and the uv's
// from the v2f_img struct
half2 distortedUV = barrelDistortion(i.uv);
fixed4 renderTex = tex2D(_MainTex, distortedUV);
fixed4 vignetteTex = tex2D(_VignetteTex, i.uv); // Process scan lines and noise
half2 scanLinesUV = half2(i.uv.x * _ScanLineTileAmount, i.uv.y * _ScanLineTileAmount);
fixed4 scanLineTex = tex2D(_ScanLineTex, scanLinesUV); half2 noiseUV = half2(i.uv.x + (_RandomValue * _SinTime.z * _NoiseXSpeed),
i.uv.y + (_Time.x * _NoiseYSpeed));
fixed4 noiseTex = tex2D(_NoiseTex, noiseUV); // Get the luminosity values from the render texture using the YIQ values
fixed lum = dot(fixed3(0.299, 0.587, 0.114), renderTex.rgb);
lum += _Brightness;
fixed4 finalColor = (lum * 2) + _NightVisionColor; // Final output
finalColor = pow(finalColor, _Contrast);
finalColor *= vignetteTex;
finalColor *= scanLineTex * noiseTex; return finalColor;
} ENDCG
}
}
FallBack "Diffuse"
}
完成后返回Unity编辑器查看效果。我们需要在面板中设置对应的图片和属性,像下面这样:

扩展链接

关于透镜形变效果:

【Unity Shaders】游戏性和画面特效——创建一个夜视效果的画面特效的更多相关文章

  1. 【Unity Shaders】游戏性和画面特效——创建一个老电影式的画面特效

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  2. 【Unity Shaders】Transparency —— 使用alpha通道创建透明效果

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  3. 【pygame游戏编程】第一篇-----创建一个窗口

    下面我们一起来创建一个背景为蓝色的窗口作为游戏编程的开始: import sys import pygame def creat_screen(): #初始化pygame pygame.init() ...

  4. 【Unity Shaders】ShadowGun系列之一——飞机坠毁的浓烟效果

    写在前面 最近一直在思考下面的学习该怎么进行,当然自己有在一边做项目一边学OpenGL,偶尔翻翻论文之类的.但是,写shader是一个需要实战和动手经验的过程,而模仿是前期学习的必经之路.很多人都会问 ...

  5. unity编辑器扩展_01(在工具栏中创建一个按钮)

    代码: [MenuItem("Tools/Test",false,1)]    static void Test()    {        Debug.Log("tes ...

  6. 【Unity Shaders】Lighting Models —— 光照模型之Lit Sphere

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  7. 【Unity Shaders】法线纹理(Normal Mapping)的实现细节

    写在前面 写这篇的目的是为了总结我长期以来的混乱.虽然题目是"法线纹理的实现细节",但其实我想讲的是如何在shader中编程正确使用法线进行光照计算.这里面最让人头大的就是各种矩阵 ...

  8. 【Unity Shaders】Transparency —— 使用渲染队列进行深度排序

    本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...

  9. java游戏开发杂谈 - 创建一个窗体

    package game1; import javax.swing.JFrame; /** * java游戏开发杂谈 * ---demo1:创建一个窗体 * * @author 台哥 * @date ...

随机推荐

  1. oracle安装过程和创建本地数据库

    环境: win7 64位 工具: PLSQL Developer 百度云下载:链接:https://pan.baidu.com/s/14L3VCG8YwHzpdhEN7ama0w 密码:jlre or ...

  2. ScalaPB(0): 找寻合适的内部系统微服务集成工具

    前一段时间我们探讨了SDP的一个基于集群的综合数据平台解决方案,由多种数据库组成,包括:JDBC, Cassandra 及MongoDB.其中Cassandra和MongoDB属于分布式数据库,可以在 ...

  3. python中str常用操作

    1. 字符串的操作 字符串的连接操作 符号: + 格式:str1 + str2 例如:str1 = 'I Love' str2 = 'You!' print(str1 + str2) >> ...

  4. Dynamics 365中使用Web API将查找字段的值设置为空值的方法。

    摘要: 本人微信和易信公众号: 微软动态CRM专家罗勇 ,回复270或者20180424可方便获取本文,同时可以在第一间得到我发布的最新的博文信息,follow me!我的网站是 www.luoyon ...

  5. 81. Search in Rotated Sorted Array II (中等)

    Suppose an array sorted in ascending order is rotated at some pivot unknown to you beforehand. (i.e. ...

  6. fireBug引入JQuery,方便书写jq调试代码

    在控制台执行下段代码,等到网络中加载完成后,即可正常运行jq代码.也可以根据需要进行修改引入其他js代码. javascript:(function(url) { var s = document.c ...

  7. 文件操作(open\read\write\close)

    为了方便演示,我们建立了一个示范文本,如下所示: hello,我是代码螺丝钉 test1 test2 test3 文件的读 f = open("示范文本","r" ...

  8. 为什么内部类访问的外部变量需要使用final修饰

    因为生命周期的原因.方法中的局部变量,方法结束后这个变量就要释放掉,final保证这个变量始终指向一个对象.首先,内部类和外部类其实是处于同一个级别,内部类不会因为定义在方法中就会随着方法的执行完毕而 ...

  9. 让你的代码量减少3倍!使用kotlin开发Android(三) 缩短五倍的Java Bean

    回顾一下 哈,没想到你已经坚持不懈看到第三篇了,不错哈~坚持就是胜利. 本文同步自博主的私人博客wing的地方酒馆 在上一篇文章中,我们介绍了扩展函数,这里对上一篇进行一点小小的补充. 还记得text ...

  10. 【完整的App项目】颖火虫笔记v2

    好久没写博客了,一方面是因为最近确实很忙,另一方面自己在改进颖火虫笔记这款App,在前面说过该App主要是模仿的印象笔记,(还不知道的,请看:http://blog.csdn.net/htq__/ar ...