[ComputeShader]实例化大网格

发表于2018-11-19
评论0 5k浏览
本篇文章给大家分享下使用ComputeShader实现实例化大网格,主要也是借助了ComputeShader对于运行的图形库的支持,包括DX11、DX12、OpenGL4.3+、OpenGLES3等,在涉及到大量的数学计算时,并且是可以并行的没有很多分支的计算,都可以采用ComputeShader。

最终效果

思路

图解ComputeShader
这个图片就是ComputeShader,值得注意的是Thread最多为1024个。应该是考虑到当前显卡的最低线程数的关系。

数据交换的效率

有图可鉴,数据最好是单向输出的,尽量避免交换。

网格实例化思路

  1. 脚本中根据核心数对需要实例化的网格进行顶点排序并记录在uv信息中,然后合并网格,也可以利用dx11的SV_InstanceID(相关API可以查看MSCN的HLSL)。
  2. 编写ComputeShader,利用GPU对大量数据进行实时运算。
  3. 编写延迟光照Shader,根据处理后的数据对网格进行实时变动。

源代码

脚本控制代码:
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using UnityEngine.Assertions;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace MatrixParticle
{
    public struct _Particle
    {
        Vector3 position;
        Vector3 direction;
        Vector3 scale;
        Vector2 uv;
        Vector4 color;
        float lifeTime;
    };
    public class MatrixParticles : MonoBehaviour
    {
        const int VERTEX_MAX = 65534;
        public ComputeShader shader;
        public Material mat;
        public Mesh mesh;
        [SerializeField]
        private int xMod = 1, yMod = 1, zMod = 1;
        [SerializeField]
        private Vector3 scale = Vector3.one;
        private ComputeBuffer particlesBuffer;
        private int initKernal, updateKernal, emitKernal;
        private int maxKernal;
        private List<MaterialPropertyBlock> propertyBlocks = new List<MaterialPropertyBlock>();
        private int perMeshNum, comMeshNum;
        private Mesh combinedMesh;
        void Start()
        {
            maxKernal = xMod * yMod * zMod * 1000;
            shader.SetInt("_xMod", xMod * 10);
            shader.SetInt("_yMod", yMod * 10);
            shader.SetInt("_zMod", zMod * 10);
            shader.SetVector("_Scale", scale);
            initKernal = shader.FindKernel("Init");
            updateKernal = shader.FindKernel("Update");
            emitKernal = shader.FindKernel("Emit");
            particlesBuffer = new ComputeBuffer(maxKernal, Marshal.SizeOf(typeof(_Particle)), ComputeBufferType.Default);
            CreateMesh();
            InitParticles();
        }
        void CreateMesh()
        {
            perMeshNum = VERTEX_MAX / mesh.vertexCount;
            comMeshNum = (int)Mathf.Ceil((float)maxKernal / perMeshNum);
            combinedMesh = CreateCombinedMesh(mesh, perMeshNum);
            for (int i = 0; i < comMeshNum; i++)
            {
                MaterialPropertyBlock property = new MaterialPropertyBlock();
                property.SetFloat("_Offset", perMeshNum * i);
                propertyBlocks.Add(property);
            }
        }
        void Update()
        {
            UpdateParticles();
            DrawParticles(Camera.main);
#if UNITY_EDITOR
            if (SceneView.lastActiveSceneView)
            {
                DrawParticles(SceneView.lastActiveSceneView.camera);
            }
#endif
        }
        void InitParticles()
        {
            shader.SetBuffer(initKernal, "_Particles", particlesBuffer);
            shader.Dispatch(initKernal, xMod, yMod, zMod);
        }
        void UpdateParticles()
        {
            shader.SetFloat("_Time", Time.deltaTime);
            shader.SetBuffer(updateKernal, "_Particles", particlesBuffer);
            shader.Dispatch(updateKernal, xMod, yMod, zMod);
        }
        public void EmitParticles(Vector3 pos, float height)
        {
            shader.SetVector("_Pos", pos);
            shader.SetFloat("_Height", -height);
            shader.SetBuffer(emitKernal, "_Particles", particlesBuffer);
            shader.Dispatch(emitKernal, xMod, yMod, zMod);
        }
        void DrawParticles(Camera camera)
        {
            mat.SetBuffer("_Particles", particlesBuffer);
            for (int i = 0; i < comMeshNum; ++i)
            {
                var props = propertyBlocks[i];
                props.SetFloat("_IdOffset", perMeshNum * i);
                Graphics.DrawMesh(combinedMesh, transform.position, transform.rotation, mat, 0, camera, 0, props);
            }
        }
        void OnDestroy()
        {
            particlesBuffer.Release();
        }
        Mesh CreateCombinedMesh(Mesh mesh, int num)
        {
            int[] meshIndices = mesh.GetIndices(0);
            int indexNum = meshIndices.Length;
            List<Vector3> verts = new List<Vector3>();
            int[] indices = new int[num * indexNum];
            List<Vector3> normals = new List<Vector3>();
            List<Vector4> tans = new List<Vector4>();
            List<Vector2> uv0 = new List<Vector2>();
            List<Vector2> uv1 = new List<Vector2>();
            for (int i = 0; i < num; i++)
            {
                verts.AddRange(mesh.vertices);
                normals.AddRange(mesh.normals);
                tans.AddRange(mesh.tangents);
                uv0.AddRange(mesh.uv);
                for (int n = 0; n < indexNum; n++)
                {
                    indices[i * indexNum + n] = i * mesh.vertexCount + meshIndices[n];
                }
                for (int n = 0; n < mesh.uv.Length; n++)
                {
                    uv1.Add(new Vector2(i, i));
                }
            }
            Mesh combinedMesh = new Mesh();
            combinedMesh.SetVertices(verts);
            combinedMesh.SetIndices(indices, MeshTopology.Triangles, 0);
            combinedMesh.SetNormals(normals);
            combinedMesh.SetTangents(tans);
            combinedMesh.SetUVs(0, uv0);
            combinedMesh.SetUVs(1, uv1);
            combinedMesh.RecalculateBounds();
            Vector3 size = new Vector3(xMod * 10 * scale.x, yMod * 10 * scale.y, zMod * 10 * scale.z);
            combinedMesh.bounds = new Bounds(transform.position + size * 0.5f, size);
            return combinedMesh;
        }
    }
}

