可编程脚本渲染管线SRP

Unity 2018.1 beta中引入的Scriptable Render Pipeline可编程脚本渲染管线,简称SRP。是一种在Unity中通过C#脚本配置和执行渲染的方式。在编写自定义渲染管线之前,必须要先理解渲染管线的含义。本文将帮助你开始学习编写自定义SRP。

本文演示项目,请访问Github下载:

https://github.com/stramit/SRPBlog/tree/master/SRP-Demo

什么是渲染管线

渲染管线是将对象显示到屏幕上所需的一系列技术的总称。它包含: 剔除、渲染对象、后期处理等一系列高级概念。这些高级概念还可以分别根据你所希望的执行方式继续分解。

例如:渲染对象可以按照以下方式进行

  • 多通道渲染:每个光照每个对象一个通道
  • 单通道渲染:每个对象一个通道
  • 延迟渲染:渲染表面属性到一个G-Buffer,执行屏幕空间光照。

这些就是当你编写一个自定义SRP时需要作出的决定。每项技术都有一些需要考虑的性能成本。

渲染入口点

当使用SRP时,你需要定一个类,用于控制渲染;这就是你将要创建的渲染管线。入口点是一个对“Render”函数的调用,它需要两个参数,渲染上下文以及一个需要渲染的摄像机列表。

public class BasicPipeInstance : RenderPipeline
{
   public override void Render(ScriptableRenderContext context, Camera[] cameras){}
}

渲染管线上下文

SRP渲染采用的是延迟执行的方式。用户要设置好需要执行的命令列表,然后再执行。用来设置这些命令的对象叫做“ScriptableRenderContext”。当你向上下文填充完操作命令后,可以通过调用“Submit”提交队列中的所有绘制调用。

举例来说,使用一个由渲染上下文执行的命令缓冲区清除一个渲染目标:

//新建一个命令缓冲区
//用于向渲染上下文发送命令
var cmd = new CommandBuffer();

//发送一个清除渲染目标的命令
cmd.ClearRenderTarget(true, false, Color.green);

//执行命令缓冲区
context.ExecuteCommandBuffer(cmd);

一个简单渲染管线示例 

下面有一个完整的渲染管线代码,仅仅用于清除屏幕。

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Experimental.Rendering;

[ExecuteInEditMode]
public class BasicAssetPipe : RenderPipelineAsset
{
    public Color clearColor = Color.green;

#if UNITY_EDITOR
    [UnityEditor.MenuItem("SRP-Demo/01 - Create Basic Asset Pipeline")]
    static void CreateBasicAssetPipeline()
    {
        var instance = ScriptableObject.CreateInstance<BasicAssetPipe>();
        UnityEditor.AssetDatabase.CreateAsset(instance, "Assets/SRP-Demo/1-BasicAssetPipe/BasicAssetPipe.asset");
    }
#endif

    protected override IRenderPipeline InternalCreatePipeline()
    {
        return new BasicPipeInstance(clearColor);
    }
}

public class BasicPipeInstance : RenderPipeline
{
    private Color m_ClearColor = Color.black;

    public BasicPipeInstance(Color clearColor)
    {
        m_ClearColor = clearColor;
    }

    public override void Render(ScriptableRenderContext context, Camera[] cameras)
    {
        base.Render(context, cameras);
        var cmd = new CommandBuffer();
        cmd.ClearRenderTarget(true, true, m_ClearColor);
        context.ExecuteCommandBuffer(cmd);
        cmd.Release();
        context.Submit();
    }
}

剔除

剔除是确定要在屏幕上显示什么对象的过程。

在Unity中,剔除包括:

  • 视锥剔除:计算存在于摄像机远近视平面之间的对象。
  • 遮挡剔除:计算哪些对象被其它对象挡住,并将它们从渲染中排除。

当渲染开始时,首先要计算的是到底要渲染什么。这包括获取摄像机,并从摄像机的视角进行剔除操作。剔除操作会返回一个可为摄像机进行渲染的对象和光照的列表。这些对象随后将被用在渲染管线中。

SRP中的剔除操作

在SRP中,你通常会选择某个摄像机的视角执行对象渲染。这与Unity内置渲染所使用的摄像机对象是相同的。SRP提供了一系列API用于剔除操作。整个流程通常看起来像下面这样:

//新建一个结构体,用于存储剔除参数
ScriptableCullingParameters   cullingParams;

//填充来自摄像机的剔除参数
if   (!CullResults.GetCullingParameters(camera, stereoEnabled, out cullingParams))
      continue;

