Unity简单的shader应用

发表于2018-10-11
评论0 1.1k浏览
今天要做的是给大家分享一个shader效果,其实它是个水波效果,当我们在池塘旁边,往池塘旁边扔进去一个石头,我们看池塘底的东西的时候,我们发现池底的东西是发生了弯曲的,所以我们这个效果就是来模仿现实生活中的这种简单的物理现象。


进入正题,讲一下这个shader的原理,这种shader的效果是作用于摄像机的,所以它的渲染层比一般场景中物体的层要高但是比ui要低一些。所以我们还是需要借助OnRenderImage(RenderTexture source,RenderTexture destination)这个函数来渲染一张图片到摄像机屏幕上。首先我们以屏幕的宽度和高度的一半为中心点。

转换为视口坐标的话(0.5,0.5)。然后根据时间的推移来计算水波推移的位置及推移位置左右的uv偏移。有了这个思路之后然后的shader就应该好写了。首先定义一个基本的shader格式:
Shader "Custom/Default" {
	Properties 
	{
	    //属性的添加。	
	}
	CGINCLUDE
	#include "UnityCG.cginc"
        //定义一些变量及顶点函数和片段函数。			
	ENDCG
	SubShader
	{
	  Pass
	  {
	     ZTest Always Cull Off ZWrite Off
		 CGPROGRAM
                 //这里基本的定义顶点和片段函数了
		 #pragma vertex vert_img
		 #pragma fragment frag
		 ENDCG
	  }
	}		
}

然后定义最主要的一个函数。同时将属性和变量一起定义了。
        Properties 
	{
		_MainTex ("MainTex", 2D) = "white" {}
		_GradTex("GradTex",2D) = "white" {}
		_DefaultColor("Default_Color",Color)=(0,0,0,0)
		_Param1("Param",float)=(0,0,0,0)//一个vector4变量 分别代表水波的中心点(x,y),还有水波速度倒数
		_Param2("Param",float)=(0,0,0,0)//一个vector4变量 分别代表屏幕宽度(x方向)和长度的比例(aspect,1),水波的反射率和折射率
		_Param3("Param",float)=(0,0,0,0)//一个vector4变量 分别代表屏幕长度(y方向)和宽度的比例(aspect,1),水波移动了多长时间
	}
	CGINCLUDE
	#include "UnityCG.cginc"
	sampler2D _MainTex;
	float2 _MainTex_TexelSize;
	half4 _DefaultColor;
	sampler2D _GradTex;
	half4 _Param1;
	half4 _Param2;
	half4 _Param3;
	float Wave(float2 pos,float2 center,float time)
	{ 
	   float dis = length(pos-center);
	   float t = time - dis * _Param1.z;
	   return (tex2D(_GradTex,float2(t,0)).a-0.5) * 2;
	}
	half4 frag(v2f_img  i):SV_Target
	{
	     const float2 dx=float2(0.01,0);
	     const float2 dy=float2(0,0.01);
	     float2 pos=i.uv*_Param2.xy;
	     float w=Wave(pos,_Param1.xy,_Param3.z);
	     float x = Wave(pos+dx,_Param1.xy,_Param3.z);
	     float y = Wave(pos+dy,_Param1.xy,_Param3.z);
	     float2 dw = float2(x-w,y-w);
	     float2 delta = dw * _Param3.xy * 0.1 * _Param2.z;
	     half4 c = tex2D(_MainTex, i.uv + delta);
	     float lerpparam = pow(length(dw) * 3 * _Param2.w, 5);
	     half4 endvalue=lerp(c,_DefaultColor,lerpparam);
	     return endvalue;
	}
	ENDCG

首先先讲一下Wave函数。Wave函数包含3个参数,分别为水波的位置(范围为0到1),当前点uv点的信息。水波偏移的时间。首先计算2个点的位置之间的距离,然后距离除以乘以速度的倒数就得到水波偏移到这个点需要的时间了。

然后通过当前水波已经移动的时间和到这个点的时间差,如果当到0.5s的时候,而到达我们最边界点可能需要的时间是大于0.5的话,那么这个点的uv信息应该是不应该发生变化的,所以这个这里返回一个0了,同样这里采用是a-0.5然后在乘以2这种方法来控制alpha的范围在0和1之间。

我们这里讲距离统一转换为时间的长短了。如果我们水波运行的时间为0.5,现在0.4距离的点应该水波应该是水波uv的偏移应该还是蛮大的,这时在0的点位置的uv偏移应该很小了(大家意淫一下,在脑海中想象一下,大家应该可以理解吧。),最后来说说最重要的片段着色函数了。

首先定义2个偏移,一个在x轴和一个y轴的偏移。

