关键技术:GPU实例、Graphics.DrawMeshInstancedIndirect()
工程截图
处理粒子化的Shader(仅支持DX11)
//图像粒子化Shader Shader "Custom/ImageParticleShader" { Properties{ //要显示的图像 _MainTex("Albedo (RGB)", 2D) = "white" {} //粒子遮罩 _MaskTex("Mask", 2D) = "white" {} _Glossiness("Smoothness", Range(0,1)) = 0.5 _Metallic("Metallic", Range(0,1)) = 0.0 } SubShader{ Tags { "Queue" = "Transparent" "IgnoreProjector" = "True" "RenderType" = "Transparent" "PreviewType" = "Plane" } LOD 200 Blend SrcAlpha OneMinusSrcAlpha ColorMask RGB Cull Off Lighting Off ZWrite Off CGPROGRAM // Physically based Standard lighting model #pragma surface surf Standard addshadow fullforwardshadows alpha:fade #pragma multi_compile_instancing //指定为每个顶点调用一次配置函数setup #pragma instancing_options procedural:setup sampler2D _MainTex; sampler2D _MaskTex; struct Input { float2 uv_MainTex; }; #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED StructuredBuffer<float4> positionBuffer; StructuredBuffer<float4> blockBuffer; #endif void setup() { //仅应针对专为程序绘制而编译的着色器变体执行此操作 //https://cloud.tencent.com/developer/article/1799606 #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED float4 data = positionBuffer[unity_InstanceID]; //设置转换矩阵,将顶点从对象空间转到世界空间 //设置缩放 unity_ObjectToWorld._11_21_31_41 = float4(data.w, 0, 0, 0); unity_ObjectToWorld._12_22_32_42 = float4(0, data.w, 0, 0); unity_ObjectToWorld._13_23_33_43 = float4(0, 0, data.w, 0); //设置坐标 unity_ObjectToWorld._14_24_34_44 = float4(data.xyz, 1); //还有一个unity_WorldToObject矩阵,其中包含逆变换,用于变换法向量。当应用非均匀变形时,需要正确地变换方向矢量。 //unity_WorldToObject与unity_ObjectToWorld互为逆变换 unity_WorldToObject = unity_ObjectToWorld; //可以通过简单地使用负位置和缩放的倒数来构造它。 unity_WorldToObject._14_24_34 *= -1; unity_WorldToObject._11_22_33 = 1.0f / unity_WorldToObject._11_22_33; #endif } half _Glossiness; half _Metallic; void surf(Input IN, inout SurfaceOutputStandard o) { #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED float2 uv = IN.uv_MainTex; fixed4 mask = tex2D(_MaskTex, uv); //映射uv到一个像素块区域 float4 block = blockBuffer[unity_InstanceID]; uv.x = block.x + uv.x * block.z; uv.y = block.y + uv.y * block.w; fixed4 c = tex2D(_MainTex, uv); o.Albedo = c.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a * mask.a; #else fixed4 col = tex2D(_MainTex, IN.uv_MainTex); o.Albedo = col.rgb; o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = col.a; #endif } ENDCG } FallBack "Diffuse" }
创建材质球
创建C#脚本
using UnityEngine; /// <summary> /// 图片粒子化 /// </summary> public class ImageParticle : MonoBehaviour { public Texture2D mainTexture; [Range(0.001f, 1)] public float particleScale = 0.01f; [Range(0f, 0.05f)] public float gap = 0f; public Mesh instanceMesh; public Material instanceMaterial; public int subMeshIndex = 0; public int instanceCount; private ComputeBuffer positionBuffer; private ComputeBuffer argsBuffer; private ComputeBuffer blockBuffer; //每个实例的索引数、实例数、起始索引位置、基顶点位置、起始实例位置 private uint[] args = new uint[5] { 0, 0, 0, 0, 0 }; private float gap_t = 1.87f; //创建粒子Mesh private Mesh CreateMesh() { //矩形的四个顶点坐标 Vector3[] vertices = new Vector3[4]; vertices[0] = new Vector3(0, 0, 0); vertices[1] = new Vector3(1f, 0, 0); vertices[2] = new Vector3(1f, 1f, 0); vertices[3] = new Vector3(0, 1f, 0); //三角形顶点索引 int[] triangles = new int[6] { 0, 1, 2, 2, 3, 0 }; //每个顶点的法线 Vector3[] normals = new Vector3[4]; normals[0] = new Vector3(0, 0, -5); normals[1] = new Vector3(0, 0, -5); normals[2] = new Vector3(0, 0, -5); normals[3] = new Vector3(0, 0, -5); //UV贴图坐标 Vector2[] uvs = new Vector2[4]; uvs[0] = new Vector2(0, 0); uvs[1] = new Vector2(1, 0); uvs[2] = new Vector2(1, 1); uvs[3] = new Vector2(0, 1); //顶点颜色 Color32[] colors32 = new Color32[4]; byte r = (byte)Random.Range(0, 255); Color32 color = new Color32(r, r, r, 255); colors32[3] = colors32[2] = colors32[1] = colors32[0] = color; //colors32[0] = new Color32(255, 0, 0, 255); //colors32[1] = new Color32(255, 0, 0, 255); //colors32[2] = new Color32(255, 0, 0, 255); //colors32[3] = new Color32(255, 0, 0, 255); Mesh mesh = new Mesh(); mesh.hideFlags = HideFlags.DontSave; mesh.vertices = vertices; mesh.triangles = triangles; mesh.colors32 = colors32; mesh.uv = uvs; mesh.normals = normals; return mesh; } void Start() { if (instanceMesh == null) instanceMesh = CreateMesh(); argsBuffer = new ComputeBuffer(1, args.Length * sizeof(uint), ComputeBufferType.IndirectArguments); UpdateBuffers(); } void Update() { if (!Mathf.Approximately(gap, gap_t)) UpdateBuffers(); // GPU绘制实例 Graphics.DrawMeshInstancedIndirect(instanceMesh, subMeshIndex, instanceMaterial, new Bounds(Vector3.zero, new Vector3(200.0f, 200.0f, 200.0f)), argsBuffer); } void UpdateBuffers() { // Ensure submesh index is in range if (instanceMesh != null) subMeshIndex = Mathf.Clamp(subMeshIndex, 0, instanceMesh.subMeshCount - 1); // Positions if (positionBuffer != null) positionBuffer.Release(); // Pixel Block if (blockBuffer != null) blockBuffer.Release(); // Pixel Block (每个粒子就是一个像素块) int blockWidth = 16; int blockHeight = 16; int blockXCount = mainTexture.width / blockWidth; int blockYCount = mainTexture.height / blockHeight; int blockCount = blockXCount * blockYCount; instanceCount = blockCount; int texWidth = blockXCount * blockWidth; int texHeight = blockYCount * blockHeight; float widthScale = (float)blockWidth / (float)texWidth; float heightScale = (float)blockHeight / (float)texHeight; blockBuffer = new ComputeBuffer(blockCount, 16); positionBuffer = new ComputeBuffer(blockCount, 16); Vector4[] blocks = new Vector4[blockCount]; Vector4[] positions = new Vector4[blockCount]; for (int y = 0; y < blockYCount; y++) { float v = (float)y / (float)blockYCount; for (int x = 0; x < blockXCount; x++) { float u = (float)x / (float)blockXCount; Vector4 block = new Vector4(u, v, widthScale, heightScale); int index = y * blockXCount + x; blocks[index] = block; positions[index] = new Vector4((particleScale + gap) * x, (particleScale + gap) * y, 0, particleScale); } } positionBuffer.SetData(positions); instanceMaterial.SetBuffer("positionBuffer", positionBuffer); blockBuffer.SetData(blocks); instanceMaterial.SetBuffer("blockBuffer", blockBuffer); gap_t = gap; // Indirect args if (instanceMesh != null) { args[0] = (uint)instanceMesh.GetIndexCount(subMeshIndex); args[1] = (uint)instanceCount; args[2] = (uint)instanceMesh.GetIndexStart(subMeshIndex); args[3] = (uint)instanceMesh.GetBaseVertex(subMeshIndex); } else { args[0] = args[1] = args[2] = args[3] = 0; } argsBuffer.SetData(args); instanceMaterial.SetTexture("_MainTex", mainTexture); } void OnDisable() { if (positionBuffer != null) positionBuffer.Release(); positionBuffer = null; if (blockBuffer != null) blockBuffer.Release(); blockBuffer = null; if (argsBuffer != null) argsBuffer.Release(); argsBuffer = null; } }
运行测试
Scene窗口截图
Game窗口截图
动态调整粒子间距