ComputeShader代码:
#pragma kernel Init
#pragma kernel Emit
#pragma kernel Update
#include "./ComputeBuffer.cginc"
RWStructuredBuffer<Particle> _Particles;
int _xMod, _yMod, _zMod;
float4 _Scale;
float4 _Pos;
float _Time;
float _Speed;
float _Height;
float4 _LocalToWorld;
inline uint Index(uint3 id)
{
	return id.x + id.y * _xMod + id.z * _xMod * _yMod;
}
inline float Random(float2 seed)
{
	return frac(sin(dot(seed.xy, float2(12.9898, 78.233))) * 43758.5453);
}
inline float3 Random3(float3 seed)
{
	return float3(Random(seed.yz), Random(seed.xz), Random(seed.xy));
}
[numthreads(10, 10, 10)]
void Init(uint3 id : SV_DispatchThreadID)
{
	uint index = Index(id);
	Particle p = _Particles[index];
	p.position = id * _Scale.xyz;
	p.direction = float3(0, 0, 1);
	p.scale = _Scale.xyz;
	p.uv = p.position.xy / (float2(_xMod, _yMod)*_Scale.xy);
	float z = p.position.z / (_zMod *_Scale.z);
	p.color = float4(z, z, z, 1);
	p.lifeTime = -Random(id.xy);
	_Particles[index] = p;
}
[numthreads(10, 10, 10)]
void Update(uint3 id : SV_DispatchThreadID)
{
	uint index = Index(id);
	Particle p = _Particles[index];
	if (p.lifeTime > 0 && p.lifeTime < _Time)
	{
		p.position = id * _Scale.xyz;
		p.lifeTime = -Random(id.xy);
	}
	p.lifeTime -= _Time;
	if (p.lifeTime < 0)
	{
		p.position += sin(p.lifeTime * 10)*float3(0, 0, 0.02f);
	}
	else
	{
		p.position += p.direction * _Time;
	}
	_Particles[index] = p;
}
[numthreads(10, 10, 10)]
void Emit(uint3 id : SV_DispatchThreadID)
{
	uint index = Index(id);
	Particle p = _Particles[index];
	float3 pos = id * _Scale.xyz;
	float dis = clamp((20 - distance(pos.xy, _Pos.xy)) / 20, 0, 1);
	dis = dis * dis * dis;
	if (dis > 0.1)
	{
		float rand = Random(pos.xy);
		float z = 1 - pos.z / (_zMod *_Scale.z);
		p.position = float3(pos.x, pos.y, pos.z + z * _Height * rand * dis);
		p.direction = float3(0, 0, -_Height * z  *  rand * dis);
		p.lifeTime = 1;
	}
	_Particles[index] = p;
}

