IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> Unity shader实现水波效果 -> 正文阅读

[游戏开发]Unity shader实现水波效果

先放效果
请添加图片描述
只想要代码可以直接拉到3.0

原理

把波产生的点作为圆心,往外的半径方向作为波的传输方向,半径长度作为s波的函数变量,通过波的公式偏移uv,从而模拟波。

1.0

一个从平面中心不断波动的效果
ps:不是后处理,直接作为material赋给平面使用,为了防止波变成椭圆,需要在变量中更新平面的长宽比。

Shader "MyShader/waterWave"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Strength("WaveStrength", Float) = 0.01
        _Speed("WaveSpeed", Float) = 10
        //waveLength越大波长越小
        _WaveLength("WaveLength", Float) = 10
        _PlaneWidth("PlaneWidth", Float) = 10
        _PlaneLength("PlaneLength", Float) = 10
        _WaveLengthChangeFactor("waveLengthChangeFactor" , Float) = 0.2
    }
    SubShader
    {
        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;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Strength;
            float _Speed;
            float _WaveLength;
            float _PlaneWidth;
            float _PlaneLength;
            float _WaveLengthChangeFactor;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                fixed2 center = fixed2(0.5, 0.5);
                fixed2 offset = i.uv - center;
                //保存水波形状为圆
                offset *= float2(_PlaneLength / _PlaneWidth, 1);
                //uv点与中心点的距离
                float dist = sqrt(offset.x * offset.x + offset.y * offset.y);
                fixed2 offsetDir = normalize(offset);
                //以距离为函数扭曲屏幕,时间控制波往后传播,dist决定波形,实现的是不断往外波动的水波
                fixed2 newUV = i.uv + _Strength * offsetDir * sin(_Time.y * _Speed + dist * _WaveLength * (1 - dist) * _WaveLengthChangeFactor);

                return tex2D(_MainTex, newUV);
            }
            ENDCG
        }
    }
}

2.0

改为后处理,在鼠标点击的任意地方都会产生水波,并且会逐渐扩散消失。

原理是点击时Csharp脚本更新当前的时间差为0,随着时间差的不断增大,将时间差*时间差因子看成是截取水波的半径,在此半径外一定范围的地方才保留uv的偏移。缺点是所有的uv由统一的时间差控制,即同一时刻只有一个水波。
修改:
为了提升观感,在每一帧都线性增大水波扩散的速度并且线性增大前文提到的半径的范围。查阅资料发现水波从里到外波长和频率不变振幅变小,并且和半径成反比(1/r)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 后处理实现水波,并且可以同时在多个点点击产生
/// </summary>
public class WaterWavePostEffect : PostEffectBase
{
    public Shader m_Shader;
    private Material m_Material;
    public Material material
    {
        get
        {
            m_Material = CheckShaderAndCreateMaterial(m_Shader, m_Material);
            return m_Material;
        }
    }
    /// <summary>
    /// 水波的振幅
    /// </summary>
    [SerializeField]
    private float WaveStrength = 0.01f;

    /// <summary>
    /// 水波往外的速度
    /// </summary>
    [SerializeField]
    private float WaveSpeed = 15;

    /// <summary>
    /// 水波的波长(越大波长越短)
    /// </summary>
    [SerializeField]
    private float WaveLength = 70;
    /// <summary>
    /// 水波从里到外波长减弱的因子
    /// </summary>
    [SerializeField]
    private float _WaveStrengthFallFactor = 1;

    /// <summary>
    /// 截取水波的半径增大的速度
    /// </summary>
    [SerializeField]
    private float WaveDistanceFactor = 1;

