unity shader - 点击屏幕水波纹特效

一直以来我都非常崇拜那些能写出特别炫酷的特效的大神,每当看到游戏中一些炫酷的特效时,我都在想这些是怎么实现的,希望自己有一天也能亲自写出这些特效,一直都想去学习图形学相关的知识,说来惭愧由于自己的拖延症加上懒,一直拖在了最近才开始学习一些shader相关的知识,前段时间刚看完《unity shader入门精要》这本书,在这当中也学到了许多shader的一些知识,当我知道这本书的作者是一个女生时我非常惊讶,没想到是一个程序媛小姐姐,还专门去百度了一下并关注了她的博客,并被她的博文给深深吸引住了,她真的太厉害了。再对比一下自己,瞬间感到无地自容。
自此开始了我的shader学习之旅…
并且希望用博客来记录分享自己的每一次学习!希望自己能够坚持下去!

作为一个小白,如果有写的不好的地方希望大家能够多多指出,多多讨论,一起成长!


下面开始一步一步实现“水波纹”后期特效

准备工作

首先我们需要一个c#脚本来抓取屏幕图像,然而unity为我们提供了一个这样的接口:OnRenderImage函数,
定义:void OnRenderImage(RenderTexture src, RenderTexture dest)
详细说明请查看官方文档

我们先创建一个用于屏幕特效处理的基类,主要是在使用之前检查了一系列条件是否满足,以后的屏幕特效处理脚本都会继承该基类。(ps: 这里直接引用了一下《unity shader入门精要》里的脚本)

using UnityEngine;
using System.Collections;

[ExecuteInEditMode]
[RequireComponent (typeof(Camera))]
public class PostEffectsBase : MonoBehaviour {

	// Called when start
	protected void CheckResources() {
		bool isSupported = CheckSupport();
		
		if (isSupported == false) {
			NotSupported();
		}
	}

	// Called in CheckResources to check support on this platform
	protected bool CheckSupport() {
		if (SystemInfo.supportsImageEffects == false || SystemInfo.supportsRenderTextures == false) {
			Debug.LogWarning("This platform does not support image effects or render textures.");
			return false;
		}
		
		return true;
	}

	// Called when the platform doesn't support this effect
	protected void NotSupported() {
		enabled = false;
	}
	
	protected void Start() {
		CheckResources();
	}

	// Called when need to create the material used by this effect
	protected Material CheckShaderAndCreateMaterial(Shader shader, Material material) {
		if (shader == null) {
			return null;
		}
		
		if (shader.isSupported && material && material.shader == shader)
			return material;
		
		if (!shader.isSupported) {
			return null;
		}
		else {
			material = new Material(shader);
			material.hideFlags = HideFlags.DontSave;
			if (material)
				return material;
			else 
				return null;
		}
	}
}

创建一个基础的抓取屏幕的脚本,主要通过shader处理抓取到的屏幕图像,代码如下:

using UnityEngine;

public class WaterWaveEffect_L : PostEffectsBase {

    public Shader shader;
    private Material _material = null;
    public Material material {
        get {
            _material = CheckShaderAndCreateMaterial (shader, _material);
            return _material;
        }
    }
	//source:unity渲染得到的图像,destination:渲染纹理到屏幕上
    void OnRenderImage (RenderTexture source, RenderTexture destination) {
        //对渲染纹理的处理
        Graphics.Blit (source, destination, material);
    }
}

使用的时候直接把该脚本拖拽到camera上,然后把我们写好的shader拖拽到该脚本的shader属性上就可以了。
下面我们来测试一下该脚本是否正确:
首先打开unity创建一个简单的场景
在这里插入图片描述
然后创建一个基础的shader:

Shader "lcl/screenEffect/waterWave_L"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;
            float4 _MainTex_TexelSize;

            fixed4 frag (v2f i) : SV_Target
            {
                return tex2D(_MainTex, i.uv);	
            }
            ENDCG
        }
    }
}

最后拖拽shader到该脚本上
在这里插入图片描述

如果最终屏幕上能显示正确的图像就说明我们该脚本正确。
在这里插入图片描述

下面开始编写我们的shader

首先我们需要让图像呈现出像水波纹一样的效果,实际上就是对uv坐标的偏移,但是我们要让它出现波纹形状,就需要用到正弦函数(sin)了,我们先来看看sin函数是什么样的(在这里推荐一个在线函数图像绘制工具),如图:
在这里插入图片描述
看起来是不是就像水波纹一样,现在我们知道了需要使用到的函数就好办了,我们把它应用到shader中去看看效果,下面就只贴关键代码了,如下:

    sampler2D _MainTex;
    float4 _MainTex_TexelSize;
    float _waveLength;//波长
    float _waveHeight;//振幅(波的高度)
    float4 _startPos;//波的传播位置(默认中心点)
	//片元着色器
    fixed4 frag (v2f i) : SV_Target
    {
    	//计算该片元到中心点的距离
        float dis = distance(i.uv,_startPos);
        //通过sin计算偏移
        float offsetX = sin(dis * _waveLength) * _waveHeight;
        i.uv.x += offsetX; 
        return tex2D(_MainTex, i.uv);	
    }    

