bazien wordpress旅游企業(yè)seo官網(wǎng)分析報(bào)告
unity有自己的粒子系統(tǒng),但是這次我們要嘗試創(chuàng)建一個(gè)我們自己的粒子系統(tǒng),而且使用計(jì)算著色器有下面這些好處。總而言之,計(jì)算著色器適合處理大規(guī)模的數(shù)據(jù)集。例如,能夠高效地處理數(shù)萬個(gè)甚至數(shù)百萬個(gè)粒子的計(jì)算。這對(duì)于粒子系統(tǒng)這樣的效果特別重要,因?yàn)榱W訑?shù)量通常很大。
首先創(chuàng)建一個(gè)粒子結(jié)構(gòu)體,然后給上要用到的屬性,以及一些相關(guān)的變量
struct Particle{public Vector3 position;public Vector3 velocity;public float life;}const int SIZE_PARTICLE = 7 * sizeof(float);public int particleCount = 1000000;
?然后通過init方法初始化粒子數(shù)據(jù),分別隨機(jī)位置和生命周期,然后重置速度為0.很明顯位置會(huì)隨機(jī)分布在-0.5-0.5之間。然后填充粒子數(shù)據(jù)到computebuffer中,分別傳遞buffer到computeshader和shader中,這里很關(guān)鍵的一部分就是我們?cè)赾omputershader中修改粒子數(shù)據(jù),可以在shader中的buffer訪問到修改后的數(shù)據(jù)
void Init(){// initialize the particlesParticle[] particleArray = new Particle[particleCount];for (int i = 0; i < particleCount; i++){// Initialize particleVector3 v = new Vector3();v.x = Random.value * 2 - 1.0f;v.y = Random.value * 2 - 1.0f;v.z = Random.value * 2 - 1.0f;v.Normalize();v *= Random.value * 0.5f;// Assign particle propertiesparticleArray[i].position.x = v.x;particleArray[i].position.y = v.y;particleArray[i].position.z = v.z + 3;//遠(yuǎn)離攝像機(jī)particleArray[i].velocity.x = 0;particleArray[i].velocity.y = 0;particleArray[i].velocity.z = 0;particleArray[i].life = Random.value * 5.0f + 1.0f;//1-6}// create compute bufferparticleBuffer = new ComputeBuffer(particleCount, SIZE_PARTICLE);particleBuffer.SetData(particleArray);// find the id of the kernelkernelID = shader.FindKernel("CSParticle");uint threadsX;shader.GetKernelThreadGroupSizes(kernelID, out threadsX, out _, out _);groupSizeX = Mathf.CeilToInt((float)particleCount / (float)threadsX);// bind the compute buffer to the shader and the compute shadershader.SetBuffer(kernelID, "particleBuffer", particleBuffer);material.SetBuffer("particleBuffer", particleBuffer);material.SetInt("_PointSize", pointSize);}
然后是OnRenderObject函數(shù),每當(dāng) Unity 需要渲染一個(gè)對(duì)象時(shí),這個(gè)函數(shù)就會(huì)被調(diào)用,確保在渲染期間可以執(zhí)行自定義的渲染操作,下面圖片是對(duì)DrawProceduralNow函數(shù)的解釋
void OnRenderObject()//相機(jī)的每個(gè)渲染過程自動(dòng)調(diào)用{material.SetPass(0);//使用第一個(gè)PassGraphics.DrawProceduralNow(MeshTopology.Points, 1, particleCount);//程序化繪制頂點(diǎn)}
?
然后看一下我們的頂點(diǎn)著色器,首先是兩個(gè)參數(shù),第二實(shí)例ID就是逐漸增加的,從0增加到particleCount,第一個(gè)參數(shù)是每個(gè)實(shí)例的頂點(diǎn)索引,因?yàn)檫@次粒子都是點(diǎn),所以永遠(yuǎn)是0,如果是三角形就會(huì)是0,1,2.
v2f vert(uint vertex_id : SV_VertexID, uint instance_id : SV_InstanceID){v2f o = (v2f)0;// Coloro.color = fixed4(1,0,0,1)// Positiono.position = UnityObjectToClipPos(float4(particleBuffer[instance_id].position,1));o.size = 1;return o;}
?好了,現(xiàn)在運(yùn)行會(huì)得到一個(gè)在屏幕中央半徑為0.5左右的紅色小球。就像下面這樣,這是因?yàn)槲覀儾]有對(duì)粒子進(jìn)行任何處理,只是設(shè)置了位置和顏色。
接下來就是讓這些粒子動(dòng)起來,我們讓粒子跟著鼠標(biāo)的位置移動(dòng),首先找到鼠標(biāo)的位置。然后設(shè)置粒子的速度并且更改粒子的位置。然后如果生命變成0,就調(diào)用函數(shù)重新生成粒子。(重新生成函數(shù)代碼與獲取鼠標(biāo)位置代碼在后面完整代碼里)
下面這個(gè)鏈接是將鼠標(biāo)位置轉(zhuǎn)換為世界空間坐標(biāo)
gamedevbeginner.com/
how-to-convert-the-mouse-position-to-world-space-in-unity-2d-3d/
[numthreads(256, 1, 1)]
void CSParticle(uint3 id : SV_DispatchThreadID)
{Particle particle = particleBuffer[id.x];// 減少粒子的生命值particle.life -= deltaTime;// 計(jì)算粒子位置與鼠標(biāo)位置的差值float3 delta = float3(mousePosition.xy, 3) - particle.position;// 計(jì)算粒子運(yùn)動(dòng)的方向float3 dir = normalize(delta);// 更新粒子的速度particle.velocity += dir;// 根據(jù)速度更新粒子的位置particle.position += particle.velocity * deltaTime;// 將更新后的粒子數(shù)據(jù)存儲(chǔ)回緩沖區(qū)particleBuffer[id.x] = particle;// 如果粒子的生命值小于 0,則重新生成粒子if (particle.life < 0){respawn(id.x);}
}
現(xiàn)在因?yàn)橹挥屑t色,有點(diǎn)單調(diào),所以我們要豐富一下顏色。
隨著粒子的生命周期減少,這是每個(gè)通道的顏色變化
v2f vert(uint vertex_id : SV_VertexID, uint instance_id : SV_InstanceID){v2f o = (v2f)0;// Colorfloat life = particleBuffer[instance_id].life;float lerpVal = life * 0.25;// 計(jì)算顏色值o.color = fixed4(1 - lerpVal + 0.1, // Red componentlerpVal + 0.1, // Green component1, // Blue componentlerpVal // Alpha component);// Positiono.position = UnityObjectToClipPos(float4(particleBuffer[instance_id].position,1));o.size = _PointSize;return o;}
最后就來看一下最終效果把
?
完整代碼:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;#pragma warning disable 0649public class ParticleFun : MonoBehaviour
{private Vector2 cursorPos;// structstruct Particle{public Vector3 position;public Vector3 velocity;public float life;}const int SIZE_PARTICLE = 7 * sizeof(float);public int particleCount = 1000000;public Material material;public ComputeShader shader;[Range(1, 10)]public int pointSize = 2;int kernelID;ComputeBuffer particleBuffer;int groupSizeX; // Use this for initializationvoid Start(){Init();}void Init(){// initialize the particlesParticle[] particleArray = new Particle[particleCount];for (int i = 0; i < particleCount; i++){// Initialize particleVector3 v = new Vector3();v.x = Random.value * 2 - 1.0f;v.y = Random.value * 2 - 1.0f;v.z = Random.value * 2 - 1.0f;v.Normalize();v *= Random.value * 0.5f;// Assign particle propertiesparticleArray[i].position.x = v.x;particleArray[i].position.y = v.y;particleArray[i].position.z = v.z + 3;//遠(yuǎn)離攝像機(jī)particleArray[i].velocity.x = 0;particleArray[i].velocity.y = 0;particleArray[i].velocity.z = 0;particleArray[i].life = Random.value * 5.0f + 1.0f;//1-6}// create compute bufferparticleBuffer = new ComputeBuffer(particleCount, SIZE_PARTICLE);particleBuffer.SetData(particleArray);// find the id of the kernelkernelID = shader.FindKernel("CSParticle");uint threadsX;shader.GetKernelThreadGroupSizes(kernelID, out threadsX, out _, out _);groupSizeX = Mathf.CeilToInt((float)particleCount / (float)threadsX);// bind the compute buffer to the shader and the compute shadershader.SetBuffer(kernelID, "particleBuffer", particleBuffer);material.SetBuffer("particleBuffer", particleBuffer);material.SetInt("_PointSize", pointSize);}void OnRenderObject()//相機(jī)的每個(gè)渲染過程自動(dòng)調(diào)用{material.SetPass(0);//使用第一個(gè)PassGraphics.DrawProceduralNow(MeshTopology.Points, 1, particleCount);//程序化繪制頂點(diǎn)}void OnDestroy(){if (particleBuffer != null)particleBuffer.Release();}// Update is called once per framevoid Update(){float[] mousePosition2D = { cursorPos.x, cursorPos.y };// Send datas to the compute shadershader.SetFloat("deltaTime", Time.deltaTime);shader.SetFloats("mousePosition", mousePosition2D);// Update the Particlesshader.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));// z = 3.cursorPos.x = p.x;cursorPos.y = p.y;}
}
Shader "Custom/Particle" {Properties { _PointSize("Point size", Float) = 5.0 } SubShader {Pass {Tags{ "RenderType" = "Opaque" }LOD 200Blend SrcAlpha oneCGPROGRAM// Physically based Standard lighting model, and enable shadows on all light types#pragma vertex vert#pragma fragment fraguniform float _PointSize;#include "UnityCG.cginc"// Use shader model 3.0 target, to get nicer looking lighting#pragma target 5.0struct v2f{float4 position : SV_POSITION;float4 color : COLOR;float life : LIFE;float size: PSIZE;};// 定義粒子結(jié)構(gòu)體struct Particle{float3 position; // 粒子位置float3 velocity; // 粒子速度float life; // 粒子的生命值};// 聲明結(jié)構(gòu)化緩沖區(qū)StructuredBuffer<Particle> particleBuffer;v2f vert(uint vertex_id : SV_VertexID, uint instance_id : SV_InstanceID){v2f o = (v2f)0;// Colorfloat life = particleBuffer[instance_id].life;float lerpVal = life * 0.25;// 計(jì)算顏色值o.color = fixed4(1 - lerpVal + 0.1, // Red componentlerpVal + 0.1, // Green component1, // Blue componentlerpVal // Alpha component);// Positiono.position = UnityObjectToClipPos(float4(particleBuffer[instance_id].position,1));o.size = _PointSize;return o;}float4 frag(v2f i) : COLOR{return i.color;}ENDCG}}FallBack Off
}
#pragma kernel CSParticle// Variables set from the CPU
float deltaTime;
float2 mousePosition;uint rng_state;// 定義粒子結(jié)構(gòu)體
struct Particle
{float3 position; // 粒子位置float3 velocity; // 粒子速度float life; // 粒子的生命值
};// 聲明結(jié)構(gòu)化緩沖區(qū)
RWStructuredBuffer<Particle> particleBuffer;uint rand_xorshift()//隨機(jī)數(shù)范圍0-4,294,967,295
{// Xorshift 算法,來自 George Marsaglia 的論文rng_state ^= (rng_state << 13); // 將狀態(tài)左移13位,并與原狀態(tài)進(jìn)行異或rng_state ^= (rng_state >> 17); // 將狀態(tài)右移17位,并與原狀態(tài)進(jìn)行異或rng_state ^= (rng_state << 5); // 將狀態(tài)左移5位,并與原狀態(tài)進(jìn)行異或return rng_state; // 返回新的狀態(tài),作為隨機(jī)數(shù)
}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 particleparticleBuffer[id].life = 4;particleBuffer[id].velocity = float3(0,0,0);
}[numthreads(256, 1, 1)]
void CSParticle(uint3 id : SV_DispatchThreadID)
{Particle particle = particleBuffer[id.x];// 減少粒子的生命值particle.life -= deltaTime;// 計(jì)算粒子位置與鼠標(biāo)位置的差值float3 delta = float3(mousePosition.xy, 3) - particle.position;// 計(jì)算粒子運(yùn)動(dòng)的方向float3 dir = normalize(delta);// 更新粒子的速度particle.velocity += dir;// 根據(jù)速度更新粒子的位置particle.position += particle.velocity * deltaTime;// 將更新后的粒子數(shù)據(jù)存儲(chǔ)回緩沖區(qū)particleBuffer[id.x] = particle;// 如果粒子的生命值小于 0,則重新生成粒子if (particle.life < 0){respawn(id.x);}
}