原文Optimizing graphics rendering in Unity games。这里对这篇文章的粗略翻译,当作自己的笔记。
介绍
在这篇文章中我们将会学习Unity在渲染一帧的时候其内部的发生的事情,在渲染的时候会发生什么类型的性能问题同时我们怎样去处理这些性能问题。
在阅读这篇文章之前,必须明白没有一个同样的尺度来提高渲染的性能问题。在我们游戏中影响性能问题的原因有很多同时非常依赖游戏运行的硬件和操作系统。通过调查,实验和细心的分析实验结果来解决性能问题是我们最重要的方式。
这篇文章包含的信息主要是大部分常见的性能问题的修改建议和一些更深层次的阅读链接。我们的游戏中可能也存在这其中的一个问题或者一些问题或者都没有包含。但是这篇文章仍然将帮助我们理解我们自己的问题同时给予我们去搜索我们问题的知识和线索。
渲染的简介
在我们开始介绍之前,让我们快速简单看下Unity渲染一帧时其内部发生了什么。理解事件流和正确的术语将会帮助我们 理解,调查和解决我们的性能问题。
注:在这篇文章中,我们将会使用”对象(object)”术语来表示一个我们游戏的渲染的对象。任何渲染组件的GameObject将会被统称为对象。
在最基本的层面上,渲染可以描述为一下几步:
- CPU计算出必须渲染的对象和决定怎么去绘制。
- CPU发送指令到GPU。
- GPU根据CPU的指令来确定绘制的内容。
现在让我们来更近一步看看究竟发生了什么。这篇文章后面将会详细的介绍上面的每一个步骤。但是现在让我们先来熟悉下文中使用的词语和理解CPU和GPU在渲染中扮演的不同角色。
描述渲染的词语通常用渲染管线(rendering pipeline),记住这是一个非常有用的情景。高效的渲染都是关于保持更高信息的传送能力。
对于每一个渲染的帧,CPU会做如下的工作:
- CPU会检查场景中的每一个对象来决定这个对象是否需要被渲染。只有当这个对象满足特定的条件的时候才会被渲染;例如,它的包围盒的一些部分在相机视锥体内。一个对象如果将不会被渲染我们称之为这个对象被裁剪了(culled)。关于相机的视锥体和视锥体裁剪的更多信息可以看着这篇文章。
- CPU搜集每一个将会被渲染的对象的信息然后将这些数据分类放入叫做draw calls命令中。一个draw call包含了一个mesh和这个mesh怎样被渲染的数据;例如,这个mesh用哪一张贴图。基于这些情况,共享设置的图像会合并到一些相同的draw call中。合并不同对象的数据到同一个draw call中我们称之为batching。
- CPU为每次draw call创建一个叫batch的数据包。batchs有可能包含了draw calls之外的数据,但是这种情况不像常见的一个性能问题,因此我们不会在这篇文章里面来讨论。
对于每个draw call包含的batchs,CPU现在必须要入如下的工作:
- CPU可以向GPU发送一个命令来改变一些被统称为渲染状态的变量。这个命令叫做SetPass call。SetPass call告诉GPU哪个设置被用来渲染下一个mesh。只有当上一个mesh渲染完成下一个mesh准备渲染的时候需要改变渲染状态的时候才需要发送SetPass call命令。
- CPU发送draw call命令到GPU。draw call会指示GPU使用上一次设置好的SetPass call去渲染制定的mesh。
- 基于这些情况,batchs的数据被渲染可能需要不止一个pass。一个pass是一段着色器的代码,使用一个新的pass需要改变渲染状态。对于一个batchs中的每个pass,CPU必须发送一个新的SetPass call命令同时必须再次发送draw call命令。这个pass才会生效。
同时,GPU会做如下的工作:
- GPU按照CPU发送的顺序来处理CPU发送的任务。
- 如果当前的任务是SetPass call,GPU就更新渲染状态。
- 如果当前的任务是draw call, GPU就渲染这个draw call的mesh。这个是分阶段进行的,每个阶段单独定义在着色器代码中。这部分渲染比较复杂我们不会详细的讨论,但是这部分对于我们去理解顶点着色器(vertex shader)这部分代码和片段着色器(fragment shader)这部分代码非常有用。顶点着色器是用来告诉GPU怎么去处理mesh的顶点数据的代码,片段着色器使用来怎样去处理每个单独像素代码。
- GPU重复处理从CPU发送过来的任务,直到所有的任务都处理完成。
现在我们理解了当Unity渲染一帧时其内部发生了什么,接下来让我们思考下当渲染发生的时候会随之发生什么问题。