Learn ComputeShader 13 Adding a mesh to each particle

这次要给每个粒子加上网格。

添加的网格只是一个简单的四边形,它需要分成两个三角形。并且三角形的顶点必须按照顺时针排列,同时每个顶点都应该有UV信息。

所以接下来就要添加顶点结构体以及相关的compute buffer

 struct Vertex
    {
        public Vector3 position; // 顶点的3D位置
        public Vector2 uv;       // 纹理坐标
        public float life;       // 生命周期
    }

    ComputeBuffer vertexBuffer;

接下来就要创建顶点数组并且初始化。这里只需要设置顶点数组的uv就可以了。位置信息后面通过computebuffer实时更新。然后传递buffer到两个shader中并且设置相关变量

        int numVertices = numParticles * 6;
        Vertex[] vertexArray = new Vertex[numVertices];

        Vector3 pos = new Vector3();

        int index;
        for (int i = 0; i < numParticles; i++)
        {
            pos.Set(Random.value * 2 - 1.0f, Random.value * 2 - 1.0f, Random.value * 2 - 1.0f);
            pos.Normalize();
            pos *= Random.value;
            pos *= 0.5f;

            particleArray[i].position.Set(pos.x, pos.y, pos.z + 3);
            particleArray[i].velocity.Set(0, 0, 0);

            // Initial life value
            particleArray[i].life = Random.value * 5.0f + 1.0f;

            index = i * 6;
            // Triangle 1 - bottom-left, top-left, top-right
            vertexArray[index].uv.Set(0, 0);
            vertexArray[index + 1].uv.Set(0, 1);
            vertexArray[index + 2].uv.Set(1, 1);

            // Triangle 2 - bottom-left, top-right, bottom-right
            vertexArray[index + 3].uv.Set(0, 0);
            vertexArray[index + 4].uv.Set(1, 1);
            vertexArray[index + 5].uv.Set(1, 0);

        }

        // create compute buffers
        particleBuffer = new ComputeBuffer(numParticles, SIZE_PARTICLE);
        particleBuffer.SetData(particleArray);
        vertexBuffer = new ComputeBuffer(numVertices, SIZE_VERTEX);
        vertexBuffer.SetData(vertexArray);

        // bind the compute buffers to the shader and the compute shader
        shader.SetBuffer(kernelID, "particleBuffer", particleBuffer);
        shader.SetBuffer(kernelID, "vertexBuffer", vertexBuffer);
        shader.SetFloat("halfSize", quadSize * 0.5f);
        material.SetBuffer("vertexBuffer", vertexBuffer);

然后去computeshader中更新顶点位置信息,其余内容都和上次一样,这里要记住三角形顶点是顺时针排列的。

int index = id.x * 6;

	// Triangle 1 - bottom-left, top-left, top-right
	vertexBuffer[index].position.x = p.position.x - halfSize;
	vertexBuffer[index].position.y = p.position.y - halfSize;
	vertexBuffer[index].position.z = p.position.z;
	vertexBuffer[index].life = p.life;

	vertexBuffer[index + 1].position.x = p.position.x - halfSize;
	vertexBuffer[index + 1].position.y = p.position.y + halfSize;
	vertexBuffer[index + 1].position.z = p.position.z;
	vertexBuffer[index + 1].life = p.life;

	vertexBuffer[index + 2].position.x = p.position.x + halfSize;
	vertexBuffer[index + 2].position.y = p.position.y + halfSize;
	vertexBuffer[index + 2].position.z = p.position.z;
	vertexBuffer[index + 2].life = p.life;

	// Triangle 2 - bottom-left, top-right, bottom-right
	vertexBuffer[index + 3].position.x = p.position.x - halfSize;
	vertexBuffer[index + 3].position.y = p.position.y - halfSize;
	vertexBuffer[index + 3].position.z = p.position.z;
	vertexBuffer[index + 3].life = p.life;

	vertexBuffer[index + 4].position.x = p.position.x + halfSize;
	vertexBuffer[index + 4].position.y = p.position.y + halfSize;
	vertexBuffer[index + 4].position.z = p.position.z;
	vertexBuffer[index + 4].life = p.life;

	vertexBuffer[index + 5].position.x = p.position.x + halfSize;
	vertexBuffer[index + 5].position.y = p.position.y - halfSize;
	vertexBuffer[index + 5].position.z = p.position.z;
	vertexBuffer[index + 5].life = p.life;

