一个场景都是有很多的模型构建而成的,如果要热更新资源就需要细化每一个资源,把每一个资源下载下来然后拼凑在一起形成完整的场景。这些装饰型的场景部件很多都是静态的,所以我们可以把这些部件以材质为分组,对于同一个材质的部件可以放到同一个空物体下,然后在空物体上添加脚本,把子物体的内容静态批处理,减少DC。
using UnityEngine; public class StaticBind : MonoBehaviour { void Start () { StaticBatchingUtility.Combine(gameObject); } }
Shader有一个值叫LOD,只有LOD值小于某个设定的值,这个Shader才会被使用,而使用了那些超过设定值的Shader的物体将不会被渲染。可以通过Shader.globalMaximumLOD来设置这个最大值,不限定设置的大小。
void Start () { Shader.globalMaximumLOD = 251; }
LOD的技术原理就是当一个物体离摄像机很远时,模型上的很多细节是无法被察觉到的,因此,LOD允许当对象逐渐远离摄像机时,减少模型上的面片数量,从而提高性能。
在Unity中,使用一个添加上LOD Group组件来使用LOD功能,将事先准备好的多个包含不同细节程度的模型分别赋值给LOD Group组件中的不同等级,Unity会自动判断当前位置上需要使用哪个等级的模型。
在建模的时候,尽量减少模型中三角面片的数目。在Unity中显示的顶点数目要比建模软件中显示的顶点数多,这是因为Unity在渲染角度上去理解,在GPU看来,有时需要把一个顶点拆分成两个或更多的顶点,一是为了分离纹理坐标,二是为了产生平滑的边界。例如一个立方体,相同的顶点,但在不同的面上,他的纹理坐标是不一样的,所以会拆分为多个顶点。而平滑边界也是类似的,不过就是同一个顶点会对应多个法线信息或切线信息。对于GPU来讲,顶点的每一个属性和顶点之间必须是一对一的关系。
所以在建模优化的时候,移除不必要的硬边以及纹理衔接,避免边界平滑和纹理分离。
一、静态批处理
应用在那些一开始就不动的物体上,只需要在Inspector上吧Static勾选上就可以了。不允许移动和其他处理了。Unity会在一开始运行的时候将这些Static的网格物体变换到世界空间中只合并处理一次为更大的顶点和索引缓存,对于使用相同材质的物体,Unity只需要使用一个DC就可以绘制完成了。对于使用不同材质的物体也可以提升渲染性能,尽管需要调用多个DC,但是静态批处理可以减少这些DC之间的状态切换,往往这些状态的切换是很费时的。注意:静态批处理会占用更多的内存来存储合并的几何结构,如果一些物体共享了相同的网格,那么在内存中每一个物体都会对应一个该网格的复制品。
二、动态批处理
动态批处理是Unity自动进行的,如果需要使用静态批处理就需要满足一定的条件:需要使用相同的材质、网格的顶点属性规模要小于900(顶点位置、法线、纹理坐标),也就是顶点数目不要超过300,因为Unity使用动态批处理时需要每一帧的去将这些网格合并、多Pass的Shader会中断批处理,例如在直射光的前提下加多了一个Spot Light灯光,Shader会调用额外的Pass去叠加灯光效果。
三、Enable GPU Instancing
这是最新渲染api提供的一种技术,可以在材质上勾选它打开。如果绘制1000个物体,他将一个模型的vbo(顶点缓冲)提交给一次显卡,至于1000个物体不同的位置、状态、颜色等等将他们整合成一个per instance attribute的buffer给gpu,在显卡上区别绘制,它大大减少了提交次数,这对于需要绘制大量相同模型的物体来说非常高效同时避免了合批的内存浪费。
Unity中自带的Plane的顶点个数是很多的,经过打印得出Plane的顶点个数为121个,对于要实现一些简单的放一个图片的Plane来说这有点浪费资源了,所以这些简单的只是显示一张图片的Plane可以自己创建出来,只使用4个顶点。
using System.Collections.Generic; using UnityEngine; public class CheckPlane : MonoBehaviour { void Start () { Mesh m = this.GetComponent<MeshFilter>().mesh; List<Vector3> list = new List<Vector3>(); m.GetVertices(list); //Unity自带的Plane的顶点个数为 121 Debug.LogError("顶点的个数为:" + list.Count); CreatPlane(); } void CreatPlane() { Mesh m = new Mesh(); GameObject go = new GameObject("Haha"); MeshFilter f = go.AddComponent<MeshFilter>(); f.mesh = m; Vector3[] array = new Vector3[4];//顶点坐标 Vector3[] array2 = new Vector3[4];//顶点面法线 Color[] array3 = new Color[4];//顶点颜色 Vector2[] array4 = new Vector2[4];//uv坐标 int[] array5 = new int[6];//三角型索引 array[0] = new Vector3(-1f, 1f, 0f);//顶点坐标 array[1] = new Vector3(1f, 1f, 0f); array[2] = new Vector3(-1f, -1f, 0f); array[3] = new Vector3(1f, -1f, 0f); array2[0] = new Vector3(0f, 1f, 0f);//顶点面法线 array2[1] = new Vector3(0f, 1f, 0f); array2[2] = new Vector3(0f, 1f, 0f); array2[3] = new Vector3(0f, 1f, 0f); array3[0] = new Color(1f, 1f, 1f);//顶点颜色 array3[1] = new Color(1f, 1f, 1f); array3[2] = new Color(1f, 1f, 1f); array3[3] = new Color(1f, 1f, 1f); array4[0] = new Vector2(0f, 0f);//uv坐标 array4[1] = new Vector2(1f, 0f); array4[2] = new Vector2(0f, 1f); array4[3] = new Vector2(1f, 1f); array5[0] = 0;//三角型索引 array5[1] = 1; array5[2] = 2; array5[3] = 2; array5[4] = 1; array5[5] = 3; m.vertices = array; m.normals = array2; m.colors = array3; m.uv = array4; m.triangles = array5; Shader shader = Shader.Find("Standard");//根据项目需要,使用Shader Material mat = new Material(shader); go.AddComponent<MeshRenderer>().material = mat; //打印下顶点个数 List<Vector3> list = new List<Vector3>(); m.GetVertices(list); Debug.LogError("自己创建的顶点个数:" + list.Count); } }
Plane[] planes = GeometryUtility.CalculateFrustumPlanes(mainCamera);//获取该相机的剪裁锥体的6个面 Bounds bounds = target.GetComponent<Collider>().bounds();//获取物体的碰撞盒子 bounds.Expand(2.0f);//扩大一下这个碰撞盒子 bool isIn = GeometryUtility.TestPlanesAABB(planes, bound);//判断是否在相机视野内 target.SetActive(isIn);
扩大碰撞盒子是为了避免模型物体突然出现在相机视野的情况,因为视野是会移动的,留点边界预留区保留效果。