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

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

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

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

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

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

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

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

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

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

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

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

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

  7. 最后,我们把所有的图层结合在一起,返回最终的像素值:
    1. // Final output
    2. finalColor = pow(finalColor, _Contrast);
    3. finalColor *= vignetteTex;
    4. finalColor *= scanLineTex * noiseTex;
    5.  
    6. return finalColor;
    7. }
完整的脚本和Shader如下:
NightVisionEffect脚本:
  1. using UnityEngine;
  2. using System.Collections;
  3.  
  4. [ExecuteInEditMode]
  5. public class NightVisionEffect : MonoBehaviour {
  6.  
  7. #region Variables
  8. public Shader nightVisionShader;
  9.  
  10. public float contrast = 2.0f;
  11. public float brightness = 1.0f;
  12. public Color nightVisionColor = Color.white;
  13.  
  14. public Texture2D vignetteTexture;
  15.  
  16. public Texture2D scanLineTexture;
  17. public float scanLineTileAmount = 4.0f;
  18.  
  19. public Texture2D nightVisionNoise;
  20. public float noiseXSpeed = 100.0f;
  21. public float noiseYSpeed = 100.0f;
  22.  
  23. public float distortion = 0.2f;
  24. public float scale = 0.8f;
  25.  
  26. private Material curMaterial;
  27. private float randomValue = 0.0f;
  28. #endregion
  29.  
  30. #region Properties
  31. public Material material {
  32. get {
  33. if (curMaterial == null) {
  34. curMaterial = new Material(nightVisionShader);
  35. curMaterial.hideFlags = HideFlags.HideAndDontSave;
  36. }
  37. return curMaterial;
  38. }
  39. }
  40. #endregion
  41.  
  42. // Use this for initialization
  43. void Start () {
  44. if (SystemInfo.supportsImageEffects == false) {
  45. enabled = false;
  46. return;
  47. }
  48.  
  49. if (nightVisionShader != null && nightVisionShader.isSupported == false) {
  50. enabled = false;
  51. }
  52. }
  53.  
  54. void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture){
  55. if (nightVisionShader != null) {
  56. material.SetFloat("_Contrast", contrast);
  57. material.SetFloat("_Brightness", brightness);
  58. material.SetColor("_NightVisionColor", nightVisionColor);
  59. material.SetFloat("_RandomValue", randomValue);
  60. material.SetFloat("_Distortion", distortion);
  61. material.SetFloat("_Scale", scale);
  62.  
  63. if (vignetteTexture) {
  64. material.SetTexture("_VignetteTex", vignetteTexture);
  65. }
  66.  
  67. if (scanLineTexture) {
  68. material.SetTexture("_ScanLineTex", scanLineTexture);
  69. material.SetFloat("_ScanLineTileAmount", scanLineTileAmount);
  70. }
  71.  
  72. if (nightVisionNoise) {
  73. material.SetTexture("_NoiseTex", nightVisionNoise);
  74. material.SetFloat("_NoiseXSpeed", noiseXSpeed);
  75. material.SetFloat("_NoiseYSpeed", noiseYSpeed);
  76. }
  77.  
  78. Graphics.Blit(sourceTexture, destTexture, material);
  79. } else {
  80. Graphics.Blit(sourceTexture, destTexture);
  81. }
  82. }
  83.  
  84. // Update is called once per frame
  85. void Update () {
  86. contrast = Mathf.Clamp(contrast, 0.0f, 4.0f);
  87. brightness = Mathf.Clamp(brightness, 0.0f, 2.0f);
  88. distortion = Mathf.Clamp(distortion, -1.0f, 1.0f);
  89. scale = Mathf.Clamp(scale, 0.0f, 3.0f);
  90. randomValue = Random.Range(-1.0f, 1.0f);
  91. }
  92.  
  93. void OnDisable () {
  94. if (curMaterial != null) {
  95. DestroyImmediate(curMaterial);
  96. }
  97. }
  98. }

