入门图形学:武器光波特效

再次安利一下这个视频,黑神话悟空
这里面战斗都太秀了,难度我凭观看感觉和黑魂血源有的一拼,我感觉我是打不过的,黑魂3的古达就把我虐死二三十次,血源更不说了,小怪我都打不赢,所以还是来实现一下其中的特效,以后可能用到我的游戏中。
这次我们就来实现一下战斗中武器挥动的光波特效,如图:
在这里插入图片描述
截图就很不清晰了,还是得直接看视频才行,不过大致能看得出来武器挥动的时候产生一个扇形(弧形)的光波,我们可以分解这个特效如下:
1.挥动的时候生成一个扇形(弧形)的网格
2.基于扇形(弧形)网格面写一个光波的特效shader
接下来做第一步,生成扇形网格面,如下图:
在这里插入图片描述
假设AB就是金箍棒,O点为挥动的轴心,金箍棒从A0B0挥动到A1B1,产生一个小弧面,从A1B1挥动到A2B2,又产生一个小弧面,一次内推,我们创建这个n个小弧形组合起来的大弧形网格。
我们根据微分思想,就假设金箍棒AB挥动中每帧的移动非常小,那么就可以认为A0B0B1A1为梯形,这里我们假设A0A1、B0B1为线段,而不是弧线。当然如果有朋友说我们必须追求效果,而不是效率,也可以酌情将A0B0B1A1就当作弧形,再次微分计算弧形的n个小弧形顶点,如下图:
在这里插入图片描述
这里我们就当梯形来计算。

using System.Collections.Generic;
using UnityEngine;

public class WeaponWaveTrapezoidMesh : MonoBehaviour
{
    
    
    [Range(0, 10)]
    public int UpdateStep = 2;          //跳帧执行

    public GameObject weaponWaveGo;     //网格体

    public Transform A;
    public Transform B;

    public Material mat;

    private int currentStep = 0;

    private MeshRenderer wpMeshRender;
    private MeshFilter wpMeshFilter;
    private Mesh mesh;

    private Vector3 currentAPos;
    private Vector3 currentBPos;

    private List<Vector3> vertexPosList = new List<Vector3>();      //储存每一帧运动后的顶点,B0A0 B1A1 ... Bn-1An-1

    void Start()
    {
    
    
        wpMeshRender = weaponWaveGo.GetOrAddComponent<MeshRenderer>();
        wpMeshRender.sharedMaterial = mat;
        wpMeshFilter = weaponWaveGo.GetOrAddComponent<MeshFilter>();

        currentBPos = B.position;
        currentAPos = A.position;

        vertexPosList.Add(currentBPos);
        vertexPosList.Add(currentAPos);
    }


    void Update()
    {
    
    
        if (currentStep <= 0)
        {
    
    
            if (CheckTransformMoved(B.position, currentBPos) || CheckTransformMoved(A.position, currentAPos))
            {
    
    
                currentBPos = B.position;
                currentAPos = A.position;
                vertexPosList.Add(currentBPos);
                vertexPosList.Add(currentAPos);
                UpdateMesh();
            }
            currentStep = UpdateStep;
        }
        currentStep--;
    }

    public void InitMesh()
    {
    
    
        mesh = new Mesh();
        currentStep = 0;
    }

    private void UpdateMesh()
    {
    
    
        if (mesh != null)
        {
    
    
            mesh.Clear();
        }
        int halfcount = vertexPosList.Count / 2;
        //包含的梯形个数
        int trapezoidcount = halfcount - 1;
        //排序顶点,B0B1...Bn-1A0A1...An-1
        Vector3[] vertices = new Vector3[vertexPosList.Count];
        for (int i = 0; i < halfcount; i++)
        {
    
    
            int bindex = i * 2;
            int aindex = i * 2 + 1;
            vertices[i] = vertexPosList[bindex];
            vertices[halfcount + i] = vertexPosList[aindex];
        }
        mesh.vertices = vertices;
        //计算拓扑三角
        int[] triangles = new int[trapezoidcount * 6];
        for (int i = 0; i < trapezoidcount; i++)
        {
    
    
            int bindex = i;
            int aindex = halfcount + i;
            int triindex = i * 6;
            triangles[triindex] = bindex;
            triangles[triindex + 1] = bindex + 1;
            triangles[triindex + 2] = aindex;
            triangles[triindex + 3] = bindex + 1;
            triangles[triindex + 4] = aindex + 1;
            triangles[triindex + 5] = aindex;
        }
        mesh.triangles = triangles;
        wpMeshFilter.sharedMesh = mesh;
    }

    public void ClearMesh()
    {
    
    
        currentStep = 0;
        vertexPosList.Clear();
        wpMeshFilter.sharedMesh = null;
    }

    /// <summary>
    /// 检测transform移动了
    /// 移动了才绘制网格
    /// </summary>
    /// <param name="fpos"></param>
    /// <param name="tpos"></param>
    /// <returns></returns>
    private bool CheckTransformMoved(Vector3 fpos, Vector3 tpos)
    {
    
    
        if (Mathf.Approximately(fpos.x, tpos.x) && Mathf.Approximately(fpos.y, tpos.y) && Mathf.Approximately(fpos.z, tpos.z))
        {
    
    
            return false;
        }
        return true;
    }
}

