入门图形学:屏幕波爆特效

最近bilibili看了黑神话悟空的UE5演示视频,感觉是真牛逼,地址:黑神花悟空UE5实机演示视频
遥想我也算是国内第一批用ue4的开发者了,15年开始用ue4.7源码版,做了一年多就又用回u3d了,哈哈,主要因为ue4不知道为何总是很慢,从c++模板工程开始编译启动慢、umap大了保存慢、c++compile慢、shader compile慢、打包编译就更慢了,还进行exit with code 0。
不知道ue4或者ue5现在改版得怎么样了,应该已经解决上面的问题了吧。
言归正传,我看完黑神话悟空的视频后,觉得整体画质和战斗感是真不错,我感觉我是做不到这么漂亮的画质了。不过其中两个功能点我可能用的上,所以用u3d实现一下:
1.战斗过程中的屏幕波爆特效
2.跑动中雪地的交互特效
这篇博客主要实现第一个屏幕波爆特效,先看看截图:
在这里插入图片描述
截的图比较模糊,视频2:50左右,打到小怪物后一个屏幕的波纹爆动,这就很有战斗感觉了。
我们可以把屏幕画面想象成一张图片,在图片的中心点为圆心,半径r为半径的圆包含的像素数组,对这个像素数组进行uv上的操作,具体要什么样的操作我们得思考一下,如下:
在这里插入图片描述
假设圆心黑点就是打击中心,圆的区域就是我们要操作的像素数组,画面呈现效果就是类似圆心的像素向外“扩散”,如下图:
在这里插入图片描述
脑海中想象以圆心为起点,四面八方发散的像素移动一段距离,会不会形成波爆效果?我们得写代码试一试,那么像素移动怎么实现呢?其实也就是像素所在的uv远离圆心。
先写一个imageeffectshader尝试一下。

