【Unity Shaders】使用Unity Render Textures实现画面特效——建立画面特效脚本系统
本系列主要参考《Unity Shaders and Effects Cookbook》一书(感谢原书作者),同时会加上一点个人理解或拓展。
这里是本书所有的插图。这里是本书所需的代码和资源(当然你也可以从官网下载)。
========================================== 分割线 ==========================================
写在前面
最近由于成为研一新生,被入学的各种事情耽误,好久没有更新博客,好惭愧。。。刚收拾好我就来更新博客啦~快表扬我一下。。。前几天有好心人给我提建议说代码和解释分开容易导致学习不连贯,其实我也有这个感觉。我我为什么不写到注释里呢?一个是因为解释的内容比较多,注释里面说不太清楚;更重要的原因是,我的Mono不支持中文输入。。。只能复制粘贴!如果有人知道Mac的Mono里面怎么输入中文可以告诉我。。。为了让学习过程更连贯,以后的“解释”部分大部分都会移到“实现”中每一步的解释里面!
这节是一个新的章节,倒数第二章喽~Render Texture是一个非常好用的东西,通过本章我们就来学习如何用它来实现一些画面特效,像老电影效果、雪花效果等等。
先来介绍一下本章。学习编写Shader一个很有用的地方就是可以创建各种自定义的画面特效,也被称为后期特效(post effects)。使用这些画面特效,我们可以创建很多美妙的实时图像,例如高光(Bloom),运动模糊(Motion Blur),HDR特效(HDR effects)等等。大多数市面上的现代游戏使用了很多这样的画面特效,以此来得到景深效果,高光效果,甚至是进行颜色矫正。
通过本章,我们将学习如何建立一个脚本系统,来控制创建这些画面特效。我们会学习什么是render texture,什么是深度缓冲,以及怎样像Photoshop那样创建画面特效。通过这些内容,你不仅可以进一步加深Shader知识,还可以自己动脑在Unity中创建很棒的实时渲染。
那么怎样创建画面特效呢?
一般,我们会抓取一个完整的画面图像(或纹理),使用Shader在GPU上处理它的像素后,再返回给Unity的渲染器渲染到屏幕上,也就是一个后期处理的过程。这允许我们可以对渲染后的游戏图像进行实时地逐像素操作,从而给了我们一个全局的控制能力。
我们想象一个场景。我们需要调整游戏最后的画面对比度。如果我们去调整每个材质,虽然的确可能做到,但这会非常麻烦。而通过利用画面特效,我们就可以整体调整画面最后的效果。
为了让我们的画面特效系统能够建立并正常运行,我们需要创建一个单独的脚本来作为游戏当前已渲染的图像(也就是Unity的render texture)的通信员。这个脚本会把当前的render texture传递给Shader。
我们第一个画面特效是一个非常简单的灰度效果。那,开始吧!
准备工作
- 创建一个新的场景,创建一个平行光。
- 创建一个新的C#脚本,命名为TestRenderImage.cs。
- 创建一个新的Shader和Material,命名为ImageEffect。
- 创建一个球体,并把新材质赋给它。这个新的材质可以使用任何Shader,这里我们使用一个简单的红色高光反射材质,Specular Material。
实现
我们总共需要一个脚本和一个Shader。
脚本:负责在编辑中实时更新图像,同时也负责从主摄像机抓取render texture,然后把该texture传递给Shader。
Shader:一旦render texture到达Shader后,我们就在这里面进行逐像素操作。
首先,我们来实现C#脚本。
- 为了让我们在Unity非运行时刻,也可以实时编辑和查看画面效果,我们需要在TestRenderImage类的上面声明下面的语句:
[ExecuteInEditMode]
public class TestRenderImage : MonoBehaviour {解释:ExecuteInEditMode的文档在这里。通常,一个脚本仅会在play模式下运行。而添加了这个声明后,我们的脚本就可以进行一些回调函数,即使Unity并不是在play模式下。而我们常见的一些回调函数的回调方式也有所不同:
Update:在scene发生变化时被调用
OnGUI:在Game视图接受到Event时被调用
OnRenderImage:在Scene视图或Game视图重绘时被调用 - 打开TestRenderImage.cs脚本。首先我们添加一些新的变量:
public class TestRenderImage : MonoBehaviour { #region Variables
public Shader curShader;
public float grayScaleAmount = 1.0f;
private Material curMaterial;
#endregion解释:这里面的#region没有实际作用,仅仅是方面coder阅读源码~
- 因为我们的画面特效要使用Shader来执行对画面图像的像素操作,所以我们需要创建一个材质来运行Shader。如果没有材质,我们就不可以访问Shader的属性。因此,我们在C#中创建一个属性来检查是否有这样一个材质,如果没有就创建一个。
#region Properties
public Material material {
get {
if (curMaterial == null) {
curMaterial = new Material(curShader);
curMaterial.hideFlags = HideFlags.HideAndDontSave;
}
return curMaterial;
}
}
#endregion - 现在,我们需要建立一些检查,来看看当前的游戏平台是否支持画面特效。如果不支持,我们在脚本的一开始就disable它:
// Use this for initialization
void Start () {
if (SystemInfo.supportsImageEffects == false) {
enabled = false;
return;
} if (curShader != null && curShader.isSupported == false) {
enabled = false;
}
} - 为了真正从Unity渲染器中抓取被渲染过的图像,我们需要使用Unity内置的OnRenderImage函数。下面的代码允许我们访问当前被渲染的图像:
void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture){
if (curShader != null) {
material.SetFloat("_LuminosityAmount", grayScaleAmount); Graphics.Blit(sourceTexture, destTexture, material);
} else {
Graphics.Blit(sourceTexture, destTexture);
}
}解释:一旦通过上述的各种检查,我们就需要调用内置的OnRenderImage函数来实现画面特效。这个函数负责从Unity渲染器中抓取当前的render texture,然后使用Graphics.Blit()函数再传递给Shader(通过sourceTexture参数),然后再返回一个处理后的图像再次传递回给Unity渲染器(通过destTexture参数)。这两个行数互相搭配是处理画面特效的很常见的方法。你可以在下面的连接中找到这两个函数更详细的信息:
OnRenderImage:该摄像机上的任何脚本都可以收到这个回调(意味着你必须把它附到一个Camera上)。允许你修改最后的Render Texture。
Graphics.Blit:sourceTexture会成为material的_MainTex。 - 我们的画面效果需要一个名为的变量,它是用于控制最终屏幕效果的灰度值的。因此,我们需要保证这个值的范围在0到1,0表示没有任何灰度效果,1表示完全灰度化。我们在Update函数里执行这个操作:
// Update is called once per frame
void Update () {
grayScaleAmount = Mathf.Clamp(grayScaleAmount, 0.0f, 1.0f);
} - 最后,由于我们在一开始创建了一个虚拟材质,我们需要在脚本被disable时,销毁该对象:
void OnDisable () {
if (curMaterial != null) {
DestroyImmediate(curMaterial);
}
}
using UnityEngine;
using System.Collections; [ExecuteInEditMode]
public class TestRenderImage : MonoBehaviour { #region Variables
public Shader curShader;
public float grayScaleAmount = 1.0f;
private Material curMaterial;
#endregion #region Properties
public Material material {
get {
if (curMaterial == null) {
curMaterial = new Material(curShader);
curMaterial.hideFlags = HideFlags.HideAndDontSave;
}
return curMaterial;
}
}
#endregion // Use this for initialization
void Start () {
if (SystemInfo.supportsImageEffects == false) {
enabled = false;
return;
} if (curShader != null && curShader.isSupported == false) {
enabled = false;
}
} void OnRenderImage (RenderTexture sourceTexture, RenderTexture destTexture){
if (curShader != null) {
material.SetFloat("_LuminosityAmount", grayScaleAmount); Graphics.Blit(sourceTexture, destTexture, material);
} else {
Graphics.Blit(sourceTexture, destTexture);
}
} // Update is called once per frame
void Update () {
grayScaleAmount = Mathf.Clamp(grayScaleAmount, 0.0f, 1.0f);
} void OnDisable () {
if (curMaterial != null) {
DestroyImmediate(curMaterial);
}
}
}
- 首先还是需要一些新的属性:
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_LuminosityAmount ("GrayScale Amount", Range(0.0, 1.0)) = 1.0
}解释:之前提到,通过Graphics.Blit()函数,我们可以把抓取获得的Render Texture作为该材质的_MainTex属性传递给Shader。
- 这次的Shader需要利用纯正的Cg Shader代码,而不是Unity内置的Surface Shader。这会使得我们更加优化画面特效,因为我们仅仅需要去计算render texture的像素。因此,我们创建一个新的pass,并使用一些新的#pragma声明:
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag #include "UnityCG.cginc"解释:这涉及到了Vertex&Fragment Shader的知识。呜,后面补一篇入门级文章好了。这里简单解释一下。Vertex&Fragment Shader如它的名字一样,需要我们实现两个函数:vertex和fragment。vertex函数负责把模型顶点位置转换到摄像机的透视坐标系中,以及处理模型各顶点对应的纹理坐标等。fragment则负责输出最后屏幕每个像素的颜色值。这里,第一个#pragma指明我们使用名为vet_img的vertex函数,这个函数Unity为我们写好了,在UnityCG.cginc里,它就是完成了两个最简单的计算,计算顶点对应的透视坐标以及纹理坐标。第二个#pragma指明我们使用名为frag的fragment函数,这是在下面的步骤里实现的。
- 为了访问新的属性,我们需要在CGPROGRAM里创建对应的变量:
uniform sampler2D _MainTex;
fixed _LuminosityAmount; - 最后,我们仅仅需要编写自己的像素函数,也就是这里的frag()函数。这是画面特效中真正改变画面像素值的地方。这个函数将会处理render texture的每个像素,然后向TestRenderImage.cs返回一个新的图像:
fixed4 frag(v2f_img i) : COLOR
{
//Get the colors from the RenderTexture and the uv's
//from the v2f_img struct
fixed4 renderTex = tex2D(_MainTex, i.uv); //Apply the Luminosity values to our render texture
float luminosity = 0.299 * renderTex.r + 0.587 * renderTex.g + 0.114 * renderTex.b;
fixed4 finalColor = lerp(renderTex, luminosity, _LuminosityAmount); return finalColor;
}
Shader "Custom/ImageEffect" {
Properties {
_MainTex ("Base (RGB)", 2D) = "white" {}
_LuminosityAmount ("GrayScale Amount", Range(0.0, 1.0)) = 1.0
}
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert_img
#pragma fragment frag #include "UnityCG.cginc" uniform sampler2D _MainTex;
fixed _LuminosityAmount; fixed4 frag(v2f_img i) : COLOR
{
//Get the colors from the RenderTexture and the uv's
//from the v2f_img struct
fixed4 renderTex = tex2D(_MainTex, i.uv); //Apply the Luminosity values to our render texture
float luminosity = 0.299 * renderTex.r + 0.587 * renderTex.g + 0.114 * renderTex.b;
fixed4 finalColor = lerp(renderTex, luminosity, _LuminosityAmount); return finalColor;
} ENDCG
}
}
FallBack "Diffuse"
}
上面的过程展示了我们如何使用一种方便的方法检验画面特效Shader。下面,我们来更深入地了解这其中render texture到底发生了什么变化,以及从头到尾它们到底是怎么被处理的。
解释
画面特效是顺序处理的,这就像Photoshop中的layers。如果你有多个屏幕特效,你可以按顺序添加给该Camera,那么它们就会按照这个顺序被处理。
上述的过程是被简化过的,但通过这些我们可以看出画面特效的核心是如何工作的。最后,我们总结上述使用Render Texture实现画面特效的核心过程:
- 在脚本中检查当前平台对特效的支持;
- 通过OnRenderImage()函数抓取render texture,再通过Graphics.Blit()函数传递给虚拟材质中的Shader进行后处理;
- Shader的_MainTex即为接收到的render texture,在frag函数里对图像进行逐像素处理后再返回给OnRenderImage函数,得到最后的屏幕画面。
【Unity Shaders】使用Unity Render Textures实现画面特效——建立画面特效脚本系统的更多相关文章
- 【Unity Shaders】Unity里的雾效模拟
写在前面 熟悉Unity的都知道,Unity可以进行基本的雾效模拟.所谓雾效,就是在远离我们视角的方向上,物体看起来像被蒙上了某种颜色(通常是灰色).这种技术的实现实际上非常简单,就是根据物体距离摄像 ...
- 【Unity Shaders】使用Unity Render Textures实现画面特效——画面特效中的亮度、饱和度和对照度
本系列主要參考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同一时候会加上一点个人理解或拓展. 这里是本书全部的插图. 这里是本书所需的代码 ...
- 【Unity Shaders】游戏性和画面特效——创建一个夜视效果的画面特效
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 【Unity Shaders】游戏性和画面特效——创建一个老电影式的画面特效
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 【Unity Shaders】Using Textures for Effects —— 实现Photoshop的色阶效果
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 【Unity Shaders】Using Textures for Effects——打包和混合textures
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 【Unity Shaders】Using Textures for Effects——让sprite sheets动起来
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 【Unity Shaders】Using Textures for Effects——通过修改UV坐标来滚动textures
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
- 【Unity Shaders】Using Textures for Effects介绍
本系列主要参考<Unity Shaders and Effects Cookbook>一书(感谢原书作者),同时会加上一点个人理解或拓展. 这里是本书所有的插图.这里是本书所需的代码和资源 ...
随机推荐
- Linux配置服务器的一点总结
一.Linux初始化服务 首先搞清楚四个概念: 进程:正在运行的程序,有自己独立的内存空间. 线程:是进程的下属单位,开销较进程小,没有自己独立的内存空间. 作业:由一系列进程组成,来完成某一项任务. ...
- C++内存机制中内存溢出、内存泄露、内存越界和栈溢出的区别和联系
当我们在用C++做底层驱动的时候,经常会遇到内存不足的警告,究其原因,往往是因为内存出现溢出,泄露或者越界等原因.那么他们之间有什么联系吗? 内存溢出(out of memory) 是指程序在申请内存 ...
- C语言程序设计预报作业
1. 阅读邹欣老师的博客--师生关系,针对文中的几种师生关系谈谈你的看法,你期望的师生关系是什么样的? 答:万物都是变化的,师生关系也一样.小学中学把老师看作春蚕,蜡烛的的比喻到了大学显然行不通了.大 ...
- eclipse创建web项目修改路径
- sb error
width: $("#StudentManagement").parent().width(), height: $("#StudentManagement") ...
- 正确在遍历中删除List元素
最近在写代码的时候遇到了遍历时删除List元素的问题,在此写一篇博客记录一下. 一般而言,遍历List元素有以下三种方式: 使用普通for循环遍历 使用增强型for循环遍历 使用iterator遍历 ...
- Mysql中where条件一个单引号引发的性能损耗
日常写SQL中可能会有一些小细节忽略了导致整个sql的性能下降了好几倍甚至几十倍,几百倍.以下这个示例就是mysql语句中的一个单引号('')引发的性能耗损,我相信很多朋友都遇到过,甚至还在这样写. ...
- setuptools安装和错误解决
错误解决:ImportError No module named setuptools GitHub: https://github.com/pypa/setuptools 下载安装 wget htt ...
- Firebird数据库相关操作
Firebird常用SQL 一.分页写法小例: 1 select first 10 templateid,code,name from template ; 2 select first 10 ski ...
- popen() 使用举例 (转载)
函数原型: #include "stdio.h" FILE *popen( const char* command, const char* mode ) 参数说明: comman ...