在上面我们首先通过distance函数计算出片元到中心点的距离,然后在根据正弦函数计算出x偏移值,需要注意的是在这里我们定义了三个float类型的变量,这里的都是通过c#传递过来的。c#代码如下:

 	//波长
    [Range (0f, 100.0f)]
    public float _waveLength = 38.0f;
    //波纹振幅(高度)
    [Range (0, 2.0f)]
    public float _waveHeight = 0.5f;
    private Vector4 startPos = new Vector4 (0.5f, 0.5f, 0, 0);

    void OnRenderImage (RenderTexture source, RenderTexture destination) {
        //设置一系列参数
        material.SetVector ("_startPos", startPos);
        material.SetFloat ("_waveLength", _waveLength);
        material.SetFloat ("_waveHeight", _waveHeight);
       //把经过shader处理后屏幕图像copy到destination中
        Graphics.Blit (source, destination, material);
    }

最终呈现出的效果如下:
在这里插入图片描述
我们还可以通过调节_waveLength,_waveHeight 参数来修改波纹形状。

限制波纹范围

下面我们给波纹限制一下范围,可以通过调节参数来修改波纹扩散的范围,代码如下:

fixed4 frag (v2f i) : SV_Target
{
     //计算该片元到中心点的距离
     float dis = distance(i.uv,_startPos);
     //通过sin计算偏移
     float offsetX = sin(dis * _waveLength) * _waveHeight;
	 //如果该片元不在波纹范围内 偏移设置为0
	 if(dis <= _currentWaveDis || dis > _currentWaveDis + _waveWidth){
         offsetX = 0;
     }

     i.uv.x += offsetX; 
     return tex2D(_MainTex, i.uv);	

 }

运行效果如下,我们可以通过调节_currentWaveDis ,_waveWidth参数来修改波纹的范围了

现在我们可以随意的控制波纹范围了,接下来就需要让该波纹动起来!让它从里往外扩散,其实很简单,给定一个时间变量就可以了,如下:

 fixed4 frag (v2f i) : SV_Target
 {
     //计算该片元到中心点的距离
     float dis = distance(i.uv,_startPos);
     //通过sin计算偏移
     float offsetX = sin(dis * _waveLength) * _waveHeight;
     //随着时间变化
     _currentWaveDis*=_Time.y;
     //如果该片元不在波纹范围内 偏移设置为0
     if(dis <= _currentWaveDis || dis > _currentWaveDis + _waveWidth){
         offsetX = 0;
     }

     i.uv.x += offsetX; 
     return tex2D(_MainTex, i.uv);	
 }

这里就不贴图。gif太麻烦。点击运行就可以看到效果了。

最后,我们需要让波纹跟随鼠标点击的位置来扩散,我们可以用c#脚本来获取鼠标点击的坐标,然后转换到uv的(0,1)区间,传递给shader,并且_currentWaveDis 变量也由c#来控制,代码如下:

c#:

    //波纹速度
    [Range (0f, 1.0f)]
    public float waveSpeed = 0.5f;
    private Vector4 startPos = new Vector4 (0.5f, 0.5f, 0, 0);
    //波纹开始运动的时间
    private float waveStartTime;
    void OnRenderImage (RenderTexture source, RenderTexture destination) {
        //波纹运动的距离
        float _currentWaveDis = (Time.time - waveStartTime) * waveSpeed;
        //设置一系列参数
        material.SetVector ("_startPos", startPos);
        material.SetFloat ("_waveLength", _waveLength);
        material.SetFloat ("_waveHeight", _waveHeight);
        material.SetFloat ("_waveWidth", _waveWidth);
        material.SetFloat ("_currentWaveDis", _currentWaveDis);
        Graphics.Blit (source, destination, material);
    }
    void Update () {
        if (Input.GetMouseButton (0)) {
            Vector2 mousePos = Input.mousePosition;
            //将mousePos转化为(0,1)区间
            startPos = new Vector4 (mousePos.x / Screen.width, mousePos.y / Screen.height, 0, 0);
            waveStartTime = Time.time;
        }
    }

shader

sampler2D _MainTex;
float4 _MainTex_TexelSize;
float _waveLength;
float _waveHeight;
float _waveWidth;
float _currentWaveDis;
float4 _startPos;
fixed4 frag (v2f i) : SV_Target
{
    //计算该片元到中心点的距离
    float dis = distance(i.uv,_startPos);
    //通过sin计算偏移
    float offsetX = sin(dis * _waveLength) * _waveHeight;
    //如果该片元不在波纹范围内 偏移设置为0
    if(dis <= _currentWaveDis || dis > _currentWaveDis + _waveWidth){
        offsetX = 0;
    }
    i.uv.x += offsetX; 
    return tex2D(_MainTex, i.uv);	
}

完整代码可以在我的 github上找到
shader脚本
c#脚本

结束…

发布了50 篇原创文章 · 获赞 864 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_28299311/article/details/102878448
今日推荐