【Unity】Compute Shader粒子效果模拟
在UE4引擎中,已经实现了GPU的粒子系统,可以快速计算数百万的粒子及其碰撞。在Unity中,可以简单的使用Compute Shader,来尝试实现GPU粒子的效果。
实现一个简单的立方体粒子效果,图片压缩的很厉害……粒子数量在6w+
第一步,我们实现一个脚本,挂在在摄像机组件上,这个脚本我们用来控制粒子的渲染。
1 using System.Collections; 2 using System.Collections.Generic; 3 using UnityEngine; 4 5 public class CBufferTest : MonoBehaviour { 6 public Shader shader; 7 public ComputeShader computeShader; 8 9 private ComputeBuffer offsetBuffer; 10 private ComputeBuffer outputBuffer; 11 private ComputeBuffer constantBuffer; 12 private ComputeBuffer colorBuffer; 13 private int _kernel; 14 private Material material; 15 16 public const int VertCount = 65536; //64*64*4*4 (Groups*ThreadsPerGroup) 17 18 //We initialize the buffers and the material used to draw. 19 void Start() 20 { 21 CreateBuffers(); 22 CreateMaterial(); 23 _kernel = computeShader.FindKernel("CSMain"); 24 } 25 26 //When this GameObject is disabled we must release the buffers or else Unity complains. 27 private void OnDisable() 28 { 29 ReleaseBuffer(); 30 } 31 32 //After all rendering is complete we dispatch the compute shader and then set the material before drawing with DrawProcedural 33 //this just draws the "mesh" as a set of points 34 void OnPostRender() 35 { 36 Dispatch(); 37 38 material.SetPass(0); 39 material.SetBuffer("buf_Points", outputBuffer); 40 material.SetBuffer("buf_Colors", colorBuffer); 41 Graphics.DrawProcedural(MeshTopology.Points, VertCount); 42 } 43 44 //To setup a ComputeBuffer we pass in the array length, as well as the size in bytes of a single element. 45 //We fill the offset buffer with random numbers between 0 and 2*PI. 46 void CreateBuffers() 47 { 48 offsetBuffer = new ComputeBuffer(VertCount, 4); //Contains a single float value (OffsetStruct) 49 50 float[] values = new float[VertCount]; 51 for (int i = 0; i < VertCount; i++) 52 { 53 values[i] = Random.value * 2 * Mathf.PI; 54 } 55 56 offsetBuffer.SetData(values); 57 58 constantBuffer = new ComputeBuffer(1, 4); //Contains a single element (time) which is a float 59 colorBuffer = new ComputeBuffer(VertCount, 12); 60 outputBuffer = new ComputeBuffer(VertCount, 12); //Output buffer contains vertices (float3 = Vector3 -> 12 bytes) 61 } 62 63 //For some reason I made this method to create a material from the attached shader. 64 void CreateMaterial() 65 { 66 material = new Material(shader); 67 } 68 69 //Remember to release buffers and destroy the material when play has been stopped. 70 void ReleaseBuffer() 71 { 72 constantBuffer.Release(); 73 offsetBuffer.Release(); 74 outputBuffer.Release(); 75 76 DestroyImmediate(material); 77 } 78 79 //The meat of this script, it sets the constant buffer (current time) and then sets all of the buffers for the compute shader. 80 //We then dispatch 32x32x1 groups of threads of our CSMain kernel. 81 void Dispatch() 82 { 83 constantBuffer.SetData(new[] { Time.time }); 84 85 computeShader.SetBuffer(_kernel, "cBuffer", constantBuffer); 86 computeShader.SetBuffer(_kernel, "offsets", offsetBuffer); 87 computeShader.SetBuffer(_kernel, "output", outputBuffer); 88 computeShader.SetBuffer(_kernel, "color", colorBuffer); 89 computeShader.Dispatch(_kernel, 64, 64, 1); 90 } 91 }
第二步,实现Compute Shader,用来计算粒子的位置以及颜色。
1 #pragma kernel CSMain 2 //We define the size of a group in the x and y directions, z direction will just be one 3 #define thread_group_size_x 4 4 #define thread_group_size_y 4 5 6 //A struct that simple holds a position 7 struct PositionStruct 8 { 9 float3 pos; 10 }; 11 12 //A struct containing an offset for use by Wave function 13 struct OffsetStruct 14 { 15 float offset; 16 }; 17 18 //A constant buffer struct that holds a time variable sent from Unity 19 struct CBufferStruct 20 { 21 float t; 22 }; 23 24 //We keep three buffers accessed by the kernel, a constant buffer that is the same for every computation, 25 //an offset buffer with a value to offset the wave, and an output buffer that is written to by the kernel 26 RWStructuredBuffer<CBufferStruct> cBuffer; 27 RWStructuredBuffer<OffsetStruct> offsets; 28 RWStructuredBuffer<PositionStruct> output; 29 RWStructuredBuffer<float3> color; 30 //A simple sine modulation of the z coordinate, with an offset by a random value between 0 and 2PI 31 float3 Wave(float3 p, int idx,uint3 id) 32 { 33 p.x=cos(cBuffer[0].t+id.x); 34 p.y=sin(cBuffer[0].t+id.y); 35 p.z = sin(cBuffer[0].t + offsets[idx].offset); 36 return p; 37 } 38 float3 SetColor(float3 p,uint3 id) 39 { 40 p.x=abs(sin(cBuffer[0].t+id.x)); 41 p.y=abs(sin(cBuffer[0].t+id.y)); 42 p.z=abs(sin(cBuffer[0].t+id.x+id.y)); 43 return p; 44 } 45 //The kernel for this compute shader, each thread group contains a number of threads specified by numthreads(x,y,z) 46 //We lookup the the index into the flat array by using x + y * x_stride 47 //The position is calculated from the thread index and then the z component is shifted by the Wave function 48 [numthreads(thread_group_size_x,thread_group_size_y,1)] 49 void CSMain (uint3 id : SV_DispatchThreadID) 50 { 51 int idx = id.x + id.y * thread_group_size_x * 32; 52 float spacing = 1; 53 54 float3 pos = float3(id.x*spacing, id.y*spacing, id.z*spacing); 55 pos = Wave(pos, idx,id); 56 color[idx]=SetColor(pos,id); 57 output[idx].pos = pos; 58 }
第三步,实现简单的V&F Shader,用于渲染像素到屏幕。
1 Shader "Custom/CBufferTest" { 2 Properties { 3 _MainTex ("Albedo (RGB)", 2D) = "white" {} 4 } 5 SubShader { 6 Tags { 7 "Queue"="Transparent" 8 "IgnoreProjector"="True" 9 "RenderType"="Transparent" 10 } 11 LOD 200 12 Cull Off 13 blend srcAlpha one 14 Pass { 15 CGPROGRAM 16 #pragma target 5.0 17 18 #pragma vertex vert 19 #pragma fragment frag 20 21 #include "UnityCG.cginc" 22 23 //The buffer containing the points we want to draw. 24 StructuredBuffer<float3> buf_Points; 25 StructuredBuffer<float3> buf_Colors; 26 //A simple input struct for our pixel shader step containing a position. 27 struct ps_input { 28 float4 pos : SV_POSITION; 29 half3 color:COLOR; 30 }; 31 32 //Our vertex function simply fetches a point from the buffer corresponding to the vertex index 33 //which we transform with the view-projection matrix before passing to the pixel program. 34 ps_input vert (uint id : SV_VertexID) 35 { 36 ps_input o; 37 float3 worldPos = buf_Points[id]; 38 o.color=buf_Colors[id]; 39 o.pos = mul (UNITY_MATRIX_VP, float4(worldPos,1.0f)); 40 41 return o; 42 } 43 44 //Pixel function returns a solid color for each point. 45 float4 frag (ps_input i) : COLOR 46 { 47 return float4(i.color,0.5); 48 } 49 50 ENDCG 51 } 52 53 } 54 FallBack "Diffuse" 55 }
可以搜索不同的几何体算法,来实现不同的效果。