绘制

现在我们有了一组剔除结果,我们可以将它们渲染到屏幕上。

但是这有很多的配置方案,所以需要提前做出一些决策。这些决策大多数由以下因素确定:

  • 渲染管线的目标硬件
  • 您希望达到的外观细节和感觉
  • 您正在做的项目类型

例如,想象一下手机端二维横版游戏和高端第一人称PC游戏。这些游戏有很多不同的约束,所以渲染管线也会有很大的不同。可能做出的一些决策的具体例子:

  • HDR vs LDR
  • Linear vs Gamma
  • MSAA vs 后处理抗锯齿
  • PBR材质 vs 简单材质
  • 光照 vs 没有光照
  • 照明技术
  • 阴影技术

在编写渲染管线时做出这些决定,将有助于在编写管线时确定限制因素。

现在,我们将编写一个简单的渲染器,它可以使一些不透明对象不受光照。

过滤:渲染队列范围(Bucket)与层

通常渲染对象具有特定的分类,它们可分为不透明的、透明的、次表面的或任意数量的其他类别。Unity使用队列的概念来表示渲染对象的时序,这些队列形成了Bucket(物体的材质决定了哪些物体要被放入)。当渲染函数被SRP调用时,您可以指定使用哪种范围的Bucket。

除了Bucket,Unity默认层也可用于过滤。

当使用SRP绘制物体时,这提供了额外的过滤功能。

  1. // 获取不透明渲染过滤设置
  2. var opaqueRange = new FilterRenderersSettings();
  3. //设置不透明渲染队列范围
  4. opaqueRange.renderQueueRange = new RenderQueueRange()
  5. {
  6. min = 0,
  7. max = (int)UnityEngine.Rendering.RenderQueue.GeometryLast,
  8. };
  9. //包括全部层
  10. opaqueRange.layerMask = ~0;

绘制设置:对象如何被绘制

过滤和剔除的使用决定了应该渲染什么,但是之后我们需要确定对象应该如何被绘制。SRP提供了各种选项来配置如何渲染已经通过筛选的对象。用于配置此数据的结构是“DrawRendererSettings”结构体。此结构允许配置许多内容:

  • 排序 – 对象渲染的顺序,例如从后到前和从前到后的绘制顺序。
  • 逐渲染器标志 – 决定什么“内建”设置应该被Unity传递到着色器,这包括每个对象的灯光探针、每个对象的光照贴图以及类似的设置。
  • 渲染标志 – 在批处理、实例化与非实例化过程中应该使用哪种算法。
  • 着色器通道 – 当前drawcall应使用哪个着色器通道。
  1. // 创建绘制渲染设置
  2. // 注意应该填写着色器pass的名字
  3. var drs = new DrawRendererSettings(myCamera, new ShaderPassName("Opaque"));
  4. // 开启drawcall实例化
  5. drs.flags = DrawRendererFlags.EnableInstancing;
  6. // 将灯光探头与光照贴图数据传递给每个渲染器
  7. drs.rendererConfiguration = RendererConfiguration.PerObjectLightProbe | RendererConfiguration.PerObjectLightmaps;
  8. // 正常的不透明物体排序
  9. drs.sorting.flags = SortFlags.CommonOpaque;

Drawing

现在我们有三件事需要发出drawcall请求:

  • 过滤结果
  • 剔除结果
  • 绘制规则

我们现在可以发出drawcall请求!与SRP中的所有内容一样,发出一个drawcall请求作为对上下文的调用。在SRP中,通常不渲染单个网格,而是发出一个调用,一次性渲染大量网格。这减少了脚本执行开销,并允许在CPU上快速执行。

为了发出一个drawcall请求,我们结合了一直在编写的那个函数。

  1. // 全部渲染器开始绘制
  2. context.DrawRenderers(cullResults.visibleRenderers, ref drs, opaqueRange);
  3. // 提交上下文,这将运算全部的指令队列。
  4. context.Submit();

这会将对象绘制到当前绑定的渲染目标中。如果愿意,可以使用CommandBuffer切换渲染目标。