然后计算一下出一个该点总的uv偏移应该是多少。
half4 c = tex2D(_MainTex, i.uv + delta);delta就算该点uv的偏移量,这个也好理解,我们同样也可以让该点的uv向y轴发生偏移而不向x轴偏移。同样也可以让x轴偏移是y轴偏移的2倍,这个大家可以试试,看看效果会是怎样的。具体的代码是上面的dx和dy。最后就是一个颜色的插值了。首先我们求出插值所需的2个区间和一个t参数。

最后我们就在通过先建一个材质来运用它,同样还需要定义我们的水波衰减的曲线就是如果水波到0.5的时候0.4的点水波应该衰减多少(这里我们通过一张贴图的alpha来模拟,我们也可以通过一个曲线来模拟,不过曲线的区间我们需要收缩到[0,1]区间),0.2的点水波应该衰减多少。所以我们仍需要一个脚本来控制这个。
public class SampleRippleEffect : MonoBehaviour
{
    public AnimationCurve Waveform = new AnimationCurve(
       new Keyframe(0.00f, 0.50f, 0, 0),
       new Keyframe(0.05f, 1.00f, 0, 0),
       new Keyframe(0.15f, 0.10f, 0, 0),
       new Keyframe(0.25f, 0.80f, 0, 0),
       new Keyframe(0.35f, 0.30f, 0, 0),
       new Keyframe(0.45f, 0.60f, 0, 0),
       new Keyframe(0.55f, 0.40f, 0, 0),
       new Keyframe(0.65f, 0.55f, 0, 0),
       new Keyframe(0.75f, 0.46f, 0, 0),
       new Keyframe(0.85f, 0.52f, 0, 0),
       new Keyframe(0.99f, 0.50f, 0, 0)
   ); 
}

上面就定义了一条衰减曲线了。接下来添加我们最重要的unity自带的api了。
  private void OnRenderImage(RenderTexture source,RenderTexture destination)
    {
        Graphics.Blit(source, destination, _material);
    }

然后紧接着定义材质,shader及shader的一些参数,我就直接贴出来了。
      new Keyframe(0.99f, 0.50f, 0, 0)
   );
    <span style="background-color: rgb(255, 102, 0);">[SerializeField] private Color _defaultColor;
    [SerializeField] private Shader _shader;
    [SerializeField,Range(1,5)] private float _moveSpeed;
    [SerializeField, Range(0, 1)] private float _reflection;
    [SerializeField, Range(0, 1)] private float _refraction;
    private Vector4 _param1;
    private Vector4 _param2;
    private Vector4 _param3;
    private float _aspect;
    private Material _material;
    private Texture2D _gradTexture;</span>

添加基本的start方法。功能是构造出我们的曲线。这里我们将曲线表现用一个贴图的alpha来表现(其实完全可以用曲线来表现,但是需要保存的数据非常多了)
 private void Start()
    {
        _totalTime = 0;
        _aspect = GetComponent<Camera>().aspect;
        _gradTexture = new Texture2D(2048, 1, TextureFormat.Alpha8, false);
        _gradTexture.wrapMode = TextureWrapMode.Clamp;
        _gradTexture.filterMode = FilterMode.Bilinear;
        for (var i = 0; i < _gradTexture.width; i++)
        {
            var x = 1.0f / _gradTexture.width * i;
            var a = Waveform.Evaluate(x);
            _gradTexture.SetPixel(i, 0, new Color(a, a, a, a));
        }
        _gradTexture.Apply();
        _material = new Material(_shader);
        _material.hideFlags = HideFlags.DontSave;
        _material.SetTexture("_GradTex", _gradTexture);
    }

接下来就是添加update方法来每帧刷新水波的移动的位置。
   private void UpdateShaderParams(Vector2 mouseVector2,float time)
    {
        _param1 = new Vector4(mouseVector2.x,mouseVector2.y,1/_moveSpeed);
        _param2 = new Vector4(_aspect, 1, _reflection, _refraction);
        _param3 = new Vector4(1,1/_aspect,time,0);
        _material.SetColor("_DefaultColor", _defaultColor);
        _material.SetVector("_Param1", _param1);
        _material.SetVector("_Param2", _param2);
        _material.SetVector("_Param3", _param3);
    }
    private Vector2 _centerVector2;
    private float _totalTime;
    private void Update()
    {
        _totalTime += Time.deltaTime;
        if (Input.GetMouseButtonDown(0)&& _totalTime>1)
        {
            _centerVector2 = Camera.main.ScreenToViewportPoint(Input.mousePosition);
            _totalTime = 0;
        }
        UpdateShaderParams(_centerVector2, _totalTime);
    }
UpdateShaderParams主要是对shader中的参数进行重新赋值操作(shader中存在分OpGL和Dx渲染下uv的y的起始位置不同,所以根据不同的平台对uv进行定义)  
来自:https://blog.csdn.net/u012565990/article/details/52207197

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引