Unity3d 人物換裝之 一個Shader處理3張圖片 減少DrawCall,
在上一篇
Unity3d人物換裝之Mesh合并(材質合并)
中,我通過一個例子,將三個帶有不同顏色 RGB的立方體,合并Mesh和材質到Character這一個GameObject中。這樣原本對3個GameObject的操作只需要對Character這一個GameObject進行操作就好了。但是我們的任務還沒有完成。
合并之前的遊戲:
合并之後的遊戲:
大家注意看合并之前和合并之後,雖然GameObject數量減少了,但是DrawCall一個都沒有減少哦!之前是4個,合并之後仍然是4個。
簡單的來說呢,就是一個材質球,一個DrawCall。也就是說呢,一個Shader,一個DrawCall。
既然知道了一個Shader一個DrawCall,那我們就開始著手去處理,把紅、綠、藍這三張圖片,在一個Shader中進行處理,只使用一個材質球,這樣就只有1個DrawCall了。
我們來建立一個Shader,就叫CombineShader吧,在預設的Shader代碼基礎上,刪掉MainTex這個紋理,添加我們自己的三個紋理:_Red 、_Green 、_Blue .
Shader "Custom/CombineShader" {Properties {_Red ("Base (RGB)", 2D) = "white" {}_Green ("Base (RGB)", 2D) = "white" {}_Blue ("Base (RGB)", 2D) = "white" {}}SubShader {Tags { "RenderType"="Opaque" }LOD 200CGPROGRAM#pragma surface surf Lambertsampler2D _Red;sampler2D _Green;sampler2D _Blue;struct Input {float2 uv_RedTex;float2 uv_GreenTex;float2 uv_BlueTex;};void surf (Input IN, inout SurfaceOutput o) {half4 c = tex2D (_Red, IN.uv_RedTex);o.Albedo = c.rgb;o.Alpha = c.a;}ENDCG} FallBack "Diffuse"}
在上面的未完成的Shader中,我取了_Red 的紋理來做取樣。我們接著修改指令碼代碼,使合并之後的GameObject Character使用CombineShader建立的材質。
using UnityEngine;using System.Collections;public class NewBehaviourScript : MonoBehaviour {// Use this for initializationvoid Start () { //擷取紋理; Texture redTex=transform.Find("CubeRed").GetComponent<MeshRenderer>().sharedMaterial.mainTexture; Texture greenTex=transform.Find("CubeGreen").GetComponent<MeshRenderer>().sharedMaterial.mainTexture; Texture blueTex=transform.Find("CubeBlue").GetComponent<MeshRenderer>().sharedMaterial.mainTexture; //合并材質; Shader combineShader = Shader.Find("Custom/CombineShader"); Material combineMaterial = new Material(combineShader); combineMaterial.SetTexture("_Red", redTex); combineMaterial.SetTexture("_Green", greenTex); combineMaterial.SetTexture("_Blue", blueTex); //合并Mesh; MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>(); CombineInstance[] combine = new CombineInstance[meshFilters.Length]; for (int i = 0; i < meshFilters.Length;i++ ) { combine[i].mesh = meshFilters[i].sharedMesh; combine[i].transform = meshFilters[i].transform.localToWorldMatrix; meshFilters[i].gameObject.SetActive(false); } transform.gameObject.AddComponent<MeshRenderer>(); transform.gameObject.AddComponent<MeshFilter>(); transform.GetComponent<MeshFilter>().mesh = new Mesh(); transform.GetComponent<MeshFilter>().mesh.CombineMeshes(combine, false); transform.gameObject.SetActive(true); //設定材質; transform.GetComponent<MeshRenderer>().sharedMaterial = combineMaterial;}// Update is called once per framevoid Update () {}}
運行之後能看到,在Character這個GameObject使用的材質球中,需要輸入三張紋理圖片。
現在再看,DrawCall數量已經降到2了,也就是說,合并之後 由原來的3個DrawCall 降到了 1個DrawCall。
但是還是有問題呢,為什麼只顯示一個立方體,哈哈,是我們代碼寫錯了。
transform.GetComponent<MeshFilter>().mesh.CombineMeshes(combine, false);
應該改為
transform.GetComponent<MeshFilter>().mesh.CombineMeshes(combine, true);
好了,這次可以顯示了,但是為什麼沒有貼圖?
為什麼沒有貼圖?因為我們只是在Unity中設定了貼圖,但是在Shader中還沒有去使用它們。
將Shader修改如下:
Shader "Custom/CombineShader" {Properties {_Red ("Base (RGB)", 2D) = "white" {}_Green ("Base (RGB)", 2D) = "white" {}_Blue ("Base (RGB)", 2D) = "white" {}}SubShader {Tags { "RenderType"="Opaque" }LOD 200CGPROGRAM#pragma surface surf Lambertsampler2D _Red;sampler2D _Green;sampler2D _Blue;struct Input {float2 uv_RedTex;float2 uv_GreenTex;float2 uv_BlueTex;float4 color:COLOR;};void surf (Input IN, inout SurfaceOutput o) {half4 colorIn;if(IN.color.a<0.33){colorIn=tex2D(_Red,IN.uv_RedTex);}else if(IN.color.a<0.6){colorIn=tex2D(_Green,IN.uv_GreenTex);}else{colorIn=tex2D(_Blue,IN.uv_BlueTex);}o.Albedo=colorIn.rgb;o.Alpha=colorIn.a;}ENDCG} FallBack "Diffuse"}
我們看到在surf 中對頂點顏色的Alpha值進行了判斷處理,這是利用頂點色Color的屬性,在代碼中進行賦值,來區分當前頂點原來是屬於哪一個立方體的。比如說color.a是0,那麼原來就屬於紅色立方體,就給它從紅色紋理來取樣。
using UnityEngine;using System.Collections;using System.Collections.Generic;public class NewBehaviourScript : MonoBehaviour {// Use this for initializationvoid Start () { //擷取紋理; Texture redTex=transform.Find("CubeRed").GetComponent<MeshRenderer>().sharedMaterial.mainTexture; Texture greenTex=transform.Find("CubeGreen").GetComponent<MeshRenderer>().sharedMaterial.mainTexture; Texture blueTex=transform.Find("CubeBlue").GetComponent<MeshRenderer>().sharedMaterial.mainTexture; //合并材質; Shader combineShader = Shader.Find("Custom/CombineShader"); Material combineMaterial = new Material(combineShader); combineMaterial.SetTexture("_Red", redTex); combineMaterial.SetTexture("_Green", greenTex); combineMaterial.SetTexture("_Blue", blueTex); //合并Mesh; MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>(); CombineInstance[] combine = new CombineInstance[meshFilters.Length]; List<Color> combineMeshColor=new List<Color>(); //Combine之後Mesh的Color; for (int i = 0; i < meshFilters.Length;i++ ) { combine[i].mesh = meshFilters[i].sharedMesh; //處理頂點位置; combine[i].transform = meshFilters[i].transform.localToWorldMatrix; //處理頂點顏色; Vector2[] UVArray = meshFilters[i].sharedMesh.uv; float bodyPart = i / (float)meshFilters.Length; for (int uvindex = 0; uvindex < UVArray.Length; uvindex++) { combineMeshColor.Add(new Color(bodyPart, bodyPart, bodyPart, bodyPart)); } meshFilters[i].gameObject.SetActive(false); } Mesh combineMesh = new Mesh(); combineMesh.CombineMeshes(combine, true); combineMesh.colors = combineMeshColor.ToArray(); combineMesh.name = gameObject.name; transform.gameObject.AddComponent<MeshRenderer>(); transform.gameObject.AddComponent<MeshFilter>(); transform.gameObject.GetComponent<MeshFilter>().sharedMesh = combineMesh; //設定材質; transform.GetComponent<MeshRenderer>().material = combineMaterial; transform.gameObject.SetActive(true);}// Update is called once per framevoid Update () {}}
最後看運行結果,DrawCall減少到2 ,Character也完整的顯示出來了。
工程樣本下載:
http://pan.baidu.com/s/1o6ytCoU