1. 渲染流程

    1. 首先接收用户提供的几何数据,并且将他们输入到一系列着色器阶段中进行处理,最后将数据转换到最终渲染的图像。顶点数据->顶点着色器->细分控制着色器->细分计算着色器->几何着色器->图元设置->剪切剔除->光栅化->片元着色器->最终渲染的图像。

    2. 不一定会用到所有的着色阶段,只有顶点着色器和片元着色器是必需的。一个复杂的引用程序可能包含许多个顶点着色器,但在同一时刻只能有一个顶点着色器起作用。

    3. 每一帧画面的渲染过程,大致如下
      i.CPU检查场景中的每个对象来确定他是否需要渲染,并且将每个需要渲染的对象加入到DrawCall指令中,部分具有相同属性的对象可能会Batch到同一个DrawCall中。
      ii.CPU会向GPU发送SetPassCall指令,在当前RenderState与下次不同时,SetPassCall会告知GPU在下次渲染Mesh时要使用哪种配置。
      iii.GPU根据CPU的指令执行任务,执行顺序与指令发送顺序相同。
      iv.DrawCall->GPU渲染Mesh,SetPassCall->GPU更新RenderState

  2. 渲染优化

    1. 填充率
      i.降低游戏分辨率
      ii.使用光照贴图并减少逐像素灯光的数量
      iii.减少FragmentShader中复杂或非必要的计算,不追求高精度的部分计算移至VertexShader中完成
      iv.减少粒子及透明物体的叠加或混合(降低OverDraw)
      v.减少后期效果的数量或优化其算法降低计算量。
    2. 显存带宽
      i.纹理的数量
      ii.纹理的尺寸
      iii.根据平台芯片选择对应的纹理压缩格式和修改项目设置中的TextureQuality。
      iv.会发生远近变化或缩放的物体需要使用Mipmap来减少带宽占用并提升表现效果,减轻摩尔纹或改善近距离分辨率不足的情况,其他情况下则可以关闭Mipmap来节省内存。
      v.关闭纹理的ReadWriteEnable,减少内存和带宽占用。
    3. 顶点数
      i.降低模型复杂度,可使用法线纹理提高表现效果。
      ii.调整模型导入属性,选择相应的压缩等级。
      iii.在项目设置中调整顶点数据的压缩内容。
      iv.使用LOD调整显示细节。
      v.使用OcclusionCulling,进行遮挡剔除。
    4. DrawCall
      i.静态批处理,会增加网格的资源量,占用内存及磁盘空间。
      ii.动态批处理,在游戏运行时进行Batching操作,需要注意的是,动态批处理的网格顶点属性要少于900个,所以在shader中使用到的顶点属性要尽可能的少,并且要避免使用多通道shader。
      iii.美术方面的预处理,根据设定的可视范围,调整合并材质及网格,能够使用Tiling的纹理尽可能采用Tilling的方式。
      iv.使用LOD调整显示细节。
      v.使用遮挡剔除,但部分情况下可能会造成额外开销。
    5. 减少反射,阴影,深度图的影响范围及调整相机的裁剪范围,会明显降低渲染开销。
  3. 脚本优化

    1. 移除脚本中的无效代码和空方法。
    2. 频繁使用的变量可以声明为全局变量。
    3. 减少游戏运行时获取组件的操作,应尽可能在全局变量中缓存。
    4. 能够在资源制作过程中序列化的,优先考虑通过序列化的方法获得组件。
    5. 客户端的可见组件,可以通过检测OnBecameVisible/OnBecameInvisible来确认是否在可见范围内,并且据此调整逻辑更新频率或内容。
    6. 频繁显示关闭的组件对象,可以通过移出屏幕的方式降低开销。
    7. 减少Object的实例化和销毁操作,尽可能使用对象池重复利用。
    8. 需要在Update中频繁查找的同一组件,尽可能的在初始化阶段缓存起来。
    9. 字符串拼接,最好使用StringBuilder。
    10. 减少高频的复杂计算,如实时性需求不高,可做分帧处理。
  4. 物理碰撞

    1. 调整Fixed Timestep,控制物理计算频率。
    2. 减少MeshCollider,优先使用BoxCollider,SphereCollider。
    3. 网络同步操作若需要使用物理组件,应打开IsKinematic,并采用相应的运动公式控制移动,检测方式为OnTrigger。
  5. GC

    1. Mono运行时的托管堆,主要是类实例,字符串和数组。
    2. 触发方式
      i.堆内存不足时,自动调用GC
      ii.手动调用GC
    3. 内存分配
      i.首先检查空闲内存是否足够。
      ii.如果不够,进行一次GC,回收内存。
      iii.如果仍然不够,会向操作系统申请内存,扩充现有堆内存。
      iv.已分配的内存不会交还给操作系统,只会回收到Mono的堆内存。
    4. 内存回收过程
      i.停止需要分配内存的线程。
      ii.遍历内存,找到无引用的内存,并标记。
      iii.回收被标记的内存到空闲内存。
      iv.继续启动被停止的线程。
    5. 内存泄漏
      i.引用丢失,导致无法通过任何途径访问到,但该对象仍有引用关系,无法被标记成垃圾。
      ii.大部分情况都是由于静态对象引用导致的,静态对象中不再使用的对象应将其设置为null,使其可以被GC标记并回收。
    6. 注意点
      i.字符串连接的处理,是在堆上分配的空间,结束后原字符串会成为垃圾,新字符串为新分配的空间。
      ii.尽可能不要使用foreach,优先使用for
      iii.使用对象池,重复使用对象,减少实例化销毁的操作。
      iv.尽量不要使用LINQ。
      v.优先使用局部变量。
      vi.需要频繁new的,优先使用结构体代替类。
07-17 21:57