1)本地资源检测功能使用疑问
2)大型 Mesh 的渲染开销
3)API 的使用
4)关于在 Android 与 iOS 平台上的 Z-fighting 问题
5)如何获取当前帧逝去的时间
UWA 问答社区:answer.uwa4d.com
UWA QQ 群 2:793972859(原群已满员)
Q:我们使用了本地资源检测这个功能后,想问下有几个维度为什么会出现在检查规则内:
1)使用 Tiled 模式的 Image 组件
2)精度过高的动画片段(Force Text 只对编辑器有效果、二进制的情况下,Float 是 4 个字节,精度应该没有区别)
3)未使用 PCM 格式的音频
4)Wrap 模式为 Repeat 的纹理
A:以下是几个规则的解释,后续也会放到网页上进行显示。
1)使用 Tiled 模式的 Image 组件:Tiled 模式的 Image 组件可能产生过多的面片,建议进一步检查。
2)精度过高的动画片段:建议把精度缩减到 3-4 位,从而降低动画片段的内存占用。
3)未使用 PCM 格式的音频:未使用 PCM 格式的音频可能存在音质问题,建议进一步检查。
4)Wrap 模式为 Repeat 的纹理:Wrapmode 使用了 Repeat 模式,容易导致贴图边缘出现杂色,建议进行检查。部分规则确实只是起到提醒作用的,即使不符合规则也有可能是合理的;目前规则也在完善和调整中,感谢使用。
该功能已于 3 月 6 日正式上线,欢迎大家前来使用 UWA 本地资源检测(https://blog.uwa4d.com/archives/UWA_Pipeline2.html)。
以下是降低动画精度的相关文章《Unity 动画文件优化探究》(https://blog.uwa4d.com/archives/Optimization_Animation.html),可以参考。
该回答由 UWA 提供
Q:请教一个问题:比如,有一个大型的 Mesh,8000 个三角面,尺寸大概有三四屏。把它绘制到屏幕,Statistic 显示的是 8000 个三角面,而实际绘制到屏幕的只有 2000 个三角面。
那么其余 6000 个三角面会不会被视口剔除掉,还是根本就没有视口剔除,直接绘制到屏幕外?所以虽然只有 2000 个有效面的渲染,但开销和 8000 个面是一样的。是这样吗?
光栅化/Fragment 会不会跳过屏幕外的点?还是另有说法。
如果有视口剔除,那么视口剔除的本身的开销又有多少?需不需要把这种大型的 Mesh 手动切成小块?
A:如果是这样大型的物体,是非常建议切成小块来进行渲染的。
视域剔除在默认渲染管线中是一定会做的,但它剔除的基本并不是以 Triangle 为单位,而是以 GameObject 为单位的,只要这个 GameObject(Mesh)的包围盒与视域体有交集,就会被认为是要渲染的,所以会将其 Triangle 全部放入 Draw Call 中,并传入 GPU 进行渲染。因此,你会看到 Statistics 中显示的三角面片是很高的。
在传入到 GPU 后,该 Draw Call 中所有的顶点都会进入 Vertex Shader 阶段,所以如果该 Mesh 是 8000 个 Triangle,那么这 8000 个都要进行计算,从目前我们所优化过的游戏项目(特别是重度 MMO)中可以发现,很多时候都是几万的网格顶点在这里进行计算,在中低端设备上都会造成不小的计算量。
而你问题中说的光栅化实际上是在 Vertex Shader 之后,也就是要到 Fragment Shader 阶段,这个时候是在经过一系列操作(比如深度检测等)后将 Triangle 向屏幕进行投射,然后通过光栅化进行颜色计算,再经过各种 Buffer 之后形成最终的 FrameBuffer,也就是我们最终看到的内容。
渲染 Pipeline 大体上是上述过程,所以通过上面的说明,题主就可以明白,光栅化和视域剔除其实没有关系,前者是 GPU 部分,后者是 CPU 部分,功能是完全不一样的。光栅化主要根据每个 Triangle 在屏幕上的投影来计算颜色,跟 Mesh 中 Triangle 的前期剔除是没有关系的。Statistic 中的统计没有问题,因为确实有一定数量的 Triangle 被传入到了 GPU 中。如果你的项目遇到了这种情况,那么我建议将其切成小块来进行渲染,因为从概率上来说,它可以降低渲染计算压力。希望上面的说明能对你的疑惑有所帮助。
该回答由 UWA 提供
Q:项目用的 Addressable 准备做常规的启动更新检测,但是 Addressable.CheckForCatalogUpdates 这个 API 返回的更新目录长度永远是 0。
已经取消了启动静默更新,并且把发布时的资源进行了修改后打包到了服务器,对比过 Catalog 本地和服务器的确实不一样,应该怎么解决这个问题呢?
A:在 AddressableAssetSetting 内勾选 Disable Catalog Update On Startup。
然后用下面的代码做了测试:
var initHandle = Addressables.InitializeAsync();
yield return initHandle;
var handler = Addressables.CheckForCatalogUpdates(false);
yield return handler;
var catalogs = handler.Result;
Debug.Log($"need update catalog:{catalogs.Count}");
foreach (var catalog in catalogs) {
Debug.Log(catalog);
}
if (catalogs.Count > 0) {
var updateHandle = Addressables.UpdateCatalogs(catalogs, false);
yield return updateHandle;
var locators = updateHandle.Result;
foreach (var locator in locators) {
foreach (var key in locator.Keys) {
Debug.Log($"update {key}");
}
}
}
Addressables.Release(handler);
我这里是可以更新到 Catalog 的。一般来说,做启动更新检测只要使用 Download 相关的 2 个函数就可以了,大致如下:
var sizeHandle = Addressables.GetDownloadSizeAsync(keys);
yield return sizeHandle;
long totalDownloadSize = sizeHandle.Result;
if (totalDownloadSize > 0) {
var downloadHandle = Addressables.DownloadDependenciesAsync(keys, Addressables.MergeMode.Union);
while (!downloadHandle.IsDone) {
float percent = downloadHandle.PercentComplete;
Debug.Log($"已经下载:{(int) (totalDownloadSize * percent)}/{totalDownloadSize}");
}
}
一般的流程是 Addressable.InitializeAsync 做初始化,这个时候其实是会去下载最新的 Catalog 的,然后通过 GetDownloadSizeAsync 从本地最新的 Catalog 计算需要下载的大小,再调用 DownloadDependenciesAsync(内部实际调用 LoadAssetsAsync)去下载 AssetBundle。
在游戏运行期间如果服务器上有更新了,那么这个时候去调用 CheckForCatalogUpdates,则可以发现有 Catalog 的更新,然后调用 UpdateCatalogs 去更新 Catalog,但是只是更新,实际并没有下载资源,再走 Download 的流程去完成不重启更新。需要注意更新前把正在使用的 AssetBundle 等资源释放掉。
感谢黄程@UWA问答社区提供了回答
Q:Unity 2018.1.6f1,相机近、远裁剪平面分别为 1 和 400,遇到如下问题:
在 Android 手机上 Z-fighting 严重,但是 iOS 设备上没有问题。
即便我使用 Standard Shader 仍然如此。
难道是因为远裁剪平面 400 太大了?为什么两个平台差距这么大?是不是哪里设置错了?
我用 GPA 查看深度缓冲,截图如下:
A:有可能是计算精度问题导致的,这个在不同的驱动中会出现问题。
建议调整视域体,特别是近平面的位置,然后看看是否达到效果。
该回答由 UWA 提供
Q:网络消息是在非主线程上处理的,计算延时用的都是系统时间,但是我需要换算回 Unity 时间才能使用。请问如何才能获取代码执行处理当前帧开始过去了多久的时间呢?
A:其实一帧的最开始是 FixedUpdate,然后可能有多个 FixedUpdate 正在处理,很难知道具体哪个是第一个,而且 FixedUpdate 的处理时机和 Update 不一样,不能做计时的参考点。那么是不是可以反过来考虑:一帧的最开始约等于上一帧的结束,一次循环处理的最后是 yield WaitForEndOfFrame。可以准备一个协程:
IEnumerator EndOfFrame() {
yield return new WaitForEndOfFrame();
//记下当前帧结束的时间,给下一帧用
}
然后在其它 Update 的地方和这个时间做比较。虽然未必非常准确,但是也可以试试。
感谢黄程@UWA问答社区提供了回答
今天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题也许都只是冰山一角,我们早已在 UWA 问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入,也许你的方法恰能解别人的燃眉之急;而他山之 “石”,也能攻你之 “玉”。