《影子冒险》—— 美术优化篇

前言

这篇文章记录一下,我独立开发微信小游戏的过程中遇到的美术资源问题,以及对应的优化策略。如果有帮助到大家,可以来体验看看我的小游戏哦,可以扫二维码,或者在微信上搜索《影子冒险》哦。
image

摩尔纹

从远处观察地面,可以观察到,地面产生了非常严重的摩尔纹,原因是因为 纹理采样混叠(Aliasing)导致,当纹理的细节频率超过摄像机采样的分辨率时,会出现高频信号与采样频率的干涉,形成摩尔纹。说简单点就是,在远距离观察纹理时,由于像素密度较低,高频细节可能会丢失。
image

mipmap

第一个手段,是采用Mipmap(多级渐远纹理)进行优化,原理举例:当20*20的像素需要映射400*400的纹理像素时,检测到一颗像素需要映射到纹理像素为20*20,在Mipmap纹理中里寻找最接近20*20纹理像素的多级渐远纹理,并使用此多级渐远纹理进行采样。
不过缺点是,一张纹理需要多占用比原先多1/3的内存空间。
开启方法:在贴图的Inspector处,选择General mipmap即可:
image
开启后效果:
image
确实较于之前好了不少,但还是有些许锯齿感。
引用:https://blog.csdn.net/qq_42428486/article/details/118856697
引用:https://docs.unity3d.com/6000.0/Documentation/Manual/texture-mipmaps-introduction.html

aniso level

各向异性过滤(Anisotropic Filtering)的核心功能是改善非垂直视角下的纹理清晰度,当相机以倾斜角度观察表面时(尤其是地面、墙面等倾斜面),纹理会因透视投影被压缩或拉伸,导致模糊或细节丢失。各向异性过滤通过沿不同方向多次采样纹理,保留更多细节。
计算逻辑是,当表面法线与视线方向不垂直时(即倾斜视角),投影到屏幕上的纹理区域会形成一个 ​各向异性的椭圆或平行四边形。这个椭圆的长轴方向即为 ​主各向异性方向​(采样沿此方向展开)。除了 主各向异性方向 ,还有 次各向异性方向 ,它是垂直于主方向的方向,用于辅助采样,确保覆盖整个变形后的纹理区域。(更多细节可以网上查一下,博主也不是很了解)
各向异性过滤会 ​沿主方向跨多个Mipmap层级采样:

  1. 高层级(低分辨率)覆盖远处模糊区域。
  2. 低层级(高分辨率)保留近处细节。

各向异性过滤等级在这里即可调整,level代表采样次数,层级越高代表覆盖更广的Mipmap层级和方向,加权混合更多采样结果,在视角动态变化时,减少Mipmap层级切换导致的视觉跳跃。
image
接下来进行下效果对比:
image
这个是level:1
image
这个是level:16,可以明显感受到丝滑了不少。
但要注意,level16相较于level1来说,每个像素可能需读取 16 倍纹理数据,会明显增加显存带宽占用,所以也不适合调整太高。我这里调到等级2,看起来就跟等级16差不多了。

调整模型UV坐标

前2个方法用完后,远处的道路纹理还是会产生细微的条纹,非常影响游戏体验,而且只在接缝处有条纹,不得不让人怀疑,是不是模型的uv坐标有问题,遂打开blender进行查看,可以查看到模型的uv坐标确实有点过于贴合非道路的像素点,怀疑是这个问题导致在游戏中,远处道路的采样异常。
image
于是重新调整一波模型的uv坐标,让下边缘尽可能离开非道路边缘像素点。然后模型中间的线也拉粗一点,避免远处的线看不清楚
image
调整完后果然,中间可恶的条纹消失了,然后整体清晰度较之间也上升了很多。
image
幸亏之前有自学过Blender操作,不然真就被这个问题搞寄了,而且真印了之前听过的一句话,“优化半天,不如建模调整一笔”。至此,条纹问题修复完毕。

drawcall次数多

大概介绍下资源背景,因为是独立制作,使用的是unity资源商店中购买的美术资源,马路和街道是一个个cube拼接起来,使用的是相同的材质球和贴图,而且整个场景中只存在一个平行光源。实际使用起来,drawcall产生的drawcll是非常巨大,截图如下:
image
可以看到,在每帧会产生4100+的drawcall,其中为阴影计算做准备的深度图是787次,生成阴影图是2533次,最终渲染模型到屏幕上是787次。带来的性能消耗查看Profiler可以看到:
image
每帧都会产生0.6ms以上的性能开销。总渲染耗时,高达2ms每帧:
image

静态合批

考虑到马路和街道是静态的,且我们使用了相同材质的资源,第一时间便考虑到静态合批的优化策略,静态合批会将使用相同的材质的网格合并成一个大网格,可以有效减少drawcll的次数。但缺点是使用静态合批,物体就必须一直静止了,但这个缺点对于我们而已,没有什么影响。