Shader "ScreenEffect/WaveEffectShader"
{
    
    
    Properties
    {
    
    
        _MainTex ("Texture", 2D) = "white" {
    
    }
        [Enum(Off,0,On,1)]_OffOn("Effect Off On",int) = 0
        _UVCenter("UV Texcoord Center",vector) = (0.5,0.5,0,0)
        _UVRadius("UV Circle Radius",Range(0,1)) = 0.1
        _UVStep("UV Step",Range(0,1)) = 0.1
        _WavePower("Wave Power",Range(0,0.2)) = 0.01
    }
    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;
            int _OffOn;
            float4 _UVCenter;
            float _UVRadius;
            float _UVStep;
            float _WavePower;

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                if(_OffOn == 1)
                {
    
    
                    float2 uv = i.uv-_UVCenter.xy;          //_UVCenter指向当前片段uv的向量
                    float dist2 = uv.x*uv.x+uv.y*uv.y;      //向量uv长度的平方
                    float radius2 = _UVRadius*_UVRadius;    //圆半径的平方
                    if(dist2<radius2)
                    {
    
    
                        float step = dist2/radius2;
                        if(step<_UVStep)
                        {
    
    
                            //判断当前片段uv在圆内,且step比例小于我们设定的uvstep
                            //则i.uv加权”一个朝外的向量“,形成像素朝外移动的效果
                            i.uv += uv*_WavePower;
                        }
                    }
                }
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

效果如下:
在这里插入图片描述
这里说一下原理,就是确定一个屏幕uv,设定一个圆形范围,如果片段在圆形范围内,则根据片段到圆心的长度进行步长的uv加权,加权的向量则是圆心朝向片段的“朝外向量”,而uv的加权则导致像素的变化,形成一种向外爆发的效果。
不过因为屏幕是有横纵比例的,但是uv却是1:1,所有我们还得修改到标准的圆形。

Shader "ScreenEffect/WaveEffectShader"
{
    
    
    Properties
    {
    
    
        _MainTex ("Texture", 2D) = "white" {
    
    }
        [Enum(Off,0,On,1)]_OffOn("Effect Off On",int) = 0
        _UVCenter("UV Texcoord Center",vector) = (0.5,0.5,0,0)
        _UVRadius("UV Circle Radius",Range(0,1)) = 0.1
        _UVStep("UV Step",Range(0,1)) = 0.1
        _WavePower("Wave Power",Range(0,0.2)) = 0.01
        _ScreenAspect("Screen Width/Height",float) = 1
    }
    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;
            int _OffOn;
            float4 _UVCenter;
            float _UVRadius;
            float _UVStep;
            float _WavePower;
            float _ScreenAspect;         //外部传入屏幕比例 宽度/高度

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                if(_OffOn == 1)
                {
    
    
                    float2 uv = i.uv-_UVCenter.xy;          //_UVCenter指向当前片段uv的向量
                    //也可以用_ScreenParams,当然直接外部一次计算aspect更好
                    float dist2 = uv.x*_ScreenAspect*uv.x*_ScreenAspect+uv.y*uv.y;      //向量uv长度的平方
                    float radius2 = _UVRadius*_UVRadius;    //圆半径的平方
                    if(dist2<radius2)
                    {
    
    
                        float step = dist2/radius2;
                        if(step<_UVStep)
                        {
    
    
                            //判断当前片段uv在圆内,且step比例小于我们设定的uvstep
                            //则i.uv加权”一个朝外的向量“,形成像素朝外移动的效果
                            i.uv += uv*_WavePower;
                        }
                    }
                }
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

创建一个ScreenAspect(width/height)的比例(16:9),然后在uv.x处理中ScreenAspect,则可以将uv.x校正到uv.y*(9/16)的范围内,形成正圆,如下:
在这里插入图片描述
接下来用c#代码进行效果操作。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using DG.Tweening;

public class WaveEffectCtrl : MonoBehaviour
{
    
    
    public Material waveEffectMat;
    [Range(0, 1f)]
    public float waveTime = 1f;
    public AnimationCurve waveCurve;

    void Start()
    {
    
    
        float sp = (float)Screen.width / (float)Screen.height;
        waveEffectMat.SetFloat("_ScreenAspect", sp);
    }

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
    
    
        Graphics.Blit(source, destination, waveEffectMat);
    }

    private void Update()
    {
    
    
        if (Input.GetMouseButtonDown(0))
        {
    
    
            Vector2 spos = Input.mousePosition;
            Vector4 uvpos = new Vector4(spos.x / Screen.width, spos.y / Screen.height, 0, 0);
            waveEffectMat.SetVector("_UVCenter", uvpos);
            waveEffectMat.SetInt("_OffOn", 1);
            float step = 0f;
            DOTween.To(() => step, x => step = x, 1f, waveTime).OnUpdate(() =>
            {
    
    
                waveEffectMat.SetFloat("_UVStep", step);
            }).OnComplete(() =>
            {
    
    
                waveEffectMat.SetInt("_OffOn", 0);
            }).SetEase(waveCurve);
        }
    }
}

每次点击屏幕,则形成一个波爆效果,顺便用dotween控制shader的参数,如下:
在这里插入图片描述
感觉怎么差点意思?我又看了看黑神话悟空的视频,感觉就是波爆圆内,像素好像更扭曲一些,而我的就一个uv朝向加权,效果还不是特别好,继续改进一下,比如添加一个sin进行扰动。

Shader "ScreenEffect/WaveEffectShader"
{
    
    
    Properties
    {
    
    
        _MainTex ("Texture", 2D) = "white" {
    
    }
        [Enum(Off,0,On,1)]_OffOn("Effect Off On",int) = 0
        _UVCenter("UV Texcoord Center",vector) = (0.5,0.5,0,0)
        _UVRadius("UV Circle Radius",Range(0,1)) = 0.1
        _UVStep("UV Step",Range(0,1)) = 0.1
        _WavePower("Wave Power",Range(0,0.5)) = 0.01
        _WaveRange("Wave Range",Range(1,1000)) = 5
        _WaveSpeed("Wave Speed",Range(1,100)) = 10
        _ScreenAspect("Screen Width/Height",float) = 1
    }
    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;
            int _OffOn;
            float4 _UVCenter;
            float _UVRadius;
            float _UVStep;
            float _WavePower;
            float _WaveRange;
            float _WaveSpeed;
            float _ScreenAspect;         //外部传入屏幕比例 宽度/高度

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                if(_OffOn == 1)
                {
    
    
                    float2 uv = i.uv-_UVCenter.xy;          //_UVCenter指向当前片段uv的向量
                    //也可以用_ScreenParams,当然直接外部一次计算aspect更好
                    float dist2 = uv.x*_ScreenAspect*uv.x*_ScreenAspect+uv.y*uv.y;      //向量uv长度的平方
                    float radius2 = _UVRadius*_UVRadius;    //圆半径的平方
                    if(dist2<radius2)
                    {
    
    
                        float step = dist2/radius2;
                        if(step<_UVStep)
                        {
    
    
                            //判断当前片段uv在圆内,且step比例小于我们设定的uvstep
                            //则i.uv加权”一个朝外的向量“,形成像素朝外移动的效果
                            //加权的乘积中使用sin和time控制一个波动的加权
                            i.uv += uv*_WavePower*sin(dist2*_WaveRange+_Time.y*_WaveSpeed);
                        }
                    }
                }
                fixed4 col = tex2D(_MainTex, i.uv);
                return col;
            }
            ENDCG
        }
    }
}

这里我在“朝外”向量加权的基础上又叠加了一个sin的“波动”乘积,使用time来控制sin的偏移,效果如下:
在这里插入图片描述
还是没有黑神话悟空的波爆有感觉,我感觉黑神话悟空中屏幕波爆,uv像素的偏移更加随机,我猜测可能用的noise贴图进行的像素uv加权。同时我这边animationcurve和speed、range、power参数也得好好调一调。
好,今天就到这里,准备洗了睡了,下一篇继续实现黑神话悟空的武器挥动波动特效,他武器挥动的战斗感做的也挺不错的。

おすすめ

転載: blog.csdn.net/yinhun2012/article/details/119823738