您的位置:首页 > 移动开发 > Unity3D

Unity3d 人物换装之 一个Shader处理3张图片 减少DrawCall

2015-03-24 00:51 411 查看
这里写的这个方法并不会提高效率。看看过程就好了,千万别用到项目中。

在上一篇
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 200

CGPROGRAM
#pragma surface surf Lambert

sampler2D _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 initialization
void 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 frame
void 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 200

CGPROGRAM
#pragma surface surf Lambert

sampler2D _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 initialization
void 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 frame
void Update () {

}
}

最后看运行结果,DrawCall减少到2 ,Character也完整的显示出来了。



工程示例下载: http://pan.baidu.com/s/1o6ytCoU
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: