【Unity Shaders】游戏性和画面特效——创建一个夜视效果的画面特效
本系列主要参考《Unity Shaders and Effects Cookbook》一书(感谢原书作者),同时会加上一点个人理解或拓展。
这里是本书所有的插图。这里是本书所需的代码和资源(当然你也可以从官网下载)。
========================================== 分割线 ==========================================
写在前面
呜呼,这本书最后一篇了有木有!!!想想就有点小激动呢。。。(可是还有很多隔过去了。)技术文章里就不说太多废话了,之后会写一篇总结的,坑也会慢慢补上的(我希望吧。)。
好啦!我们本节要学习的特效是一个更加常见的画面特效。在市场上很多的第一人称射击游戏(FPS)中,我们都可以看到夜视效果的身影,比如《使命召唤》、《光晕》等等。这种效果使屏幕蒙上一层非常独特的黄绿色。下面是《使命召唤》中的夜视效果:
为了实现这种夜视效果,我们需要使用Photoshop来分解上述效果的实现。我们可以从网上找一些参考图片,然后和已有图层合并,看看我们需要哪种混合效果,以及我们要怎样组合不同的图层。下面的图像显示了在Photoshop中进行上述过程的最后结果:
而最后Shader实现的结果像下面这样:
下面,我们开始将Photoshop中的最终图像分解成几个部分,来更好的理解这些资源是如何被整合在一起的。而后,我们会深入讲解实现过程。
准备工作
之前说过,使用Photoshop可以让我们方便地构分层图像,以便我们可以更好地理解如何得到一个夜视效果。现在,我们就把夜视效果分解成几个组成图层。
- 偏绿色的画面(Tinted green):夜视效果的第一个图层就是它标志性的绿色,几乎所有的夜视图像都是这种色调的。这可以让我们的特效看起来就是明显的夜视效果。
- 扫描线(Scan lines):为了增加夜视效果的真实性,我们在上述着色层的上面添加了一层扫描线。在这里,我们使用了一张用Photoshop创建的纹理,然后让用户选择平铺系数来控制扫描线的宽窄和大小。
- 噪点(Noise):下一个图层是一个简单的噪点图层,它平铺在上述两种图层的上方,用于分解图像为我们的特效添加更多的细节效果。
- 晕影(Vignette):夜视效果的最后一层就是晕影。从上面《使命召唤》的游戏截图可以看出,它使用一个晕影来模拟一个视野效果。我们使用下面的图层来作为晕影纹理。
- 准备好一张晕影纹理、一张噪声纹理和一张扫描线纹理,你可以在本书资源(见本文最上方)中找到对应的纹理。
- 创建一个新的脚本,命名为NightVisionEffect.cs,以及一个新的Shader,命名为NightVisionEffectShader.shader。
- 使用前一章第一篇里的代码填充上述新的脚本和Shader。
- 把OldFilmEffect脚本添加到Camera上,并使用OldFilmEffectShader给OldFilmEffect脚本中的Cur Shader赋值。
实现
- 第一步我们要定义一些需要在面板中显示的变量,以便让用户进行调整。在脚本中添加如下代码:
#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 - 下面,我们需要填充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);
}
} - 最后,我们需要保证一些变量的范围。这些范围可以根据需要后续更改:
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);
}
- 首先,还是添加一些新的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
} - 我们还需要在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; - 我们的特效还需要一个透镜变形(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)。一旦得到了图像中心,我们可以根据像素距离中心的远近对像素应用一个拉伸。具体分析可见本文最下方的相关链接。
- 现在,我们到了整个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。
- 下一步,我们需要处理扫描线和噪点纹理,通过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。
- 最后一层图层是偏绿的颜色效果。我们仅仅需要处理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是为了不至于让整个画面太暗。
- 最后,我们把所有的图层结合在一起,返回最终的像素值:
// Final output
finalColor = pow(finalColor, _Contrast);
finalColor *= vignetteTex;
finalColor *= scanLineTex * noiseTex; return finalColor;
}
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 Shaders】游戏性和画面特效——创建一个夜视效果的画面特效的更多相关文章
- 【Unity Shaders】游戏性和画面特效——创建一个老电影式的画面特效
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 【Unity Shaders】Transparency —— 使用alpha通道创建透明效果
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 【pygame游戏编程】第一篇-----创建一个窗口
下面我们一起来创建一个背景为蓝色的窗口作为游戏编程的开始: import sys import pygame def creat_screen(): #初始化pygame pygame.init() ...
- 【Unity Shaders】ShadowGun系列之一——飞机坠毁的浓烟效果
写在前面 最近一直在思考下面的学习该怎么进行,当然自己有在一边做项目一边学OpenGL,偶尔翻翻论文之类的.但是,写shader是一个需要实战和动手经验的过程,而模仿是前期学习的必经之路.很多人都会问 ...
- unity编辑器扩展_01(在工具栏中创建一个按钮)
代码: [MenuItem("Tools/Test",false,1)] static void Test() { Debug.Log("tes ...
- 【Unity Shaders】Lighting Models —— 光照模型之Lit Sphere
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 【Unity Shaders】法线纹理(Normal Mapping)的实现细节
写在前面 写这篇的目的是为了总结我长期以来的混乱.虽然题目是"法线纹理的实现细节",但其实我想讲的是如何在shader中编程正确使用法线进行光照计算.这里面最让人头大的就是各种矩阵 ...
- 【Unity Shaders】Transparency —— 使用渲染队列进行深度排序
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- java游戏开发杂谈 - 创建一个窗体
package game1; import javax.swing.JFrame; /** * java游戏开发杂谈 * ---demo1:创建一个窗体 * * @author 台哥 * @date ...
随机推荐
- I/O控制的主要功能
主要功能: 1. 解释用户的I/O系统调用.将用户I/O系统调用转换为I/O控制模块认识的命令模式. 2. 设备驱动.根据得到的I/O命令,启动物理设备完成指定的I/O操作. 3. 中断处理.对 ...
- 【.NET Core】docker Jenkins ASP.NET Core自动化部署
本文基于GitHub演示自动化部署,实际上你可以选择任意的Git托管环境. 使用的模式:DooD(Docker-outside-of-Docker). 本文所有内容均开源 链接 欢迎关注我的GitHu ...
- 一个页面从输入url到页面加载显示完成,中间都经历了什么
第一种解释: 一般会经历以下几个过程: 1.首先,在浏览器地址栏中输入url 2.浏览器先查看浏览器缓存-系统缓存-路由器缓存,如果缓存中有,会直接在屏幕中显示页面内容.若没有,则跳到第三步操作. 3 ...
- 解决scroll在ios上卡顿问题和兼容ios不支持:active伪类情况
//有时候因为滚动层级元素过多会产生卡顿,特别在ios上十分明显,如果不想更换其他实现方式,可以加:-webkit-overflow-scrolling: touch; 开启硬件加速: 兼容ios不支 ...
- Uncaught (in promise) TypeError:的错误
1.错误 创建一个vue实例,在data定义一些变量,如activityTime. 在methods里面用了axios发送请求. 在then的回调里使用this.activityTime 报错! 2. ...
- DDD实战进阶第一波(八):开发一般业务的大健康行业直销系统(业务逻辑条件判断最佳实践)
这篇文章其实是大健康行业直销系统的番外篇,主要给大家讲讲如何在领域逻辑中,有效的处理业务逻辑条件判断的最佳实践问题. 大家都知道,聚合根.实体和值对象这些领域对象都自身处理自己的业务逻辑.在业务处理过 ...
- 天梯赛-L1-018. 大笨钟
L1-018. 大笨钟 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 陈越 微博上有个自称"大笨钟V"的家伙,每 ...
- Java8 按照类属性去重
测试po package com.shiwulian.test.po; public class Person {private String id;private String name;priva ...
- Docker常见仓库WordPress
WordPress 基本信息 WordPress 是开源的 Blog 和内容管理系统框架,它基于 PhP 和 MySQL. 该仓库提供了 WordPress 4.0 版本的镜像. 使用方法 启动容器需 ...
- 实战 PureMVC
最近看PureMVC,在IBM开发者社区发现此文,对PureMVC的讲解清晰简洁,看了可快速入门.另外,<腾讯桌球>游戏的开发者吴秦,也曾进一步剖析PureMVC,可结合看加深理解. 引言 ...