Shader代码:
Shader "QQ/Mesh"
{
	Properties
	{
		_Color("Color",color)=(0.5,0.5,0.5,1)
		_MainTex("Texture", 2D) = "white" {}
	}
		SubShader
	{
		Tags{ "RenderType" = "Opaque" }
		CGINCLUDE
		#pragma multi_compile_fog
		#pragma target 5.0
		#include "UnityCG.cginc"
		#include "AutoLight.cginc"
		#include "./ComputeBuffer.cginc"
		uniform StructuredBuffer<Particle> _Particles;
		uniform float _IdOffset;
		uniform fixed4 _Color;
		uniform sampler2D _MainTex;
		uniform float4 _MainTex_ST;
		uniform float4 _LightColor0;
		inline int GetID(float2 uv)
		{
			return (int)(uv.x + 0.5 + _IdOffset);
		}
		ENDCG
			Pass
		{
			Tags{"LightMode" = "Deferred"}
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma multi_compile_shadowcaster
			#pragma multi_compile ___ UNITY_HDR_ON
			struct G_Buffer
		{
			fixed4 diffuse : SV_Target0;
			float4 specSmoothness : SV_Target1;
			float4 normal : SV_Target2;
			fixed4 emission : SV_Target3;
		};
			struct a2v
		{
			float4 vertex : POSITION;
			float3 normal : NORMAL;
			float2 uv : TEXCOORD0;
			float2 id : TEXCOORD1;
		};
		struct v2f
		{
			float4 pos : SV_POSITION;
			float3 normal : NORMAL;
			float2 uv : TEXCOORD0;
			float4 color : TEXCOORD1;
		};
		v2f vert(a2v v)
		{
			Particle p = _Particles[GetID(v.id)];
			v.vertex.xyz *= p.scale;
			v.vertex.xyz += p.position;
			v2f o;
			o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
			o.uv = p.uv;
			o.color = p.color;
			o.normal = UnityObjectToWorldNormal(v.normal);
			return o;
		}
		G_Buffer frag(v2f i)
		{
			i.normal = normalize(i.normal);
			fixed4 col = tex2D(_MainTex, i.uv)*i.color;
			clip(col.a - 0.2);
			G_Buffer g;
			g.diffuse = _Color;
			g.specSmoothness = 0;
			g.normal = half4(i.normal * 0.5 + 0.5, 1);
			g.emission = col;
#ifndef UNITY_HDR_ON
			g.emission.rgb = exp2(-g.emission.rgb);
#endif
			return g;
		}
			ENDCG
		}
			Pass
		{
			Tags{ "LightMode" = "ShadowCaster" }
			ZWrite On
			ZTest LEqual
			Offset 1, 1
			CGPROGRAM
			#pragma vertex vert_
			#pragma fragment frag_
			#pragma multi_compile_shadowcaster
			struct a2v_ {
			float4 vertex : POSITION;
			float2 uv : TEXCOORD0;
			float2 id : TEXCOORD1;
			};
			struct v2f_ {
				V2F_SHADOW_CASTER;
				float2 uv : TEXCOORD1;
			};
			v2f_ vert_(a2v_ v) {
				Particle p = _Particles[GetID(v.id)];
				v.vertex.xyz *= p.scale;
				v.vertex.xyz += p.position;
				v2f_ o;
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = p.uv;
				TRANSFER_SHADOW_CASTER(o)
				return o;
			}
			float4 frag_(v2f_ i) : COLOR{
				float4 col = tex2D(_MainTex,i.uv);
				clip(col.a - 0.2);
				SHADOW_CASTER_FRAGMENT(i)
			}
			ENDCG
		}
}
FallBack "Diffuse"
}

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引