可编程脚本渲染管线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”函数的调用,它需要两个参数,渲染上下文以及一个需要渲染的摄像机列表。


publicclass BasicPipeInstance : RenderPipeline

{

  publicoverridevoid 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]publicclass BasicAssetPipe : RenderPipelineAsset

{

    publicColor clearColor = Color.green;

#ifUNITY_EDITOR    [UnityEditor.MenuItem("SRP-Demo/01 - Create Basic Asset Pipeline")]

    staticvoid CreateBasicAssetPipeline()

    {

        varinstance = ScriptableObject.CreateInstance();

        UnityEditor.AssetDatabase.CreateAsset(instance, "Assets/SRP-Demo/1-BasicAssetPipe/BasicAssetPipe.asset");

    }#endifprotectedoverride IRenderPipeline InternalCreatePipeline()

    {

        returnnew BasicPipeInstance(clearColor);

    }

}

publicclass BasicPipeInstance : RenderPipeline

{

    privateColor m_ClearColor = Color.black;

    public BasicPipeInstance(Color clearColor)

    {

        m_ClearColor = clearColor;

    }

    publicoverridevoid Render(ScriptableRenderContext context, Camera[] cameras)

    {

        base.Render(context, cameras);

        varcmd =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平台!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容

  • 这个是我刚刚整理出的Unity面试题,为了帮助大家面试,同时帮助大家更好地复习Unity知识点,如果大家发现有什么...
    dingz阅读 590评论 0 0
  • 这个是我刚刚整理出的Unity面试题,为了帮助大家面试,同时帮助大家更好地复习Unity知识点,如果大家发现有什么...
    编程小火鸡阅读 3,882评论 2 35
  • 一:什么是协同程序? 在主线程运行的同时开启另一段逻辑处理,来协助当前程序的执行,协程很像多线程,但是不是多线程,...
    胤醚貔貅阅读 2,066评论 0 13
  • 儿子的成长像一首诗 一首我不断吟唱的诗 回顾往事,竟不能自已。 出生的第一天,睁着小眼儿,还不能分享姥姥、小姨和阿...
    流花河阅读 213评论 0 3
  • 今天是什么日子 起床:5:18 就寝:1:00 天气:小雨 心情:微困 纪念日:天使班3.0践行的第39天 任务清...
    Lucky有情阅读 98评论 0 0