问题描述:
对于3D游戏工程来说,美术资源的存储几乎占据了绝大多数的空间,而对于一个3d 模型文件,MeshFilter(网格过滤器)负责存储物体的网格 以及贴图。依靠MeshRender(网格渲染器)跟据MeshFilter的信息去绘制此物体。Mesh 属性存储众多物理信息,比如顶点的位置、法线、切线。能否在不破坏原有模型外观的情况下尽量减少模型所占体积呢。
解决思路:
这里可以根据模型精度制定压缩规则,使用 Dictionary<VertexAttribute, VertexAttributeFormat> 字典存储(K值对应,顶点依赖属性。V表示它的精度)
比如低模的文件,远景或者边缘物体,小尺寸物体可以不需要添加UV。
根据不同的规则对文件夹下的模型进行分类,依照压缩类型,使用上述字典DescriptorReplaceMap来存储需要替换的顶点属性格式,然后将例如位置、法线和切线的属性格式从Float32或Float16进行替换。这样可以在保持Mesh数据结构不变的情况下,减少存储这些属性所需的内存空间。
压缩过程
根据压缩规则OptimizeType进行分类
OptimizeType 压缩方式
[SerializeField]
public enum OptimizeType
{
None,
CompressByUnityDefault, //使用unity默认压缩方式
CompressAllToFloat16, //不建议使用,可以使用Unity自带配置
CompressPosNormalAndTangentTo8Channel, //基本等同于CompressAllToFloat16,建议高模
[Obsolete("法线精度较低,不适用高模、低模")]
CompressPosNormalAndTangentTo6Channel, //会较大缺少精度
CompressPosNormalTo6Channel, //会删除切线并保留较高精度的法线,建议低模+地形
CompressNormal, //不压缩position, 删除切线并保留较高精度的法线(建议精度较大的不分块地形(与剔除UV联用))
CompressPosToFloat32, //只保留position
[Obsolete("此类型暂时无法使用")]
CompressPosNormalTo4Channel, //将法线压缩到pos的w分量,目前unity的pos.w只能存放符号位
}
填充字典 DescriptorReplaceMap
/// <summary>
/// get optimize mesh
/// worldVertexs : 为空时正常压缩,不为空时将会存储相对坐标
/// </summary>
public static Mesh GetOptimizeMesh(Mesh originMesh, OptimizeType optimizeType, bool withoutUVs = false, Vector3[] worldVertexs = null)
{
if (originMesh == null || originMesh?.tangents.Length == 0)
{
Debug.LogWarning("mesh is null or has been optimize");
return null;
}
DescriptorReplaceMap.Clear();
switch (optimizeType)
{
case OptimizeType.None:
break;
case OptimizeType.CompressByUnityDefault:
DescriptorReplaceMap.Add(VertexAttribute.Position, VertexAttributeFormat.Float32);
DescriptorReplaceMap.Add(VertexAttribute.Normal, VertexAttributeFormat.Float16);
DescriptorReplaceMap.Add(VertexAttribute.Tangent, VertexAttributeFormat.Float16);
break;
case OptimizeType.CompressAllToFloat16:
DescriptorReplaceMap.Add(VertexAttribute.Position, VertexAttributeFormat.Float16);
DescriptorReplaceMap.Add(VertexAttribute.Normal, VertexAttributeFormat.Float16);
DescriptorReplaceMap.Add(VertexAttribute.Tangent, VertexAttributeFormat.Float16);
break;
case OptimizeType.CompressPosNormalAndTangentTo8Channel:
DescriptorReplaceMap.Add(VertexAttribute.Position, VertexAttributeFormat.Float16);
//DescriptorReplaceMap.Add(VertexAttribute.Position, VertexAttributeFormat.Float32);
DescriptorReplaceMap.Add(VertexAttribute.Normal, VertexAttributeFormat.Float16);
break;
case OptimizeType.CompressPosNormalAndTangentTo6Channel:
DescriptorReplaceMap.Add(VertexAttribute.Position, VertexAttributeFormat.Float16);
DescriptorReplaceMap.Add(VertexAttribute.Normal, VertexAttributeFormat.UInt16);
break;
case OptimizeType.CompressPosNormalTo6Channel:
DescriptorReplaceMap.Add(VertexAttribute.Position, VertexAttributeFormat.Float16);
DescriptorReplaceMap.Add(VertexAttribute.Normal, VertexAttributeFormat.Float16);
break;
case OptimizeType.CompressNormal:
DescriptorReplaceMap.Add(VertexAttribute.Position, VertexAttributeFormat.Float32);
DescriptorReplaceMap.Add(VertexAttribute.Normal, VertexAttributeFormat.Float16);
break;
case OptimizeType.CompressPosToFloat32:
DescriptorReplaceMap.Add(VertexAttribute.Position, VertexAttributeFormat.Float32);
break;
case OptimizeType.CompressPosNormalTo4Channel:
DescriptorReplaceMap.A