[ComputeShader]实例化大网格
发表于2018-11-19
本篇文章给大家分享下使用ComputeShader实现实例化大网格,主要也是借助了ComputeShader对于运行的图形库的支持,包括DX11、DX12、OpenGL4.3+、OpenGLES3等,在涉及到大量的数学计算时,并且是可以并行的没有很多分支的计算,都可以采用ComputeShader。
最终效果
思路
图解ComputeShader
这个图片就是ComputeShader,值得注意的是Thread最多为1024个。应该是考虑到当前显卡的最低线程数的关系。
数据交换的效率
有图可鉴,数据最好是单向输出的,尽量避免交换。
网格实例化思路
- 脚本中根据核心数对需要实例化的网格进行顶点排序并记录在uv信息中,然后合并网格,也可以利用dx11的SV_InstanceID(相关API可以查看MSCN的HLSL)。
- 编写ComputeShader,利用GPU对大量数据进行实时运算。
- 编写延迟光照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" }