精华内容
下载资源
问答
  • 渲染管线

    2018-11-11 01:21:15
    渲染管线

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

    也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

                   

    渲染管线

         要可视化一个3D场景到屏幕,场景要转换成2D图形。这个处理过程叫渲染。如图9-1

          <!--[if !vml]--><!--[endif]-->

     

    3D场景中的对象通过网格来描述,网格是它的顶点的集合(和索引,如果合适)。网格中的顶点可以有许多不同的属性,像位置,颜色,法线和纹理坐标。

    渲染处理的开始,对象网格的顶点发送到渲染管线,这是顶点处理,光栅化和像素处理的地方。处理的结尾,生成许多像素,存储到场景的最终图像。因为一个对象的许多三角形可能竞争屏幕上相同的像素,渲染管线的最后阶段-合并输出-决定哪个像素更靠近相机。输出合并存储这些像素到最终图像并决定哪个像素被丢弃。这个决定基于相机和对象间的距离,因此只有最近的对象被显示,但这个决定也可能被透明度信息影响。在旧版DirectXOpenGL应用程序接口(APIs)中,所有渲染管线阶段是固定的(预排程序)。这意味着游戏程序员可用固定效果设置。这强制所有游戏用同样的渲染程序,只能改变几个预订参数。结果是游戏看起来都差不多。

    DirectX8.1起,由于一个叫着色器的小程序诞生,编程渲染管线的一些部分成为可能。着色器允许你定义输入哪种数据和从每个图形处理单元的可编程阶段输出(GPU),更重要的,处理发生在每个阶段。使用着色器,你可以为游戏创建许多用固定管线不能做到的新效果。

    XNA,你用着色器渲染一些对象到屏幕。为减轻游戏开发无需编程你自己的着色器,XNA提供一些包含一个着色器和效果基础设置的助手类。例如,你可用SpriteBatch类绘制2D精灵,BasicEffect类绘制3D模型。这两个类用透明于你的方式使用着色器。顾名思义,这些类只支持基础渲染。SpriteBatch类将渲染你保存在磁盘上的样子,但不添加任何射灯,反光,或荡漾效果。BasicEffect类可以用基础光照渲染你的3D世界。如果你想编程一些效果,你可以创建你自己的着色器。

     

    着色器

    着色器是一个在GPU内部执行并定义从XNA程序收到的数据如何在渲染管线的可编程阶段处理的小程序。着色器通常写进HLSL(高级着色语言)。

    两个着色器一般应用:顶点着色和像素着色。光栅化在顶点着色和像素着色之间执行。

     

    顶点着色

    用于顶点处理阶段的着色器叫顶点着色器,见9-1。顶点着色器的基本任务是从你的XNA程序读取顶点的原始坐标,转换他们到2D屏幕坐标,并交给下一个阶段。额外的,处理坐标的同时,你也可以选择去处理顶点的其他属性,像颜色,法线等等。

    顶点着色器允许你执行许多任务,固体变形(solids deforming),骨骼动画,粒子运动(particle motion

     

    光栅化

    在光栅化阶段,你的GPU确定每个三角形占有哪些屏幕像素。所有这些像素发送到像素着色器,允许你做一个处理的最后阶段。

    9-2是光栅化三角形,生成许多像素。尤其注意顶点属性是所有生成的像素之间的线性插值。

     

    <!--[if !vml]--><!--[endif]-->

     

    像素着色器

    像素着色器的主要任务是接受一个像素作为输入,计算像素的最终颜色,并传递到合并输出。每个像素可以提供多种数据的像素着色器,由你的顶点着色器生成并由光栅化成线性插值。这允许你的像素着色器依照光照条件调整像素的颜色,添加反射,执行凹凸贴图等。你也可以用像素着色器应用后处理效果在整个要渲染的场景,像亮度,色彩增强,饱和度和模糊。

    额外的,像素着色器可以改变像素深度。这个深度用在输出合并时决定哪个像素被绘制哪个不被绘制。这个深度指示原始三角形离相机有多远。但是,如果你想影响输出合并的决定,你可以自己指定这个值。

     

    高级着色语言HLSL

    XNA通过微软的HLSL天生支持着色编程。HLSL有几个内建功能,数学运算,访问纹理,流量控制。HLSL支持的数据类型和C语言的类似,向量,矩阵,采样器

     

    HLSL 数据类型

    HLSL支持许多不同数据类型,包含标量(scalars),向量和矩阵。表9-1显示该语言含有的标量数据类型。注意可以为所有该语言包含的标量类型创建向量和矩阵,像float2float4bool3×3,等等。

    类型         

    Bool          true false

    Int           32位有符号整型

    Half          16位浮点数

    Float         32位浮点数

    Double        64位浮点数

     

    HLSL中现有的其它数据类型是采样器(sampler)类型,用来从纹理采样数据。不同采样器类型,像,sampler1Dsampler2Dsampler3D分别用来采样1D2D3D纹理。与取样类型相关联的几个状态,指定要采样的纹理,用来过滤用,纹理如何编址。采样器要定义在HLSL代码文件的顶部。下面是2D纹理采样器的例子:

    Texture skyTexture;

    sampler2D skySampler = sampler_state

    {

       Texture = skyTexture;// 代表要被采样的纹理,只能通过用采样器读取

       MinFilter = Linear;// 这三个是过滤状态(filtering states)

       MagFilter = Linear;

       MipFilter = Linear;

       AddressU = Wrap;// 地址状态(addressing states)

       AddressV = Wrap;

       AddressW = Wrap;

    }

     

    统一和变化的输入

          HLSL有两类输入数据类型:

    统一(Uniform)输入数据:这是在完整输入数据的处理期间使所有着色器里的顶点/像素不发生变化的数据类型。例如,渲染一棵树时,它的纹理,世界矩阵和光照条件都不变。这种数据设置在您的XNA应用程序中。

    变化(Varying)输入数据:这是在着色器每次执行时都变化的数据。例如,渲染一棵树时,顶点着色器要处理数的所有顶点。这意味着顶点携带的信息在每个顶点着色周期都被改变。和统一输入数据不同 ,你用语义(Semantics)声明变化输入数据。

     

    语义(Semantics

    语义是HLSL中用来映射输入和输出数据来使名字可变的预定义词。例如,3D对象的每个顶点可能有一个float4包含3D位置,另一个float4包含2个纹理坐标。你的顶点着色器怎么知道用哪个表示位置?

    解决方案是添加POSITION0(前一个O是字母,后一个0是数字)语义到顶点处理阶段去映射每个顶点的位置属性到不同变量,如下:

    Float4 vertexPosition:POSITION0;

    这个语义对所有变化输入数据(从应用程序接收或在渲染阶段传递)是必须的。例如,所有从顶点着色器输出并将在像素着色器中使用的数据必须和语义相关。语义是不区分大小写且用冒号(:)指定在变量名之后。

    输入顶点着色器语义

    Input            描述                   类型

    Position[n]      对象空间的顶点位置     float4

    COLOR[n]         漫反射色和镜面反射色   float4

    NORMAL[n]        法线向量               float4

    TEXCOORD[n]      纹理坐标               float4

    TANGENT[n]       切线向量               float4

    BINORMAL[n]      副法线向量             float4

    BLENDINDICES[n]  骨融合指数             int4

    BLENDWEIGHT[n]   骨融合比重             float4

     

    输出顶点着色器语义

    Output           描述                   类型

    Position[n]      同质空间的顶点位置     float4x,y,z,w

    COLOR[n]         漫反射色和镜面反射色   float4

    TEXCOORD[n]      纹理坐标               float4

    FOG              顶点雾化               float

     

    你为从顶点着色器接收的变化数据使用输入顶点着色器语义。最常用的语义如POSITIONCOLORNORMALTEXTURE。如果顶点有切线或副法线向量你才使用TANGENTBINORMAL语义,比如你想做些凹凸贴图。当顶点连接到骨头时采用BLENDINDICESBLENDWEIGHT

    POSITION语义是顶点着色器唯一必要的输出。如果你想从顶点着色器传递其它数据到像素着色器,TEXCOORD[n]就用的到了。

    [n]是定义要使用的资源数的可选整数。例如,一个模型有3个纹理,TEXCOORD语义的[n]可以是0,1,2;如TEXCOORD0TEXCOORD1TEXCOORD2.

     

    像素着色器语义

    Input            描述                   类型

    COLOR[n]         漫反射色和镜面反射色   float4

    TEXCOORD[n]      纹理坐标               float4

    COLOR[n]         输出色                 float4

    DEPTH[n]         输出深度               float

     

    由于像素着色器是在光栅化阶段之后执行,可用的输入语义是像素颜色和一些纹理坐标。纹理坐标标注映射到当前像素的纹理位置,这些坐标可用于从顶点着色器改变数据到像素着色器。

    从像素着色器输出的最终数据是像素颜色和深度,像素颜色的输出是必须的,深度是可选的。

     

    方法

    HLSL允许创建像C语言的语法的方法,每个方法有声明和定义。方法声明包含方法名和返回值,可能还有参数列表。返回类型可以使用和他关联的语义。下面是一个作为像素着色器入口点的方法:

    Float4 simplePS(float4 inputColor: COLOR0):COLOR0

    {

       Return inputColor * 0.5f;

    }

     

    因为这个方法用作像素着色器的入口点,它的参数要有一个语义关联。在这情况下,该方法把接收到的颜色参数乘以0.5来缩放并作为最终像素颜色返回。注意方法的参数可以有其它修饰符像inoutinout,用来定义输入,输出和输入/输出参数。

    一小部分固有功能内建在HLSL中。有数学,纹理访问等。这些方法并不一定直接映射到GPU的汇编指令。事实上,其中许多方法被映射到GPU的汇编指令,他们很可能为这些任务提供最好的执行方式。

     

    创建采样着色器

    这节,你将把你学到的放到一起,创建第一个用HLSL的着色器。作为一个好习惯,你从声明固定和变化变量开始:

    // 从应用程序接收的矩阵-固定(世界,视图,投影)

    Float4×4 matWVP:WorldViewProjection;

    // 用作顶点输入的结构-变化

    Struct vertexInput

    {

       Float4 position:POSITION0;

    };

    // 用来传递顶点着色输出到像素着色输入的结构-变化

    Struct vertexOutput

    {

       Float4 hposition:POSITION;

       Float3 color:COLOR0;

    }

    你的着色器预测matWVP矩阵将被XNA程序设置。由相机创建该矩阵。当你的顶点着色器变化3D位置到2D屏幕坐标这样做是需要的。

    你用vertexInput结构定义顶点着色器可以预测到的信息。如你所见,这个顶点着色器将能出来所有包含位置数据的顶点。

    你用vertexOutput结构定义从顶点着色器传递到光栅化器并经过线性插值到像素着色器的数据种类。你的顶点着色器将生成强制位置以及颜色。

    这里一个重点是顶点着色器输出的顶点位置不能被像素着色器访问。这个2D屏幕位置是光栅化器必须的,但他被光栅化器消耗了。如果你的像素着色器需要这个2D屏幕位置,你要作为额外的TEXCOORDINATE[n]语义来传递。

    接下来,它自己声明顶点着色器:

    pixelInput simpleVS(vertexInput IN)

    {

       pixelInput OUT;

       // 变换顶点位置

       OUT.hposition = mul(IN.position, matWVP);

       OUT.color = float3(1.0f,1.0f,0.0f);

       Return OUT;

    }

    顶点着色器在XNA程序每次渲染顶点时调用。这个顶点被你的着色器作为vertexInput对象接收并出来成pixelInput对象。在SimpleVS方法里,你把他乘以mtWVP矩阵来计算输出2D屏幕位置。输出顶点颜色设成黄,#010100.

    接下来,你定义像素着色器:

    Float4 simplePS(pixelInput IN):COLOR0

    {

       Return float4(IN.color.rgb,1.0f);

    }

    该像素着色器简单返回从顶点处理阶段接收的颜色。这个颜色作为最终像素颜色。

     

    技术,过程和效果

    在其最基本的形式,一个技术并不比顶点着色和像素着色的组合多点什么。下面是一个用你刚定义的顶点着色器和像素着色器的技术:

    Technique basicTechnique

    {

       Pass p0

    {

       vertexShader = compile vs_2_0 simpleVS();

       PixelShader = compile ps_2_0 simplePS();

    }

    }

    一个即使也定义着色器要和哪个着色模型组合。在这情况,你为每个着色器用着色模型2.0.更高版本有更复杂的着色器和更多功能,但前提是GPU要支持。

    如你所见,每个着色器都封装进过程(pass)。一个通道读入所有要用技术绘制的顶点,处理它们,处理返回的像素,并渲染这些像素到后备缓冲,等待所有像素被处理后发送到屏幕。有些技术,可能在同一帧你想为所有顶点做2次这个处理。为此,这个技术将有2个过程,顶点着色和像素着色都有。

    为了一些高级效果,你可能想用多个技术。你将用第一个技术变换场景到临时的中间图像,该图像用作另一个技术的输入。

    所遇着色器和效果的组合叫effect。一个简单的效果将包含一个顶点着色器,一个像素着色器,和一个技术。一个高级效果将包含多个这些部分。

    效果,技术和着色器之间的区别有助于着色程序,使着色代码可以在不同技术里重用,也可以创建不同技术面向低端和高端GPU

    一般你将所有着色器和技术依照相同效果保存在一个文件中,XNA调用每个HLSL代码文件一个effect。这允许你把效果当作游戏资源,就像模型和纹理。所有通过XNA内容管道处理,生成内容管理可以在运行时加载的可管理对象。

     

    Effect

    XNA程序,效果要被加载到Effect类的对象。Effect类允许你配置效果的固定参数,选择当前效果技术并用做渲染,代码如下:

    Effect effect;

    // 加载效果

    Effect = content.Load<Effect>(“/effects/simpleEffect”);

    // 设置技术

    Effect.CurrentTechnique = lightEffectTechniques[“basicTechnique”];

    // 配置固定效果参数

    Effect.Parameters[“matWVP”].SetValue(worldViewProjectionMatrix);

    该代码用内容管理器的content.Load方法从HLSL代码文件加载simpleEffect效果。之后定义哪个效果要被使用;在这情况下,用basicTechnique技术。最后,设置定义在HLSL代码文件:matWVP中的固定效果参数。

    用法如下:

    // 开始效果

    Effect.Begin();

    // 记住效果有很多通道

    Foreach(EffectPass pass in effect.CurrentTechnique.Passes)

    {

       Pass.Begin();

       // 这里是绘制代码

       Pass.End();

    }

    // 结束效果

    Effect.End();

    要绘制3D对象,你想要启动你要用作绘制对象的效果,然后遍历所选技术的所有过程。对于每个过程,你要启动过程,绘制对象,结束过程。最后,结束效果。效果通道由XNAEffectPass类负责。如果你想改变在启动一个过程后改变效果参数,你要调用Effect类的CommitChanges方法更新改变。

    上面的步骤只用在你想用自定义效果绘制模型时。平时绘制模型,可以用存储在模型的ModelMesh对象中的效果。

     

    效果助手(Effect Helper)类

    当一个效果通过内容管理器加载后,你不知道它有什么参数或技术。同样,要修改一个效果参数,你要先在效果里查询这个效果,然后修改它。所以,你可以像这样配置一个叫lightPosition的效果参数:

    Effect.Parameters[“lightPosition”].SetValue(new Vector3(0.0f,40.0f,0.0f));

    当你修改lightPosition参数的值,一个查询是在内部产生。这会有2个问题:计算此参数的查询的开销是必须的,且可能查询的是无效的参数。使用助手类可以解决这个问题。

    要简化自创效果的管理,你可以为每个效果创建一个唯一的助手类。每个效果助手类将存储所有效果参数的引用,避免查询参数的开销。代码如下:

    EffectParameter param1 = effect.Parameters[“lightPosition”];

    Param1.SetValue(new Vector3(0.0f,40.0f,0.0f));

     

    材质

    材质是你要创建来存储用作配置效果的参数的类。例如,你可以用一个应用纹理的效果渲染2个面。在此情况下,每个面的材质就是它的纹理,要配置渲染面的效果就用这个纹理。因此,如果2个面共享同样的材质,你可以设置渴望的效果和渴望的材质,并按顺序渲染每个面避免改变当前设置的效果或参数。

    下面是你将创建的2个基础材质类:

    LightMaterial:这个类将存储用作光照表面属性(镜面色,漫反射色,高光色)。

    TextureMaterial:这个类将存储用作应用纹理到表面的纹理映射和贴图(tile)。

    你可以用这2个基础材质类创建更复杂类型的材质,比如多纹理材质。如下是LightMaterial类的完整代码:

     

    P255(或者直接看书的源代码)

     

    你把光照的漫反射和镜面反射色以XNAVector3形式存储在LightMaterial类的diffuseColorspecularColor属性中。你存储光照的镜面反射强度为float值,存在specularPower属性中。注意向量的(x,y,z)分别代表颜色的(rgb)。如下是TextureMaterial类的完整代码:

     

    P256

     

    你存储纹理为XNATexture2D形式在TextureMaterial类的texture属性.纹理UV贴图用作凹凸贴图,以XNAVector2形式存储在uvTile属性。

     

    着色创作工具

    着色器开发期间,你要不断修改着色器,调整参数,并在不同资源(模型,纹理等)上测试。每次检验修改都要重编译和执行游戏,这个处理过程很慢也很累。为此你可以使用着色创作工具。

    最好的工具之一是NVIDIAFX Composer,自己上网下去。这是一个IDE,支持HLSL在内的多种着色语言和FBXX等多种模型文件。你可以实时查看修改代码的结果。反正就是好东西

               

    给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow

    这里写图片描述
    展开全文
  • 创建渲染管线资源和渲染管线实例 如果你正在创建你自己的可编程渲染管线(SRP),你的工程必须包含以下: 一个继承自RenderPipelineAsset的脚本,并且还需要重新里边的CreatePipeline()方法,这个脚本定义了你的渲染...

    创建渲染管线资源和渲染管线实例

    如果你正在创建你自己的可编程渲染管线(SRP),你的工程必须包含以下:

    • 一个继承自RenderPipelineAsset的脚本,并且还需要重新里边的CreatePipeline()方法,这个脚本定义了你的渲染管线资源

    • 一个继承自 RenderPipeline的脚本,同时还需要重写 Render()方法, 这个脚本定义了你的渲染管线实例

    • 你从 RenderPipelineAsset 脚本创建的渲染管线资源. 这个资源的行为可以看作是渲染管线实例的工厂类

      因为这些元素之间的联系是很紧密的,所以你应该在同一时间创建它们

    创建一个基本的渲染管线资源和渲染管线实例

    下边的实例展示了如何创建脚本去定义渲染管线实例以及渲染管线资源本身,这包括了用基本的自定义渲染管线资源去实例化渲染管线实例

    1.创建一个名称为:ExampleRenderPipelineAsset.cs的C#脚本

    2.复制和粘贴以下代码到你新创建的脚本中:

    using UnityEngine;
    using UnityEngine.Rendering;
        
    // The CreateAssetMenu attribute 可以让你在Unity编辑器中创建这个类的实例
    [CreateAssetMenu(menuName = "Rendering/ExampleRenderPipelineAsset")]
    public class ExampleRenderPipelineAsset : RenderPipelineAsset
    {
        // unity会在渲染第一帧的时候调用这个方法
        //如果渲染管线资源的设置改变了,unity会销毁当前渲染管线实例,
        //并且会在下一帧渲染之前再次调用这个方法
        protected override RenderPipeline CreatePipeline() {
            //实例化这个自定义SRP用于渲染而使用的渲染管线
            return new ExampleRenderPipelineInstance();
        }
    }
    

    3.创建一个名称为ExampleRenderPipelineInstance.cs的C#脚本

    4.复制和粘贴以下代码到你新创建的脚本中:

    using UnityEngine;
    using UnityEngine.Rendering;
        
    public class ExampleRenderPipelineInstance : RenderPipeline
    {
        public ExampleRenderPipelineInstance() {
        }
        //unity在当前渲染画面中为每个不同类型的摄像机每一帧调用这个方法一次
        protected override void Render (ScriptableRenderContext context, Camera[] cameras) {
            //这里是你可以编写自定义渲染代码的地方,自定义这个方法从而自定义你的SRP
        }
    }
    

    5.在Project视图中,你要么点击加号(+)按钮,要么打开相关联的菜单按钮,然后找到Create按钮,接着选择Rendering->Example Render Pipeline Asset.然后unity会在Project试图下生成一个新的渲染管线资源

    创建可配置的渲染管线资源和渲染管线实例

    默认情况下,渲染管线资源是存储渲染管线实例用于渲染的信息,以及存储默认的材质和shader在编辑器中所使用的数据.在你的RenderPipelineAsset脚本中,你可以扩展你的渲染管线资源,用其来存储额外的数据,你也可以在你的工程中拥有多个配置不同的渲染管线资源.比如说,你可以使用渲染管线资源为每个不同的硬件层保存配置数据.高清晰渲染管线(HDRP)和通用渲染管线(URP)都包含在这里的实例中.

    以下的实例展示了如何创建一个用于定义渲染管线资源的RenderPipelineAsset脚本,并且脚本中含有你可以在Inspector面板中为每个实例设置的公有数据.渲染管线实例在其构造函数中接收渲染管线资源并且使用该管线资源中的数据

    1.创建一个名称为ExampleRenderPipelineAsset.cs的脚本

    2.复制和粘贴以下代码到你新创建的脚本中:

    using UnityEngine;
    using UnityEngine.Rendering;
        
    // The CreateAssetMenu attribute 可以让你在unity编辑器中创建这个类的实例
    [CreateAssetMenu(menuName = "Rendering/ExampleRenderPipelineAsset")]
    public class ExampleRenderPipelineAsset : RenderPipelineAsset
    {
        //为每一个渲染管线资源在Inspector面板中定义的数据
        public Color exampleColor;
        public string exampleString;
        
        	//unity会在渲染第一帧之前调用这个方法
        //如果渲染管线资源的设置改变了,unity会销毁当前渲染管线实例,并且会在下一帧之前再次调用这个方法
        protected override RenderPipeline CreatePipeline() {
            //实例化这个自定义SRP用于渲染的渲染管线,同时给这个渲染管线资源传递一个引用
            //接着渲染管线实例就可以访问上边定义好的配置数据
            return new ExampleRenderPipelineInstance(this);
        }
    }
    

    3.创建一个名为ExampleRenderPipelineInstance.cs的脚本

    4.拷贝和粘贴以下代码到新创建的脚本:

    using UnityEngine;
    using UnityEngine.Rendering;
        
    public class ExampleRenderPipelineInstance : RenderPipeline
    {
        //使用这个变量去引用渲染管线资源,这个渲染管线资源在构造函数中被赋值
        private ExampleRenderPipelineAsset renderPipelineAsset;
        //这个构造函数有一个ExampleRenderPipelineAsset类的实例作为它的参数
        public ExampleRenderPipelineInstance(ExampleRenderPipelineAsset asset) {
            renderPipelineAsset = asset;
        }
        
        //unity在当前渲染画面中为每个不同类型的摄像机每一帧调用这个方法一次
        protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
            //这是一个使用来自渲染管线资源的数据的例子
            Debug.Log(renderPipelineAsset.exampleString);    
            //你可以在这里编写自定义渲染的代码,自定义这个方法以至于自定义你的SRP
        }
    }
    
    

    5.在Project视图中,你既可以点击加号(+)按钮,也可以点击相关的菜单,然后找到Create,再然后选择Rendering->Example Render Pipeline Asset.然后 unity就会在Project视图下创建一个新的渲染管线资源

    展开全文
  • 至2018.1版本,Unity中除了默认渲染管线,还提供了轻量级渲染管线(Lightweight Pipeline)和高清晰渲染管线(HD Pipleline)二个渲染管线。当然也支持自定义渲染管线。与高清晰渲染管线相比,...

     

    前言

    Unity2018中引入了可编程渲染管线(Scriptable Render Pipeline,简称SRP),是一种在Unity中通过C#脚本配置和执行渲染的方式。至2018.1版本,Unity中除了默认渲染管线,还提供了轻量级渲染管线(Lightweight Pipeline)和高清晰渲染管线(HD Pipleline)二个渲染管线。当然也支持自定义渲染管线。与高清晰渲染管线相比,轻量级渲染管线的开发已经比较成熟。

    这篇文章主要是分析轻量级渲染管线的C#代码都做了哪些工作。

     

    可编程渲染管线能做什么

    为了解决仅有一个默认渲染管线,造成的可配置型、可发现性、灵活性等问题。Unity在管线设计的概念上做了转移,决定在C++端保留一个非常小的渲染内核,让C#端可以通过API暴露出更多的选择性,也就是说,Unity会提供一系列的C# API以及内置渲染管线的C#实现;这样一来,一方面可以保证C++端的代码都能严格通过各种白盒测试,另一方面C#端代码就可以在实际项目中调整,有任何问题也可以方便地进行调试。

    新的管线对用户而言主要是C# 端的API以及由这些API编写的一系列定制化的内置渲染管线。而在内部实现上,引擎C++端会负责多线程实现性能关键的部分,如上图所示,而C#端负责更高层的渲染指令调度。[1]

    可编程渲染管线的使用层设计

    用户可以直接使用开源的内置管线,或者在内置管线的基础上进行修改,甚至直接编写定制化的管线。具体使用上渲染管线在工程中会生成特定的Asset,如下图所示,这个Asset序列化了这条管线的一些公共设置变量,并负责在运行时创建实际的渲染上下文;当这个Asset的设置变量在运行时发生变化,引擎会销毁当前上下文然后重新创建管线(这个操作在现有固定管线中无法做到)。

     

    简单的渲染管线示例

    可编程渲染管线的使用层设计中,最少需要两个类,一个是渲染管线资源,一个是渲染管线实例。

    下面是一个不透明渲染管线的C#代码查看详细

    using UnityEngine;
    using UnityEngine.Experimental.Rendering;
    using UnityEngine.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();
            }
        }
    }

    这里定义了一个继承自RenderPipelineAsset的类,通过这个类创建一个渲染管线实例:继承自RenderPipeline的类。

    渲染管线实例中的Render方法是渲染入口点,它需要两个参数,渲染上下文以及一个需要渲染的摄像机列表。

    在Render方法中依次进行了剔除、绘制、过滤等操作,通过渲染命令缓冲向渲染上下文发送命令来实现绘制。

    轻量级渲染管线

    以下内容来自Unity官方在GitHub中的项目,本文将在代码中通过注释来解释渲染管线的功能。

    渲染管线资源

    渲染管线资源是项目中实际使用的资源,它包含一系列的可调整设置,并通过这些设置创建渲染管线实例,在设置发生变更时重新创建。

    #if UNITY_EDITOR
    using System;
    using UnityEditor;
    using UnityEditor.ProjectWindowCallback;
    #endif
    
    namespace UnityEngine.Experimental.Rendering.LightweightPipeline
    {
        /// <summary>
        /// 阴影级联
        /// </summary>
        public enum ShadowCascades
        {
            NO_CASCADES = 0,
            TWO_CASCADES,
            FOUR_CASCADES,
        }
        /// <summary>
        /// 阴影类型
        /// </summary>
        public enum ShadowType
        {
            NO_SHADOW = 0,
            HARD_SHADOWS,
            SOFT_SHADOWS,
        }
        /// <summary>
        /// 阴影分辨率
        /// </summary>
        public enum ShadowResolution
        {
            _256 = 256,
            _512 = 512,
            _1024 = 1024,
            _2048 = 2048,
            _4096 = 4096
        }
        /// <summary>
        /// MSAA质量
        /// MSAA是“多重采样抗锯齿”,可以使画面更加平滑。
        /// 超级采样抗锯齿(Super Sampling Anti-Aliasing)的原理是把当前分辨率成倍提高,然后再把画缩放到当前的显示器上。
        /// 这样的做法实际上就是在显示尺寸不变的情况提高分辨率,让单个像素变得极小,这样就能够大幅减轻画面的锯齿感了。
        /// 不过是由于对整个显示画面的放大,因此它消耗的显示资源也是非常大的。
        /// 不过MSAA是寻找出物体边缘部分的像素,然后对它们进行缩放处理。
        /// 由于只是物体的外层像素进行缩放处理,忽略掉了不会产生锯齿的内部像素,
        /// 所以显卡不会像处理SSAA(超级采样抗锯齿)那样需要庞大的计算量,因此MSAA比起SSAA来更有效。
        /// </summary>
        public enum MSAAQuality
        {
            Disabled = 1,
            _2x = 2,
            _4x = 4,
            _8x = 8
        }
        /// <summary>
        /// 降采样,在ForwardLitPass中使用
        /// </summary>
        public enum Downsampling
        {
            None = 0,
            _2xBilinear,
            _4xBox,
            _4xBilinear
        }
        /// <summary>
        /// 默认材质类型
        /// LightweightPipelineEditorResources的资源中获得
        /// </summary>
        public enum DefaultMaterialType
        {
            Standard = 0,
            Particle,
            Terrain,
            UnityBuiltinDefault
        }
    
        public class LightweightPipelineAsset : RenderPipelineAsset, ISerializationCallbackReceiver
        {
            // 这两个路径用于查找ScriptableObject,优先在"Assets"中查找,若不存在直接取默认
            public static readonly string s_SearchPathProject = "Assets";
            public static readonly string s_SearchPathPackage = "Packages/com.unity.render-pipelines.lightweight";
    
            /// <summary>
            /// GetDefaultShader()方法调用默认shader的私有变量
            /// </summary>
            Shader m_DefaultShader;
    
            // Default values set when a new LightweightPipeli ne asset is created
            /// <summary>
            /// 版本号,用于开发过程的迭代,在ISerializationCallbackReceiver接口的OnAfterDeserialize()中可以将旧版数据转换为新版使用的数据
            /// </summary>
            [SerializeField] int k_AssetVersion = 3;
            #region Editor中包含的元素
            /// <summary>
            /// 最大逐像素光源个数
            /// </summary>
            [SerializeField] int m_MaxPixelLights = 4;
            /// <summary>
            /// 支持顶点光照
            /// </summary>
            [SerializeField] bool m_SupportsVertexLight = false;
            /// <summary>
            /// 需要深度纹理
            /// </summary>
            [SerializeField] bool m_RequireDepthTexture = false;
            /// <summary>
            /// 需要软粒子
            /// </summary>
            [SerializeField] bool m_RequireSoftParticles = false;
            /// <summary>
            /// 需要不透明贴图
            /// </summary>
            [SerializeField] bool m_RequireOpaqueTexture = false;
            /// <summary>
            /// 不透明降采样
            /// </summary>
            [SerializeField] Downsampling m_OpaqueDownsampling = Downsampling._2xBilinear;
            /// <summary>
            /// 支持HDR
            /// </summary>
            [SerializeField] bool m_SupportsHDR = false;
            /// <summary>
            /// MSAA
            /// </summary>
            [SerializeField] MSAAQuality m_MSAA = MSAAQuality._4x;
            /// <summary>
            /// 渲染比例
            /// </summary>
            [SerializeField] float m_RenderScale = 1.0f;
            /// <summary>
            /// 支持动态批处理
            /// </summary>
            [SerializeField] bool m_SupportsDynamicBatching = true;
            /// <summary>
            /// 支持定向阴影
            /// </summary>
            [SerializeField] bool m_DirectionalShadowsSupported = true;
            /// <summary>
            /// 阴影图集分辨率
            /// </summary>
            [SerializeField] ShadowResolution m_ShadowAtlasResolution = ShadowResolution._2048;
            /// <summary>
            /// 阴影距离
            /// </summary>
            [SerializeField] float m_ShadowDistance = 50.0f;
            /// <summary>
            /// 阴影级联
            /// </summary>
            [SerializeField] ShadowCascades m_ShadowCascades = ShadowCascades.FOUR_CASCADES;
            /// <summary>
            /// 二级级联分界
            /// </summary>
            [SerializeField] float m_Cascade2Split = 0.25f;
            /// <summary>
            /// 四级级联分界
            /// </summary>
            [SerializeField] Vector3 m_Cascade4Split = new Vector3(0.067f, 0.2f, 0.467f);
            /// <summary>
            /// 支持非平行光阴影
            /// </summary>
            [SerializeField] bool m_LocalShadowsSupported = true;
            /// <summary>
            /// 非平行光阴影图集分辨率
            /// </summary>
            [SerializeField] ShadowResolution m_LocalShadowsAtlasResolution = ShadowResolution._512;
            /// <summary>
            /// 支持软阴影
            /// </summary>
            [SerializeField] bool m_SoftShadowsSupported = false; 
            #endregion
            //以下内容,关系到LightweightPipelineCore中的 static PipelineCapabilities s_PipelineCapabilities
            [SerializeField] bool m_KeepAdditionalLightVariants = true;
            [SerializeField] bool m_KeepVertexLightVariants = true;
            [SerializeField] bool m_KeepDirectionalShadowVariants = true;
            [SerializeField] bool m_KeepLocalShadowVariants = true;
            [SerializeField] bool m_KeepSoftShadowVariants = true;
            /// <summary>
            /// 4个shader :BlitShader;CopyDepthShader;ScreenSpaceShadowShader;SamplingShader;
            /// </summary>
            [SerializeField] LightweightPipelineResources m_ResourcesAsset;
    
            // Deprecated
            [SerializeField] ShadowType m_ShadowType = ShadowType.HARD_SHADOWS;
    
    #if UNITY_EDITOR
            /// <summary>
            /// 三个材质
            /// </summary>
            [NonSerialized]
            LightweightPipelineEditorResources m_EditorResourcesAsset;
    
            [MenuItem("Assets/Create/Rendering/Lightweight Pipeline Asset", priority = CoreUtils.assetCreateMenuPriority1)]
            static void CreateLightweightPipeline()
            {
                ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, CreateInstance<CreateLightweightPipelineAsset>(),
                    "LightweightAsset.asset", null, null);
            }
    
            //[MenuItem("Assets/Create/Rendering/Lightweight Pipeline Resources", priority = CoreUtils.assetCreateMenuPriority1)]
            static void CreateLightweightPipelineResources()
            {
                var instance = CreateInstance<LightweightPipelineResources>();
                AssetDatabase.CreateAsset(instance, string.Format("Assets/{0}.asset", typeof(LightweightPipelineResources).Name));
            }
    
            //[MenuItem("Assets/Create/Rendering/Lightweight Pipeline Editor Resources", priority = CoreUtils.assetCreateMenuPriority1)]
            static void CreateLightweightPipelineEditorResources()
            {
                var instance = CreateInstance<LightweightPipelineEditorResources>();
                AssetDatabase.CreateAsset(instance, string.Format("Assets/{0}.asset", typeof(LightweightPipelineEditorResources).Name));
            }
    
            /// <summary>
            /// 创建带初始化的窗口
            /// </summary>
            class CreateLightweightPipelineAsset : EndNameEditAction
            {
                public override void Action(int instanceId, string pathName, string resourceFile)
                {
                    var instance = CreateInstance<LightweightPipelineAsset>();
                    instance.m_EditorResourcesAsset = LoadResourceFile<LightweightPipelineEditorResources>();
                    instance.m_ResourcesAsset = LoadResourceFile<LightweightPipelineResources>();
                    AssetDatabase.CreateAsset(instance, pathName);
                }
            }
    
            /// <summary>
            /// 加载ScriptableObject资源,先查找Asset文件夹,若不存在使用默认
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <returns></returns>
            static T LoadResourceFile<T>() where T : ScriptableObject
            {
                T resourceAsset = null;
                var guids = AssetDatabase.FindAssets(typeof(T).Name + " t:scriptableobject", new[] {s_SearchPathProject});
                foreach (string guid in guids)
                {
                    string path = AssetDatabase.GUIDToAssetPath(guid);
                    resourceAsset = AssetDatabase.LoadAssetAtPath<T>(path);
                    if (resourceAsset != null)
                        break;
                }
    
                // There's currently an issue that prevents FindAssets from find resources withing the package folder.
                if (resourceAsset == null)
                {
                    string path = s_SearchPathPackage + "/LWRP/Data/" + typeof(T).Name + ".asset";
                    resourceAsset = AssetDatabase.LoadAssetAtPath<T>(path);
                }
                return resourceAsset;
            }
    
            LightweightPipelineEditorResources editorResources
            {
                get
                {
                    if (m_EditorResourcesAsset == null)
                        m_EditorResourcesAsset = LoadResourceFile<LightweightPipelineEditorResources>();
    
                    return m_EditorResourcesAsset;
                }
            }
    #endif
            LightweightPipelineResources resources
            {
                get
                {
    #if UNITY_EDITOR
                    if (m_ResourcesAsset == null)
                        m_ResourcesAsset = LoadResourceFile<LightweightPipelineResources>();
    #endif
                    return m_ResourcesAsset;
                }
            }
            /// <summary>
            /// 创建渲染管线的方法
            /// </summary>
            /// <returns></returns>
            protected override IRenderPipeline InternalCreatePipeline()
            {
                return new LightweightPipeline(this);
            }
    
            /// <summary>
            /// 获得ScriptableObject中的材质资源
            /// </summary>
            /// <param name="materialType"></param>
            /// <returns></returns>
            Material GetMaterial(DefaultMaterialType materialType)
            {
    #if UNITY_EDITOR
                if (editorResources == null)
                    return null;
    
                switch (materialType)
                {
                    case DefaultMaterialType.Standard:
                        return editorResources.DefaultMaterial;
    
                    case DefaultMaterialType.Particle:
                        return editorResources.DefaultParticleMaterial;
    
                    case DefaultMaterialType.Terrain:
                        return editorResources.DefaultTerrainMaterial;
    
                    // Unity Builtin Default
                    default:
                        return null;
                }
    #else
                return null;
    #endif
            }
            /// <summary>
            /// 获得版本号
            /// </summary>
            /// <returns></returns>
            public int GetAssetVersion()
            {
                return k_AssetVersion;
            }
            /// <summary>
            /// 最大逐像素光源数量
            /// </summary>
            public int maxPixelLights
            {
                get { return m_MaxPixelLights; }
            }
            /// <summary>
            /// 支持顶点光照
            /// </summary>
            public bool supportsVertexLight
            {
                get { return m_SupportsVertexLight; }
            }
            /// <summary>
            /// 支持深度纹理
            /// </summary>
            public bool supportsCameraDepthTexture
            {
                get { return m_RequireDepthTexture; }
            }
            /// <summary>
            /// 支持软粒子
            /// </summary>
            public bool supportsSoftParticles
            {
                get { return m_RequireSoftParticles; }
            }
            /// <summary>
            /// 支持不透明贴图
            /// </summary>
            public bool supportsCameraOpaqueTexture
            {
                get { return m_RequireOpaqueTexture; }
            }
            /// <summary>
            /// 不透明降采样 Downsampling._2xBilinear
            /// </summary>
            public Downsampling opaqueDownsampling
            {
                get { return m_OpaqueDownsampling; }
            }
            /// <summary>
            /// 支持HDR
            /// </summary>
            public bool supportsHDR
            {
                get { return m_SupportsHDR; }
            }
            /// <summary>
            /// MSAA的程度
            /// </summary>
            public int msaaSampleCount
            {
                get { return (int)m_MSAA; }
                set { m_MSAA = (MSAAQuality)value; }
            }
            /// <summary>
            /// 渲染比例
            /// </summary>
            public float renderScale
            {
                get { return m_RenderScale; }
                set { m_RenderScale = value; }
            }
            /// <summary>
            /// 支持动态批处理
            /// </summary>
            public bool supportsDynamicBatching
            {
                get { return m_SupportsDynamicBatching; }
            }
            /// <summary>
            /// 支持定向阴影
            /// </summary>
            public bool supportsDirectionalShadows
            {
                get { return m_DirectionalShadowsSupported; }
            }
            /// <summary>
            /// 定向阴影图集分辨率
            /// </summary>
            public int directionalShadowAtlasResolution
            {
                get { return (int)m_ShadowAtlasResolution; }
            }
            /// <summary>
            /// 阴影距离
            /// </summary>
            public float shadowDistance
            {
                get { return m_ShadowDistance; }
                set { m_ShadowDistance = value; }
            }
            /// <summary>
            /// 阴影级联(1、2、4)
            /// </summary>
            public int cascadeCount
            {
                get
                {
                    switch (m_ShadowCascades)
                    {
                        case ShadowCascades.TWO_CASCADES:
                            return 2;
                        case ShadowCascades.FOUR_CASCADES:
                            return 4;
                        default:
                            return 1;
                    }
                }
            }
            /// <summary>
            /// 阴影级联2级
            /// </summary>
            public float cascade2Split
            {
                get { return m_Cascade2Split; }
            }
            /// <summary>
            /// 阴影级联4级
            /// </summary>
            public Vector3 cascade4Split
            {
                get { return m_Cascade4Split; }
            }
            /// <summary>
            /// 支持非平行光阴影
            /// </summary>
            public bool supportsLocalShadows
            {
                get { return m_LocalShadowsSupported; }
            }
            /// <summary>
            /// 非平行光阴影分辨率
            /// </summary>
            public int localShadowAtlasResolution
            {
                get { return (int)m_LocalShadowsAtlasResolution; }
            }
            /// <summary>
            /// 支持软阴影
            /// </summary>
            public bool supportsSoftShadows
            {
                get { return m_SoftShadowsSupported; }
            }
            /// <summary>
            /// 自定义Shader变量分离,False
            /// </summary>
            public bool customShaderVariantStripping
            {
                get { return false; }
            }
            //以下内容,关系到LightweightPipelineCore中的 static PipelineCapabilities s_PipelineCapabilities
            public bool keepAdditionalLightVariants
            {
                get { return m_KeepAdditionalLightVariants; }
            }
    
            public bool keepVertexLightVariants
            {
                get { return m_KeepVertexLightVariants; }
            }
    
            public bool keepDirectionalShadowVariants
            {
                get { return m_KeepDirectionalShadowVariants; }
            }
    
            public bool keepLocalShadowVariants
            {
                get { return m_KeepLocalShadowVariants; }
            }
    
            public bool keepSoftShadowVariants
            {
                get { return m_KeepSoftShadowVariants; }
            }
            /// <summary>
            /// Lightweight-Default.mat
            /// </summary>
            /// <returns></returns>
            public override Material GetDefaultMaterial()
            {
                return GetMaterial(DefaultMaterialType.Standard);
            }
            /// <summary>
            /// Lightweight-DefaultParticle.mat
            /// </summary>
            /// <returns></returns>
            public override Material GetDefaultParticleMaterial()
            {
                return GetMaterial(DefaultMaterialType.Particle);
            }
            /// <summary>
            /// null
            /// </summary>
            /// <returns></returns>
            public override Material GetDefaultLineMaterial()
            {
                return GetMaterial(DefaultMaterialType.UnityBuiltinDefault);
            }
            /// <summary>
            /// Lightweight-DefaultTerrain.mat
            /// </summary>
            /// <returns></returns>
            public override Material GetDefaultTerrainMaterial()
            {
                return GetMaterial(DefaultMaterialType.Terrain);
            }
            /// <summary>
            /// null
            /// </summary>
            /// <returns></returns>
            public override Material GetDefaultUIMaterial()
            {
                return GetMaterial(DefaultMaterialType.UnityBuiltinDefault);
            }
            /// <summary>
            /// null
            /// </summary>
            /// <returns></returns>
            public override Material GetDefaultUIOverdrawMaterial()
            {
                return GetMaterial(DefaultMaterialType.UnityBuiltinDefault);
            }
            /// <summary>
            /// null
            /// </summary>
            /// <returns></returns>
            public override Material GetDefaultUIETC1SupportedMaterial()
            {
                return GetMaterial(DefaultMaterialType.UnityBuiltinDefault);
            }
            /// <summary>
            /// null
            /// </summary>
            /// <returns></returns>
            public override Material GetDefault2DMaterial()
            {
                return GetMaterial(DefaultMaterialType.UnityBuiltinDefault);
            }
            /// <summary>
            /// 获取默认shader
            /// LightweightShaderUtils内有静态数据
            /// 实际字符串为"LightweightPipeline/Standard (Physically Based)"
            /// Lightweight-Default.mat也使用该Shader
            /// </summary>
            /// <returns></returns>
            public override Shader GetDefaultShader()
            {
                if (m_DefaultShader == null)
                    m_DefaultShader = Shader.Find(LightweightShaderUtils.GetShaderPath(ShaderPathID.STANDARD_PBS));
                return m_DefaultShader;
            }
    
            /// <summary>
            /// "Hidden/LightweightPipeline/BlitShader"
            /// </summary>
            public Shader blitShader
            {
                get { return resources != null ? resources.BlitShader : null; }
            }
            /// <summary>
            /// "Hidden/LightweightPipeline/CopyDepthShader"
            /// </summary>
            public Shader copyDepthShader
            {
                get { return resources != null ? resources.CopyDepthShader : null; }
            }
            /// <summary>
            /// "Hidden/LightweightPipeline/ScreenSpaceShadows"
            /// </summary>
            public Shader screenSpaceShadowShader
            {
                get { return resources != null ? resources.ScreenSpaceShadowShader : null; }
            }
            /// <summary>
            /// "Hidden/LightweightPipeline/SamplingShader"
            /// </summary>
            public Shader samplingShader
            {
                get { return resources != null ? resources.SamplingShader : null; }
            }
    
            public void OnBeforeSerialize()
            {
            }
    
            /// <summary>
            /// m_ShadowType已弃用,将旧版资源的m_ShadowType转为m_SoftShadowsSupported字段
            /// </summary>
            public void OnAfterDeserialize()
            {
                if (k_AssetVersion < 3)
                {
                    k_AssetVersion = 3;
                    m_SoftShadowsSupported = (m_ShadowType == ShadowType.SOFT_SHADOWS);
                }
            }
        }
    }

    下面是两个上文引用到的,用来存储资源的脚本:

    using UnityEngine;
    
    public class LightweightPipelineResources : ScriptableObject
    {
        public Shader BlitShader;
        public Shader CopyDepthShader;
        public Shader ScreenSpaceShadowShader;
        public Shader SamplingShader;
    }
    
    using System;
    using UnityEngine;
    
    public class LightweightPipelineEditorResources : ScriptableObject
    {
        public Material DefaultMaterial;
        public Material DefaultParticleMaterial;
        public Material DefaultTerrainMaterial;
    }

    渲染管线实例

     

    渲染管线实例包含运行时逻辑、静态信息(RenderTexture、Buffer等),是渲染指令的设置处。

    using System;
    using System.Collections.Generic;
    #if UNITY_EDITOR
    using UnityEditor.Experimental.Rendering.LightweightPipeline;
    #endif
    using UnityEngine.Rendering;
    using UnityEngine.Rendering.PostProcessing;
    using UnityEngine.XR;
    
    namespace UnityEngine.Experimental.Rendering.LightweightPipeline
    {
        public partial class LightweightPipeline : RenderPipeline
        {
            /// <summary>
            /// 渲染管线资源
            /// </summary>
            public LightweightPipelineAsset pipelineAsset { get; private set; }
            /// <summary>
            /// 摄像机按深度排序
            /// </summary>
            CameraComparer m_CameraComparer = new CameraComparer();
            /// <summary>
            /// 前向渲染类
            /// </summary>
            LightweightForwardRenderer m_Renderer;
            /// <summary>
            /// 剔除结果
            /// </summary>
            CullResults m_CullResults;
            /// <summary>
            /// 非平行光源编号索引,用于阴影生成
            /// </summary>
            List<int> m_LocalLightIndices = new List<int>();
            /// <summary>
            /// 标记逐相机渲染进行过程
            /// </summary>
            bool m_IsCameraRendering;
    
            public LightweightPipeline(LightweightPipelineAsset asset)
            {
                pipelineAsset = asset;
    
                SetSupportedRenderingFeatures();
                SetPipelineCapabilities(asset);
    
                PerFrameBuffer._GlossyEnvironmentColor = Shader.PropertyToID("_GlossyEnvironmentColor");
                PerFrameBuffer._SubtractiveShadowColor = Shader.PropertyToID("_SubtractiveShadowColor");
    
                PerCameraBuffer._ScaledScreenParams = Shader.PropertyToID("_ScaledScreenParams");
                m_Renderer = new LightweightForwardRenderer(asset);
    
                // Let engine know we have MSAA on for cases where we support MSAA backbuffer
                if (QualitySettings.antiAliasing != pipelineAsset.msaaSampleCount)
                    QualitySettings.antiAliasing = pipelineAsset.msaaSampleCount;
    
                Shader.globalRenderPipeline = "LightweightPipeline";
                m_IsCameraRendering = false;
            }
    
            public override void Dispose()
            {
                base.Dispose();
                Shader.globalRenderPipeline = "";
                SupportedRenderingFeatures.active = new SupportedRenderingFeatures();
    
    #if UNITY_EDITOR
                SceneViewDrawMode.ResetDrawMode();
    #endif
    
                m_Renderer.Dispose();
            }
    
            public override void Render(ScriptableRenderContext context, Camera[] cameras)
            {
                if (m_IsCameraRendering)
                {
                    Debug.LogWarning("Nested camera rendering is forbidden. If you are calling camera.Render inside OnWillRenderObject callback, use BeginCameraRender callback instead.");
                    return;
                }
    
                //初始化每帧数据
                base.Render(context, cameras);
                BeginFrameRendering(cameras);
    
                GraphicsSettings.lightsUseLinearIntensity = true;
                SetupPerFrameShaderConstants();
    
                // Sort cameras array by camera depth
                Array.Sort(cameras, m_CameraComparer);
    
                foreach (Camera camera in cameras)
                {
                    BeginCameraRendering(camera);
                    string renderCameraTag = "Render " + camera.name;
                    CommandBuffer cmd = CommandBufferPool.Get(renderCameraTag);
                    //using 结束或中途中断会调用()中类的Dispose()方法
                    using (new ProfilingSample(cmd, renderCameraTag))
                    {
                        //初始化逐相机的数据
                        CameraData cameraData;
                        InitializeCameraData(camera, out cameraData);
                        SetupPerCameraShaderConstants(cameraData);
                        //剔除
                        ScriptableCullingParameters cullingParameters;
                        if (!CullResults.GetCullingParameters(camera, cameraData.isStereoEnabled, out cullingParameters))
                        {
                            CommandBufferPool.Release(cmd);
                            continue;
                        }
    
                        cullingParameters.shadowDistance = Mathf.Min(cameraData.maxShadowDistance, camera.farClipPlane);
                        //这里执行的new ProfilingSample(cmd, renderCameraTag)中设置的cmd.BeginSample(name);命令
                        context.ExecuteCommandBuffer(cmd);
                        cmd.Clear();
    
    #if UNITY_EDITOR
                        try
    #endif
                        {
                            m_IsCameraRendering = true;
    #if UNITY_EDITOR
                            // Emit scene view UI
                            if (cameraData.isSceneViewCamera)
                                ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
    #endif
                            //剔除结果
                            CullResults.Cull(ref cullingParameters, context, ref m_CullResults);
                            List<VisibleLight> visibleLights = m_CullResults.visibleLights;
    
                            RenderingData renderingData;
                            InitializeRenderingData(ref cameraData, visibleLights,
                                m_Renderer.maxSupportedLocalLightsPerPass, m_Renderer.maxSupportedVertexLights,
                                out renderingData);
                            m_Renderer.Setup(ref context, ref m_CullResults, ref renderingData);
                            m_Renderer.Execute(ref context, ref m_CullResults, ref renderingData);
                        }
    #if UNITY_EDITOR
                        catch (Exception)
                        {
                            CommandBufferPool.Release(cmd);
                            throw;
                        }
                        finally
    #endif
                        {
                            m_IsCameraRendering = false;
                        }
                    }
                    //这里执行的ProfilingSample.Dispose()中设置的m_Cmd.EndSample(m_Name);命令
                    context.ExecuteCommandBuffer(cmd);
                    CommandBufferPool.Release(cmd);
                    context.Submit();
                }
            }
    
            public static void RenderPostProcess(CommandBuffer cmd, PostProcessRenderContext context, ref CameraData cameraData, RenderTextureFormat colorFormat, RenderTargetIdentifier source, RenderTargetIdentifier dest, bool opaqueOnly)
            {
                context.Reset();
                context.camera = cameraData.camera;
                context.source = source;
                context.sourceFormat = colorFormat;
                context.destination = dest;
                context.command = cmd;
                context.flip = cameraData.camera.targetTexture == null;
    
                if (opaqueOnly)
                    cameraData.postProcessLayer.RenderOpaqueOnly(context);
                else
                    cameraData.postProcessLayer.Render(context);
            }
            /// <summary>
            /// 设置支持的渲染特征,active是静态的
            /// </summary>
            void SetSupportedRenderingFeatures()
            {
    #if UNITY_EDITOR
                SupportedRenderingFeatures.active = new SupportedRenderingFeatures()
                {
                    //反射探针
                    reflectionProbeSupportFlags = SupportedRenderingFeatures.ReflectionProbeSupportFlags.None,
                    //默认的光照贴图的混合烘焙模式
                    defaultMixedLightingMode = SupportedRenderingFeatures.LightmapMixedBakeMode.Subtractive,
                    //支持的光照贴图的混合烘焙模式
                    supportedMixedLightingModes = SupportedRenderingFeatures.LightmapMixedBakeMode.Subtractive,
                    //支持的光照贴图烘焙模式
                    supportedLightmapBakeTypes = LightmapBakeType.Baked | LightmapBakeType.Mixed,
                    //支持的光照贴图类型
                    supportedLightmapsModes = LightmapsMode.CombinedDirectional | LightmapsMode.NonDirectional,
                    //支持光照探针代理
                    rendererSupportsLightProbeProxyVolumes = false,
                    //支持运动矢量
                    rendererSupportsMotionVectors = false,
                    //接受阴影
                    rendererSupportsReceiveShadows = true,
                    //反射探针
                    rendererSupportsReflectionProbes = true
                };
                SceneViewDrawMode.SetupDrawMode();
    #endif
            }
            /// <summary>
            /// 初始化摄像机数据
            /// </summary>
            /// <param name="camera"></param>
            /// <param name="cameraData"></param>
            void InitializeCameraData(Camera camera, out CameraData cameraData)
            {
                //接近1的pipelineAsset.renderScale(或者XRSettings.eyeTextureResolutionScale),置为1
                const float kRenderScaleThreshold = 0.05f;
                cameraData.camera = camera;
    
                bool msaaEnabled = camera.allowMSAA && pipelineAsset.msaaSampleCount > 1;
                if (msaaEnabled)
                    cameraData.msaaSamples = (camera.targetTexture != null) ? camera.targetTexture.antiAliasing : pipelineAsset.msaaSampleCount;
                else
                    cameraData.msaaSamples = 1;
                //场景相机
                cameraData.isSceneViewCamera = camera.cameraType == CameraType.SceneView;
                //存在RT且不是场景相机
                cameraData.isOffscreenRender = camera.targetTexture != null && !cameraData.isSceneViewCamera;
                cameraData.isStereoEnabled = IsStereoEnabled(camera);
                cameraData.isHdrEnabled = camera.allowHDR && pipelineAsset.supportsHDR;
    
                cameraData.postProcessLayer = camera.GetComponent<PostProcessLayer>();
                cameraData.postProcessEnabled = cameraData.postProcessLayer != null && cameraData.postProcessLayer.isActiveAndEnabled;
    
                // PostProcess for VR is not working atm. Disable it for now.
                cameraData.postProcessEnabled &= !cameraData.isStereoEnabled;
    
                Rect cameraRect = camera.rect;
                cameraData.isDefaultViewport = (!(Math.Abs(cameraRect.x) > 0.0f || Math.Abs(cameraRect.y) > 0.0f ||
                                                  Math.Abs(cameraRect.width) < 1.0f || Math.Abs(cameraRect.height) < 1.0f));
    
                // Discard variations lesser than kRenderScaleThreshold.
                // Scale is only enabled for gameview.
                // In XR mode, grab renderScale from XRSettings instead of SRP asset for now.
                // This is just a temporary change pending full integration of XR with SRP
    
                if (camera.cameraType == CameraType.Game)
                {
    #if !UNITY_SWITCH
                    if (cameraData.isStereoEnabled)
                    {
                        cameraData.renderScale = XRSettings.eyeTextureResolutionScale;
                    } else
    #endif
                    {
                        cameraData.renderScale = pipelineAsset.renderScale;
                    }
                }
                else
                {
                    cameraData.renderScale = 1.0f;
                }
    
                cameraData.renderScale = (Mathf.Abs(1.0f - cameraData.renderScale) < kRenderScaleThreshold) ? 1.0f : cameraData.renderScale;
    
                cameraData.requiresDepthTexture = pipelineAsset.supportsCameraDepthTexture || cameraData.isSceneViewCamera;
                cameraData.requiresSoftParticles = pipelineAsset.supportsSoftParticles;
                cameraData.requiresOpaqueTexture = pipelineAsset.supportsCameraOpaqueTexture;
                cameraData.opaqueTextureDownsampling = pipelineAsset.opaqueDownsampling;
    
                bool anyShadowsEnabled = pipelineAsset.supportsDirectionalShadows || pipelineAsset.supportsLocalShadows;
                cameraData.maxShadowDistance = (anyShadowsEnabled) ? pipelineAsset.shadowDistance : 0.0f;
                //这是一个额外添加的脚本
                LightweightAdditionalCameraData additionalCameraData = camera.gameObject.GetComponent<LightweightAdditionalCameraData>();
                if (additionalCameraData != null)
                {
                    cameraData.maxShadowDistance = (additionalCameraData.renderShadows) ? cameraData.maxShadowDistance : 0.0f;
                    cameraData.requiresDepthTexture &= additionalCameraData.requiresDepthTexture;
                    cameraData.requiresOpaqueTexture &= additionalCameraData.requiresColorTexture;
                }
                else if (!cameraData.isSceneViewCamera && camera.cameraType != CameraType.Reflection && camera.cameraType != CameraType.Preview)
                {
                    cameraData.requiresDepthTexture = false;
                    cameraData.requiresOpaqueTexture = false;
                }
    
                cameraData.requiresDepthTexture |= cameraData.postProcessEnabled;
            }
            /// <summary>
            /// 初始化渲染数据
            /// </summary>
            /// <param name="cameraData"></param>
            /// <param name="visibleLights"></param>
            /// <param name="maxSupportedLocalLightsPerPass"></param>
            /// <param name="maxSupportedVertexLights"></param>
            /// <param name="renderingData"></param>
            void InitializeRenderingData(ref CameraData cameraData, List<VisibleLight> visibleLights, int maxSupportedLocalLightsPerPass, int maxSupportedVertexLights, out RenderingData renderingData)
            {
                //用于生成阴影的非平行光源
                m_LocalLightIndices.Clear();
                //有阴影投射平行光源
                bool hasDirectionalShadowCastingLight = false;
                //有阴影投射非平行光源
                bool hasLocalShadowCastingLight = false;
                //初始化阴影的相关变量
                if (cameraData.maxShadowDistance > 0.0f)
                {
                    for (int i = 0; i < visibleLights.Count; ++i)
                    {
                        Light light = visibleLights[i].light;
                        bool castShadows = light != null && light.shadows != LightShadows.None;
                        if (visibleLights[i].lightType == LightType.Directional)
                        {
                            hasDirectionalShadowCastingLight |= castShadows;
                        }
                        else
                        {
                            hasLocalShadowCastingLight |= castShadows;
                            m_LocalLightIndices.Add(i);
                        }
                    }
                }
    
                renderingData.cameraData = cameraData;
                InitializeLightData(visibleLights, maxSupportedLocalLightsPerPass, maxSupportedVertexLights, out renderingData.lightData);
                InitializeShadowData(hasDirectionalShadowCastingLight, hasLocalShadowCastingLight, out renderingData.shadowData);
                renderingData.supportsDynamicBatching = pipelineAsset.supportsDynamicBatching;
            }
            /// <summary>
            /// 初始化阴影数据
            /// </summary>
            /// <param name="hasDirectionalShadowCastingLight"></param>
            /// <param name="hasLocalShadowCastingLight"></param>
            /// <param name="shadowData"></param>
            void InitializeShadowData(bool hasDirectionalShadowCastingLight, bool hasLocalShadowCastingLight, out ShadowData shadowData)
            {
                // Until we can have keyword stripping forcing single cascade hard shadows on gles2
                bool supportsScreenSpaceShadows = SystemInfo.graphicsDeviceType != GraphicsDeviceType.OpenGLES2;
                //渲染平行光阴影
                shadowData.renderDirectionalShadows = pipelineAsset.supportsDirectionalShadows && hasDirectionalShadowCastingLight;
    
                // we resolve shadows in screenspace when cascades are enabled to save ALU as computing cascade index + shadowCoord on fragment is expensive
                shadowData.requiresScreenSpaceShadowResolve = shadowData.renderDirectionalShadows && supportsScreenSpaceShadows && pipelineAsset.cascadeCount > 1;
                //阴影级联
                shadowData.directionalLightCascadeCount = (shadowData.requiresScreenSpaceShadowResolve) ? pipelineAsset.cascadeCount : 1;
                shadowData.directionalShadowAtlasWidth = pipelineAsset.directionalShadowAtlasResolution;
                shadowData.directionalShadowAtlasHeight = pipelineAsset.directionalShadowAtlasResolution;
                //阴影级联统一为Vector3表达
                switch (shadowData.directionalLightCascadeCount)
                {
                    case 1:
                        shadowData.directionalLightCascades = new Vector3(1.0f, 0.0f, 0.0f);
                        break;
    
                    case 2:
                        shadowData.directionalLightCascades = new Vector3(pipelineAsset.cascade2Split, 1.0f, 0.0f);
                        break;
    
                    default:
                        shadowData.directionalLightCascades = pipelineAsset.cascade4Split;
                        break;
                }
                //渲染非平行光阴影
                shadowData.renderLocalShadows = pipelineAsset.supportsLocalShadows && hasLocalShadowCastingLight;
                shadowData.localShadowAtlasWidth = shadowData.localShadowAtlasHeight = pipelineAsset.localShadowAtlasResolution;
                shadowData.supportsSoftShadows = pipelineAsset.supportsSoftShadows;
                shadowData.bufferBitCount = 16;
    
                shadowData.renderedDirectionalShadowQuality = LightShadows.None;
                shadowData.renderedLocalShadowQuality = LightShadows.None;
            }
            /// <summary>
            /// 初始化光源数据
            /// </summary>
            /// <param name="visibleLights"></param>
            /// <param name="maxSupportedLocalLightsPerPass"></param>
            /// <param name="maxSupportedVertexLights"></param>
            /// <param name="lightData"></param>
            void InitializeLightData(List<VisibleLight> visibleLights, int maxSupportedLocalLightsPerPass, int maxSupportedVertexLights, out LightData lightData)
            {
                //控制最大可见光源数量<=pipelineAsset.maxPixelLights
                int visibleLightsCount = Math.Min(visibleLights.Count, pipelineAsset.maxPixelLights);
                lightData.mainLightIndex = GetMainLight(visibleLights);
    
                // If we have a main light we don't shade it in the per-object light loop. We also remove it from the per-object cull list
                int mainLightPresent = (lightData.mainLightIndex >= 0) ? 1 : 0;
                //计算附加光数量,maxSupportedLocalLightsPerPasss是4或16
                int additionalPixelLightsCount = Math.Min(visibleLightsCount - mainLightPresent, maxSupportedLocalLightsPerPass);
                int vertexLightCount = (pipelineAsset.supportsVertexLight) ? Math.Min(visibleLights.Count, maxSupportedLocalLightsPerPass) - additionalPixelLightsCount : 0;
                //计算逐顶点光数量,maxSupportedVertexLights是4
                vertexLightCount = Math.Min(vertexLightCount, maxSupportedVertexLights);
    
                //附加光数量
                lightData.pixelAdditionalLightsCount = additionalPixelLightsCount;
                //不支持顶点光时vertexLightCount是0,最后是additionalPixelLightsCount;
                //支持时最后是 Math.Min(visibleLights.Count, maxSupportedLocalLightsPerPass)
                lightData.totalAdditionalLightsCount = additionalPixelLightsCount + vertexLightCount;
                lightData.visibleLights = visibleLights;
                lightData.visibleLocalLightIndices = m_LocalLightIndices;
            }
            /// <summary>
            /// 查找第一个Direction光源,返回数组中编号,若无返回-1
            /// </summary>
            /// <param name="visibleLights"></param>
            /// <returns></returns>
            // Main Light is always a directional light
            int GetMainLight(List<VisibleLight> visibleLights)
            {
                int totalVisibleLights = visibleLights.Count;
    
                if (totalVisibleLights == 0 || pipelineAsset.maxPixelLights == 0)
                    return -1;
    
                for (int i = 0; i < totalVisibleLights; ++i)
                {
                    VisibleLight currLight = visibleLights[i];
    
                    // Particle system lights have the light property as null. We sort lights so all particles lights
                    // come last. Therefore, if first light is particle light then all lights are particle lights.
                    // In this case we either have no main light or already found it.
                    if (currLight.light == null)
                        break;
    
                    // In case no shadow light is present we will return the brightest directional light
                    if (currLight.lightType == LightType.Directional)
                        return i;
                }
    
                return -1;
            }
            /// <summary>
            /// 设置每帧的Shader常量_GlossyEnvironmentColor、_SubtractiveShadowColor
            /// </summary>
            void SetupPerFrameShaderConstants()
            {
                // When glossy reflections are OFF in the shader we set a constant color to use as indirect specular
                SphericalHarmonicsL2 ambientSH = RenderSettings.ambientProbe;
                Color linearGlossyEnvColor = new Color(ambientSH[0, 0], ambientSH[1, 0], ambientSH[2, 0]) * RenderSettings.reflectionIntensity;
                Color glossyEnvColor = CoreUtils.ConvertLinearToActiveColorSpace(linearGlossyEnvColor);
                Shader.SetGlobalVector(PerFrameBuffer._GlossyEnvironmentColor, glossyEnvColor);
    
                // Used when subtractive mode is selected
                Shader.SetGlobalVector(PerFrameBuffer._SubtractiveShadowColor, CoreUtils.ConvertSRGBToActiveColorSpace(RenderSettings.subtractiveShadowColor));
            }
            /// <summary>
            /// 设置每个相机的Shader常量_ScaledScreenParams
            /// </summary>
            /// <param name="cameraData"></param>
            void SetupPerCameraShaderConstants(CameraData cameraData)
            {
                float cameraWidth = (float)cameraData.camera.pixelWidth * cameraData.renderScale;
                float cameraHeight = (float)cameraData.camera.pixelWidth * cameraData.renderScale;
                Shader.SetGlobalVector(PerCameraBuffer._ScaledScreenParams, new Vector4(cameraWidth, cameraHeight, 1.0f + 1.0f / cameraWidth, 1.0f + 1.0f / cameraHeight));
            }
    
            bool IsStereoEnabled(Camera camera)
            {
    #if !UNITY_SWITCH
                bool isSceneViewCamera = camera.cameraType == CameraType.SceneView;
                return XRSettings.isDeviceActive && !isSceneViewCamera && (camera.stereoTargetEye == StereoTargetEyeMask.Both);
    #else
                return false;
    #endif
            }
        }
    }
    

     

     

     

    参考资料

     

     

     

    [1] Unite 2017 | Unity可编程渲染管线剖析.

    [2] RenderPipeLine.

    [3] 详解可编程脚本渲染管线SRP.

    [4] 轻量级渲染管线:优化实时性.

     

     

    展开全文
  • OpenGL渲染管线

    2020-12-24 09:47:26
    渲染管线

    渲染管线

    展开全文
  • 1.固定渲染管线与可编程渲染管线的区别: 1)、固定渲染管线 ——这是标准的几何&光照(T&L)管线,功能是固定的,它控制着世界、视、投影变换及固定光照控制和纹理混合。T&L管线可以被渲染状态控制,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,404
精华内容 1,361
关键字:

渲染管线