Part 3:片段着色器

现在给粒子系统添加颜色,类似上一篇教程,我们将基于标准化偏移或位移值来插补颜色。首先添加合适的属性和变量。

材质属性:

_ForceFieldRadius("Force Field Radius", Float) = 4.0
_ForceFieldPosition("Force Field Position", Vector) = (0.0, 0.0, 0.0, 0.0)
 
 [HDR] _ColourA("Color A", Color) = (0.0, 0.0, 0.0, 0.0)
[HDR] _ColourB("Color B", Color) = (1.0, 1.0, 1.0, 1.0)

着色器变量:

float _ForceFieldRadius;
float3 _ForceFieldPosition;
 
float4 _ColourA;
float4 _ColourB;

标准化偏移值是指粒子和力场之间的距离,以力场半径为标准值。如果我们将函数返回类型改为float4,我们可以在xyz中保存偏移值,在w中保存标准化偏移标量。

float4 GetParticleOffset(float3 particleCenter)
{
    float distanceToParticle = distance(particleCenter, _ForceFieldPosition);
 
    if (distanceToParticle < _ForceFieldRadius)
    {
        float distanceToForceFieldRadius = _ForceFieldRadius - distanceToParticle;
        float3 directionToParticle = normalize(particleCenter - _ForceFieldPosition);
 
        float4 particleOffset;
 
        particleOffset.xyz = directionToParticle * distanceToForceFieldRadius;
        particleOffset.w = distanceToForceFieldRadius / _ForceFieldRadius
 
        return particleOffset;
    }
 
    return 0;
}

然后在片段函数中,只要检索数值并用它插补在二个颜色之间即可。

fixed4 frag(v2f i) : SV_Target
{
    // 采样纹理
    fixed4 col = tex2D(_MainTex, i.tc0);
 
    //让纹理颜色和粒子系统的顶点颜色输入相乘
    col *= i.color;
 
    float3 particleCenter = float3(i.tc0.zw, i.tc1.x);
    float particleOffsetNormalizedLength = GetParticleOffset2(particleCenter).w;
 
    col = lerp(col * _ColourA, col * _ColourB, particleOffsetNormalizedLength);
 
    col *= col.a;
 
    // 应用模糊效果
    UNITY_APPLY_FOG(i.fogCoord, col);
    return col;
}

现在只要稍作调整,我们就可以看到彩色的粒子系统。
「U3D」GPU粒子力场

Part 4:优化和扩展功能

在前面部分,我们使着色器代码尽可能简单,但我们可以修改部分代码,从而更好地符合GPU编程时的最佳实践,并添加负半径值的支持。

首先,我们可以通过获取粒子到力场距离和0之间的较大值,从而去掉if语句。因为如果粒子到力场距离大于半径,即粒子在力场外,我们会得到一个负值,负值比0小,因此会得到0。在GPU的超级并行状态时,我们要避免分支结构,以顺利传输数据。

一个小细节是在将半径用作除数时,我们给半径加了一个小数,从而防止在半径为0时出现未定义的行为。

float4 GetParticleOffset(float3 particleCenter)
{
    float distanceToParticle = distance(particleCenter, _ForceFieldPosition);
    float3 directionToParticle = normalize(particleCenter - _ForceFieldPosition);
 
    float distanceToForceFieldRadius = _ForceFieldRadius - distanceToParticle;
    distanceToForceFieldRadius = max(distanceToForceFieldRadius, 0.0);
 
    float4 particleOffset;
 
    particleOffset.xyz = directionToParticle * distanceToForceFieldRadius;
    particleOffset.w = distanceToForceFieldRadius / (_ForceFieldRadius + 0.0001); // 添加小数来避免除数为0,以及在r=0.0时出现未定义的颜色或行为。
 
    return particleOffset;
}

然后,我们会允许使用负半径值,这样不会远离力场中心移动粒子,而是将粒子向粒子中心吸引。我们首先将半径处理为绝对值,将它乘以sign函数,再将结果用于调整偏移方向。

