【Shader】实验08——水面的环形涟漪

接上篇,这篇详细写写水面的环形涟漪模拟的shader。

所有的shader实验源码都可以在GitHub找到。
水面的环形涟漪

C#脚本

首先把碰撞检测的C#脚本写了

C#脚本的主要任务:
1、碰撞检测
2、如果发生碰撞,把碰撞点传给shader,作为环形涟漪模拟的圆心
3、如果发生碰撞,首先传一个开始范围值,过一定时间后再传一个结束范围值,这样就可以模拟人物踩上水后,先出现外围涟漪,再从内圈开始消失。

using System.Collections;
using UnityEngine;

public class SimpleWater : MonoBehaviour
{
    [Range(0,0.5f)]
    public float waveWidth = 0.1f;

    [Range(0.01f, 0.1f)]
    public float timeMince = 0.02f;

    [Range(0,1)]
    public float waveDissolveRatioTime = 0.5f;

    private Vector3 point = new Vector3(0, 0, 0);

    private Material material;

    private float panelWidth;
    private float panelHeight;

    private bool isCollision = false;

    private void Awake()
    {
        material = GetComponent<MeshRenderer>().material;

        if (material == null)
            Debug.Log("material is null!");

        panelWidth = transform.localScale.x * 5;
        panelHeight = transform.localScale.z * 5;

        material.SetVector("_wavePos", new Vector4(0, 0, 0, 0));
        material.SetFloat("_StartWaveWidth", 0);
        material.SetFloat("_EndWaveWidth", 0);
    } 

    private void OnCollisionEnter(Collision collision)
    {
        ContactPoint contact = collision.contacts[0];
        //碰撞点坐标 
        Vector3 pos = contact.point; 

        pos -= transform.position;

        pos.x /= panelWidth;
        pos.z /= panelHeight;

        pos.x = pos.x / 2 + 0.5f;
        pos.z = pos.z / 2 + 0.5f;

        isCollision = true;

        StartCoroutine(ChangeWaveWidth("_StartWaveWidth", 0.8f,true));

        //把panel的长宽也传递过去,以消除缩放影响
        material.SetVector("_wavePos", new Vector4(1 - pos.x, 1 - pos.z, panelWidth, panelHeight));
    }

    private IEnumerator ChangeWaveWidth(string name, float time, bool isStartWaveWidth)
    {
        //把设定的waveWidth每timeMince传给shader一次,每传一次加一点,从0逐渐接近设定的waveWidth值
        for (int i = 0; i < time / timeMince; i++)
        {
            material.SetFloat(name, waveWidth * i / (time * 50));

            //在设定的wave消失时间(比例)时,开始给shader的EndWaveWidth,这样就可以模拟波从内圈开始消失了
            if (Mathf.Abs(i - waveDissolveRatioTime * time / timeMince) <= 0.05f)
            {
                if (isStartWaveWidth)
                    StartCoroutine(ChangeWaveWidth("_EndWaveWidth", 1f, false));
            }
            yield return new WaitForSeconds(timeMince);
        }

        StopCoroutine(ChangeWaveWidth(name, time, isStartWaveWidth));
    }
}

主要是用协程开启动画传值,关键部分都详细写了注释,就不再重复了。

然后就是重头戏——Shader的部分。

Shader

要模拟环形涟漪,首先计算各点到圆心的向量,用点的uv坐标减去圆心uv坐标即可

float2 dv = i.uv.xy - float2(_wavePos.x, _wavePos.y);
dv = dv * float2(_wavePos.z / _wavePos.w, 1);//消除scale缩放影响

然后计算点到圆心的距离,在距离上使用正弦公式计算采样的offset

float distance = sqrt(dv.x * dv.x + dv.y * dv.y);
float sinFactor = sin(_Frequency * distance - _Time.y);

计算振幅amplitude,决定波的上下起伏大小,同时使用_StartWaveWidth和_EndWaveWidth来限制波的范围,用smoothstep而不是clamp是为了边缘过渡平滑

float amplitude = _Amplitude * smoothstep(0, _StartWaveWidth, _StartWaveWidth - distance.x) * smoothstep(0, _EndWaveWidth, distance.x - _EndWaveWidth);

计算圆心到点的方向,再乘上上述计算结果,就得到了采样贴图的offset。在uv坐标上加上offset和上一篇文章提到的折射偏移speed就可以了。

float2 direction = normalize(dv);
float2 wave_offset = normalize(dv) * sinFactor * amplitude;
fixed3 bump = UnpackNormal(tex2D(_Bump, i.uv.zw + speed + wave_offset)).rgb;

源码

