屏幕水幕特效的实现

在上一篇文章中有提到过,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函数中,稍微聊一聊。

 

第一步,先从顶点着色器输出结构体中获取顶点的坐标:

[cpp]  view plain  copy
 print ?
  1. //【1】获取顶点的坐标值  
  2. 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的值将为负数。

 

所以,第二步的代码就是如下:

[cpp]  view plain  copy
 print ?
  1. //【2】解决平台差异的问题。校正方向,若和规定方向相反,则将速度反向并加1  
  2.     #if UNITY_UV_STARTS_AT_TOP  
  3.      if(_MainTex_TexelSize.y < 0)  
  4.        _DropSpeed= 1 - _DropSpeed;  
  5.            #endif  

 

第三步,定义三层水流的纹理,按照不同的参数取值,进行采样:

[cpp]  view plain  copy
 print ?
  1.  //【3】设置三层水流效果,按照一定的规律在水滴纹理上分别进行取样  
  2. float3 rainTex1 = tex2D(_ScreenWaterDropTex, float2(uv.x * 1.15* _SizeX, (uv.y* _SizeY*1.1) + _CurTime* _DropSpeed *0.15)).rgb / _Distortion;  
  3. float3 rainTex2 = tex2D(_ScreenWaterDropTex, float2(uv.x * 1.25* _SizeX - 0.1, (uv.y*_SizeY * 1.2) + _CurTime *_DropSpeed * 0.2)).rgb / _Distortion;  
  4. float3 rainTex3 = tex2D(_ScreenWaterDropTex, float2(uv.x* _SizeX *0.9, (uv.y *_SizeY *1.25) + _CurTime * _DropSpeed* 0.032)).rgb / _Distortion;  


第四步,整合一下三层水流效果的颜色信息,用一个float2型的变量存放下来:

[cpp]  view plain  copy
 print ?
  1. //【4】整合三层水流效果的颜色信息,存于finalRainTex中  
  2. float2 finalRainTex = uv.xy - (rainTex1.xy - rainTex2.xy - rainTex3.xy) / 3;  


 

第五步,自然是在屏幕所在的纹理_MainTex中进行一次最终的采样,算出最终结果颜色值,存放于float3型的finalColor中。

[cpp]  view plain  copy
 print ?
  1. //【5】按照finalRainTex的坐标信息,在主纹理上进行采样  
  2. float3 finalColor = tex2D(_MainTex, float2(finalRainTex.x, finalRainTex.y)).rgb;  


第六步,因为返回的是一个fixed4型的变量,rgba。所以需要给float3型的finalColor配上一个alpha分量,并返回:

//【6】返回加上alpha分量的最终颜色值

[cpp]  view plain  copy
 print ?
  1. return fixed4(finalColor, 1.0);  

 

OK,关于此Shader的实现细节,差不多就需要讲到这些。下面再看一下C#脚本文件的实现。


C#脚本实现部分

 


C#脚本文件的实现并没有什么好讲的,唯一的地方,水滴纹理的载入,上面已经提到过了,实现代码如下:

[csharp]  view plain  copy
 print ?
  1. //载入素材图  
  2.  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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值