转载作品

粒子特效教程 | GPU粒子力场

一只大眼睛 发表于   2019-12-10 12:17:22
4619 9


我们将继续分享加拿大游戏特效大神Mirza Beig的粒子特效的系列教程,趁着春节假期的空闲,来梳理一下如何学习制作精美的粒子特效吧。



在本教程中,我们将学习使用Unity粒子系统制作球体GPU力场。下图是我们将制作的效果预览。fba602f6-cb25-4c73-98af-f00fc001c200_640[1].gif

 



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


817965b4-b6a4-4225-a718-6cb2bb6b8bb3_640[1].jpg




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

ff011cc1-a5f0-43b1-bdde-fb10d43a7cdb_640[1].png




 将Emission模块的Rate over Time设为2,000。

1f49685d-5093-4187-8282-896710be929d_640_1[1].png




 将Shape设为Box,Scale设为(25, 0, 25)。 

ae85b83f-a63a-4123-938e-376996a1f880_640_2[1].png




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

90d58022-d80c-484c-a262-311c83b36493_640_3[1].png



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


Shader "Custom/Particles/GPU Force Field Unlit (Tutorial)"
{
    Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    }
 
    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;
 
            v2f vert(appdata v)
            {
                v2f o;
 
                float3 vertexOffset = 0;
 
                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;
                col *= col.a;
 
                // 应用模糊效果
                UNITY_APPLY_FOG(i.fogCoord, col);
                return col;
            }
            ENDCG
        }
    }
}




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

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;
}使用该着色器创建新材质,并将其指定给粒子系统。现在我们应该可以进行如下操作。a34b8ca7-a526-4a77-806e-f592d78953cf_640_1[1].gif                                                      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;
}现在只要稍作调整,我们就可以看到彩色的粒子系统。1cfb83f3-ca6d-49d3-88e1-6b43e3e9fb4c_640_2[1].gif

小结

本教程结束了,这些都是熟练掌握制作精美粒子特效的基础,希望大家要熟练掌握起来。在下一篇教程中,我们将学习如何添加多个GPU力场的支持,敬请期待。


标签:
确定
评论(9)
KKOMM
到了添加Center这步,就不行了,下面完全搞不懂在哪进行
回复
2020-05-14 15:11
元素启录
怪物猎人世界里的引导虫!!?
回复
2020-05-11 01:17
XM丶
不会程序= =有连连看shader图吗
回复
2019-12-20 17:46
天堂
这个不是官方公众号里面的东西嘛
回复
2019-12-17 11:16
RWlzz
是啊,所以好像也说了这是转载的
回复
2019-12-17 14:44
a350031779
好厉害啊,佩服大佬
回复
2019-12-16 21:44
黎安Dunleon
我等不会代码的渣渣特效师不配做特效@~@
回复
2019-12-16 18:03
zxmadao
大佬牛逼
回复
2019-12-16 17:45
inspiration
666 大佬
回复
2019-12-16 17:27
原点
WJJ牛逼
回复
2019-12-16 17:06
没有更多啦~
  • 扫码加入QQ群 或搜索QQ群号: 572860166
  • 扫码关注公众号 或微信搜索: cokey游戏特效