在日常遊戲開發中,一個3D模型需要隱身(半透明),常規步驟需要處理一下幾個問題:
透明Shader處理
模型多部件網格合併
模型多材質合併
需要同時處理圖片的透明和剔除,shader中一個pass是處理不了的。常規做法是進行兩次渲染,一次渲染半透明效果,一次進行透明剔除。
首先,利用AlphaTest進行剔除處理,需要開啟ZWrite選項,渲染一遍。
Pass
{
AlphaTest Greater [_CutOff]
ZWrite On
ColorMask 0
SetTexture [_MainTex]
{
ConstantColor [_Color]
Combine Texture * constant
}
}
其次,半透明渲染的時候需要關閉ZWrite選項
Pass
{
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
ZTest LEqual
SetTexture[_MainTex]
{
ConstantColor[_Color]
Combine Texture * constant
}
}
最終效果,飄帶為半透明,裙邊為剔除效果
模型有些糙,大家湊合看
在一個3D遊戲中,模型的換裝是很常見的功能,沒有換裝也會有簡單的武器,飾品類部件的綁定。這種情況下實現半透明隱身效果,就會出現模型間相互穿插的問題。
模型中支持換發功能,身體和頭髮屬於兩個部件,透明後相互穿插,效果十分不好
出現這種情況是因為兩個部件之間的半透明後,並不知道彼此的深度關係(半透明效果在關閉ZWrite模式下渲染),只有將其合併到同一個Mesh(網格)中才能實現比較完美的透明效果。
unity官方mesh合併文檔
當然只靠官方文檔並沒有什麼卵用,unity官方文檔的一貫風格,你們懂得~~我們還是要自己寫代碼,或者也可以使用像Mesh Baker這樣的現成工具實現,對於Mesh Baker的使用這裡就不累述了,有很詳細的文檔和例子。網格合併的同時還進行了材質合併,代碼在下一部分以前給出。
多個部件一般都是在不同的材質中,這樣在渲染一個3D模型的時候就需要同時處理多個材質球,打開Unity我們就會發現每使用一個材質球就會產生一個drawcall。合併多材質也是unity性能優化的一種方式。
未材質合併下的batches為4
材質合併後的batches為2
在模型的最外層,我掛載了一個Model3D.cs的腳本,用於處理模型和材質的合併,材質合併還需要對UV處理,代碼中也已經包含。
void Combine()
{
List<CombineInstance> combineInstances = new List<CombineInstance>();
List<Material> materials = new List<Material>();
List<Transform> bones = new List<Transform>();
Transform[] transforms = GetComponentsInChildren<Transform>(); List<Texture2D> textures = new List<Texture2D>();
int width = 0;
int height = 0;
int uvCount = 0;
List<Vector2[]> uvList = new List<Vector2[]>();
//蒙皮模型
foreach (SkinnedMeshRenderer smr in GetComponentsInChildren<SkinnedMeshRenderer>())
{
if (_material == null)
_material = Instantiate(smr.sharedMaterial) as Material;
for (int sub = 0; sub < smr.sharedMesh.subMeshCount; sub++)
{
CombineInstance ci = new CombineInstance();
ci.mesh = smr.sharedMesh;
ci.subMeshIndex = sub;
ci.transform = smr.transform.localToWorldMatrix;
combineInstances.Add(ci);
}
uvList.Add(smr.sharedMesh.uv);
uvCount += smr.sharedMesh.uv.Length;
if (smr.material.mainTexture != null)
{
//保存材質
materials.AddRange(smr.GetComponent<Renderer>().materials); //保存貼圖
foreach (var mat in materials)
{
textures.Add(mat.mainTexture as Texture2D);
}
}
//保存骨骼信息
foreach (Transform bone in smr.bones)
{
bones.Add(bone);
}
Destroy(smr.gameObject);
}
SkinnedMeshRenderer r = GetComponent<SkinnedMeshRenderer>(); if (!r)
r = gameObject.AddComponent<SkinnedMeshRenderer>();
r.sharedMesh = new Mesh();
//合併子網格
r.sharedMesh.CombineMeshes(combineInstances.ToArray(), true, false);
r.bones = bones.ToArray();
r.material = _material;
Texture2D skinnedMeshAtlas = new Texture2D(width, height);
Rect[] packingResult = skinnedMeshAtlas.PackTextures(textures.ToArray(), 0);
Vector2[] atlasUVs = new Vector2[uvCount];
//合併材質,處理uv
int j = 0;
for (int i = 0; i < uvList.Count; i++)
{
foreach (Vector2 uv in uvList[i])
{
atlasUVs[j].x = Mathf.Lerp(packingResult[i].xMin, packingResult[i].xMax, uv.x);
atlasUVs[j].y = Mathf.Lerp(packingResult[i].yMin, packingResult[i].yMax, uv.y);
j++;
}
}
r.material.mainTexture = skinnedMeshAtlas;
r.sharedMesh.uv = atlasUVs;
}
上述代碼中還存在一個問題,就是只合併了SkinnedMeshRenderer類型的網格,在unity中,帶動作的模型FBX檔案導入到項目中的時候,unity會默認導入為SkinnedMeshRenderer類型。
但是如果當前的FBX不帶動作(很多的武器是不需要動作,直接依靠綁點動作的),unity會默認導入為MeshRenderer類型,這時候這段代碼就無法將該模型的網格進行合併。
SkinnedMeshRenderer(帶動作包含骨骼信息)
MeshRenderer(不帶動作不包含骨骼信息)
這種情況有兩種解決辦法:1、浪費一根骨骼的資源,在所有不含動作的部件中加入一根骨骼,這樣導入到unity中,就會默認統一導入為SkinnedMeshRenderer類型,也就不存在不同類型網格合併問題。
2、實際上SkinnedMeshRenderer和Mesh類型是可以進行合併的,如Mesh Baker中就可以實現,具體的方法我沒有具體研究,有興趣的朋友可以自己看看。
合併後的運行效果,沒有穿幫現象
1、使用該shader渲染的時候,如果是非透明情況,需要將_cutoff的數值調整為接近1,且小於1的數值,如0.95,這樣的顯示效果才正確。
2、修改透明度調低顏色的Alpha值時需要同步調低_cutoff,Alpha值略大於_cutoff值即可,否則會出現模型層級不對問題(渲染先後順序)。