    /// <summary>
    /// 截取水波的宽度
    /// </summary>
    [SerializeField]
    private float WaveWidth = 0.1f;
    //水波往外扩散的距离,在此范围内的uv坐标才会收到影响
    private float curWaveDistance = 0;
    private float waveStartTime = float.MinValue;
    private float curWaveDistanceFactor;
    private Vector4 wavePos = new Vector4(0.5f, 0.5f, 0, 0);
    private void Awake()
    {
        curWaveDistanceFactor = WaveDistanceFactor;
    }
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if(material != null)
        {
            curWaveDistance = (Time.time - waveStartTime) * curWaveDistanceFactor;
            m_Material.SetFloat("_WaveSpeed", WaveSpeed);
            m_Material.SetFloat("_WaveStrength", WaveStrength);
            m_Material.SetFloat("_WaveLength", WaveLength);
            m_Material.SetFloat("_CurWaveDistance", curWaveDistance);
            m_Material.SetFloat("_WaveStrengthFallFactor", _WaveStrengthFallFactor);
            m_Material.SetFloat("_WaveWidth", WaveWidth);
            m_Material.SetVector("_WaveStartPos", wavePos);
            Graphics.Blit(source, destination, m_Material);
        }
    }
    private void Update()
    {
        if(Input.GetMouseButtonDown(0))
        {
            float timer = 0;
            curWaveDistanceFactor = WaveDistanceFactor;
            //Input.mousePostion 获得鼠标点击位置的屏幕像素空间坐标
            Vector2 mousePos = Input.mousePosition;
            wavePos = new Vector4(mousePos.x / Screen.width, mousePos.y / Screen.height ,0, 0);
            //水波产生
            waveStartTime = Time.time;
            while(timer <= 2)
            {
                curWaveDistanceFactor += 0.2f * Time.deltaTime;
                timer += Time.deltaTime;
            }
        }
        //Debug.Log(waveStartTime);
    }

}
Shader "MyShader/waterWavePostEffect"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    }
        SubShader
        {
            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;
                };

                sampler2D _MainTex;
                float4 _MainTex_ST;
                float _WaveStrength;
                float _WaveSpeed;
                float _WaveLength;
                float _WaveStrengthFallFactor;
                //不断延伸的半径距离,在离这个距离的_WaveWidth内才能产生水波
                float _CurWaveDistance;
                float _WaveWidth;
                float4 _WaveStartPos;
                v2f vert(appdata v)
                {
                    v2f o;
                    o.vertex = UnityObjectToClipPos(v.vertex);
                    o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                    return o;
                }

                fixed4 frag(v2f i) : SV_Target
                {
                    //鼠标点击的地方为中心点
                    fixed2 center = fixed2(_WaveStartPos.x, _WaveStartPos.y);
                    fixed2 offset = i.uv - center;
                    //保存水波形状为圆
                    offset *= float2(_ScreenParams.x / _ScreenParams.y, 1);
                    //uv点与中心点的距离
                    float dist = sqrt(offset.x * offset.x + offset.y * offset.y);
                    //圆心往外的方向
                    fixed2 offsetDir = normalize(offset);
                    //在_CurWaveDistance的_WaveWidth距离内才会产生效果,Step(a,x),x<a返回0,x >= a 返回1
                    float _CurWaveWidth = _WaveWidth * (1 + dist);
                    float discardFactor = step(abs(dist - _CurWaveDistance), _CurWaveWidth);
                    //以距离为函数扭曲屏幕,时间控制波往后传播,dist决定波形,实现的是不断往外波动的水波
                    fixed2 offsetFactor = sin(_Time.y * _WaveSpeed + dist * _WaveLength);
                    //水波从里到外波长不变振幅以1/r衰减
                    float _CurWaveStrength = _WaveStrength * (1 / dist * _WaveStrengthFallFactor);
                    fixed2 newUV = i.uv + _CurWaveStrength * offsetDir * offsetFactor * discardFactor;
                    return tex2D(_MainTex, newUV);
                }
                ENDCG
            }
        }
}

3.0

通过数组的存储使得同一时刻多个水波能够实现,逻辑将水波产生点和时间差改为数组,在片元着色器里面for循环分别每个产生水波产生点带来的uv偏移进行叠加,for循环结束后再赋值颜色。速度和半径范围的线性增大程度只和uv位置有关无需Csharp脚本传递。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 后处理实现水波,并且可以同时在多个点点击产生
/// </summary>
public class WaterWavePostEffectPlus : PostEffectBase
{
    public Shader m_Shader;
    private Material m_Material;
    private Vector4[] wavPoses = new Vector4[10];
    public Material material
    {
        get
        {
            m_Material = CheckShaderAndCreateMaterial(m_Shader, m_Material);
            return m_Material;
        }
    }
    /// <summary>
    /// 水波的振幅
    /// </summary>
    [SerializeField]
    private float WaveStrength = 0.03f;

    /// <summary>
    /// 水波往外的速度
    /// </summary>
    [SerializeField]
    private float WaveSpeed = 15;

    /// <summary>
    /// 水波的波长(越大波长越短)
    /// </summary>
    [SerializeField]
    private float WaveLength = 70;
    /// <summary>
    /// 水波从里到外波长减弱的因子
    /// </summary>
    [SerializeField]
    private float _WaveStrengthFallFactor = 0.2f;

    /// <summary>
    /// 截取水波的半径增大的速度
    /// </summary>
    [SerializeField]
    private float WaveDistanceFactor = 0.6f;

