这是一个 ComputeShader 结合 Shader 实现粒子喷泉的例子。
通过 ComputeShader 计算喷泉粒子的位置,并通过 Shader 将其绘制出来。

效果

原理

生成指定数量具有随机速度的点数据,ComputeShader 中将其初速度与重力加速度叠加,并根据存活时间粗略计算其位置,之后使用 Shader 将其绘制出来。

代码

1.ComputeShader

#pragma kernel PenQuan

struct Particle
{
    float3 position;
    float3 velocity;//梓喵出没博客(azimiao.com)
    float3 startingVelocity;
    float lifetime;
};

RWStructuredBuffer<Particle> particleBuffer;

float deltaTime;

[numthreads(256, 1, 1)]
void PenQuan(uint3 id : SV_DispatchThreadID)
{
    //粗略计算其位移
    particleBuffer[id.x].position += particleBuffer[id.x].velocity * deltaTime;
    particleBuffer[id.x].velocity +=  float3(0,-10,0) * deltaTime;
    particleBuffer[id.x].lifetime += deltaTime;//梓喵出没博客(azimiao.com)

    if (particleBuffer[id.x].lifetime >= 5) {
        particleBuffer[id.x].lifetime = 0;
        particleBuffer[id.x].position.x = 0;
        particleBuffer[id.x].position.y = 0;
        particleBuffer[id.x].position.z = 0;
        particleBuffer[id.x].velocity = particleBuffer[id.x].startingVelocity;
    }
}

2.Shader

Shader "PenQuan"
{
    Properties
    {
        _Tint ("Tint", Color) = (0, 0, 0.5, 0.3)
        _Randomness ("Random", Range(0.0, 1.0)) = 0.5
    }

    SubShader
    {
        Pass
        {
            Blend SrcAlpha one

            CGPROGRAM
            #pragma target 5.0

            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct Particle
            {
                float3 position;
                float3 velocity;
                float3 startingVelocity;
                float lifetime;
            };

            // Pixel shader input
            struct PS_INPUT
            {
                float4 position : SV_POSITION;
                float4 color : COLOR;
            };

            //共享 buffer
            StructuredBuffer<Particle> particleBuffer;

            // Properties variables
            uniform float4 _Tint;
            uniform float _Randomness;

            // Vertex shader
            PS_INPUT vert(uint vertex_id : SV_VertexID, uint instance_id : SV_InstanceID)
            {
                PS_INPUT o = (PS_INPUT)0;

                // Color
                float speed = length(particleBuffer[instance_id].velocity);

                //根据当前速度修改颜色
                float4 c = _Tint;

                c.r *= 1.0 - _Randomness;
                c.g *= 1.0 - _Randomness;
                c.b *= 1.0 - _Randomness;

                c.r += fmod(abs(particleBuffer[instance_id].velocity.x), _Randomness);
                c.g += fmod(abs(particleBuffer[instance_id].velocity.y), _Randomness);
                c.b += fmod(abs(particleBuffer[instance_id].velocity.z), _Randomness);
                c.a = 1;
                o.color = c;

                // Position
                o.position = UnityObjectToClipPos(float4(particleBuffer[instance_id].position, 1.0f));
                return o;
            }
            // Pixel shader
            float4 frag(PS_INPUT i) : COLOR
            {
                return i.color;
            }
            ENDCG
        }
    }
    Fallback Off
}

3.CSharp

public class PenQuanTest : MonoBehaviour {

    private struct Particle
    {
        public Vector3 position;
        public Vector3 velocity;
        public Vector3 startingVelocity;
        public float lifetime;
    }

    public int particleCount = 10000;

    public Material material;

    public ComputeShader computeShader;

    //结构体大小,用于初始化 buffer 时使用
    private const int SIZE_PARTICLE = 40;

    private int mComputeShaderKernelID;

    //保持引用
    ComputeBuffer particleBuffer;

    private const int WARP_SIZE = 256;

    private int mWarpCount;


    private Particle[] particleArray;

    void Start()
    {
        if (particleCount <= 0)
            particleCount = 1;
        mWarpCount = Mathf.CeilToInt((float)particleCount / WARP_SIZE);

        particleArray = new Particle[particleCount];

        //初始化粒子位置与随机速度信息
        for (int i = 0; i < particleCount; ++i)
        {
            particleArray[i].position.x = 0;
            particleArray[i].position.y = 0;
            particleArray[i].position.z =0;

            Vector2 v = Random.insideUnitCircle * 1.5f;
            particleArray[i].velocity.x = v.x;
            particleArray[i].velocity.y = 10f;
            particleArray[i].velocity.z = v.y;

            particleArray[i].startingVelocity = particleArray[i].velocity;

            particleArray[i].lifetime = Random.Range(-5f, 5f);
        }

        particleBuffer = new ComputeBuffer(particleCount, SIZE_PARTICLE);
        particleBuffer.SetData(particleArray);

        mComputeShaderKernelID = computeShader.FindKernel("PenQuan");

        computeShader.SetBuffer(mComputeShaderKernelID, "particleBuffer", particleBuffer);
        material.SetBuffer("particleBuffer", particleBuffer);
    }

    void OnDestroy()
    {
        if (particleBuffer != null)
            particleBuffer.Release();
    }

    // Update is called once per frame
    void Update()
    {
        computeShader.SetFloat("deltaTime", Time.deltaTime);
        computeShader.Dispatch(mComputeShaderKernelID, mWarpCount, 1, 1);
    }

    void OnRenderObject()
    {
        material.SetPass(0);
        Graphics.DrawProceduralNow(MeshTopology.Points, 1, particleCount);
    }
}

引用资料

本文在如下代码上进行了修改,以实现正常运行与效果优化。

  1. [代码资料] 【Github】 lukakostic ComputeShaderExamples
梓喵出没博客(azimiao.com)版权所有,转载请注明链接:https://www.azimiao.com/7650.html
欢迎加入梓喵出没博客交流群:313732000

我来吐槽

*

*