Unity Shader:用几何着色器实现复联3灭霸的终极大招灰飞烟灭

这里写图片描述
(上图:效果图1。普通网格模型)
这里写图片描述
(上图:效果图2。几何着色器粒子化特效进行中。)
这里写图片描述
(上图:效果图3。几何着色器粒子化特效进行中。)

1,用几何着色器进行图元转换

在OpenGL渲染管线中,几何着色器(Geometry Shader)有一个独一无二的工功能,既是图元转换。这里不对理论进行过多的讨论,可简单理解为渲染点,线,面之间的转换。
实现很简单,只有注意语法即可,以本文中Shader为例:
-声明着色器:

#pragma geometry geom

-设置输出顶点数量:

[maxvertexcount(120)]

-声明输入与输出struct:

            struct v2g
            {
                float4 vertex : SV_POSITION;
                fixed4 color:COLOR;
                float3 normal:NORMAL;
            };

            struct g2f
            {
                float4 vertex : SV_POSITION;
                fixed4 color:COLOR;
            };

-设置几何着色器输入参数与输出值:“PointStream”决定了输出类型,“triangle v2g”必须与顶点着色器输出的图元类型一致,当输入为三角形图元时,结构数组长度必须为[3],既是每次同时被输入一个三角形的三个顶点。

           void geom(inout PointStream<g2f> OutputStream,triangle v2g input[3])
           {
                ...
           }

-将输出顶点传送至输出stream上:

OutputStream.Append(o);

其他图元之间的转换语法上大同小异。

2,用几何着色器对图元进行细分

除了细分着色器,几何着色器也可以用来进行细分工作。
例如本例中将模型进行了粒子化,为了加强效果,增加粒子数量,对每个三角形图元都进行了细分。但是输出的顶点数是有上限的,根据输出stream的结构体(上面的g2f)的大小,Shader会对输出顶点数做出限制,本例中,每个三角形最多可以转换为120个粒子顶点,“[maxvertexcount(120)]”。

细分的算法:

-首先需要三个向量,一个位置向量作为起点,以及从起点至另两个顶点的方向向量:

              V1 = (input[1].vertex - input[0].vertex).xyz;
              V2 = (input[2].vertex - input[0].vertex).xyz;
              V0 = input[0].vertex.xyz;