接着我们去到顶点着色器就可以获取到修改后的顶点信息,和上次一样的设置颜色,然后转换位置到裁剪空间,这次还要加上uv,因为我们一会要对提额图采样。这次不一样的是每一个instance有六个顶点,这是我们在onrenderobject中设置的。

v2f vert(uint vertex_id : SV_VertexID, uint instance_id : SV_InstanceID)
		{
			v2f o = (v2f)0;

			int index = instance_id * 6 + vertex_id;
			float lerpVal = vertexBuffer[index].life * 0.25;

			// 计算颜色值
			o.color = fixed4(
				1 - lerpVal + 0.1,  // Red component
				lerpVal + 0.1,      // Green component
				1,                  // Blue component
				lerpVal             // Alpha component
			);

			// 计算顶点的世界空间到裁剪空间的位置
			o.position = UnityWorldToClipPos(float4(vertexBuffer[index].position, 1));

			// 设置纹理坐标
			o.uv = vertexBuffer[index].uv;
			
			return o;
		}

接下来是片段着色器,只需要加上贴图采样

		float4 frag(v2f i) : COLOR
		{
			fixed4 color = tex2D(_MainTex,i.uv)*i.color;
			return color;
		}

目前效果:目前我们看不到五角星,尽管在shader中我们对五角星贴图进行了采样,这是因为我们没有设置混合模式。这样的话透明度就没有了作用,仍然会显示成正方形。

增加下面这些代码以后,就可以正常渲染了

		LOD 200
		Blend SrcAlpha OneMinusSrcAlpha
		ZWrite Off

 最终效果:

完整代码:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

#pragma warning disable 0649

public class QuadParticles : MonoBehaviour
{

    private Vector2 cursorPos;

    // struct
    struct Particle
    {
        public Vector3 position;
        public Vector3 velocity;
        public float life;
    }

    struct Vertex
    {
        public Vector3 position; // 顶点的3D位置
        public Vector2 uv;       // 纹理坐标
        public float life;       // 生命周期
    }


    const int SIZE_PARTICLE = 7 * sizeof(float);
    const int SIZE_VERTEX = 6 * sizeof(float);

    public int particleCount = 10000;
    public Material material;
    public ComputeShader shader;
    [Range(0.01f, 1.0f)]
    public float quadSize = 0.1f;

    int numParticles;
    int numVerticesInMesh;
    int kernelID;
    ComputeBuffer particleBuffer;
    ComputeBuffer vertexBuffer;
    
    int groupSizeX; 
    
    // Use this for initialization
    void Start()
    {
        Init();
    }

