我们将学习如何在Unity粒子系统中使用自定义顶点流(Vertex Streams)。顶点流通过粒子系统的Renderer模块来设置,它可以将额外的单个粒子数据传递到着色器。

着色器可以使用该数据,为系统中的每个粒子创建各种独特的效果,所有粒子都会在GPU上以极快的速度处理。

最终的效果场景如下图所示,虽然本文实现的效果不是非常惊艳,但它为后续教程中学习创作精美特效奠定基础。
「U3D」自定义粒子顶点流

Part 1:基础部分

首先,我们使用Unity模板创建一个简单的无光粒子着色器。在项目窗口中单击右键,选择Create -> Shader -> Unlit Shader。
「U3D」自定义粒子顶点流

我们将该文件命名为Simple Particle Unlit。

创建新材质,指定着色器,然后设置纹理属性为默认粒子纹理。
「U3D」自定义粒子顶点流

现在我们创建粒子系统,指定该材质到粒子系统Renderer模块的Materials字段。
「U3D」自定义粒子顶点流

我们会得到下图的效果。
「U3D」自定义粒子顶点流

图中的效果存在一些透明度问题,我们稍后会解决这些问题。

在相同粒子系统的Renderer模块中,勾选Custom Vertex Streams启用自定义顶点流,然后单击右下方的“+”按钮,添加Lifetime分类中的AgePercent流。

AgePercent是1D值,表示标准化范围[0.0, 1.0]内粒子的“生命周期”。0.0表示粒子生成时间,0.5表示粒子生命周期终点,1.0表示粒子消逝时间。
「U3D」自定义粒子顶点流

我们忽略顶点流与着色器输入不匹配红色警告信息,现在将顶点流传给着色器,我们需要接收并处理顶点流的数据。

在顶点流显示中,可以注意到数据已被紧凑地打包了。实际2D UV坐标位于TEXCOORD0.xy,AgePercent数据位于TEXCOORD0.z。要记住这些信息,以便我们知道在着色器中何处以及如何获取此数据。
「U3D」自定义粒子顶点流

每个texcoord都可以是4D向量,即CG/HLSL代码中,以[x, y, z, w]形式保存的float4变量。如果我们要添加额外的1D流,它将位于TEXCOORD0.w。如果数据比当前texcoord的可用空间大,它会作为余下部分移动到下一个texcoord,例如texcoord1或texcoord2等。

下图是相应的设置案例,里面没有添加这些顶点流。
「U3D」自定义粒子顶点流

我们可以看到,InverseStartLifetime(1D值)被添加到TEXCOORD0.w,Center(3D值)被添加到TEXCOORD1.xyz,Rotation3D(3D值)一部分被添加到TEXCOORD1 (w)。

另一部分被添加到TEXCOORD2 (xy)。(w|xy)表示xy属于下一个texcoord,即TEXCOORD2,尽管它显示TEXCOORD1.w|xy。因此Velocity从TEXCOORD2.zw开始,而Rotation3D有一部分存在TEXCOORD1.xy中,Velocity也有一部分存在TEXCOORD3 (x)中。

这可能有点难理解,因为Rotation3D的xyz值存在TEXCOORD1.w(Rotation3D的x)和TEXCOORD2.xy(Rotation3D的yz)中。它类似对Velocity中xyz的处理,Velocity的xyz存在TEXCOORD2.zw (xy)和TEXCOORD3.x (z)中。

现在关注AgePercent,回到我们的自定义着色器,开始进行处理。

该着色器只处理TEXCOORD0的x和y以获得实际纹理的UV坐标。AgePercent位于TEXCOORD0.z,因此我们需要在顶点输入和输出结构,分别为appdata和v2f将float2改为float3。

接下来,我们需要在着色器的顶点部分初始化UV,使UV在被传递到片段部分前包含合适的数值,这些“部分”其实是一个.shader文件中的顶点着色器和片段/像素着色器。在栅格化前,先处理顶点操作。

最后在片段部分,即给对象上色的像素着色器部分,我们可以利用该数据。我们根据粒子寿命,使用该数据向纹理(col)的粒子插补红色。

经过修改后,以下是完整的着色器代码。

Shader "Unlit/Simple Particle Unlit"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
 
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // 实现模糊效果
            #pragma multi_compile_fog
 
            #include "UnityCG.cginc"
 
            struct appdata
            {
                float4 vertex : POSITION;
                float3 uv : TEXCOORD0;
            };
 
            struct v2f
            {
                float3 uv : TEXCOORD0;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
            };
 
            sampler2D _MainTex;
            float4 _MainTex_ST;
     
        v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
 
                // 初始化当前uv.z(包含粒子的寿命百分比)
                o.uv.z = v.uv.z;
 
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
 
            fixed4 frag (v2f i) : SV_Target
            {
                // 采样纹理
                fixed4 col = tex2D(_MainTex, i.uv);
 
                float particleAgePercent = i.uv.z;
                float4 colourRed = float4(1, 0, 0, 1);
 
                // 根据粒子寿命百分比,从纹理颜色插值为红色
                col = lerp(col, colourRed * col.a, particleAgePercent);
 
                // 应用模糊效果
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}

我们得到了下图的结果,或许它并不惊艳,但这仅只是开始。
「U3D」自定义粒子顶点流

Next page