//如果你想修改剔除参数,可在这里进行
cullingParams.isOrthographic   = true;

//新建一个结构体,用于存储剔除结果
CullResults   cullResults = new CullResults();

//执行剔除操作
CullResults.Cull(ref   cullingParams, context, ref cullResults);

现在可以使用填充的剔除结果执行渲染了。

绘制

现在我们已有了一组剔除结果,可以将它们渲染到屏幕了。但还有很多东西需要配置,所以有一些选择需要事先确定。这些选择的驱动因素有:

  • 渲染管线的目标硬件
  • 希望获得的观感
  • 制作的项目的类型

例如一个移动2D滚轴游戏和一个PC端第一人称游戏,这些游戏所受的约束条件大不相同,因此它们的渲染管线自然也就大相径庭。以下是一些实际可能需要面对的选择:

  • 高动态范围 vs 低动态范围
  • 线性 vs 伽马
  • 多重采样抗锯齿(MSAA) vs 后期处理抗锯齿
  • PBR材质 vs 简单材质
  • 光照 vs 无光照
  • 光照技术
  • 阴影技术

在编写渲染管线时作好这些决定将有助于确定在创作时遇到的许多约束。

现在我们将演示一个无光照的简易渲染器,这个渲染器可以将一些对象渲染为不透明。

 过滤:渲染区域(Bucket)和图层(Layer)

一般来说,渲染对象会有某个特定分类,比如不透明、透明、次面,或其它的什么类别。Unity用一个称为队列(queue) 的概念表示需要进行渲染的对象,这些队列进而组成存放对象的区域(bucket)(源自对象上的材质)。当从SRP调用渲染时,需要制定所使用区域的范围。

除了区域之外,标准的Unity图层也可以被用于过滤。这为通过SRP绘制对象时提供了额外的过滤能力。

//获取不透明渲染过滤器设置
var   opaqueRange = new FilterRenderersSettings();

//设置不透明队列的范围
opaqueRange.renderQueueRange   = new RenderQueueRange()
{
      min = 0,
      max = (int)UnityEngine.Rendering.RenderQueue.GeometryLast,
};

//Include   all layers包括所有图层
opaqueRange.layerMask   = ~0;

绘制设置:应当如何绘制

过滤和剔除确定了渲染什么,但随后我们还需要确定如何渲染。SRP提供了一系列不同的选项,配置被过滤对象的渲染方式。用于进行这个数据的结构体叫做“DrawRenderSettings”。这个结构体可对许多方面进行配置:

  • 排序——对象渲染的顺序,例如自后向前和自前向后
  • 每渲染器标志 —— 应当从Unity传递给着色器的“内置”设置,这包括每个对象的光照探头,每个对象的光照贴图之类的东西。
  • 渲染标志 —— 用于进行批处理的算法,实例化 vs 非实例化
  • 着色器通道 —— 当前绘制调用应当使用哪个着色器通道
//新建绘制渲染设置
//注意它需要输入一个着色器通道名
var drs =   new DrawRendererSettings(Camera.current, new ShaderPassName("Opaque"));

//启用绘制调用上的实例化
drs.flags =   DrawRendererFlags.EnableInstancing;

//传递光照探针和光照贴图数据给每个渲染器
drs.rendererConfiguration   = RendererConfiguration.PerObjectLightProbe |   RendererConfiguration.PerObjectLightmaps;

//像普通不透明对象一样排序对象
drs.sorting.flags   = SortFlags.CommonOpaque;

绘制

现在我们已有了发送一个绘制调用所需的三样东西:

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

我们可以发送一个绘制调用了。就像SRP中的所有东西一样,绘制调用也是以一个针对上下文发出的调用。在SRP中,你通常不会渲染单独的网格,而是发出一个调用,一次性渲染大批量的网格。这不仅减少了脚本执行上的开销,也使CPU上的执行得以快速作业化。

要发送一个绘制调用,需要将我们已有的东西进行合并。

//绘制所有渲染器
context.DrawRenderers(cullResults.visibleRenderers,   ref drs, opaqueRange);
//提交上下文,这将执行所有队列中的命令。
context.Submit();

这将会把对象绘制到当前绑定的渲染目标中。你可以使用一个命令缓冲区,按需切换渲染目标。

这是一个可以渲染不透明对象的渲染器:

using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Experimental.Rendering;
[ExecuteInEditMode]
public class OpaqueAssetPipe : RenderPipelineAsset
{
#if UNITY_EDITOR
[UnityEditor.MenuItem("SRP-Demo/02 - Create Opaque Asset Pipeline")]
static void CreateBasicAssetPipeline()
{
var instance = ScriptableObject.CreateInstance<OpaqueAssetPipe>();
UnityEditor.AssetDatabase.CreateAsset(instance, "Assets/SRP-Demo/2-OpaqueAssetPipe/OpaqueAssetPipe.asset");
}
#endif
protected override IRenderPipeline InternalCreatePipeline()
{
return new OpaqueAssetPipeInstance();
}
}
public class OpaqueAssetPipeInstance : RenderPipeline
{
public override void Render(ScriptableRenderContext context, Camera[] cameras)
{
base.Render(context, cameras);
foreach (var camera in cameras)
{
ScriptableCullingParameters cullingParams;
if (!CullResults.GetCullingParameters(camera, out cullingParams))
continue;
CullResults cull = CullResults.Cull(ref cullingParams, context);    

context.SetupCameraProperties(camera);
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, false, Color.black);
context.ExecuteCommandBuffer(cmd);
cmd.Release();
var settings = new DrawRendererSettings(camera, new ShaderPassName("BasicPass"));
settings.sorting.flags = SortFlags.CommonOpaque;
var filterSettings = new FilterRenderersSettings(true) { renderQueueRange = RenderQueueRange.opaque };
context.DrawRenderers(cull.visibleRenderers, ref settings, filterSettings);
context.DrawSkybox(camera);
context.Submit();
}
}
}    

这个示例可以进一步扩展,添加透明渲染:

using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Experimental.Rendering;
[ExecuteInEditMode]
public class TransparentAssetPipe : RenderPipelineAsset
{
#if UNITY_EDITOR
[UnityEditor.MenuItem("SRP-Demo/03 - Create Transparent Asset Pipeline")]
static void CreateBasicAssetPipeline()
{
var instance = ScriptableObject.CreateInstance<TransparentAssetPipe>();
UnityEditor.AssetDatabase.CreateAsset(instance, "Assets/SRP-Demo/3-TransparentAssetPipe/TransparentAssetPipe.asset");
}
#endif
protected override IRenderPipeline InternalCreatePipeline()
{
return new TransparentAssetPipeInstance();
}
}
public class TransparentAssetPipeInstance : RenderPipeline
{
public override void Render(ScriptableRenderContext context, Camera[] cameras)
{
base.Render(context, cameras);
foreach (var camera in cameras)
{
ScriptableCullingParameters cullingParams;
if (!CullResults.GetCullingParameters(camera, out cullingParams))
continue;
CullResults cull = CullResults.Cull(ref cullingParams, context);
context.SetupCameraProperties(camera);
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, false, Color.black);
context.ExecuteCommandBuffer(cmd);
cmd.Release();    

var settings = new DrawRendererSettings(camera, new ShaderPassName("BasicPass"));
settings.sorting.flags = SortFlags.CommonOpaque;
var filterSettings = new FilterRenderersSettings(true) { renderQueueRange = RenderQueueRange.opaque };
context.DrawRenderers(cull.visibleRenderers, ref settings, filterSettings);
context.DrawSkybox(camera);
settings.sorting.flags = SortFlags.CommonTransparent;
filterSettings.renderQueueRange = RenderQueueRange.transparent;
context.DrawRenderers(cull.visibleRenderers, ref settings, filterSettings);
context.Submit();
}
}
}    

这里要重点注意的是,渲染透明时,渲染顺序会变为自后向前。

小结

我们希望本篇文章能帮你入门,开始编写你自己的自定义SRP。下载Unity 2018.1 beta 即刻开始创作自定义SRP的旅程吧。更多关于Unity 2018.1的信息请访问Unity Connect平台!

原文地址:https://www.cnblogs.com/unity2018/p/8492463.html

时间: 2024-08-11 18:10:07

可编程脚本渲染管线SRP的相关文章

shell编程脚本语法

学习了两个月的Linux,记住了很多命令,知道了脚本的作用,也被脚本杀死了大概一半的脑细胞,现在脚本还不能熟练运用,感觉亏了.心疼我的脑细胞,痛恨脚本,但不得不说,脚本是一个好东西啊,用起来真的方便,但是写起来真的烧脑袋呦!下面来总结一下这周学习的脚本语法,哇,语法虽然不多也不难,但是结合起来熟练运用还有一定的难度,何况现在的脚本才几行,以后要写几行,心里没点数吗!废话少说,开始 跳过最基础的命令行堆积的脚本,总结一下让脚本更简洁实用的语法 首先,条件选择if语句登场 if语句用法:常见的单分支