    void Init()
    {
        // find the id of the kernel
        kernelID = shader.FindKernel("CSMain");

        uint threadsX;
        shader.GetKernelThreadGroupSizes(kernelID, out threadsX, out _, out _);
        groupSizeX = Mathf.CeilToInt((float)particleCount / (float)threadsX);
        numParticles = groupSizeX * (int)threadsX;

        // initialize the particles
        Particle[] particleArray = new Particle[numParticles];

        int numVertices = numParticles * 6;
        Vertex[] vertexArray = new Vertex[numVertices];

        Vector3 pos = new Vector3();

        int index;
        for (int i = 0; i < numParticles; i++)
        {
            pos.Set(Random.value * 2 - 1.0f, Random.value * 2 - 1.0f, Random.value * 2 - 1.0f);
            pos.Normalize();
            pos *= Random.value;
            pos *= 0.5f;

            particleArray[i].position.Set(pos.x, pos.y, pos.z + 3);
            particleArray[i].velocity.Set(0, 0, 0);

            // Initial life value
            particleArray[i].life = Random.value * 5.0f + 1.0f;

            index = i * 6;
            // Triangle 1 - bottom-left, top-left, top-right
            vertexArray[index].uv.Set(0, 0);
            vertexArray[index + 1].uv.Set(0, 1);
            vertexArray[index + 2].uv.Set(1, 1);

            // Triangle 2 - bottom-left, top-right, bottom-right
            vertexArray[index + 3].uv.Set(0, 0);
            vertexArray[index + 4].uv.Set(1, 1);
            vertexArray[index + 5].uv.Set(1, 0);

        }

        // create compute buffers
        particleBuffer = new ComputeBuffer(numParticles, SIZE_PARTICLE);
        particleBuffer.SetData(particleArray);
        vertexBuffer = new ComputeBuffer(numVertices, SIZE_VERTEX);
        vertexBuffer.SetData(vertexArray);

        // bind the compute buffers to the shader and the compute shader
        shader.SetBuffer(kernelID, "particleBuffer", particleBuffer);
        shader.SetBuffer(kernelID, "vertexBuffer", vertexBuffer);
        shader.SetFloat("halfSize", quadSize * 0.5f);
        material.SetBuffer("vertexBuffer", vertexBuffer);


    }

    void OnRenderObject()
    {
        material.SetPass(0);
        Graphics.DrawProceduralNow(MeshTopology.Triangles, 6, numParticles);
    }
    
    void OnDestroy()
    {
        if (particleBuffer != null){
            particleBuffer.Release();
        }
    }

    // Update is called once per frame
    void Update()
    {
        float[] mousePosition2D = { cursorPos.x, cursorPos.y };

        // Send datas to the compute shader
        shader.SetFloat("deltaTime", Time.deltaTime);
        shader.SetFloats("mousePosition", mousePosition2D);

        // Update the Particles
        shader.Dispatch(kernelID, groupSizeX, 1, 1);
    }

    void OnGUI()
    {
        Vector3 p = new Vector3();
        Camera c = Camera.main;
        Event e = Event.current;
        Vector2 mousePos = new Vector2();

        // Get the mouse position from Event.
        // Note that the y position from Event is inverted.
        mousePos.x = e.mousePosition.x;
        mousePos.y = c.pixelHeight - e.mousePosition.y;

        p = c.ScreenToWorldPoint(new Vector3(mousePos.x, mousePos.y, c.nearClipPlane + 14));

        cursorPos.x = p.x;
        cursorPos.y = p.y;
        
    }
}

Shader "Custom/QuadParticle" {
	Properties     
    {
        _MainTex("Texture", 2D) = "white" {}     
    }  

	SubShader {
		Pass {
		Tags{ "Queue"="Transparent" "RenderType"="Transparent" "IgnoreProjector"="True" }
		LOD 200
		Blend SrcAlpha OneMinusSrcAlpha
		ZWrite Off

		CGPROGRAM
		// Physically based Standard lighting model, and enable shadows on all light types
		#pragma vertex vert
		#pragma fragment frag

		#include "UnityCG.cginc"

		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 5.0

		struct Vertex{
			float3 position;
			float2 uv;
			float life;
		};
		
		struct v2f{
			float4 position : SV_POSITION;
			float4 color : COLOR;
			float2 uv: TEXCOORD0;
			float life : LIFE;
		};
		// particles' data
		StructuredBuffer<Vertex> vertexBuffer;
		sampler2D _MainTex;
		
		v2f vert(uint vertex_id : SV_VertexID, uint instance_id : SV_InstanceID)
		{
			v2f o = (v2f)0;

			int index = instance_id*6 + vertex_id;
			float lerpVal = vertexBuffer[index].life * 0.25f;
			o.color = fixed4(1.0f - lerpVal+0.1, lerpVal+0.1, 1.0f, lerpVal);
			o.position = UnityWorldToClipPos(float4(vertexBuffer[index].position, 1.0f));
			o.uv = vertexBuffer[index].uv;

			return o;
		}

		float4 frag(v2f i) : COLOR
		{
			fixed4 color = tex2D( _MainTex, i.uv ) * i.color;
			return color;
		}


		ENDCG
		}
	}
	FallBack Off
}
#pragma kernel CSMain

 // Particle's data
