在上一篇文章中有提到过,Unity中的屏幕特效通常分为两部分来实现:
- Shader实现部分
- 脚本实现部分
下面依然是从这两个方面对本次的特效进行实现。
而在这之前,需要准备好一张水滴(水滴太多了也就成了水幕了)的效果图片(google“water drop”一下,稍微筛选一下就有了,最好是能找到或者自己加工成无缝衔接的),放置于我们特效的脚本实现文件目录附加的一个Resources的文件夹中,那么我们在脚本中适当的地方写上一句:
Texture2 = Resources.Load("ScreenWaterDrop")as Texture2D;
就可以读取到这张图片了。
浅墨准备的图片如下(无水印本图片的可以在浅墨的Github中找到,或者直接下文章末尾的项目工程)。
ScreenWaterDrop.png:
Shader实现部分
老规矩,先上详细注释的代码。
//-----------------------------------------------【Shader脚本说明】---------------------------------------------------
// 屏幕水幕特效的实现代码-Shader脚本部分
// 2015年10月 Created by 浅墨
// 更多内容或交流,请访问浅墨的博客:http://blog.csdn.net/poem_qianmo
//---------------------------------------------------------------------------------------------------------------------
Shader "浅墨Shader编程/Volume9/ScreenWaterDropEffect"
{
//------------------------------------【属性值】------------------------------------
Properties
{
//主纹理
_MainTex ("Base (RGB)", 2D) = "white" {}
//屏幕水滴的素材图
_ScreenWaterDropTex ("Base (RGB)", 2D) = "white" {}
//当前时间
_CurTime ("Time", Range(0.0, 1.0)) = 1.0
//X坐标上的水滴尺寸
_SizeX ("SizeX", Range(0.0, 1.0)) = 1.0
//Y坐标上的水滴尺寸
_SizeY ("SizeY", Range(0.0, 1.0)) = 1.0
//水滴的流动速度
_DropSpeed ("Speed", Range(0.0, 10.0)) = 1.0
//溶解度
_Distortion ("_Distortion", Range(0.0, 1.0)) = 0.87
}
//------------------------------------【唯一的子着色器】------------------------------------
SubShader
{
Pass
{
//设置深度测试模式:渲染所有像素.等同于关闭透明度测试(AlphaTest Off)
ZTest Always
//===========开启CG着色器语言编写模块===========
CGPROGRAM
//编译指令:告知编译器顶点和片段着色函数的名称
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
//编译指令: 指定着色器编译目标为Shader Model 3.0
#pragma target 3.0
//包含辅助CG头文件
#include "UnityCG.cginc"
//外部变量的声明
uniform sampler2D _MainTex;
uniform sampler2D _ScreenWaterDropTex;
uniform float _CurTime;
uniform float _DropSpeed;
uniform float _SizeX;
uniform float _SizeY;
uniform float _Distortion;
uniform float2 _MainTex_TexelSize;
//顶点输入结构
struct vertexInput
{
float4 vertex : POSITION;//顶点位置
float4 color : COLOR;//颜色值
float2 texcoord : TEXCOORD0;//一级纹理坐标
};
//顶点输出结构
struct vertexOutput
{
half2 texcoord : TEXCOORD0;//一级纹理坐标
float4 vertex : SV_POSITION;//像素位置
fixed4 color : COLOR;//颜色值
};
//--------------------------------【顶点着色函数】-----------------------------
// 输入:顶点输入结构体
// 输出:顶点输出结构体
//---------------------------------------------------------------------------------
vertexOutput vert(vertexInput Input)
{
//【1】声明一个输出结构对象
vertexOutput Output;
//【2】填充此输出结构
//输出的顶点位置为模型视图投影矩阵乘以顶点位置,也就是将三维空间中的坐标投影到了二维窗口
Output.vertex = mul(UNITY_MATRIX_MVP, Input.vertex);
//输出的纹理坐标也就是输入的纹理坐标
Output.texcoord = Input.texcoord;
//输出的颜色值也就是输入的颜色值
Output.color = Input.color;
//【3】返回此输出结构对象
return Output;
}
//--------------------------------【片段着色函数】-----------------------------
// 输入:顶点输出结构体
// 输出:float4型的颜色值
//---------------------------------------------------------------------------------
fixed4 frag(vertexOutput Input) : COLOR
{
//【1】获取顶点的坐标值
float2 uv = Input.texcoord.xy;
//【2】解决平台差异的问题。校正方向,若和规定方向相反,则将速度反向并加1
#if UNITY_UV_STARTS_AT_TOP
if (_MainTex_TexelSize.y < 0)
_DropSpeed = 1 - _DropSpeed;
#endif
//【3】设置三层水流效果,按照一定的规律在水滴纹理上分别进行取样
float3 rainTex1 = tex2D(_ScreenWaterDropTex, float2(uv.x * 1.15* _SizeX, (uv.y* _SizeY *1.1) + _CurTime* _DropSpeed *0.15)).rgb / _Distortion;
float3 rainTex2 = tex2D(_ScreenWaterDropTex, float2(uv.x * 1.25* _SizeX - 0.1, (uv.y *_SizeY * 1.2) + _CurTime *_DropSpeed * 0.2)).rgb / _Distortion;
float3 rainTex3 = tex2D(_ScreenWaterDropTex, float2(uv.x* _SizeX *0.9, (uv.y *_SizeY * 1.25) + _CurTime * _DropSpeed* 0.032)).rgb / _Distortion;
//【4】整合三层水流效果的颜色信息,存于finalRainTex中
float2 finalRainTex = uv.xy - (rainTex1.xy - rainTex2.xy - rainTex3.xy) / 3;
//【5】按照finalRainTex的坐标信息,在主纹理上进行采样
float3 finalColor = tex2D(_MainTex, float2(finalRainTex.x, finalRainTex.y)).rgb;
//【6】返回加上alpha分量的最终颜色值
return fixed4(finalColor, 1.0);
}
//===========结束CG着色器语言编写模块===========
ENDCG
}
}
}
屏幕特效Shader中真正有营养的核心代码,一般都是位于像素着色器中。也就是这里的frag函数中,稍微聊一聊。
第一步,先从顶点着色器输出结构体中获取顶点的坐标:
- //【1】获取顶点的坐标值
- float2 uv = Input.texcoord.xy;
第二步,因为需要水幕纹理在屏幕中从上向下滚动,而不同平台的原点位置是不同的,Direct3D原点在左上角,OpenGL原点在左下角。所以在这边需要对情况进行统一。
统一的方法里用到了UNITY_UV_STARTS_AT_TOP宏,它是Unity中内置的宏,其值 取为1 或0 ; 在纹理 V 坐标在“纹理顶部”为零的平台上值取 1。如Direct3D;而OpenGL 平台上取 0。
另外,这边还用到了MainTex_TexelSize变量。
float2型的MainTex_TexelSize(其实是_TexelSize后缀,前面的名称会根据定义纹理变量的改变而改变)也是Unity中内置的一个变量,存放了纹理中单个像素的尺寸,也就是说,如果有一张2048 x 2048的纹理,那么x和y的取值都为1.0/2048.0。而且需要注意,当该纹理被Direct3D的抗锯齿操作垂直反转过后,xxx_TexelSize的值将为负数。
所以,第二步的代码就是如下:
- //【2】解决平台差异的问题。校正方向,若和规定方向相反,则将速度反向并加1
- #if UNITY_UV_STARTS_AT_TOP
- if(_MainTex_TexelSize.y < 0)
- _DropSpeed= 1 - _DropSpeed;
- #endif
第三步,定义三层水流的纹理,按照不同的参数取值,进行采样:
- //【3】设置三层水流效果,按照一定的规律在水滴纹理上分别进行取样
- float3 rainTex1 = tex2D(_ScreenWaterDropTex, float2(uv.x * 1.15* _SizeX, (uv.y* _SizeY*1.1) + _CurTime* _DropSpeed *0.15)).rgb / _Distortion;
- float3 rainTex2 = tex2D(_ScreenWaterDropTex, float2(uv.x * 1.25* _SizeX - 0.1, (uv.y*_SizeY * 1.2) + _CurTime *_DropSpeed * 0.2)).rgb / _Distortion;
- float3 rainTex3 = tex2D(_ScreenWaterDropTex, float2(uv.x* _SizeX *0.9, (uv.y *_SizeY *1.25) + _CurTime * _DropSpeed* 0.032)).rgb / _Distortion;
第四步,整合一下三层水流效果的颜色信息,用一个float2型的变量存放下来:
- //【4】整合三层水流效果的颜色信息,存于finalRainTex中
- float2 finalRainTex = uv.xy - (rainTex1.xy - rainTex2.xy - rainTex3.xy) / 3;
第五步,自然是在屏幕所在的纹理_MainTex中进行一次最终的采样,算出最终结果颜色值,存放于float3型的finalColor中。
- //【5】按照finalRainTex的坐标信息,在主纹理上进行采样
- float3 finalColor = tex2D(_MainTex, float2(finalRainTex.x, finalRainTex.y)).rgb;
第六步,因为返回的是一个fixed4型的变量,rgba。所以需要给float3型的finalColor配上一个alpha分量,并返回:
//【6】返回加上alpha分量的最终颜色值
- return fixed4(finalColor, 1.0);
OK,关于此Shader的实现细节,差不多就需要讲到这些。下面再看一下C#脚本文件的实现。
C#脚本实现部分
C#脚本文件的实现并没有什么好讲的,唯一的地方,水滴纹理的载入,上面已经提到过了,实现代码如下:
- //载入素材图
- ScreenWaterDropTex = Resources.Load("ScreenWaterDrop") asTexture2D;
//-----------------------------------------------【C#脚本说明】---------------------------------------------------
// 屏幕水幕特效的实现代码-C#脚本部分
// 2015年10月 Created by 浅墨
// 更多内容或交流,请访问浅墨的博客:http://blog.csdn.net/poem_qianmo
//---------------------------------------------------------------------------------------------------------------------
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
[AddComponentMenu("浅墨Shader编程/Volume9/ScreenWaterDropEffect")]
public class ScreenWaterDropEffect : MonoBehaviour
{
//-------------------变量声明部分-------------------
#region Variables
//着色器和材质实例
public Shader CurShader;//着色器实例
private Material CurMaterial;//当前的材质
//时间变量和素材图的定义
private float TimeX = 1.0f;//时间变量
private Texture2D ScreenWaterDropTex;//屏幕水滴的素材图
//可以在编辑器中调整的参数值
[Range(5, 64), Tooltip("溶解度")]
public float Distortion = 8.0f;
[Range(0, 7), Tooltip("水滴在X坐标上的尺寸")]
public float SizeX = 1f;
[Range(0, 7), Tooltip("水滴在Y坐标上的尺寸")]
public float SizeY = 0.5f;
[Range(0, 10), Tooltip("水滴的流动速度")]
public float DropSpeed = 3.6f;
//用于参数调节的中间变量
public static float ChangeDistortion;
public static float ChangeSizeX;
public static float ChangeSizeY;
public static float ChangeDropSpeed;
#endregion
//-------------------------材质的get&set----------------------------
#region MaterialGetAndSet
Material material
{
get
{
if (CurMaterial == null)
{
CurMaterial = new Material(CurShader);
CurMaterial.hideFlags = HideFlags.HideAndDontSave;
}
return CurMaterial;
}
}
#endregion
//-----------------------------------------【Start()函数】---------------------------------------------
// 说明:此函数仅在Update函数第一次被调用前被调用
//--------------------------------------------------------------------------------------------------------
void Start()
{
//依次赋值
ChangeDistortion = Distortion;
ChangeSizeX = SizeX;
ChangeSizeY = SizeY;
ChangeDropSpeed = DropSpeed;
//载入素材图
ScreenWaterDropTex = Resources.Load("ScreenWaterDrop") as Texture2D;
//找到当前的Shader文件
CurShader = Shader.Find("浅墨Shader编程/Volume9/ScreenWaterDropEffect");
//判断是否支持屏幕特效
if (!SystemInfo.supportsImageEffects)
{
enabled = false;
return;
}
}
//-------------------------------------【OnRenderImage()函数】------------------------------------
// 说明:此函数在当完成所有渲染图片后被调用,用来渲染图片后期效果
//--------------------------------------------------------------------------------------------------------
void OnRenderImage(RenderTexture sourceTexture, RenderTexture destTexture)
{
//着色器实例不为空,就进行参数设置
if (CurShader != null)
{
//时间的变化
TimeX += Time.deltaTime;
//时间大于100,便置0,保证可以循环
if (TimeX > 100) TimeX = 0;
//设置Shader中其他的外部变量
material.SetFloat("_CurTime", TimeX);
material.SetFloat("_Distortion", Distortion);
material.SetFloat("_SizeX", SizeX);
material.SetFloat("_SizeY", SizeY);
material.SetFloat("_DropSpeed", DropSpeed);
material.SetTexture("_ScreenWaterDropTex", ScreenWaterDropTex);
//拷贝源纹理到目标渲染纹理,加上我们的材质效果
Graphics.Blit(sourceTexture, destTexture, material);
}
//着色器实例为空,直接拷贝屏幕上的效果。此情况下是没有实现屏幕特效的
else
{
//直接拷贝源纹理到目标渲染纹理
Graphics.Blit(sourceTexture, destTexture);
}
}
//-----------------------------------------【OnValidate()函数】--------------------------------------
// 说明:此函数在编辑器中该脚本的某个值发生了改变后被调用
//--------------------------------------------------------------------------------------------------------
void OnValidate()
{
ChangeDistortion = Distortion;
ChangeSizeX = SizeX;
ChangeSizeY = SizeY;
ChangeDropSpeed = DropSpeed;
}
//-----------------------------------------【Update()函数】------------------------------------------
// 说明:此函数在每一帧中都会被调用
//--------------------------------------------------------------------------------------------------------
void Update()
{
//若程序在运行,进行赋值
if (Application.isPlaying)
{
//赋值
Distortion = ChangeDistortion;
SizeX = ChangeSizeX;
SizeY = ChangeSizeY;
DropSpeed = ChangeDropSpeed;
}
//找到对应的Shader文件,和纹理素材
#if UNITY_EDITOR
if (Application.isPlaying != true)
{
CurShader = Shader.Find("浅墨Shader编程/Volume9/ScreenWaterDropEffect");
ScreenWaterDropTex = Resources.Load("ScreenWaterDrop") as Texture2D;
}
#endif
}
//-----------------------------------------【OnDisable()函数】---------------------------------------
// 说明:当对象变为不可用或非激活状态时此函数便被调用
//--------------------------------------------------------------------------------------------------------
void OnDisable()
{
if (CurMaterial)
{
//立即销毁材质实例
DestroyImmediate(CurMaterial);
}
}
}
摘录:
http://blog.csdn.net/poem_qianmo/article/details/49556461