此文章将介绍使用Unity粒子系统制作球体GPU力场。

Part 1:粒子系统

为了能够预览我们的效果, 需要一个用于测试的粒子系统,只需布满白色粒子的平面场即可。
「U3D」GPU粒子力场

我们创建一个新粒子系统,重置它的Transform组件。在Main模块中,勾选Prewarm,将Start Speed设为0,使Start Size在0.25~ 0.3之间随机取值,Max Particles设为10,000。
「U3D」GPU粒子力场

将Emission模块的Rate over Time设为2,000、将Shape设为Box,Scale设为(25, 0, 25)。

现在我们得到了基本的平面场,现在仅需启用自定义顶点流,添加Center流,和之前一样,请无视警告信息,一旦我们使用新的着色器分配新材质,警告会自动消失。

至此,我们的预设阶段就完成了。
「U3D」GPU粒子力场

Part 2:顶点着色器

使用《伴随Simplex噪声的GPU粒子动画》教程中扩展基础着色器的代码来创建一个新着色器,下面是只修改了部分名称的代码内容。

我们要创建一个球体力场,由于球体由半径和世界空间位置定义,所以我们要添加这二个额外的属性。

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)
}

添加着色器的关联变量。

sampler2D _MainTex;
float4 _MainTex_ST;
 
float _ForceFieldRadius;
float3 _ForceFieldPosition;

创建一个新函数,它会接收粒子位置或中心点,返回float3值,即用x、y和z定义的位置。我们最终需要顶点和片段部分的结果,所以不必重复编写相同代码,只要将该效果的代码添加到函数中即可。

float3 GetParticleOffset(float3 particleCenter) {}

力场的基本逻辑如下:

if (particle is within force field)
{
    move particle to edge of force field (radius)
}

我们可以通过检查球体中心和粒子位置间的距离是否小于球体半径,判断粒子位置是否在球体之中。

float distanceToParticle = distance(particleCenter, _ForceFieldPosition);

如果距离小于力场半径,我们会进行处理。

float3 GetParticleOffset(float3 particleCenter)
{
    float distanceToParticle = distance(particleCenter, _ForceFieldPosition);
    if (distanceToParticle < _ForceFieldRadius) { }
}

在if语句中,我们需要获取粒子到力场边缘的距离,并使用半径方向,向外移动该距离的长度。

float distanceToForceFieldRadius = _ForceFieldRadius - distanceToParticle;
float3 directionToParticle = normalize(particleCenter - _ForceFieldPosition);
 
return directionToParticle * distanceToForceFieldRadius;

如果粒子不在力场内,会返回0,即没有偏移,等价于float3(0.0, 0.0, 0.0),这样我们的偏移计算函数就完成了。

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

我们可以在顶点着色器使用该函数,从TEXCOORD流获取粒子中心位置,将位置传入偏移函数,然后使用返回值作为偏移量。

v2f vert(appdata v)
{
    v2f o;
 
    float3 particleCenter = float3(v.tc0.zw, v.tc1.x);
    float3 vertexOffset = GetParticleOffset3(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;
}

使用该着色器创建新材质,并将其指定给粒子系统。现在我们应该可以进行如下操作。
「U3D」GPU粒子力场