struct Particle
{
	float3 position;
	float3 velocity;
	float life;
};
struct Vertex
{
	float3 position;
	float2 uv;
	float life;
};

// Particle's data, shared with the shader
RWStructuredBuffer<Particle> particleBuffer;
RWStructuredBuffer<Vertex> vertexBuffer;

// Variables set from the CPU 
float deltaTime;
float2 mousePosition;
float halfSize;

uint rng_state;

// http://www.reedbeta.com/blog/quick-and-easy-gpu-random-numbers-in-d3d11/
uint rand_xorshift()
{
	// Xorshift algorithm from George Marsaglia's paper
	rng_state ^= (rng_state << 13);
	rng_state ^= (rng_state >> 17);
	rng_state ^= (rng_state << 5);
	return rng_state;
}

void respawn(uint id)
{
	rng_state = id;
	float tmp = (1.0 / 4294967296.0);
	float f0 = float(rand_xorshift()) * tmp - 0.5;
	float f1 = float(rand_xorshift()) * tmp - 0.5;
	float f2 = float(rand_xorshift()) * tmp - 0.5;
	float3 normalF3 = normalize(float3(f0, f1, f2)) * 0.8f;
	normalF3 *= float(rand_xorshift()) * tmp;
	particleBuffer[id].position = float3(normalF3.x + mousePosition.x, normalF3.y + mousePosition.y, normalF3.z + 3.0);
	// reset the life of this particle
	particleBuffer[id].life = 4;
	particleBuffer[id].velocity = float3(0,0,0);
}

[numthreads(256, 1, 1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
	Particle p = particleBuffer[id.x];
	
	// subtract the life based on deltaTime
	p.life -= deltaTime;

	float3 delta = float3(mousePosition.xy, 3) - p.position;
	float3 dir = normalize(delta);

	p.velocity += dir;
	p.position += p.velocity * deltaTime;

	particleBuffer[id.x] = p;
	
	if (particleBuffer[id.x].life < 0){
		respawn(id.x);
		p = particleBuffer[id.x];
	}
	
	//Set the vertex buffer //
	int index = id.x * 6;
	//Triangle 1 - bottom-left, top-left, top-right   
	vertexBuffer[index].position.x = p.position.x-halfSize;
	vertexBuffer[index].position.y = p.position.y-halfSize;
	vertexBuffer[index].position.z = p.position.z;
	vertexBuffer[index].life = p.life;
	vertexBuffer[index+1].position.x = p.position.x-halfSize;
	vertexBuffer[index+1].position.y = p.position.y+halfSize;
	vertexBuffer[index+1].position.z = p.position.z;
	vertexBuffer[index+1].life = p.life;
	vertexBuffer[index+2].position.x = p.position.x+halfSize;
	vertexBuffer[index+2].position.y = p.position.y+halfSize;
	vertexBuffer[index+2].position.z = p.position.z;
	vertexBuffer[index+2].life = p.life;
	//Triangle 2 - bottom-left, top-right, bottom-right  // // 
	vertexBuffer[index+3].position.x = p.position.x-halfSize;
	vertexBuffer[index+3].position.y = p.position.y-halfSize;
	vertexBuffer[index+3].position.z = p.position.z;
	vertexBuffer[index+3].life = p.life;
	vertexBuffer[index+4].position.x = p.position.x+halfSize;
	vertexBuffer[index+4].position.y = p.position.y+halfSize;
	vertexBuffer[index+4].position.z = p.position.z;
	vertexBuffer[index+4].life = p.life;
	vertexBuffer[index+5].position.x = p.position.x+halfSize;
	vertexBuffer[index+5].position.y = p.position.y-halfSize;
	vertexBuffer[index+5].position.z = p.position.z;
	vertexBuffer[index+5].life = p.life;
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值