可编程图形渲染管线

着色程序分为两类:vertex shader program(顶点着色程序)和fragment shader program(片断着色程序).为了清楚的解释顶点着色和片断着色的含义,我们首先从阐述GPU上的两个组件:Programmable Vertex Processor(可编程顶点处理器,又称为顶点着色器)和 Programmable Fragment Processor(可编程片断处理器,又称为片断着色器). 顶点和片段处理器被分离成可编程单元,可编程顶点处理器是一个硬件单元,可以运行顶点

shell编程脚本练习题

1.使用for循环在/oldboy目录下通过随机小写10个字母加固定字符串oldboy批量创建10个html文件,名称例如为: [[email protected] oldboy]# sh /server/scripts/oldboy.sh [[email protected] oldboy]# ls coaolvajcq_oldboy.html  qnvuxvicni_oldboy.html  vioesjmcbu_oldboy.html gmkhrancxh_oldboy.html  tmd

Linux命令:bash脚本编程--脚本

练习:写一个脚本adminuser33.sh,其用法格式为: adminuser33.sh --add -del -h|--help -v|--verbose 其中,-h选项只能单独使用,用于显示帮助信息:--add选项时,新增用户:如果同时使用了-v选项,则新增用户后显示新增用户:--del选项时,删除用户. #!/bin/bash#DEBUG=0ADD=0DEL=0 for I in `seq 1 $#` ; do if [ $# -gt 0 ]; then case $1 in      

Shell编程------脚本范例

1. 批量添加用户 建立用户名和密码组合的txt文件userdata.txt,如下: username1 123 username2 123 username3 123 username4 123 username5 123 username6 123 批量添加以上用户的脚本是: #! /bin/bash while read line do username=$(echo $line | cut -f1 -d' ') #或 username=$(echo $line | awk '{print

PostFX v2后期处理特效包:升级更惊艳的视觉效果

https://mp.weixin.qq.com/s/BMkLLuagbhRSWspzeGhK7g Post-Processing Stack后期处理特效包能够轻松创建和调整高质量视觉效果,实现更为惊艳而逼真的特效.在Unity 2018.1 beta版本推出后,我们根据用户的反馈为Post-Processing Stack后期处理特效包添加了一些功能,并修复了大量bug:我们还添加了针对移动端的支持.体积混合以及一整套为自定义用户效果提供的框架. Post-Processing Stack后期

菜鸟的Linux之路2 bash脚本编程之一

shell脚本编程 脚本编程是一种编程能力的体现. 编程语言分为以下几类: 1,机器语言 2,汇编语言 3,高级语言: (1)静态语言:编译型语言->强类型语言 强类型语言指的是变量的类型在程序执行前声明好,不能在程序执行过程中随意改变变量类型的语言. 强类型语言常见的有:C.C++.Java.C#等. 编译型语言在程序执行前会依靠编译器将程序全部转换成可执行的二进制格式. 编译型语言需要事先确定好变量类型,因为它没有解释器,解释器的机制可以帮助程序自动声明变量的类型. 强类型的语言,变量在使用

bash脚本编程详细剖析

bash脚本编程详细剖析          背景:bash脚本编程是Linux学习一个至关重要的部分,想完成一个脚本可能很简单:但是想让自己的脚本写的让人觉得心旷神怡实为不简单.bash是所有Linux发行版的几乎都有的,因此我们这里以bash脚本为例,讨论bash脚本的编写方法.对于bash脚本编程中一些比较重要的知识点,我这里也会给予案例演示. 一.脚本编程中前话: 我们都知道,bash脚本编程说白了就是命令的堆积.只不过这种堆积的方式不是杂乱无章的堆积,而是按照一定要求和格式的链接.这说明

算法及shell脚本编程基础

bash存在多命令执行的特性,例如:# COMMAND1 $(COMMAND2):还有进程之间的通信(IPC):# COMMAND1 | COMMAND2- 一.命令执行结构与算法 命令执行中,存在顺序执行结构:分号分隔 # COMMAND1 ; COMMAND2 ; -.同时存在选择执行结构:逻辑运算与.或.非.异或,其中最主要的为选择执行结构,具体讨论如下. 1.与:逻辑乘法 && 状态返回值:0对应TRUE,1-255对应FALSE 具体算法为:True &&true