    /// <summary>
    /// 截取水波的宽度
    /// </summary>
    [SerializeField]
    private float WaveWidth = 0.1f;
    //水波往外扩散的距离,在此范围内的uv坐标才会收到影响
    //for循环下每个点都是独立的,需要记录其自己的开始时间,传播距离和距离变化因子
    private float[] curWaveDistance = new float[10];
    private float[] waveStartTime = new float[10];
    private float[] curWaveDistanceFactor = new float[10];
    private Vector4 wavePos = new Vector4(0.5f, 0.5f, 0, 0);
    //用于指定当前赋值点的下标
    private int index = 0;
    private void Awake()
    {
        for(int i = 0; i < curWaveDistanceFactor.Length; i++)
        {
            curWaveDistanceFactor[i] = WaveDistanceFactor;
        }
        for (int i = 0; i < curWaveDistance.Length; i++)
        {
            curWaveDistance[i] = 0;
        }
        for (int i = 0; i < curWaveDistanceFactor.Length; i++)
        {
            curWaveDistanceFactor[i] = float.MinValue;
        }
        index = 0;
    }
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (material != null)
        {
            for(int i = 0; i< wavPoses.Length; i++)
            {
                curWaveDistance[i] = (Time.time - waveStartTime[i]) * curWaveDistanceFactor[i];
            }
            m_Material.SetFloatArray("_CurWaveDistance", curWaveDistance);
            m_Material.SetVectorArray("_WaveStartPoses", wavPoses);

            m_Material.SetFloat("_WaveSpeed", WaveSpeed);
            m_Material.SetFloat("_WaveStrength", WaveStrength);
            m_Material.SetFloat("_WaveLength", WaveLength);

            m_Material.SetFloat("_WaveStrengthFallFactor", _WaveStrengthFallFactor);
            m_Material.SetFloat("_WaveWidth", WaveWidth);
            //m_Material.SetVector("_WaveStartPos", wavePos);
            Graphics.Blit(source, destination, m_Material);
        }
        else
        {
            Graphics.Blit(source, destination);
        }
    }
    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if(index == 9)
            {
                index = 0;
            }
            AddWavePos(index);
            index++;
        }
            
        //Debug.Log(waveStartTime);
    }
    /// <summary>
    /// 点击屏幕增加一个水波产生的原点
    /// </summary>
    /// <param name="i">水波点的下标</param>
    void AddWavePos(int i)
    {
        Debug.Log("点击屏幕一次");
        float timer = 0;
        curWaveDistanceFactor[i] = WaveDistanceFactor;
        //Input.mousePostion 获得鼠标点击位置的屏幕像素空间坐标
        Vector2 mousePos = Input.mousePosition;
        wavePos = new Vector4(mousePos.x / Screen.width, mousePos.y / Screen.height, 0, 0);
        wavPoses[i] = wavePos;
        //水波产生
        waveStartTime[i] = Time.time;
        while (timer <= 2)
        {
            curWaveDistanceFactor[i] += 0.2f * Time.deltaTime;
            timer += Time.deltaTime;
        }
    }
}

Shader "MyShader/waterWavePostEffectPlus"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    }
        SubShader
    {
        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;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _WaveStrength;
            float _WaveSpeed;
            float _WaveLength;
            float _WaveStrengthFallFactor;
            //不断延伸的半径距离,在离这个距离的_WaveWidth内才能产生水波
            float _CurWaveDistance[10];
            float _WaveWidth;
            float4 _WaveStartPoses[10];
            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                for (int j = 0; j < 10; j++) {
                    //鼠标点击的地方为中心点
                    fixed2 center = fixed2(_WaveStartPoses[j].x, _WaveStartPoses[j].y);
                    fixed2 offset = i.uv - center;
                    //保持水波形状为圆
                    offset *= float2(_ScreenParams.x / _ScreenParams.y, 1);
                    //uv点与中心点的距离
                    float dist = sqrt(offset.x * offset.x + offset.y * offset.y);
                    //圆心往外的方向
                    fixed2 offsetDir = normalize(offset);
                    //在_CurWaveDistance的_WaveWidth距离内才会产生效果,Step(a,x),x<a返回0,x >= a 返回1
                    float _CurWaveWidth = _WaveWidth * (1 + dist);
                    float discardFactor = step(abs(dist - _CurWaveDistance[j]), _CurWaveWidth);
                    //以距离为函数扭曲屏幕,时间控制波往后传播,dist决定波形,实现的是不断往外波动的水波
                    fixed2 offsetFactor = sin(_Time.y * _WaveSpeed + dist * _WaveLength);
                    //水波从里到外波长不变振幅以1/r衰减
                    float _CurWaveStrength = _WaveStrength * (1 / dist * _WaveStrengthFallFactor);
                    i.uv = i.uv + _CurWaveStrength * offsetDir * offsetFactor * discardFactor;
                }
            return tex2D(_MainTex, i.uv);
            }
            ENDCG
        }
    }
}

调一调参数就能产生一些比较好的效果,代码并没有非常斟酌的去写但是注释写了挺多,看懂了之后大家可以自行优化。

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-04-01 00:25:36  更:2022-04-01 00:29:48 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 18:48:47-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码