将4个小人合成一个,使用gpu Instance批量绘制可以使用gpu Instance的特性降低批处理,但是gpu Instance只支持mesh filter所有需要将蒙皮动画烘培成顶点动画,通过MaterialPropertyBlock给shader传值区分显示的人物。
烘培人物和动画代码
using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(BakingAnimMesh))]
public class BakingAnimMeshEditor : Editor
{
public override void OnInspectorGUI()
{
DrawDefaultInspector();
BakingAnimMesh myScript = (BakingAnimMesh)target;
if (GUILayout.Button("提取"))
{
myScript.SaveAsset();
}
}
}
public class BakingAnimMesh : MonoBehaviour
{
public AnimationClip clip;//指定要烘培的动画
public Animator[] animator;//有动画组件的人物
public SkinnedMeshRenderer[] sk;//动画人物的蒙皮网格渲染器
int pnum;//总像素点
Texture2D texture;
int size = 0;//图片的宽高
int vertexCount = 0;//总顶点数
int frameCount;//总帧数
public string path = "mini";//导出名称
//导出动画图片,合并后的网格,合并后的贴图
public void SaveAsset()
{
//总帧数 = 动画时长 * 帧率
frameCount = Mathf.CeilToInt(clip.length * 30);
Debug.Log("frameCount:" + frameCount);
//顶点个数
int[] vertexNum = new int[sk.Length + 1];
vertexNum[0] = 0;
for (int i = 0; i < sk.Length; i++)
{
vertexCount += sk[i].sharedMesh.vertexCount;
vertexNum[i + 1] = vertexCount;
}
Debug.Log("vertexCount:" + vertexCount);
//总像素点 = 总顶点数 * 总帧数
pnum = vertexCount * frameCount;
Debug.Log("pnum:" + pnum);
//将像素点数转换成2的倍数的贴图宽高
size = Mathf.NextPowerOfTwo(Mathf.CeilToInt(Mathf.Sqrt(pnum)));
Debug.Log("size:" + size);
texture = new Texture2D(size, size, TextureFormat.RGBAFloat, false, true);
//计算顶点的最大最小值
_CalculateVertexMinAndMax(out float min, out float max);
Debug.Log("min:" + min);
Debug.Log("max:" + max);
//烘培图片
_BakeAnimationClip(max, min);
//合并导出网格
Mesh mesh = new Mesh();
//合并网格贴图
List<Texture2D> textures = new List<Texture2D>();
for (int i = 0; i < sk.Length; i++)
{
textures.Add(sk[i].sharedMaterial.mainTexture as Texture2D);
}
Texture2D meshtexture = new Texture2D(2048, 2048, TextureFormat.RGBAFloat, false, true);
Rect[] rects = meshtexture.PackTextures(textures.ToArray(), 0);
meshtexture.Apply();
//合并网格
List<CombineInstance> combines = new List<CombineInstance>();
List<Vector2[]> olduvs = new List<Vector2[]>();
for (int i = 0; i < sk.Length; i++)
{
CombineInstance combine = new CombineInstance();
combine.mesh = sk[i].sharedMesh;
combine.transform = sk[i].transform.localToWorldMatrix;
Vector2[] olduv = sk[i].sharedMesh.uv;
olduvs.Add(olduv);
Vector2[] newuv = new Vector2[olduv.Length];
for (int j = 0; j < olduv.Length; j++)
{
newuv[j] = new Vector2(rects[i].x + rects[i].width * olduv[j].x, rects[i].y + rects[i].height * olduv[j].y);
}
combine.mesh.uv = newuv;
combines.Add(combine);
}
mesh.CombineMeshes(combines.ToArray(), true, true);
for (int i = 0; i < sk.Length; i++)
{
sk[i].sharedMesh.uv = olduvs[i];
}
//导出网格
AssetDatabase.CreateAsset(mesh, "Assets/" + path + ".asset");
//导出贴图
File.WriteAllBytes(Application.dataPath + "/" + path + ".png", meshtexture.EncodeToPNG());
}
public void _BakeAnimationClip(float max, float min)
{
var mesh = new Mesh();
//计算差值
float vertexDiff = max - min;
//通过差值返回0到1的一个数(简易函数,传入float返回float)
Func<float, float> cal = (v) => (v - min) / vertexDiff;
//顶点计数
int currentPixelIndex = 0;
//循环动画的所有帧
for (int i = 0; i < frameCount; i++)
{
//计算每帧的时间
float t = i * 1f / 30;
for (int k = 0; k < animator.Length; k++)
{
//播放指定时间的动画
clip.SampleAnimation(animator[k].gameObject, t);
mesh.Clear(false);
sk[k].BakeMesh(mesh);
var vertices = mesh.vertices;
//Debug.Log("vertices:" + vertices.Length);
//循环网格的所有顶点
for (int v = 0; v < vertices.Length; v++)
{
var vertex = vertices[v];
//把顶点坐标转换成0到1的颜色值
Color c = new Color(cal(vertex.x), cal(vertex.y), cal(vertex.z));
//一维转二维
//通过顶点id计算该颜色在图片中的位置
int x = currentPixelIndex % size;
int y = currentPixelIndex / size;
//把颜色写入图片
texture.SetPixel(x, y, c);
currentPixelIndex++;
}
}
}
//保存图片
texture.Apply();
Debug.Log("currentPixelIndex:" + currentPixelIndex);
File.WriteAllBytes(Application.dataPath + "/" + path + "Anim.png", texture.EncodeToPNG());
}
public void _CalculateVertexMinAndMax(out float vertexMin, out float vertexMax)
{
//默认为float的最大最小值
float min = float.MaxValue;
float max = float.MinValue;
Mesh preCalMesh = new Mesh();
for (int f = 0; f < frameCount; f++)
{
float t = f * 1f / 30;
for (int k = 0; k < animator.Length; k++)
{
//播放指定时间的动画
clip.SampleAnimation(animator[k].gameObject, t);
//取出当前人物蒙皮的网格
sk[k].BakeMesh(preCalMesh);
//循环网格的所有顶点
for (int v = 0; v < preCalMesh.vertexCount; v++)
{
var vertex = preCalMesh.vertices[v];
//取x,y,z的最小值
//min = Mathf.Floor(Mathf.Min(min, vertex.x, vertex.y, vertex.z));
min = Mathf.Min(min, vertex.x, vertex.y, vertex.z);
//取x,y,z的最大值
//max = Mathf.Ceil(Mathf.Max(max, vertex.x, vertex.y, vertex.z));
max = Mathf.Max(max, vertex.x, vertex.y, vertex.z);
}
}
}
vertexMin = min;
vertexMax = max;
}
}
显示动画的shader
Shader "Unlit/MyGPUInstance"
{
Properties
{
_MainTex("Texture", 2D) = "white" {}
_FaceTex("Face", 2D) = "white" {}
_AnimTex("Anim", 2D) = "white" {}
_Fmax("fmax",Float) = 87
_VCount("vCount",Float) = 3466
_Size("size",Float) = 1024
_VertexMax("VertexMax",Float) = 2
_VertexMin("VertexMin",Float) = 1
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
//第一步: sharder 增加变体使用shader可以支持instance
#pragma multi_compile_instancing
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fog
#include "UnityCG.cginc"
UNITY_INSTANCING_BUFFER_START(Props)
UNITY_DEFINE_INSTANCED_PROP(int, _VMax0)//顶点最大数
UNITY_DEFINE_INSTANCED_PROP(int, _VMin0)//顶点最小数
UNITY_DEFINE_INSTANCED_PROP(float4, _FaceVec)//脸的贴图坐标
UNITY_INSTANCING_BUFFER_END(Props)
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
//返回顶点id
uint vid : SV_VertexID;
//第二步:instancID 加入顶点着色器输入结构
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float4 faceVec : VECTOR;
int clip : INT;
//第三步:instancID 加入顶点着色器输出结构
UNITY_VERTEX_INPUT_INSTANCE_ID
};
sampler2D _MainTex;
sampler2D _FaceTex;
sampler2D _AnimTex;
uint _Fmax;
uint _VCount;
float _Size;
float _VertexMax;
float _VertexMin;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
//第四步:instanceid在顶点的相关设置
UNITY_SETUP_INSTANCE_ID(v);
//第五步:传递 instanceid 顶点到片元
UNITY_TRANSFER_INSTANCE_ID(v, o);
int vmax = UNITY_ACCESS_INSTANCED_PROP(Props, _VMax0);
int vmin = UNITY_ACCESS_INSTANCED_PROP(Props, _VMin0);
o.faceVec = UNITY_ACCESS_INSTANCED_PROP(Props, _FaceVec);
//当前帧数
uint f = fmod(ceil(_Time.y * 30), _Fmax);
if (v.vid < vmax && v.vid > vmin)
{
//当前顶点
uint index = v.vid + f * _VCount;
//计算当前顶点在图片中的xy坐标
uint x = index % (uint)_Size;
uint y = index / _Size;
//把xy坐标转换为0到1的uv坐标
float uvx = x / _Size;
float uvy = y / _Size;
//获取图片中uv坐标的颜色,赋值给顶点坐标
v.vertex = tex2Dlod(_AnimTex, float4(uvx, uvy, 0, 0)) * (_VertexMax - _VertexMin) + _VertexMin;
o.clip = 1;
}
else {
o.clip = -1;
}
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
//第六步:instanceid在片元的相关设置
UNITY_SETUP_INSTANCE_ID(i);
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
fixed4 face = tex2D(_FaceTex, float2(i.uv.x * i.faceVec.x + i.faceVec.z,i.uv.y * i.faceVec.y + i.faceVec.w));
col = lerp(col, face, face.a);
clip(i.clip);
//得到由CPU设置的颜色
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}
}
多次创建代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CreatCube : MonoBehaviour
{
public GameObject cube;
public int n = 10;
// Start is called before the first frame update
void Start()
{
Init();
}
public void Init()
{
//创建预制体
for (int x = -n; x < n; x++)
{
for (int z = -n; z < n; z++)
{
if (Random.Range(0,10) < 1)
{
GameObject c = Instantiate(cube, transform);
c.transform.position = new Vector3(x, 0, z);
MaterialPropertyBlock props = new MaterialPropertyBlock();
switch (Random.Range(0, 4))
{
case 1:
props.SetInt("_VMax0", 6859);
props.SetInt("_VMin0", 3466);
props.SetVector("_FaceVec", new Vector4(8, 8, -4.2f, 0));
c.transform.eulerAngles = new Vector3(0, Random.Range(0, 360), 0);
break;
case 2:
props.SetInt("_VMax0", 6859 + 3564);
props.SetInt("_VMin0", 6859);
props.SetVector("_FaceVec", new Vector4(8, 8, -0.2f, -4));
c.transform.eulerAngles = new Vector3(-90, Random.Range(0, 360), 0);
break;
case 3:
props.SetInt("_VMax0", 13581);
props.SetInt("_VMin0", 6859 + 3564);
props.SetVector("_FaceVec", new Vector4(8, 8, -4.2f, -4));
c.transform.eulerAngles = new Vector3(-90, Random.Range(0, 360), 0);
break;
default:
props.SetInt("_VMax0", 3466);
props.SetInt("_VMin0", 0);
props.SetVector("_FaceVec", new Vector4(8, 8, -0.2f, 0));
c.transform.eulerAngles = new Vector3(0, Random.Range(0, 360), 0);
break;
}
c.GetComponent<MeshRenderer>().SetPropertyBlock(props);
}
}
}
}
}
代码中的部分数据只适用我的模型