一:摘要
?
? ? ? ? 通過制作一個模型GPU消散效果來學習GPU Instancing 也就是實例化。
目標效果是楊超大佬文章《GPU shatter》里面的消散效果如圖:
?Tags:模型頂點分裂(Mesh Vertex Splitting), 實例化繪制(GPU Instancing Drawing),頂點運動(Vertex Anim)。
二:實現原理簡述
1:構建獲取數據:(instancing數據及模型信息)
? ? ? ? instancing數據需要的M矩陣,及自己想要傳遞的信息。
????????鹿模型mesh的頂點信息(mesh.vertices)和索引信息(mesh.triangles)以及面數(N)等信息,通過computerBuffer傳遞給材質。
2:構建instancing用的Triangle mesh(uv and?vertices)
3:Render
????????正常render鹿模型。
????????通過instancing繪制三角面,數量位置等信息已通過鹿模型獲取并傳遞,M矩陣也構建,隱藏可以得到另一個鹿模型。
4:構建動畫(compute shader anim? or vertex anim)
? ? ? ? 最簡單的就是使用vertex anim頂點動畫。方便易懂。
? ? ? ? compute shader動畫復雜一點但是性能應該會更好。
5:調參
? ? ? ? 把效果跳的稍微能看一點
三:實現
1:獲取模型數據
? 第一步:構建instaning數據(M矩陣構建)
//創建對應結構體private struct MeshProperties{public Matrix4x4 drawMeshInsM;}//在初始化時構建M矩陣void OnEnable(){//num為面數for (int i = 0; i < num; i++){Vector3 pos = commonDrawGO.transform.position;pos.x = -pos.x;Quaternion rotation = commonDrawGO.transform.rotation;Vector3 scale = commonDrawGO.transform.localScale;//通過Transform信息構建對應模型tmpProperties.drawMeshInsM = Matrix4x4.TRS(pos, rotation, scale);properties[i] = tmpProperties;}// 通過computeBuffer傳參給Material//(使用computeBuffer是因為之前寫的用到了CS)meshPropertiesBuffer = new ComputeBuffer(num, meshPropertiesSize);meshPropertiesBuffer.SetData (properties);GPUDrawMat.SetBuffer("_Properties", meshPropertiesBuffer);}
第二步:構建mesh數據(頂點等)
//mesh起始索引等信息uint[] args = new uint[5] { 0, 0, 0, 0, 0 };args[0] = (uint) mesh.GetIndexCount(0);args[1] = (uint) num;args[2] = (uint) mesh.GetIndexStart(0);args[3] = (uint) mesh.GetBaseVertex(0);//verticesGPUDrawMat.SetBuffer("_Properties", meshPropertiesBuffer);meshVerticesBuffer =new ComputeBuffer(TargetMesh.vertexCount, sizeof(float) * 3);meshVerticesBuffer.SetData(TargetMesh.vertices);GPUDrawMat.SetBuffer("_Vertices", meshVerticesBuffer);//triangles meshindicesBuffer =new ComputeBuffer(TargetMesh.triangles.Length, sizeof(int));meshindicesBuffer.SetData(TargetMesh.triangles);GPUDrawMat.SetBuffer("_Indices", meshindicesBuffer);
? ? ? ?2:構建Triangle
構建triangle時為了實現邊線亮中間暗淡效果,同時為了解決邊界鋸齒以及邊界線不等寬問題對uv進行了設計。看采樣貼圖及很好理解。
構建等邊三角形以及漸變貼圖解決(圖片是求美術大佬用sp生成的)
uv信息其實和頂點位置是一樣的,但是頂點位置原點在三角形中心,頂點uv在左下角。
private Mesh CreateTriMesh(){Mesh ans = new Mesh();//等邊三角形三點位置Vector3[] vertices = new Vector3[3];vertices[0] = new Vector3(0, 0.134f, 0) - Vector3.one * 0.5f;vertices[1] = new Vector3(1, 0.134f, 0) - Vector3.one * 0.5f;vertices[2] = new Vector3(0.5f, 1, 0) - Vector3.one * 0.5f;//等邊三角形三點UVVector2[] uvs = new Vector2[3];uvs[0] = new Vector2(0, 0.134f);uvs[1] = new Vector2(1, 0.134f);uvs[2] = new Vector2(0.5f, 1);int[] indices = new int[3];indices[0] = 0;indices[1] = 1;indices[2] = 2;ans.vertices = vertices;ans.uv = uvs;ans.triangles = indices;return ans;}
3:Render
第一步:C++++端
//instancing繪制
Graphics.DrawMeshInstancedIndirect(mesh, 0, GPUDrawMat, bounds, argsBuffer);
//另外一個走默認渲染就行
第二步:shader端(Vert And?Frag)
struct MeshProperties{float4x4 drawMeshInsM;};
StructuredBuffer<MeshProperties> _Properties;StructuredBuffer<float3> _Vertices;StructuredBuffer<int> _Indices;v2f vert(appdata_t i, uint instanceID: SV_InstanceID,uint vertexID : SV_VertexID) {//通過vertexID(0,1,2)和instanceID去_Vertices獲取真實的頂點信息//然后再乘上對應的M矩陣。float4 pos = mul(_Properties[instanceID].drawMeshInsM,float4(_Vertices[_Indices[vertexID + instanceID * 3.0]] - center,1));//}
4:構建動畫及著色
這里直接以頂點動畫為例,其實也寫了computershader的但是寫的有瑕疵
第一步:構建旋轉函數(Rotate)
前面有提到原點再三角中心,所以先構建一個旋轉函數
經典的構建旋轉矩陣,先把點移動到原點,然后再乘以旋轉函數,再移動回自己的位置
void Rotate(inout float4 vertex, float3 center, float3 around, float angle){float4x4 translation = float4x4(1, 0, 0, -center.x,0, 1, 0, -center.y,0, 0, 1, -center.z,0, 0, 0, 1);float4x4 translationT = float4x4(1, 0, 0, center.x,0, 1, 0, center.y,0, 0, 1, center.z,0, 0, 0, 1);around.x = -around.x;around = normalize(around);float s = sin(angle);float c = cos(angle);float ic = 1.0 - c;float4x4 rotation = float4x4(ic * around.x * around.x + c , ic * around.x * around.y - s * around.z, ic * around.z * around.x + s * around.y, 0.0,ic * around.x * around.y + s * around.z, ic * around.y * around.y + c , ic * around.y * around.z - s * around.x, 0.0,ic * around.z * around.x - s * around.y, ic * around.y * around.z + s * around.x, ic * around.z * around.z + c , 0.0,0.0 , 0.0 , 0.0 , 1.0);vertex = mul(translationT, mul(rotation, mul(translation, vertex)));if((instanceID + 1.0) % _BatchCount < _BatchCount * _Range){o.insID = 1;}else{o.insID = 0;}}
第二步:構建位移動畫(Pos And Scale)
//構建中心點float3 center = _Vertices[_Indices[ instanceID * 3.0]] +_Vertices[_Indices[ instanceID * 3.0 + 1]] + _Vertices[_Indices[ instanceID * 3.0 + 2]];center /=3;//構建位置float4 pos = mul(_Properties[instanceID].drawMeshInsM,float4(_Vertices[_Indices[vertexID + instanceID * 3.0]] - center,1));float4 pos1 = float4(_Vertices[_Indices[vertexID + instanceID * 3.0]],1);float3 around = normalize(GetRandomF3(pos.xyz));//float3(0.0,1.0,0.0);//動畫時間數據float statyTime = 0.4;float offsetIntensity = saturate((_BatchCount * _Range - (instanceID + 1.0)%_BatchCount)/10 -statyTime);float offsetIntensity1 = max(-statyTime,((_BatchCount * _Range - (instanceID + 1.0)%_BatchCount)/10 - statyTime)) + statyTime;offsetIntensity1 = min(offsetIntensity1 * 3 ,1.0);o.alphaLerp = offsetIntensity1;pos1.xyz = (1 - offsetIntensity) * pos1.xyz + offsetIntensity * center;float angle = _Speed * offsetIntensity;float3 positionWS = pos1;float3 viewDir = normalize(_WorldSpaceCameraPos.xyz - positionWS);//around = viewDir;Rotate(pos1,center,around,angle );pos1 = mul(_Properties[instanceID].drawMeshInsM,pos1);pos1.y += offsetIntensity * _FlowSpeed * 0.1;
第四步:著色
沒有技巧全是smoothstep出來(按理不該這么做,性能很差)
//frag //使用insID來表示當前Tri是否還需要顯示是否消失half4 frag(v2f i, uint instanceID: SV_InstanceID) : SV_Target {float insID = i.insID;if(insID > 0.9){fixed4 col = tex2D(_MainTex, i.uv);float uuu1 = smoothstep(_Pos - _Width * 0.5 - _SmoothRange,_Pos - _Width * 0.5,col.r);float uuu2 = 1 - smoothstep(_Pos + _Width * 0.5 ,_Pos + _Width * 0.5+ _SmoothRange,col.r);float lines = uuu1 * uuu2;float tris = saturate((uuu2 - uuu1) * uuu2);return (lines * _LineColor + tris * _TriColor) * i.alphaLerp;}return 0;}
5:調參
????????略
四:總結
? ? ? ? 通過對模型進行拆分使用instancing進行重繪制,對模型數據結構以及instancing做了簡單了解,還有用到的頂點動畫較為簡單,以及有很多可以優化的地方,比如M矩陣其實都是一樣的,有些位置數據是沒用的可以省略等等等。
后續會補上源代碼鏈接