原理就是检测圆柱体移动后,就更新网格AnBn顶点列表,顺便绘制网格,效果如下:
在这里插入图片描述
接下来就是要处理绘制的弧形网格的着色渲染问题,首先我们得处理uv,因为圆柱体挥动本身就是不规则的类弧形,我们计算弧形的映射uv还不如直接处理成长方形的映射uv,如下图:
在这里插入图片描述
脑海中想象弧形拉伸展开成一个平面矩形,这样就好得到uv了。

        //计算矩形uv
        Vector2[] uvs0 = new Vector2[vertexPosList.Count];
        float segx = 1f / (float)trapezoidcount;     //均分uv.x
        for (int i = 0; i < halfcount; i++)
        {
    
    
            Vector2 BnUV = new Vector2(i * segx, 1f);
            Vector2 AnUV = new Vector2(i * segx, 0f);
            uvs0[i] = BnUV;
            uvs0[halfcount + i] = AnUV;
        }
        mesh.uv = uvs0;

效果如下:
在这里插入图片描述
接下来继续,我们需要将网格的渲染做成一个类似之前的“屏幕波动”的效果,所以我们需要将背景采样出来,然后通过像素扰动做效果。

Shader "Weapon/WeaponWaveEffectShader"
{
    
    
    Properties
    {
    
    
       _MainAlpha("Main Alpha",Range(0,1)) = 1
       _OverlayColor("Overlay Color",Color) = (1,1,1,1)
       _WaveRange("Wave Range",Range(0,1)) = 0.5
       _WavePower("Wave Power",Range(0,200)) = 10
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Transparent" "Queue"="Geometry" }
        LOD 100

        //使用grabtextre来采样背景
        GrabPass{
    
    }

        Pass
        {
    
    
            Blend SrcAlpha OneMinusSrcAlpha
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
    
    
                float4 vertex : POSITION;
            };

            struct v2f
            {
    
    
                float4 spos : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _GrabTexture;

            float _MainAlpha;               //alpha值
            float4 _OverlayColor;           //叠加颜色
            float _WaveRange;               
            float _WavePower;

            v2f vert (appdata v)
            {
    
    
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.spos = ComputeGrabScreenPos(o.vertex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                float2 uv = float2(i.spos.x/i.spos.w,i.spos.y/i.spos.w);
                //以uv.y轴做sin(-1,1)周期加权
                //以uv.y为sin的入参,产生周期扰动
                uv.y += sin(uv.y*_WavePower)*_WaveRange;
                fixed4 col = tex2D(_GrabTexture,uv);
                col *= _OverlayColor;
                col.a = _MainAlpha;
                return col;
            }
            ENDCG
        }
    }
}

这里我们使用_GrabPass来采样背景,然后使用ComputeGrabScreenPos计算uv,再通过sin函数对uv做周期的像素扰动,则得到了武器光波的效果,如下:
在这里插入图片描述
上面shader的核心也是以前讲过的_GrabPass和ScreenEffect,所以不再详细介绍。
ps:我们也可以使用photoshop制作像素uv扰动的采样图,这样的话像素扰动也更加随机,而且美术人员制作的采样图也更有艺术感。
最后我又看了看黑神话悟空的效果,武器光波上面都有一道白光,这个我就用photoshop实现,如下:
在这里插入图片描述
用photoshop拉一个黑色的渐变,我觉得既然是黑神话悟空,那棍影就应该是黑色的,然后在shader中采样叠加。

Shader "Weapon/WeaponWaveEffectShader"
{
    
    
    Properties
    {
    
    
       _MainAlpha("Main Alpha",Range(0,1)) = 1
       _OverlayColor("Overlay Color",Color) = (1,1,1,1)
       _WaveRange("Wave Range",Range(0,1)) = 0.5
       _WavePower("Wave Power",Range(0,200)) = 10
       _SharpTex("Weapon Top Sharp Texture",2D) = "white" {
    
    }
       _SharpAlpha("Weapon Top Sharp Alpha",Range(0,1)) = 1
    }
    SubShader
    {
    
    
        Tags {
    
     "RenderType"="Transparent" "Queue"="Geometry" }
        LOD 100

        //使用grabtextre来采样背景
        GrabPass{
    
    }

        Pass
        {
    
    
            Blend SrcAlpha OneMinusSrcAlpha
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
    
    
                float2 uv : TEXCOORD0;
                float4 vertex : POSITION;
            };

            struct v2f
            {
    
    
                float2 uv : TEXCOORD1;
                float4 spos : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            sampler2D _GrabTexture;

            float _MainAlpha;               //alpha值
            float4 _OverlayColor;           //叠加颜色
            float _WaveRange;               
            float _WavePower;

            sampler2D _SharpTex;            //刀光贴图
            float4 _SharpTex_ST;
            float _SharpAlpha;

            v2f vert (appdata v)
            {
    
    
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.spos = ComputeGrabScreenPos(o.vertex);
                o.uv = TRANSFORM_TEX(v.uv,_SharpTex);
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
    
    
                float2 uv = float2(i.spos.x/i.spos.w,i.spos.y/i.spos.w);
                //以uv.y轴做sin(-1,1)周期加权
                //以uv.y为sin的入参,产生周期扰动
                uv.y += sin(uv.y*_WavePower)*_WaveRange;
                fixed4 col = tex2D(_GrabTexture,uv);
                //叠加刀光棍影
                //通过lerp进行插值
                fixed4 scol = tex2D(_SharpTex,i.uv);
                col = lerp(col,scol*_SharpAlpha,scol.a*_SharpAlpha);
                col *= _OverlayColor;
                col.a = _MainAlpha;
                return col;
            }
            ENDCG
        }
    }
}

效果如下:
在这里插入图片描述
我又看了下黑神话的视频,感觉想做出看起来舒服的武器光波效果,还是得更加注重细节,得美术感艺术感好才能做出好看的效果。
好,后面有时间继续实现雪地的交互效果。

Guess you like

Origin blog.csdn.net/yinhun2012/article/details/119888402