首先在Project Settings/Player/Other Settings中开启Static Batching。
image
在场景中,将资源标记为static,并且选择change children。
image
然后运行后,可以注意到drawcall此时明显大幅度降低,其中为阴影计算做准备的深度图是2次,生成阴影图是8次,最终渲染模型到屏幕上是166次:
image
这个渲染屏幕上次数有点奇怪,按理来说合并成一个大网格,应该只有1次才对,仔细观察中间有些mesh打断了渲染,但这些mesh明明也是采用了静态合批才对,为啥不能一起渲染出来:
image
网上查阅资料有说,Unity静态合批的单个网格顶点数超过65535​(16位索引限制),会被拆分为多个批次。但我写了工具查看,总顶点数不过52080,远远没有到上限。而且使用的材质都是一样的,所以有点奇怪。如果有懂行的大佬,希望可以在评论区帮忙解个疑惑,这里提前道谢了。
image
抛开这个疑问,让我们来看下开启静态合批后的性能优化有多少吧:
image
image
可以注意到,性能提升有限,主要是坑爹的静态合批没有合并干净导致的,导致模型渲染次数还是很频繁。而且,除了提升外,也要注意静态合批后生成的mesh数据,会一直额外占用内存空间,直到场景卸载后才能够被释放掉。

GPU Instance

除了静态合批,我们也可以试试利用GPU Instance进行优化,核心原理是通过单次Draw Call批量渲染多个相同网格和材质的物体,减少CPU与GPU之间的通信开销。而且相较于传统渲染(每个物体需单独提交网格、材质数据和变换矩阵到GPU,导致多次Draw Call),GPU Instancing可以将多个相同网格和材质的物体打包,通过一次Draw Call提交所有实例的共享数据(如网格、材质)和差异化数据​(如位置、缩放、颜色等)。

使用方法非常简单,找到对应的材质,开启GPU Instance即可。
image
让我们看看性能优化怎么样吧,可以发现drawcall此时降低幅度更加惊人,其中为阴影计算做准备的深度图是18次,生成阴影图是22次,最终渲染模型到屏幕上只有15次:
image
再让我们看看性能如何吧:
image
image
开启GPU Instance后,可以带来25%的性能提升,也是十分不错了。

手动提前合并网格

既然静态合批有问题,那我们试一下手动提前合并网格,通过自定义组件MeshCombine(代码后面放出),我们将马路和街道都合并成了同一个的网格,此时再运行游戏:
image
我们可以注意到,这个性能提升更多了,drawcall次数大幅度减少,达到我心目中静态合批应该有的效果:
image
CPU性能更是优异决绝,每帧平均仅仅耗时0.4ms,带来了80%多的性能提升。
image

总结

至此,drawcall优化结束,做个总结。
drawcall次数由4100+降低到15。
耗时由原来平均每帧2ms降低至0.4ms,性能提升80%。

放一下手动合并网格的代码,有兴趣的可以参考使用:

using UnityEngine;

namespace XiaYun.Core
{
    [RequireComponent(typeof(MeshFilter))]
    [RequireComponent(typeof(MeshRenderer))]
    public class MeshCombine : MonoBehaviour
    {
        #if UNITY_EDITOR

        [ContextMenu("Combine Mesh")]
        private void CombineMeshes()
        {
            MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
            CombineInstance[] combiners = new CombineInstance[meshFilters.Length - 1];  // 排除父物体自身
            
            Matrix4x4 parentMatrix = transform.worldToLocalMatrix;
            int index = 0;
        
            for (int i = 0; i < meshFilters.Length; i++)
            {
                // 跳过父物体自身的MeshFilter
                if (meshFilters[i].transform == transform)
                {
                    meshFilters[i].GetComponent<MeshRenderer>().enabled = true;
                    continue;
                }

                // 配置合并参数
                combiners[index].mesh = meshFilters[i].sharedMesh;
            
                // 转换矩阵:子物体本地坐标系 → 父物体本地坐标系
                combiners[index].transform = parentMatrix * meshFilters[i].transform.localToWorldMatrix;
            
                // 禁用子物体渲染器
                if (meshFilters[i].GetComponent<MeshRenderer>())
                {
                    meshFilters[i].GetComponent<MeshRenderer>().enabled = false;
                }
            
                index++;
            }
            
            // 创建合并后的网格
            Mesh combinedMesh = new Mesh();
            combinedMesh.name = "CombinedMesh";
            combinedMesh.CombineMeshes(combiners);
            
            // 设置父物体的网格组件
            MeshFilter meshFilter = GetComponent<MeshFilter>();
            meshFilter.mesh = combinedMesh;

            // 优化网格数据
            combinedMesh.Optimize();
            combinedMesh.RecalculateBounds();

            Debug.Log($"成功合并 {combiners.Length} 个子网格,总顶点数:{combinedMesh.vertexCount}");
        }

        [ContextMenu("UnCombine Mesh")]
        private void UnCombineMeshes()
        {
            MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
            
            for (int i = 0; i < meshFilters.Length; i++)
            {
                // 跳过父物体自身的MeshFilter
                if (meshFilters[i].transform == transform)
                {
                    meshFilters[i].GetComponent<MeshRenderer>().enabled = false;
                };
                
                if (meshFilters[i].GetComponent<MeshRenderer>())
                {
                    meshFilters[i].GetComponent<MeshRenderer>().enabled = true;
                }
            }
            
            MeshFilter meshFilter = GetComponent<MeshFilter>();
            meshFilter.mesh = null;
        }
        
        #endif
    }
}
posted @ 2025-04-28 13:13  陈侠云  阅读(324)  评论(0)    收藏  举报
//雪花飘落效果