本文完善了Krasnodar工作室Plarium的一系列出版物,涉及在Unity中使用3D模型的各个方面。 先前的文章:
“在Unity中使用Mesh的功能” ,
“ Unity:Mesh的过程编辑” ,
“将3D模型导入Unity和陷阱” ,
“纹理扫描中的像素压痕” 。
大约2年前,我们写
了一篇文章 ,讨论了在场景中优化3D几何形状的选项,该场景在摄像机角度和相应对象的旋转受到限制的情况下。 从那以后并没有发生太多的事情,但是改进解决方案,考虑不同方法并监视其他方法的机会困扰着开发人员。 在本文中,我们将描述基于绘制多边形的算法的改进版本,并讨论如何将部分工作转移到3D包中。

在现场裁剪
在上一篇文章中,我们已经考虑了该算法的基本原理:熄灭所有效果和透明对象,以一种颜色绘制未处理的多边形,并以不同颜色绘制已处理的多边形,然后渲染并提取结果。 在旧版本中,他们进行了绘画,因此所有黑色都是多余的,只有一个三角形被标记为红色。
在对该文章
的评论中,一位读者指出了通过在一组多边形和一组唯一数字之间建立一对一的对应关系来优化算法的可能性。 这样就有可能以相同的方式处理多个三角形。 考虑此选项。
在这种情况下,以及上一次,都应该进行一些预训练,以禁用舞台上所有吹口哨的对象和保证不影响目标模型可见性的对象。 相机视图几乎是独立处理的;它们仅通过可见多边形的公共索引缓冲区连接。 另外,对每个角度执行几何形状预处理,在此期间,将多边形折回并返回相机(
背面 )。 这样做是因为在算法的某个阶段创建的临时网格的顶点数比原始顶点大得多。 此数字可能轻易超过65,535的阈值,这将需要在计算中使用其他手势并导致性能降低。 无论如何,这些多边形将被删除,因为它们的颜色不会落入框架中。 但是,由于每个三角形都有可能产生三个无用顶点,因此提前消除不必要的多边形将简化算法的主要阶段并降低存储成本。
假设有一些3D模型,其几何形状由网格表示。 要以唯一的颜色绘制特定的多边形,您需要以该颜色绘制其所有顶点。 由于一般情况下一个顶点可以属于不同的多边形,因此无法直接解决问题。 无论我们如何着色任何顶点,渲染时,根据视频卡侧面的插值算法,其颜色都会在拥有该顶点的所有三角形上蔓延。
显示具有公共顶点的多边形时的颜色插值示例因此,有必要以某种方式将网格划分为单独的独立多边形,同时保留对象的拓扑和几何形状。 Dictum事实。 我们以这样的方式变换三角形和顶点的数组,以便为每个三角形创建3个唯一的顶点,其位置由原始网格的相应顶点确定。 值得注意的是,在一般情况下,与原始网格相比,此类网格的顶点数量要大得多。 并且如果该数字超过65 535,则在创建网格时,必须指定适当的索引格式。
将原始网格转换为每个多边形具有唯一顶点的网格private static Mesh GetNotSmoothMesh(Mesh origin) { var oVertices = origin.vertices; var oTriangles = origin.triangles; var vertices = new Vector3[oTriangles.Length]; var triangles = new int[oTriangles.Length]; for (int i = 0; i < triangles.Length; i++) { vertices[i] = oVertices[oTriangles[i]]; triangles[i] = i; } return new Mesh() { indexFormat = vertices.Length > 65535 ? IndexFormat.UInt32 : IndexFormat.UInt16, vertices = vertices, triangles = triangles }; }
现在,您需要指定该网格的多边形,以便在渲染操作之后可以确定屏幕上出现了哪个多边形。 如前所述,我们为多边形生成唯一的颜色,并用相应的颜色绘制每个三个顶点。 结果是一个新的网格,我们将其称为
Byte-Colored Mesh 。
字节色网格每个顶点仅属于一个多边形的网格着色 private static void ColorizePolygons(Mesh mesh) { var pColors = ColorsOfPolygons(mesh); var colors = new Color[mesh.vertexCount]; for (int i = 0; i < colors.Length; i++) { colors[i] = pColors[i / 3]; } mesh.colors = colors; } private static Color[] GetColorsOfPolygons(Mesh mesh) { var colors = new Color[mesh.triangles.Length / 3]; for (int i = 0; i < colors.Length; i++) { var color = Int2Color(i);
记住颜色。 现在该渲染了。 我们对所有摄像机角度进行3D渲染,并在处理每个摄像机角度时,补充在帧中检测到颜色的唯一多边形索引的缓冲区。 在计算相机时,您需要关闭抗锯齿功能,以避免由于相邻像素的插值而出现新的颜色。
值得一提的是,由于离散化,某些三角形可能不会显示,这是因为它们在屏幕上的投影尺寸特别小,而不是因为某些东西与它们重叠或在错误的一侧旋转了。 我们已经实现了算法的保守版本。 在这种情况下,计算三角形在屏幕上
的投影的
AABB ,并且如果其侧面中的至少一个小于图片中的纹素侧面,则将这样的多边形标记为可见。 当以低于目标设备的屏幕分辨率的分辨率运行算法时,此方法可防止伪影。 如果忽略小的多边形,只要使用的渲染纹理的分辨率高于目标设备屏幕的分辨率,则结果也是可以接受的。
我们在
Unity中实现了这种裁剪算法,并使用它来优化静态对象,这些对象的模型在场景中的各个位置多次出现。 这主要是风景:石头,树木,雕像,花瓶等,是指经常使用的预制件。 我们想在3D程序包创建阶段更早地优化此类对象,但谁知道关卡设计师想要放置他最喜欢的烛台的幻影姿势。
使用此类工具修剪同一类型的对象集会减少场景的大小,因为在
静态批处理期间
,无论如何在构建阶段复制通用
预制网格物体的数据是在场景中使用该网格物体表示的活动绘制对象的次数。 我们的方法还可以释放纹理地图集(例如
lightmap)中的空间 。 我们使用节省的空间来增加模型中那些在清洗后仍然存在的部分的细节。
3D裁剪
不过,最好是艺术家可以减少编辑器中所有不必要的内容,从而减少内容准备的阶段数。 当模型用于相对于摄像机只有一个预定旋转的场景中时,这是合理的。 以前,在集成到项目中之前,通常会手动手动简化将对象完全偏向用户的对象。 重要的是要注意,由于包装
UV开发的复杂性,在
Unity中以编程方式实现这种简化要困难得多,因此3D封装阶段的自动化有时会使艺术家的生活更轻松。
Blender是我们公司中用于处理3D模型的工具之一。 我们爬进去了。 像
Blender这样的“成人”软件似乎应该具有类似的功能。 但是,事实证明他不应该这样做。 我不得不看自己的自行车。
第一个想法是使用熟悉的选择工具-从一个摄影机角度基本上重复艺术家的手动工作的一部分:选择可见的多边形,反转选择,删除。 计划是这样的:移动摄像机,确定模型在每个位置的
AABB投影,然后请求选择与
AABB对应的区域的多边形的结果,获取当前视图的多边形集与先前视图的多边形的并集,最后删除未选择的多边形。
但是,在脚本执行期间,在任务方面发现了明显的缺点。
Blender中的选择工具
(矩形选择,圆形选择) 会随着屏幕每单位面积上所选元素数量的增加而
失去准确性 (某些多边形仍处于未选择状态),这使得它们无法在我们的自动化工具中使用。 有趣的事实:在相同的
3ds Max中 ,未观察到此类问题。
在Blender远处突出显示
选择结果下一个尝试旨在直接解决该问题:我们将来自摄影机的光线通过视口的每个像素发送,并查看哪些多边形首先与至少一条光线相交。 我们不希望这种方法获得准确的结果,但是值得尝试。 结果是显而易见的:在
CPU或具有少量光线的相同孔上进行处理时,生产率非常低。
尽管如此,我们还是立足于实施更高级的方法。 这个想法是在每个多边形上选择一定数量的随机点,然后从摄像机沿其方向发送光线。 这种方法效果很好,但是我们遇到了一些边界情况:也切掉了多边形,其中光束与其法线之间的角度大约等于π/ 2。 因此,当相机由于透视变形而变焦时,切出区域可能会打开。
在艺术家看来,这种方法过于激进,因此我们决定只着重于
背面 。
结论
众所周知,在创建游戏时,对设备资源的谨慎态度是影响最终产品质量的最重要因素。 对于移动平台而言,尤其是在积极使用RAM的情况下,尤其如此。 减少多边形的数量使您可以更有效地填充纹理地图集的空间,并稍微减少计算量。
另外,在使用上述工具等时,不要忘记工时成本和错误成本。 所提出的方法假设艺术部门的运作良好,尤其是涉及将模型集成到项目中的员工。
因此,有了本文讨论的条件和工具,我们遵守以下规则。 如果假设所创建的模型总是向用户一侧翻转,并且如果从这些角度来看,模型中某些部分的重叠部分很小,那么画家
可以在3D编辑器中使用我们的
背面修整
工具 ,检查正确性并继续包装
UV开发。 如果模型经常在不同的位置使用或具有更复杂的几何形状,则在导入到项目中之后,我们将运行本文第一部分中描述的算法,并使用该算法处理场景中的所有静态对象。