NightVisionEffectShader如下:

  1. Shader "Custom/NightVisionEffectShader" {
  2. Properties {
  3. _MainTex ("Base (RGB)", 2D) = "white" {}
  4. _VignetteTex ("Vignette Texture", 2D) = "white" {}
  5. _ScanLineTex ("Scan Line Texture", 2D) = "white" {}
  6. _ScanLineTileAmount ("Scale Line Tile Amount", Float) = 4.0
  7. _NoiseTex ("Noise Texture", 2D) = "white" {}
  8. _NoiseXSpeed ("Noise X Speed", Float) = 100.0
  9. _NoiseYSpeed ("Noise Y Speed", Float) = 100.0
  10. _NightVisionColor ("Night Vision Color", Color) = (1, 1, 1, 1)
  11. _Contrast ("Contrast", Range(0, 4)) = 2
  12. _Brightness ("Brightness", Range(0, 2)) = 1
  13. _RandomValue ("Random Value", Float) = 0
  14. _Distortion ("Distortion", Float) = 0.2
  15. _Scale ("Scale (Zoom)", Float) = 0.8
  16. }
  17. SubShader {
  18. Pass {
  19. CGPROGRAM
  20.  
  21. #pragma vertex vert_img
  22. #pragma fragment frag
  23.  
  24. #include "UnityCG.cginc"
  25.  
  26. uniform sampler2D _MainTex;
  27. uniform sampler2D _VignetteTex;
  28. uniform sampler2D _ScanLineTex;
  29. fixed _ScanLineTileAmount;
  30. uniform sampler2D _NoiseTex;
  31. fixed _NoiseXSpeed;
  32. fixed _NoiseYSpeed;
  33. fixed4 _NightVisionColor;
  34. fixed _Contrast;
  35. fixed _Brightness;
  36. fixed _RandomValue;
  37. fixed _Distortion;
  38. fixed _Scale;
  39.  
  40. float2 barrelDistortion(float2 coord) {
  41. // Lens distortion algorithm
  42. // See http://www.ssontech.com/content/lensalg.htm
  43.  
  44. float2 h = coord.xy - float2(0.5, 0.5);
  45. float r2 = h.x * h.x + h.y * h.y;
  46. float f = 1.0 + r2 * (_Distortion * sqrt(r2));
  47.  
  48. return f * _Scale * h + 0.5;
  49. }
  50.  
  51. fixed4 frag(v2f_img i) : COLOR {
  52. // Get the colors from the Render Texture and the uv's
  53. // from the v2f_img struct
  54. half2 distortedUV = barrelDistortion(i.uv);
  55. fixed4 renderTex = tex2D(_MainTex, distortedUV);
  56. fixed4 vignetteTex = tex2D(_VignetteTex, i.uv);
  57.  
  58. // Process scan lines and noise
  59. half2 scanLinesUV = half2(i.uv.x * _ScanLineTileAmount, i.uv.y * _ScanLineTileAmount);
  60. fixed4 scanLineTex = tex2D(_ScanLineTex, scanLinesUV);
  61.  
  62. half2 noiseUV = half2(i.uv.x + (_RandomValue * _SinTime.z * _NoiseXSpeed),
  63. i.uv.y + (_Time.x * _NoiseYSpeed));
  64. fixed4 noiseTex = tex2D(_NoiseTex, noiseUV);
  65.  
  66. // Get the luminosity values from the render texture using the YIQ values
  67. fixed lum = dot(fixed3(0.299, 0.587, 0.114), renderTex.rgb);
  68. lum += _Brightness;
  69. fixed4 finalColor = (lum * 2) + _NightVisionColor;
  70.  
  71. // Final output
  72. finalColor = pow(finalColor, _Contrast);
  73. finalColor *= vignetteTex;
  74. finalColor *= scanLineTex * noiseTex;
  75.  
  76. return finalColor;
  77. }
  78.  
  79. ENDCG
  80. }
  81. }
  82. FallBack "Diffuse"
  83. }
完成后返回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. [bzoj1041][HAOI2008]圆上的整点

    我能想得出怎么做才奇怪好吗 题解:http://blog.csdn.net/csyzcyj/article/details/10044629 #include<iostream> #inc ...

  2. Python的序列类型——List

    List 列表 List,本质是一个链表,从链表的实现角度来讲,链表的每一个结点都存放着值和指向下一个节点的指针. 因此链表在内存的存储可以是不连续的,它是一种高效的数据结构.因此列表与字符串的区别是 ...

  3. postman 模拟请求中添加 header,post请求中传json参数

    1. GET 请求 2.Post 请求 (请求参数为Json,header中带有参数) 问题延伸 GET请求不能够 添加 Body 吗?[答案]

  4. Java 中 json字符串转换为类

    使用到alibaba.fastjson包 具体实现 JSONObject jsonObject = JSONObject.parseObject(msg); SmsSenderStatus smsSe ...

  5. 入口开始,解读Vue源码(一)-- 造物创世

    Why? 网上现有的Vue源码解析文章一搜一大批,但是为什么我还要去做这样的事情呢?因为觉得纸上得来终觉浅,绝知此事要躬行. 然后平时的项目也主要是Vue,在使用Vue的过程中,也对其一些约定产生了一 ...

  6. 谷歌Chrome浏览器之No Sandbox

     想着还是要把这个分享出来,前两天,早上过来,Chrome打开后,输入网址回车,毫无反应,不加载,不跳转,打不开浏览器设置.总之就是除了能打开Chrome浏览器,不能进行任何其他操作,关闭重开也是这样 ...

  7. JAVA 访问WebRoot下的目录文件

    转自 http://blog.csdn.net/jian_csdn/article/details/46119313 ClassLoader classLoader = Thread.currentT ...

  8. 传统方法过渡到ES6去优雅地实现JavaScript的继承

    众所周知,面向对象编程有三个重要的概念: 封装.继承.多态.而JS作为面向对象的弱类型语言,应该说是基于对象的语言,正如常说的,JS的世界里,万物皆对象.虽然JS本身不是面向对象的语言,我们可以通过模 ...

  9. 3.5 find() 判断是否存在某元素

    vector 判断是否存在某元素: if(find(A.begin(), A.end(), A[i]) != A.end()){ // 若存在 A[i] // find() 返回一个指针 }

  10. Node.js系列文章:利用console输出日志文件

    通常我们在写Node.js程序时,都习惯使用console.log打印日志信息,但这也仅限于控制台输出,有时候我们需要将信息输出到日志文件中,实际上利用console也可以达到这个目的的,今天就来简单 ...