Shader "MyShaderTest/7_SimpleWater"
{
	Properties
	{
		_MainTex("Main Tex",2D) = "white"{}
		_MainColor("Main Color",Color) = (1,1,1,1)

		_Bump("Bump",2D) = "bump" {}
		_BumpScale("Bump Scale",float) = 1
		_BumpSpeed("Bump Speed",Range(0,2)) = 1

		_ReflectionColor("Reflection Color",Color) = (1,1,1,1)

		_RefractionScale("Refraction Scale",float) = 1

		_Frequency("Frequency",float) = 0
		_Amplitude("Amplitude",float) = 1
	}

	SubShader
	{
		Tags{ "RenderType" = "Opaque" }

		Pass
		{
			Tags{ "LightMode" = "ForwardBase" }

				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag
				#pragma multi_compile_fwdbase

				#include "UnityCG.cginc"
				#include "Lighting.cginc"

				struct a2v
				{
					float4 vertex : POSITION;
					float3 normal : NORMAL;
					float4 tangent : TANGENT;
					float2 texcoord : TEXCOORD0;
				};

				struct v2f
				{
					float4 pos : SV_POSITION;
					float4 uv : TEXCOORD0;
					float4 Ttw0 : TEXCOORD2;
					float4 Ttw1 : TEXCOORD3;
					float4 Ttw2 : TEXCOORD4;
				};

				sampler2D _MainTex;
				half4 _MainTex_ST;
				half4 _MainTex_TexelSize;
				half4 _MainColor;

				sampler2D _Bump;
				half4 _Bump_ST;
				half _BumpScale;
				half _BumpSpeed;

				fixed4 _ReflectionColor;
				half _RefractionScale;

				float _Frequency;
				float _Amplitude;

				float4 _wavePos;
				float _StartWaveWidth;
				float _EndWaveWidth;


				v2f vert(a2v v)
				{
					v2f o;
					float3 worldPos = mul(unity_ObjectToWorld, v.vertex);

					float3 worldNormal = UnityObjectToWorldNormal(v.normal);
					float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
					float3 worldBitangent = cross(worldNormal, worldTangent) * v.tangent.w;

					o.pos = mul(UNITY_MATRIX_VP, float4(worldPos, 1));
					o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
					o.uv.zw = TRANSFORM_TEX(v.texcoord, _Bump);

					#if UNITY_UV_STARTS_AT_TOP
						if (_MainTex_TexelSize.y < 0.0)
						{
							o.uv.y = 1.0 - o.uv.y;
							o.uv.w = 1.0 - o.uv.w;
						}
					#endif

					o.Ttw0 = float4(worldTangent.x, worldBitangent.x, worldNormal.x, worldPos.x);
					o.Ttw1 = float4(worldTangent.y, worldBitangent.y, worldNormal.y, worldPos.y);
					o.Ttw2 = float4(worldTangent.z, worldBitangent.z, worldNormal.z, worldPos.z);

					return o;
				}

				fixed4 frag(v2f i) : SV_Target
				{
					float3 worldPos = float3(i.Ttw0.w,i.Ttw1.w,i.Ttw2.w);
					fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
					float2 speed = half2(_BumpSpeed, _BumpSpeed) * _Time.y;
					float2 dv = i.uv.xy - float2(_wavePos.x, _wavePos.y);
					dv = dv * float2(_wavePos.z / _wavePos.w, 1);//消除scale缩放影响

					float distance = sqrt(dv.x * dv.x + dv.y * dv.y);
					float sinFactor = sin(_Frequency * distance - _Time.y);
					float amplitude = _Amplitude * smoothstep(0, _StartWaveWidth, _StartWaveWidth - distance.x) * smoothstep(0, _EndWaveWidth, distance.x - _EndWaveWidth);
					float2 direction = normalize(dv);
					float2 wave_offset = normalize(dv) * sinFactor * amplitude;
					fixed3 bump = UnpackNormal(tex2D(_Bump, i.uv.zw + speed + wave_offset)).rgb;

					bump.xy *= _BumpScale;
					bump.z = sqrt(1 - saturate(dot(bump.xy, bump.xy)));
					bump = normalize(half3(dot(bump, i.Ttw0.xyz), dot(bump, i.Ttw1.xyz), dot(bump, i.Ttw2.xyz)));

					float2 mainTex_uv_offset = bump.xy * _MainTex_TexelSize.xy * _RefractionScale;
					fixed3 refrColor = tex2D(_MainTex, i.uv.xy + mainTex_uv_offset).rgb * _MainColor;
					half3 reflColor = dot(worldViewDir, bump) * _ReflectionColor;

					return fixed4(reflColor + refrColor, 1);
				}
				ENDCG
		}
	}
	FallBack "Diffuse"
}

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
unity涟漪shader是一种在游戏开发中用于实现涟漪效果的渲染技术。该技术利用shader编程和图形计算的原理,通过对顶点位置或纹理进行变换,在渲染过程中加入了波纹的效果,从而实现了真实的水面效果。 在涟漪shader中,首先需要定义水面的网格,可以是一个平面或者是任意形状的网格,然后通过shader程序对该网格进行顶点偏移或UV纹理变换,从而实现水面上波纹的扩散效果。可以通过修改shader中的参数和噪声纹理,来调整波纹的强度、速度和密度等属性,使得波纹看起来更加真实。 涟漪shader通常包括增量波函数、法线计算、纹理坐标偏移等关键步骤。增量波函数用于计算每个顶点的波纹偏移量,可以通过正弦或余弦函数模拟波浪效果。法线计算用于描述水面的倾斜方向,并将波纹效果应用到法线上,增加水面的真实感。纹理坐标偏移则是将波纹效果应用到纹理上,使得水面的纹理随着波浪的扩散而发生变化。 涟漪shader在游戏中可以用于实现水面、池塘、湖泊等域场景的真实表现。同时,通过与环境光遮蔽、折射、反射等技术结合,可以进一步增强水面效果,使得游戏场景更加生动逼真。 总之,unity涟漪shader是一种用于实现水面波纹效果的渲染技术,通过shader编程和计算图形变换,能够使游戏场景中的域表面产生真实的涟漪效果,增强游戏的视觉效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值