-接下来,利用V1,V2对三角形进行参数化。
这里写图片描述
(上图:参考自http://web.engr.oregonstate.edu/~mjb/cs519/Handouts/geometry_shaders.1pp.pdf

这个算法可理解为,从V0出发,以V1为方向行进x个单位,以V2为方向行进y个单位,可到达三角形内任意一点:
这里写图片描述
(上图:从V0出发到任意点P0,P1)

              int numLayers =1<<_Level;     //2^_Level
              float dt = 1.0f / float( numLayers );
              float t = 1.0f;
              for( int it = 0; it < numLayers; it++ )
              {
                float smax = 1.0f - t;
                int nums = it + 1;
                float ds = smax / float( nums - 1 );
                float s = 0;
                for( int is = 0; is < nums; is++ )
                {
                    float3 v = V0 + s*V1 + t*V2;
                    ......
                    s += ds;
                } 
                t -= dt;
              }

上面代码删去与细分无关的部分,核心思想既是在双重循环中等距的向V1,V2方向移动,对三角形进行细分。
这里写图片描述
这里写图片描述
(上图:用此算法对一个Quad进行细分,由于输出顶点数达到了极限(120),中间部分为空白。)
_Level函数可用来控制细分中t方向的密度:
这里写图片描述
(上图:_Level=4)
这里写图片描述
(上图:_Level=1)

3,用几何着色器构建粒子系统

此shader中Shader中的粒子特效有位移动画和淡出效果。

-粒子位移

通过控制脚本,CPU不断更新Shader1的unityTime变量。

Shader.SetGlobalFloat ("unityTime", Time.time);

在Shader内计算动画累计时间:

              float time_SinceBirth=(unityTime-_ShaderStartTime)*0.1f;

计算重心坐标:

              CG=(input[0].vertex.xyz + input[1].vertex.xyz+ input[2].vertex.xyz)/3.0f;

位移:根据动画时间进行位移加速。此行代码决定了此Shader中粒子的移动效果,如果想模拟真实物理效果可套入一些公式。此行代码没什么特殊思想,视觉上表现为粒子向某个方向进行直线指数级加速移动。

                    v = CG + vel*(_Speed*time_SinceBirth+1.0f) + 0.5f*_DispDir.xyz*sin(it*is)*(_Speed*time_SinceBirth)*(_Speed*time_SinceBirth);

-淡出效果

根据动画累计时间对alpha值进行递减让粒子逐渐消失:

                    o.color=_FinalColor;
                    o.color.w=1.0f-smoothstep(0,1.0f,time_SinceBirth);

除了通过alpha进行淡出处理也可以通过语义ponitsize对粒子size进行缩小处理以达到淡出(但目前在unity sahder中使用此语义无法通过编译)。

4,源码:

Shader:

Shader "Unlit/ParticleExp_Beta"
{
    Properties
    {
        //细分相关变量
        _Level("Level",int)=0
        _DispDir("Displacement Direction",Vector)=(0,0,0)
        _uVelScale("VelScale",float)=2
        //粒子化特效相关变量
        _Speed("Speed",Range(0,1))=1
        _ShaderStartTime("Shader Start Time",float)=0
        _FinalColor("Final Color",color)=(1,1,1,1)
    }

    SubShader
    {
        Tags{"RenderType"="Transparent" "Queue" = "Transparent"}
        LOD 100

        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha // use alpha blending
            cull off

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma geometry geom

            #include "UnityCG.cginc"
            //CPU输入变量
            ////细分相关变量
            uniform int _Level;
            uniform float3 _DispDir;
            uniform float _uVelScale;
            ////粒子化特效相关变量
            uniform float _Speed;           //粒子位移速度
            uniform float _ShaderStartTime; //粒子化起始时间
            uniform fixed4 _FinalColor;     //粒子颜色

            //内部变量
            float3 V0, V1, V2;
            float3 CG;
            float unityTime;

            struct appdata
            {
                float4 vertex : POSITION;
                float3 normal:NORMAL;
            };

            struct v2g
            {
                float4 vertex : SV_POSITION;
                fixed4 color:COLOR;
                float3 normal:NORMAL;
            };

            struct g2f
            {
                float4 vertex : SV_POSITION;
                fixed4 color:COLOR;
            };

            v2g vert (appdata v)
            {
                v2g o;
                o.vertex = v.vertex;
                o.normal=UnityObjectToWorldNormal(v.normal);
                return o;
            }

           [maxvertexcount(120)]//v2g input[3]
           void geom(inout PointStream<g2f> OutputStream,triangle v2g input[3])
           {
              float time_SinceBirth=(unityTime-_ShaderStartTime)*0.1f;
              g2f o = (g2f)0;
              V1 = (input[1].vertex - input[0].vertex).xyz;
              V2 = (input[2].vertex - input[0].vertex).xyz;
              V0 = input[0].vertex.xyz;
              CG=(input[0].vertex.xyz + input[1].vertex.xyz+ input[2].vertex.xyz)/3.0f;

              int numLayers =1<<_Level;     //2^_Level
              float dt = 1.0f / float( numLayers );
              float t = 1.0f;
              for( int it = 0; it < numLayers; it++ )
              {
                float smax = 1.0f - t;
                int nums = it + 1;
                float ds = smax / float( nums - 1 );
                float s = 0;
                for( int is = 0; is < nums; is++ )
                {
                    float3 v = V0 + s*V1 + t*V2;
                    float3 vel = _uVelScale * ( v - CG );
                    v = CG + vel*(_Speed*time_SinceBirth+1.0f) + 0.5f*_DispDir.xyz*sin(it*is)*(_Speed*time_SinceBirth)*(_Speed*time_SinceBirth);
                    o.vertex = UnityObjectToClipPos(float4( v, 1.0f ));
                    o.color=_FinalColor;
                    o.color.w=1.0f-smoothstep(0,1.0f,time_SinceBirth);
                    OutputStream.Append(o);
                    s += ds;
                } 
                t -= dt;
              }
           }

            fixed4 frag (g2f i) : SV_Target
            {
                return i.color;
            }
            ENDCG
        }
    }
}

控制脚本:

挂在场景中任意物体上,用来接受输入以及向shader传入一些必要实时参数。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ParticleExpController : MonoBehaviour {

    public Material particleExp;
    public MeshRenderer[] smRs;
    private Material[] originalMaterial;
    public GameObject model;

    // Use this for initialization
    void Start () {

    }

    IEnumerator EXP(){
        smRs=model.GetComponentsInChildren<MeshRenderer>();
        Material p_exp = new Material (particleExp);
        p_exp.SetFloat ("_ShaderStartTime", Time.time);
        for (int i = 0; i < smRs.Length; i++) {
            Material[] temp=smRs[i].materials;
            for(int j=0;j<smRs[i].materials.Length;j++){
                temp [j] = p_exp;
            }
            smRs [i].materials = temp;
            yield return new WaitForSeconds (0.5f);
        }
    }

    // Update is called once per frame
    void Update () {
        if(Input.GetKeyDown(KeyCode.E)){
            StartCoroutine (EXP ());
        }

        Shader.SetGlobalFloat ("unityTime", Time.time);
    }
}

参考:
OPENGL编程指南–Khronos Group
GLSL Geometry Shader–Mike Bailey–
(http://web.engr.oregonstate.edu/~mjb/cs519/Handouts/geometry_shaders.1pp.pdf)
射线和三角形的相交检测–DirectX
http://www.cnblogs.com/graphics/archive/2010/08/09/1795348.html

维护日志:
2018-2-24:填词改句
2018-6-10:改标题

猜你喜欢

转载自blog.csdn.net/liu_if_else/article/details/79313618