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

本文通过分析黑神话悟空的UE5演示视频,探讨如何在Unity中使用Shader实现屏幕波爆特效,详细讲解了Shader代码实现过程,并通过C#脚本控制Shader参数,最终实现类似的效果。同时,对比了与黑神话悟空原效果的差距,提出可能通过使用noise贴图提升效果。
摘要由CSDN通过智能技术生成

最近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参数也得好好调一调。
好,今天就到这里,准备洗了睡了,下一篇继续实现黑神话悟空的武器挥动波动特效,他武器挥动的战斗感做的也挺不错的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值