float4 GetParticleOffset(float3 particleCenter)
{
    float distanceToParticle = distance(particleCenter, _ForceFieldPosition);
    float forceFieldRadiusAbs = abs(_ForceFieldRadius);
 
    float3 directionToParticle = normalize(particleCenter - _ForceFieldPosition);
 
    float distanceToForceFieldRadius = forceFieldRadiusAbs - distanceToParticle;
    distanceToForceFieldRadius = max(distanceToForceFieldRadius, 0.0);
 
    distanceToForceFieldRadius *= sign(_ForceFieldRadius);
 
    float4 particleOffset;
 
    particleOffset.xyz = directionToParticle * distanceToForceFieldRadius;
    particleOffset.w = distanceToForceFieldRadius / (_ForceFieldRadius + 0.0001); // 添加小数来避免除数为0,以及在r=0.0时出现未定义的颜色或行为。
 
    return particleOffset;
}

这样,反向力场制作完成。
「U3D」GPU粒子力场

下面是完整的着色器代码。

Shader "Custom/Particles/GPU Force Field Unlit (Tutorial)"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
 
        _ForceFieldRadius("Force Field Radius", Float) = 4.0
        _ForceFieldPosition("Force Field Position", Vector) = (0.0, 0.0, 0.0, 0.0)
 
        [HDR] _ColourA("Color A", Color) = (0.0, 0.0, 0.0, 0.0)
        [HDR] _ColourB("Color B", Color) = (1.0, 1.0, 1.0, 1.0)
    }
 
    SubShader
    {
        Tags { "Queue" = "Transparent" "RenderType" = "Opaque" }
        LOD 100
 
        Blend One One // 加法混合
        ZWrite Off //关闭深度测试
 
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            //实现模糊效果
            #pragma multi_compile_fog
 
            #include "UnityCG.cginc"
 
            struct appdata
            {
                float4 vertex : POSITION;
                fixed4 color : COLOR;
                float4 tc0 : TEXCOORD0;
                float4 tc1 : TEXCOORD1;
            };
 
            struct v2f
            {
                float4 tc0 : TEXCOORD0;
                float4 tc1 : TEXCOORD1;
                UNITY_FOG_COORDS(1)
                float4 vertex : SV_POSITION;
                fixed4 color : COLOR;
            };
 
            sampler2D _MainTex;
            float4 _MainTex_ST;
 
            float _ForceFieldRadius;
            float3 _ForceFieldPosition;
 
            float4 _ColourA;
            float4 _ColourB;
 
            float4 GetParticleOffset(float3 particleCenter)
            {
                float distanceToParticle = distance(particleCenter, _ForceFieldPosition);
                float forceFieldRadiusAbs = abs(_ForceFieldRadius);
  
                float3 directionToParticle = normalize(particleCenter - _ForceFieldPosition);
 
                float distanceToForceFieldRadius = forceFieldRadiusAbs - distanceToParticle;
                distanceToForceFieldRadius = max(distanceToForceFieldRadius, 0.0);
 
                distanceToForceFieldRadius *= sign(_ForceFieldRadius);
 
                float4 particleOffset;
  
                particleOffset.xyz = directionToParticle * distanceToForceFieldRadius;
                particleOffset.w = distanceToForceFieldRadius / (_ForceFieldRadius + 0.0001); //添加小数来避免除数为0,以及在r=0.0时出现未定义的颜色或行为。
 
                return particleOffset;
            }
 
           v2f vert(appdata v)
            {
                v2f o;
 
                float3 particleCenter = float3(v.tc0.zw, v.tc1.x);
 
                float3 vertexOffset = GetParticleOffset(particleCenter);
 
                v.vertex.xyz += vertexOffset;
                o.vertex = UnityObjectToClipPos(v.vertex);
 
                // 从保存在颜色顶点输入的粒子系统接收数据,并将该数据用于初始化颜色。
                o.color = v.color;
 
                o.tc0.xy = TRANSFORM_TEX(v.tc0, _MainTex);
 
                //初始化tex coord变量
                o.tc0.zw = v.tc0.zw;
                o.tc1 = v.tc1;
 
                UNITY_TRANSFER_FOG(o,o.vertex);
                return o;
            }
 
            fixed4 frag(v2f i) : SV_Target
            {
                //采样纹理
                fixed4 col = tex2D(_MainTex, i.tc0);
 
                // 让纹理颜色和粒子系统的顶点颜色输入相乘
                col *= i.color;
 
                float3 particleCenter = float3(i.tc0.zw, i.tc1.x);
                float particleOffsetNormalizedLength = GetParticleOffset(particleCenter).w;
 
                col = lerp(col * _ColourA, col * _ColourB, particleOffsetNormalizedLength);
 
                col *= col.a;
 
                // 应用模糊效果
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}