Done is better than perfect

0%

Unity渲染管线介绍

什么渲染管线

渲染管线跟工厂的流水线一样,工厂的流水线为了生成最终的一个产品,每个流水线的工人只干一件特定的事情,最后将一个产品完成。计算机的图形渲染管线和工厂的流水线一样,只是图形渲染管线最终生成的是一张二维图片。 我们将一个渲染流程分成3个阶段:应用阶段几何阶段光栅化阶段。注意这仅仅是概念性阶段,每个阶段本身通常也是一个流水线系统,即包含了子流水线阶段。如下图: 渲染流水线三个概念图

  • 应用阶段

从名字我们可以看出,这个阶段是由我们的应用主导的,因此通常由CPU负责实现。换句话说,我们这些开发者具有这个阶段的绝对控制权。 在这阶段中,开发者有3个主要任务: 1. 我们要准备好场景数据,例如摄像机的位置、视椎体、场景中包含了哪些模型、使用了哪些光源等等; 2. 为了渲染性能,我们往往需要做一个粗粒度剔除(culling)工作,以把哪些不可见的物体踢出去,这样就不需要再移交给几何阶段进行处理; 3. 最后我们要设置好每个模型的渲染状态,这些渲染状态包括但不限于它们使用的材质(漫反射颜色、高光反色颜色)、使用的纹理、使用的Shader等。 这一阶段最重要的输出是渲染所需的几何信息,即渲染图元(rendering primitives)。通俗来讲,渲染图元可以是点、线、三角面等。这些渲染图元将会被传递给下一个阶段——几何阶段。

  • 几何阶段

几何阶段用于处理所有和我们要绘制的几何相关的事情。例如,决定需要绘制的图元是什么,怎么绘制它们,在哪里绘制它们。这一阶段通常在GPU上进行。 几何阶段负责和每个渲染图元打交道,进行逐顶点、逐多边形的操作。这个阶段可以进一步分成更小的流水阶段,这在下一章中会讲到。几何阶段的一个重要任务就是把顶点坐标变换到屏幕空间中,再交给光栅器进行处理。通过对输入的渲染图元进行多步处理后,这一阶段将会输出屏幕空间的二维顶点坐标、每个顶点对应的深度值,着色相等相关信息**,并传递给下一个阶段。

  • 光栅化阶段

这一阶段将会使用上个阶段传递的数据来生成屏幕上的像素,并渲染出最终的图像。这个阶段也是在GPU上运行。光栅化的任务主要是决定每个渲染图元中的哪些像素应该被绘制在屏幕上。它需要对上一个阶段得到的逐顶点数据(例如纹理坐标,顶点颜色等)进行插值,然后在进行逐像素处理。和上一个阶段相似,光栅化阶段也可以分成更小的流水线阶段。

在Unity中,我们能够选择不同的渲染管线。Unity提供了3种(内建,SRP(URP和HDRP))预定义的管线对于不同的兼容性和性能特性,我们也可以创建自己的渲染管线。每种类型的管线有这不同的兼容性适配不同类型的游戏,应用和平台,并且每种管线之间切换是非常困难的,因为不同的管线使用的shader不同。所以项目初期选择管线是非常重要的。Unity为我们提供了以下的渲染管线:

  • 内建管线是Unity的默认渲染管线。它被定义为通用的渲染管线,限制了它的自定义。
  • URP是一个快速、可自定义的可编程渲染管线,让我们可以在各种平台上创建优化的图形。
  • HDRP是一个让您在高端平台上创建尖端的高保真图形的可编程管线。
  • 自定义可以通过Unity的可编程渲染管线API自己创建渲染管线

各种渲染管线之间支持的特性对比

内建管线

内建渲染管线是Unity的传统的管线,它不是基于可编程渲染管线的。我们能够配置它不同的渲染路径,以及通过命令buffer(command buffer)和回调(callback)来对其进行简单的扩展。这个章节将包含以下的内容:

  • 渲染路径
  • 使用命令buffer扩展内建渲染管线
  • Shader实例

渲染路径

内建渲染管线支持不同的渲染路径,渲染路径被定义为对光照和着色的一些列操作(策略)。不同的渲染路径有不同的能力和性能表现。使用什么样的渲染路径取决于你项目的类型和目标硬件。我们能够在项目的Graphics窗口和Camera面板上去设置使用不同的渲染路径。如果在你运行的设备上不支持选择的渲染路径Unity则会给切换到兼容性更好的渲染路径,比如:在运行的设备上不支持延迟渲染,Unity则会切换到前向渲染路径。Unity总共为我们提供了4种渲染路径:前向渲染路径延迟渲染路径, 旧版延迟渲染路劲旧版顶点光照渲染路径,现在内建渲染管线的渲染路径主要是:前向渲染路径延迟渲染路径。不同渲染路径的对比如下图:

不同渲染路径对比

前向渲染路径

前向渲染路径是Unity内建渲染管线的默认渲染渲染路径,它是一个通用的渲染路径。在前向渲染路径中实时光照是非常耗的。为了降低这种消耗,你可以选择在任意时刻有多少灯光是需要逐像素光照,其他的灯光则可以使用更低消耗的逐顶点或逐对象光照。如果你的项目没有大量的实时灯光,或者灯光效果不那么重要,前向渲染路径是一个不错的选择。 前向渲染渲染每个对象时使用一个或多个pass进行着色,使用pass的数量取决于对象被多少灯光影响。前向渲染也会对灯光本身进行不同的处理,具体取决于它们的设置和光照强度。 在前向渲染中,场景中一些(最多4个)最亮的灯光将进行逐像素渲染,然后最大4个点光源进行逐顶点光照,其他的灯光则使用球谐函数(Spherical Harmonics)进行计算。一个灯光是否进行逐像素光照或其他的光照方式取决于一下几点: - 灯光的渲染模式设置为“Not Important”时都是以逐顶点或SH进行光照的。 - 最亮的方向光总是逐像素的。 - 灯光的渲染模式设置为“Important”时都是逐像素光照的。 - 如果上面的灯光数量还没达到QualitySetting中设置的逐像素灯光的上限时,则其他灯光以亮度优先的方式优先作为逐像素灯光使用。

每个渲染对象在渲染时则使用以下规则: - 基础pass只用于一个逐像素方向光和所有的逐顶点/SH光。 - 其他逐像素灯光则在附加pass中着色,每个灯光执行一次附加pass。

例如,下图的一个对象被8个灯光(A-H)所影响 灯光

注意灯组重叠; 例如,最后一个逐像素光照混合到逐顶点光照模式中,因此当物体和光照四处移动时,“光爆”会减少。

基础pass

基础pass渲染对象时,使用逐像素方向光和所有的逐顶点/SH光,基础pass也添加任意的光照图,环境和自发光。方向光在基础pass上能够产生阴影。注意光照图对象不能从SH灯光中获取照明信息。当pass的flag设置为“OnlyDirectional”时, forward base pass仅作用于主方向光,环境/光照探针和光照图(SH和逐顶点灯光数据则不会被包含到pass的数据中)。

附加pass

附加pass对于每个影响此对象的额外(除基础pass中使用的逐像素灯光外)逐像素灯光执行渲染。这些灯光在附加pass默认是不会有阴影(因此,前向渲染支持一个方向光带阴影)。除非multi_compile_fwdadd_fullshadows被使用。

前向渲染性能注意事项

每个被动态逐像素灯光影响的像素在渲染此像素时将增加大量的工作,并且这也会导致一个对象的渲染pass被执行多次。在低端设备(像手机或低端的PC)上应该避免出现超过一个的逐像素灯管照明到任意单个对象上,并且对于静态对象应该使用光照图而不每帧去计算它们的光照信息。逐顶点动态灯光在顶点变换的时候将增加大量的工作,所以尽量避免多个灯光同时照明一个对象。 避免将两个距离很远的mesh进行合并,因为当有多个逐像素灯光时,每个mesh不得不被渲染多次,两个间隔比较远的mesh合并就不能被单独计算光照只有mesh有一个三角形被灯光覆盖整个mesh都将参与光照计算。 在渲染期间,Unity将查找一个mesh周围的多有灯光并且计算哪些灯光对其影响最大。在Quality窗口去设置逐像素灯光数量和逐定点灯光数量的上限。每个灯光基于其到mesh的远近以及照明强度确定其对mehs的重要性,纯粹根据游戏的环境有些灯光是比其他灯光更重要的。基于这个原因,每个灯光上有个Render Mode的设置,我们能够将其设置为Important或Not Important,灯光标记为Not Important有更低的渲染开销。 例如:一个赛车游戏,在昏暗的环境下开着前大灯,此时此灯光就应当将其设置为Improtant,像尾灯则可以设置为Not Important。 优化逐素光照可以节省 CPU 和 GPU 的工作:CPU 需要执行的DrawCall调用更少,GPU 需要处理的顶点和要栅格化的所有额外对象渲染的像素更少。

球谐函数(Spherical Harmonics) 灯光的渲染速度非常快。 它们在CPU上的开销很小,实际上对 GPU 来说是无额外开销的(也就是说,base pass 总是计算 SH 光照;但由于SH光照的工作方式,无论多少SH光照,成本都是完全相同的)。 SH灯光的缺点: - 由于SH是在逐顶点计算的,不是逐像素,这也意味着它们不支持灯光Cookies或法线贴图。 - SH照明的频率非常低。 使用SH灯无法实现锐利的照明过渡。它们也只影响漫反射照明。 - SH照明不是局部的; 靠近某个表面的点或点 SH 灯会“看起来不对”。

总而言之,SH灯通常对于小的动态物体来说已经足够了。

延迟渲染路径

延迟着色是内建渲染管线中光照和阴影保真度最高的渲染路径。延迟渲染需要GPU的支持,并且还有一些限制。它不支持半透明对象,正交投影和硬件抗锯齿(可以同通过后处理解决-边缘查找做模糊)。它对剔除蒙版的支持有限(最多只能使用四个剔除蒙版。也就是你的剔除图层蒙版必须至少包含所有层减去四个任意层,因此必须设置32层中的28层。否则你会得到不正确的图形),并将Renderer.receiveShadows标志视为始终为真。 如果你的项目有大量的实时灯光并且需要更好的光照效果,并且你的目标硬件平台支持延迟渲染,那么这渲染路径将是个不错的选择。

在使用延迟着色的时候,对象是没有灯光数量限制的。所有的灯光都将是逐像素的,这意味着它们都与法线贴图正确交互,另外所有的灯光都能有cookies和阴影。 延迟着色的优点是光照的处理开销与灯光照亮的像素数成正比。光照的开销是由场景中的光量大小决定的不管它照亮了多少游戏对象。因此,可以通过让灯光照明范围更小来改善性能。延迟着色还具有高度一致和可预测的行为。每个灯光的效果都是逐像素计算的。

延迟着色的需求

延迟着色它需要显卡支持多目标渲染(MRT),着色模型(Shader Model)3.0并且支持深度渲染纹理。大多数在2006年以后生产的PC显卡都支持延迟着色。在移动端,延迟着色至少需要是OpenGL ES3.0。

前向渲染性能注意事项

在延迟渲染中实时灯光的性能消耗跟这个灯光所照明的像素是成正比的,不依赖于场景的复杂度。所以小的Point光源或Spot光源的性能消耗是非常低的,如果照明对象是被其他对象部分或完全遮挡的那么它的性能开销将更小。

当然,带阴影的灯光是比不带阴影的灯光更耗的。在延迟着色中,对于每个投射阴影的灯光,阴影投射对象还是需要被渲染一次或多次。

实现细节

在延迟渲染完成后,再使用前向渲染路径渲染那些不支持延迟着色的Shader对象。 在G-buffer中渲染目标(RT0-RT4)的默认布局如下,数据类型存储在每个渲染目标的不同通道中:

  • RT0, ARGB32格式:漫反射颜色(RGB), 遮挡(A)。
  • RT1, ARGB32格式:高光颜色(RGB), 粗糙度(A)。
  • RT2, ARGB2101010格式:世界空间下的法线,未被使用(A)。
  • RT3, ARGB2101010(非HDR)或ARGBHalf(HDR每个通道16bits)格式:自发光+光照+lightmap+反射探针+深度缓冲和模板缓冲

所以默认的G-buffer布局每个像素是160bits/pixel(非HDR)或192bits/pixel(HDR)。 如果对于混合光照使用了Shadowmask或Distance Shadowmask模式,那么第5个渲染目标(RT)将被使用:

  • RT5, ARGB32 格式:灯光遮挡值(RGBA)

因此G-buffer布局大小将增至每个192bits/pixel(非HDR)或224bits/pixel(HDR)。

如果硬件不支持五个并发渲染目标,则使用阴影遮罩的对象将回退到前向渲染路径。 当摄像机不使用 HDR 时,发射+光照缓冲区 (RT3) 采用对数编码,因此提供的动态范围高于 ARGB32 纹理通常可能提供的范围。

请注意,当摄像机使用 HDR 渲染时,不会为发射 + 光照缓冲区 (RT3) 创建单独的渲染目标;而是将摄像机渲染到的渲染目标(即传递给图像效果的渲染目标)用作 RT3。

G-Buffer pass

G-Buffer pass 将每个游戏对象渲染一次。漫射和镜面反射颜色、表面平滑度、世界空间法线和自发光+环境光+反射光+光照贴图都将渲染到G-Buffer纹理中。G-Buffer纹理设置为全局着色器属性供着色器以后访问(_CameraGBufferTexture0 .._CameraGBufferTexture3 指定)。

光照pass

光照pass根据G-Buffer和深度来计算光照。光照是在屏幕空间内计算的,因此处理所需的时间与场景复杂性无关。光照将添加到发射缓冲区。

不穿过相机近平面的点光源和聚光灯被渲染为3D形状,并启用了Z缓冲区针对场景的测试。 这使得部分或完全遮挡的点光源和聚光灯的渲染成本非常低。穿过近平面的方向光和点或聚光灯被渲染为全屏四边形。

如果光源启用了阴影,那么也会在此通道中渲染并应用阴影。请注意,阴影并非是“无成本”的;需要渲染阴影投射物,并且必须应用更复杂的光照着色器。

唯一可用的光照模型是标准 (Standard) 光照模型。如果需要不同的模型,可修改光照pass着色器,方法是将内置着色器中的 Internal-DeferredShading.shader 文件的修改版本放入“Assets”文件夹中名为“Resources”的文件夹内。然后打开 Graphics 设置(菜单:Edit > Project Settings,然后单击 Graphics 类别)。将“Deferred”下拉选单改为“Custom Shader”。然后,更改当前使用的着色器对应的着色器 (Shader) 选项。

扩展内建渲染管线

Unity为我们提供了命令缓冲区(CommandBuffers)来扩展内建渲染管线。一个命令缓冲区(CommandBuffers)是一系列渲染命令列表(例如:渲染目标设置,渲染某个mesh)。你能够安排这些命令在内建渲染管线的某些点上执行,这也允许你自定义并扩展Unity的渲染功能。你能使用Graphics.ExecuteCommandBuffer立即执行CommandBuffers,也可以在渲染管线的某个点执行。通过使用Camera.AddCommandBuffer并传入CameraEvent枚举, 以及Light.AddCommandBuffer并且传入LightEvent枚举来安排CommandBuffers的执行时机

对于全部可以执行的命令,参见CommandBuffer API。有些命令仅在特定平台才支持,比如射线追踪就只在DX12上支持。

extending-unity-5-rendering-pipeline-command-buffers

Shader基础模板

前向渲染路径Shader模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Shader "Unlit/NewUnlitShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog

#include "UnityCG.cginc"

struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};

struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};

sampler2D _MainTex;
float4 _MainTex_ST;

v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
// sample the texture
fixed4 col = tex2D(_MainTex, i.uv);
// apply fog
UNITY_APPLY_FOG(i.fogCoord, col);
return col;
}
ENDCG
}
}

Fallback "ExampleOtherShader"
}

延迟渲染路径Shader模板

GBuffer Shader (每个Mesh执行一次)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
Shader "Custom/deferred"
{
//unity参数入口
Properties
{
_MainTex("贴图",2D)="white"{}
_Diffuse("漫反射",Color) = (1,1,1,1)
_Specular("高光色",Color) = (1,1,1,1)
_Gloss("平滑度",Range(1,100)) = 50
}
SubShader
{
//非透明队列
Tags { "RenderType" = "Opaque" }
LOD 100
//延迟渲染
Pass
{
//设置 光照模式为延迟渲染
Tags{"LightMode" = "Deferred"}
CGPROGRAM
// 声明顶点着色器、片元着色器和输出目标
#pragma target 3.0
#pragma vertex vert
#pragma fragment frag
//排除不支持MRT的硬件
//#pragma exclude_renderers norm
// unity 函数库
#include"UnityCG.cginc"
//定义UNITY_HDR_ON关键字
//在c# 中 Shader.EnableKeyword("UNITY_HDR_ON"); Shader.DisableKeyword("UNITY_HDR_ON");
// 设定hdr是否开启
#pragma multi_compile __ UNITY_HDR_ON
// 贴图
sampler2D _MainTex;
// 题图uv处理
float4 _MainTex_ST;
// 漫反射光
float4 _Diffuse;
// 高光
float4 _Specular;
// 平滑度
float _Gloss;
// 顶点渲染器所传入的参数结构,分别是顶点位置、法线信息、uv坐标
struct a2v
{
float4 pos:POSITION;
float3 normal:NORMAL;
float2 uv:TEXCOORD0;
};
// 片元渲染器所需的传入参数结构,分别是像素位置、uv坐标、像素世界位置、像素世界法线
struct v2f
{
float4 pos:SV_POSITION;
float2 uv : TEXCOORD0;
float3 worldPos:TEXCOORD1;
float3 worldNormal:TEXCOORD2;
};
// 延迟渲染所需的输出结构。前向渲染只需要输出1个Target,而延迟渲染的片元需要输出4个Target
struct DeferredOutput
{
// RGB存储漫反射颜色,A通道存储遮罩
float4 gBuffer0:SV_TARGET0;
// RGB存储高光(镜面)反射颜色,A通道存储高光反射的指数部分,也就是平滑度
float4 gBuffer1:SV_TARGET1;
// RGB通道存储世界空间法线,A通道没用
float4 gBuffer2:SV_TARGET2;
// Emission + lighting + lightmaps + reflection probes (高动态光照渲染/低动态光照渲染)用于存储自发光+lightmap+反射探针深度缓冲和模板缓冲
float4 gBuffer3:SV_TARGET3;
};
// 顶点渲染器
v2f vert(a2v v)
{
v2f o;
// 获取裁剪空间下的顶点坐标
o.pos = UnityObjectToClipPos(v.pos);
// 应用uv设置,获取正确的uv
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
// 获取顶点的世界坐标
o.worldPos = mul(unity_ObjectToWorld, v.pos).xyz;
// 获取世界坐标下的法线
o.worldNormal = UnityObjectToWorldNormal(v.normal);
return o;
}
// 片元着色器
DeferredOutput frag(v2f i)
{
DeferredOutput o;
// 像素颜色 = 贴图颜色 * 漫反射颜色
fixed3 color = tex2D(_MainTex, i.uv).rgb * _Diffuse.rgb;
// 默认使用高光反射输出!!
o.gBuffer0.rgb = color; // RGB存储漫反射颜色,A通道存储遮罩
o.gBuffer0.a = 1; // 漫反射的透明度
o.gBuffer1.rgb = _Specular.rgb; // RGB存储高光(镜面)反射颜色,
o.gBuffer1.a = _Gloss / 100; // 高光(镜面)反射颜色 的
o.gBuffer2 = float4(i.worldNormal * 0.5 + 0.5, 1); // RGB通道存储世界空间法线,A通道没用
// 如果没开启HDR,要给颜色编码转换一下数据exp2,后面在lightpass2里则是进行解码log2
#if !defined(UNITY_HDR_ON)
color.rgb = exp2(-color.rgb);
#endif
// Emission + lighting + lightmaps + reflection probes (高动态光照渲染/低动态光照渲染)用于存储自发光+lightmap+反射探针深度缓冲和模板缓冲
o.gBuffer3 = fixed4(color, 1);
return o;
}
ENDCG
}
}
}

Light Shader (每个光源执行一次)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
Shader "Unlit/deferredLight"
{
SubShader
{
// 第一个pass用于合成灯光
Pass
{
// 由于像素信息已经经过深度测试,所以可以关闭深度写入
ZWrite Off
// 如果开启了LDR混合方案就是DstColor zero(当前像素 + 0),
// 如果开启了HDR混合方案就是One One(当前像素 + 缓冲像素),由于延迟渲染就是等于把灯光渲染到已存在的gbuffer上,所以使用one one
Blend [_SrcBlend] [_DstBlend]
CGPROGRAM
// 定义运行平台
#pragma target 3.0
// 我们需要所有的关于灯光的变体,使用multi_compile_lightpass
#pragma multi_compile_lightpass
// 不使用nomrt着色器
#pragma exclude_renderers nomrt
//定义UNITY_HDR_ON关键字
//在c# 中 Shader.EnableKeyword("UNITY_HDR_ON"); Shader.DisableKeyword("UNITY_HDR_ON");
// 设定hdr是否开启
#pragma multi_compile __ UNITY_HDR_ON
// 定义顶点渲染器和片元渲染器的输入参数
#pragma vertex vert
#pragma fragment frag
// 引入shader 相关宏宏
#include "UnityCG.cginc"
#include "UnityDeferredLibrary.cginc"
#include "UnityGBuffer.cginc"
//定义从 Deferred模型对象输入的屏幕像素数据
sampler2D _CameraGBufferTexture0;// 漫反射颜色
sampler2D _CameraGBufferTexture1;// 高光、平滑度
sampler2D _CameraGBufferTexture2;// 世界法线
//顶点渲染器输出参数结构,包含顶点坐标、法线
struct a2v
{
float4 pos:POSITION;
float3 normal:NORMAL;
};
//片元渲染器输出结构,包含像素坐标、uv坐标
struct Deffred_v2f
{
float4 pos: SV_POSITION;
float4 uv:TEXCOORD;
float3 ray : TEXCOORD1;
};
// 顶点渲染器
Deffred_v2f vert(a2v v)
{
Deffred_v2f o;
//将顶点坐标从模型坐标转化为裁剪坐标
o.pos = UnityObjectToClipPos(v.pos);
// 获取屏幕上的顶点坐标
o.uv = ComputeScreenPos(o.pos);
// 模型空间转 视角空间做i奥
o.ray = UnityObjectToViewPos(v.pos) * float3(-1,-1,1);
// 插值
o.ray = lerp(o.ray, v.normal, _LightAsQuad);
return o;
}

//片段渲染器
//设置片段渲染器输出结果的数据格式。如果开始hdr就使用half4,否则使用fixed4
#ifdef UNITY_HDR_ON
half4
#else
fixed4
#endif
frag(Deffred_v2f i) : SV_Target
{
// 定义光照属性
float3 worldPos;//像素的世界位置
float2 uv;//uv
half3 lightDir;//灯光方向
float atten;// 衰减
float fadeDist;// 衰减距离
//计算灯光数据,并填充光照属性数据,返回灯光的坐标,uv、方向衰减等等
UnityDeferredCalculateLightParams(i, worldPos, uv, lightDir, atten, fadeDist);

// 灯光颜色
half3 lightColor = _LightColor.rgb * atten;
//gbuffer与灯光合成后的像素数据
half4 diffuseColor = tex2D(_CameraGBufferTexture0, uv);// 漫反射颜色
half4 specularColor = tex2D(_CameraGBufferTexture1, uv);// 高光颜色
float gloss = specularColor.a * 100;//平滑度
half4 gbuffer2 = tex2D(_CameraGBufferTexture2, uv);// 法线
float3 worldNormal = normalize(gbuffer2.xyz * 2 - 1);// 世界法线

// 视角方向 = 世界空间的摄像机位置 - 像素的位置
fixed3 viewDir = normalize(_WorldSpaceCameraPos - worldPos);
// 计算高光的方向 = 灯光方向与视角方向中间的点
fixed3 halfDir = normalize(lightDir + viewDir);

// 漫反射 = 灯光颜色 * 漫反射颜色 * max(dot(像素世界法线, 灯光方向))
half3 diffuse = lightColor * diffuseColor.rgb * max(0,dot(worldNormal, lightDir));
// 高光 = 灯光颜色 * 高光色 * pow(max(0,dot(像素世界法线,计算高光的方向)), 平滑度);
half3 specular = lightColor * specularColor.rgb * pow(max(0,dot(worldNormal, halfDir)),gloss);
// 像素颜色 = 漫反射+高光,透明度为1
half4 color = float4(diffuse + specular,1);

//如果开启了hdr则使用exp2处理颜色
#ifdef UNITY_HDR_ON
return color;
#else
return exp2(-color);
#endif
}

ENDCG
}

//转码pass,主要用于LDR转码
Pass
{
//使用深度测试,关闭剔除
ZTest Always
Cull Off
ZWrite Off
//模板测试
Stencil
{
ref[_StencilNonBackground]
readMask[_StencilNonBackground]

compback equal
compfront equal
}
CGPROGRAM
//输出平台
#pragma target 3.0
#pragma vertex vert
#pragma fragment frag
// 剔除渲染器
#pragma exclude_renderers nomrt
//
#include "UnityCG.cginc"
//缓冲区颜色
sampler2D _LightBuffer;
struct a2v
{
float4 pos:POSITION;
float2 uv:TEXCOORD0;
};
struct v2f
{
float4 pos:SV_POSITION;
float2 uv:TEXCOORD0;

};
//顶点渲染器
v2f vert(a2v v)
{
v2f o;
// 坐标转为裁剪空间
o.pos = UnityObjectToClipPos(v.pos);
o.uv = v.uv;
// 通常用于判断D3D平台,在开启抗锯齿的时候图片采样会用到
#ifdef UNITY_SINGLE_PASS_STEREO
o.uv = TransformStereoScreenSpaceTex(o.uv,1.0);
#endif
return o;
}
//片段渲染器
fixed4 frag(v2f i): SV_Target
{
return -log2(tex2D(_LightBuffer,i.uv));
}

ENDCG
}
}
}

SRP

可编程渲染管线(SRP)是一种可以通过C#脚本控制渲染的技术。URP和HDRP是基于SRP技术开发的。SRP是一个轻量级的API层,我们可以通过这些C# API来安排和配置渲染命令。Unity传递这些命令到底层的图形架构,然后发送指令到图形API。我们可以基于SRP创建自定义的渲染管线。

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

每个基于SRP的渲染管线都有两个关键的元素:

  • 渲染管线实例, 这是一个实例类定义渲染管线的功能。它继承至RenderPipeline, 并且覆写它的Render()方法。
  • 渲染管线资源, 这是一个存储渲染管线实例使用的数据的资源,这个脚本继承至RenderPipelineAsset类并且覆写其中的CreatePipeline方法。

ScriptableRenderContext

ScriptableRenderContext是自定义C#代码和Unity底层图形代码之间沟通的接口类。

入口点和回调

当工作在SRP时,使用以下这些确保Unity在特定的时候嗲用你的C#代码

  • RenderPipeline.Render 是SRP的主入口函数,Unity将自动的调用此函数。如果你写一个自定渲染管线这就你代码开始的地方。
  • RenderPipelineManager 渲染管线的管理类,此类提供了一些事件,可以订阅这些事件然后执行你的特定代码。
    • beginFrameRendering 将产生GC 使用beginContextRendering代替
    • endFrameRendering 将产生GC 使用endContextRendering代替
    • beginContextRendering
    • endContextRendering
    • beginCameraRendering
    • endCameraRendering

安排和执行渲染命令

在SRP中,我们可以通过CommandBuffer或直接调用ScriptableRenderContext中的API来安排和执行渲染命令。

使用ScriptableRenderContext APIs

在SRP中,ScriptableRenderContext做为与底层图形沟通的接口。SRP的渲染工作是延迟执行的;你可以使用ScriptableRenderContext去构建一个渲染命令列表,然后告诉Unity去执行它们,最后Unity图形架构发送命令到图形API。 安排渲染命令,有如下方式:

  • 传递一个CommandBuffer到ScriptableRenderContext, 通过使用ScriptableRenderContext.ExecuteCommandBuffer.
  • 直接调用ScriptableRenderContext中的API,比如:ScriptableRenderContext.Cull或ScriptableRenderContext.DrawRenderers.

当安排好命令后,通过调用ScriptableRenderContext.Submit方法告诉Unity去执行你的命令。注意:不论你是使用的CommandBuffer还是直接调用的API, 在ScriptableRenderContext中都同样的,并且也只有调用了Submit才会去执行。 下面这个实例说明了如何通过CommandBuffer安排和执行命令去清除渲染目标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using UnityEngine;
using UnityEngine.Rendering;

public class ExampleRenderPipeline : RenderPipeline
{
public ExampleRenderPipeline()
{
}

protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
// 创建并安排命令
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, true, Color.red);
context.ExecuteCommandBuffer(cmd);
cmd.Release();

// 告诉Unity执行命令
context.Submit();
}
}

自定义渲染管线

Unity提供了两个内置的SRP渲染管线:HDRP和URP。虽然这个两个渲染管线给我们提供了自定义的扩展选项,但是如果你想获取的更多的控制,则可以创建一个自定义的渲染管线。

基于SRP创建自定渲染管线

此节将介绍如何基于SRP创建自定义的渲染管线。

创建一个项目并安装依赖包

这些说明向您展示如何使用SRP Core包创建自定义渲染管线。 SRP Core是由Unity提供的一个包,其中包含可重用的代码,可帮助您创建自己的渲染管线,包括用于使用特定于平台的图形API的样板代码、用于常见渲染操作的实用程序函数和着色器库这些着色器也被URP和HDRP使用的。有关SRP Core的更多信息,请参阅SRP Core软件包文档

  1. 创建一个新的Unity项目
  2. SRP源码仓库中下载对应Unity版本的SRP包。
  3. 将下载的包放到工程中
    • com.unity.render-pipelines.core : SRP的核心包
    • com.unity.render-pipelines.shadergraph :一个可视化的shader编辑器
    • com.unity.render-pipelines.visualeffectgraph :视觉效果图像编辑器

如果你觉得完全去自定一个渲染管线过于复杂,那么你可以基于URP或HDRP进行修改,扩展和自定义你需要修改的部分,需要安装的包如下:

URP:

  • com.unity.render-pipelines.core
  • com.unity.render-pipelines.shadergraph
  • com.unity.render-pipelines.universal

HDRP:

  • com.unity.render-pipelines.core
  • com.unity.render-pipelines.shadergraph
  • com.unity.render-pipelines.high-defintion

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

创建自定义的渲染管线,在项目中必须包含:

  • 一个继承至RenderPipelineAsset类并覆写了CreatePipeline方法的脚本。这个脚本定义了渲染管线的资源。
  • 一个继承至RenderPipeline,并且覆写了Render方法的脚步,这个脚本定义了渲染管线的实例。

因为这些元素都是紧密相关的,必须同时创建。

创建基础的渲染管线资源和实例

实例代码如下:

渲染资源脚本:

1
2
3
4
5
6
7
8
9
10
11
using UnityEngine;
using UnityEngine.Rendering;

[CreateAssetMenu(menuName = "Rendering/ExampleRenderPipelineAsset")]
public class ExampleRenderPipelineAsset : RenderPipelineAsset
{
// Unity在第一帧渲染前调用这个方法,如果渲染管线的设置改变,Unity将销毁当前的渲染管线实例,并调用此方法重新创建一个
protected override RenderPipeline CreatePipeline() {
return new ExampleRenderPipelineInstance();
}
}

渲染实例脚本:

1
2
3
4
5
6
7
8
9
10
using UnityEngine;
using UnityEngine.Rendering;

public class ExampleRenderPipelineInstance : RenderPipeline
{
public ExampleRenderPipelineInstance() { }
protected override void Render (ScriptableRenderContext context, Camera[] cameras) {
// 渲染主入口
}
}

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

默认情况下,渲染管线资源存储关于用于渲染的渲染管线实例以及默认材质和着色器的信息。 在您的RenderPipelineAsset脚本中,您可以扩展您的渲染管线资源,以便它存储额外的数据,并且您可以在您的项目中拥有多个具有不同配置的不同渲染管线资源。例如,您可以使用渲染管线资源来保存每个不同硬件层的配置数据。 高清渲染管线 (HDRP) 和通用渲染管线 (URP) 包括这方面的示例。

以下示例展示了如何创建一个RenderPipelineAsset脚本,该脚本定义一个带有公共数据的渲染管线资源,您可以使用Inspector窗口为每个实例设置这些数据,以及在其构造函数中接收渲染管线资源并使用这些数据。

实例代码如下:

渲染资源脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using UnityEngine;
using UnityEngine.Rendering;

[CreateAssetMenu(menuName = "Rendering/ExampleRenderPipelineAsset")]
public class ExampleRenderPipelineAsset : RenderPipelineAsset
{
// 定义的数据
public Color exampleColor;
public string exampleString;

protected override RenderPipeline CreatePipeline() {
return new ExampleRenderPipelineInstance(this);
}
}

渲染实例脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
using UnityEngine;
using UnityEngine.Rendering;

public class ExampleRenderPipelineInstance : RenderPipeline
{
private ExampleRenderPipelineAsset renderPipelineAsset;

public ExampleRenderPipelineInstance(ExampleRenderPipelineAsset asset) {
renderPipelineAsset = asset;
}

protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
Debug.Log(renderPipelineAsset.exampleString);
}
}

创建一个简单的渲染循环

渲染循环是在单个帧中发生的所有渲染操作的术语。此节代码示例说明使用SRP的基础概念。你能使用这些信息去构建自己的自定可编程渲染管线,也可以用于理解SRP是如何工作的。

准备你的项目

在开始写渲染循环之前,你必须准备你的项目,步骤如下:

  1. 创建一个SRP兼容的shader。
  2. 创建一个或多个渲染的对象。
  3. 创建自定义SRP的基础结构。
  4. 可选步骤,如果你计划去扩展简单的自定SRP去添加一些更复杂的功能,安装SRP核心包。SRP核心包包含SRP的shader库(能使用这些shader去保证Batcher的兼容性),并且还包含了一些通用的操作逻辑功能。更多信息,参见SRP核心包文档

创建一个SRP兼容shader

在SRP中,使用Pass的LightMode标记确定几何对象将如何绘制。更多Pass的标记参见ShaderLab

本节的任务是使用LightMode为ExampleLightModeTag的标记来创建一个非常简单的无光照的Shader对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 无光照,不兼容SRP Batcher的Shader
Shader "Examples/SimpleUnlitColor"
{
SubShader
{
Pass
{
// 必须于ScriptableRenderContext.DrawRenderer中的ShaderTagId匹配
Tags { "LightMode" = "ExampleLightModeTag"}

HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag

float4x4 unity_MatrixVP;
float4x4 unity_ObjectToWorld;

struct Attributes
{
float4 positionOS : POSITION;
};

struct Varyings
{
float4 positionCS : SV_POSITION;
};

Varyings vert (Attributes IN)
{
Varyings OUT;
float4 worldPos = mul(unity_ObjectToWorld, IN.positionOS);
OUT.positionCS = mul(unity_MatrixVP, worldPos);
return OUT;
}

float4 frag (Varyings IN) : SV_TARGET
{
return float4(0.5,1,0.5,1);
}
ENDHLSL
}
}
}

创建渲染对象

为了测试渲染循环,必须创建一些东西进行渲染。Unity标准流程。

创建自定义SRP的基础结构

前面章节所描述的步骤:

  1. 创建渲染管线资源和渲染管线实例脚本
  2. 将当前创建的管线资源设置为激活的渲染管线

创建渲染循环

在一个简单的渲染循环中,基础的操作是:

  • 清理渲染目标,主要的目的是移除上一帧渲染的对象。
  • 剔除,主要作用是过滤掉摄像机完全看不见的对象。
  • 绘制,告诉GPU那些对象要绘制,以及如何绘制。

清理渲染目标

清理意味着清楚上一帧绘制的所有对象。渲染目标一般是屏幕,当然也能是RT。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using UnityEngine;
using UnityEngine.Rendering;

public class ExampleRenderPipeline : RenderPipeline {
public ExampleRenderPipeline() {
}

protected override void Render (ScriptableRenderContext context, Camera[] cameras) {
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, true, Color.black);
context.ExecuteCommandBuffer(cmd);
cmd.Release();

context.Submit();
}
}

剔除

剔除是过滤掉相机不可见的对象。在SRP中,剔除需要如下步骤:

  1. 构建关于相机的ScriptableCullingParameters结构体,可以通过调用Camera.TryGetCullingParameters来获取。
  2. 可以选步骤,可以手动的方式构建ScriptableCullingParameters结构体。
  3. 调用ScriptableRenderContext.Cull, 并将结果存儲在CullingResults结构体中。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using UnityEngine;
using UnityEngine.Rendering;

public class ExampleRenderPipeline : RenderPipeline {
public ExampleRenderPipeline() {
}

protected override void Render (ScriptableRenderContext context, Camera[] cameras) {
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, true, Color.black);
context.ExecuteCommandBuffer(cmd);
cmd.Release();

// 遍历所有相机
foreach (Camera camera in cameras)
{
// 获取剔除参数
camera.TryGetCullingParameters(out var cullingParameters);

// 剔除并存储剔除结果
var cullingResults = context.Cull(ref cullingParameters);
}

// 通知图像API执行命令
context.Submit();
}
}

绘制

绘图是指示图形API以给定设置绘制一组给定几何图形的过程。在SRP中的绘制应有以下步骤:

  1. 执行剔除(上一节描述的),并存储结果在CullingResults结构体中。
  2. 创建并且配置FilteringSetting结构体,此结构描述了如果去过滤剔除结果。
  3. 创建并配置DrawingSettings结构体, 此结构描述哪些几何体将被绘制以及如何绘制它。
  4. 可以选步骤,默认Unity基于Shader对象设置渲染状态(深度,蒙版和模板等)。如果你想对一些或所有打算绘制的几何对象覆写这些渲染状态,你能使用RenderStateBlock结构体去做这件事情。
  5. 调用ScriptableRenderContext.DrawRenderers,并传递上面创建的结构体对象。Unity将根据上面的设置的信息进行绘制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
using UnityEngine;
using UnityEngine.Rendering;

public class ExampleRenderPipeline : RenderPipeline {
public ExampleRenderPipeline() {
}

protected override void Render (ScriptableRenderContext context, Camera[] cameras) {
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, true, Color.black);
context.ExecuteCommandBuffer(cmd);
cmd.Release();

// 遍历所有相机
foreach (Camera camera in cameras)
{
// 获取剔除参数
camera.TryGetCullingParameters(out var cullingParameters);

// 剔除并存储剔除结果
var cullingResults = context.Cull(ref cullingParameters);

//设置视图、投影和剪切平面全局着色器变量。
context.SetupCameraProperties(camera);

//根据 LightMode Pass 标签值,告诉Unity要绘制哪个几何图形
ShaderTagId shaderTagId = new ShaderTagId("ExampleLightModeTag");

// 告诉Unity,基于当前相机该如何去排序
var sortingSettings = new SortingSettings(camera);

// 创建用于描述需要绘制的几何体,以及如何绘制他们
DrawingSettings drawingSettings = new DrawingSettings(shaderTagId, sortingSettings);

// 告诉Unity如何去过滤裁剪的结果,进一步指定要绘制的几何体
// 使用FilteringSettings.defaultValue指定不进行过滤
FilteringSettings filteringSettings = FilteringSettings.defaultValue;

// 基于前面的设置绘制几何体
context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);

// 安排天空盒子的绘制
if (camera.clearFlags == CameraClearFlags.Skybox && RenderSettings.skybox != null)
{
context.DrawSkybox(camera);
}

// 提交执行
context.Submit();
}
}
}

URP

URP是Unity基于SRP创建的渲染管线。URP提供了艺术家友好的工作流程让你能够快速且容易的创建跨越多个平台的图形,从移动端到高端的控制台和PCs。

渲染管线概念

URP资源

任何基于URP的Unity项目都必须有URP资源去配置相关的设置。URP资源控制了各种图形和质量设置。它是继承至RenderPipelineAsset的可编程对象。当你在Graphics设置中将URP资源后,Unity将自动切换到URP管线。我们可以有多个URP资源并且可以来回切换。

具体的URP资源的配置信息参见官方文档

URP全局设置

如果你的项目安装了URP的包,Unity将在Project Setting窗口中的Graphic页签里显示URP的全局设置。 URP全局设置

具体设置参见官方文档

渲染器

URP的渲染器实现了两个渲染路径:

  • 前向渲染路径
  • 延迟渲染路径

两渲染路径的对比如下: URP渲染路径对比

渲染器特性

渲染器特性是一种资源,可让您将额外的渲染通道添加到URP渲染器并配置其行为。URP包含预构建的名为渲染对象 (Render Objects) 的渲染器特性。有关如何向渲染器添加渲染器特性的信息,请参阅如何向渲染器添加渲染器功能页面

Unity自带支持3种渲染特性: 1. 渲染对象特性(Render Objects) 2. 环境光遮挡(Ambient Occlusion) 3. 贴花(Decal)

灯光

使用URP能实现适合艺术风格范围内的真实光照。Unity的所有渲染管线共享光照功能,但是不同的渲染管线也有一些重要的不同。 URP不同于Unity的通用光照功能的特性如下:

  • 在灯光组件的Inspector窗口中,显示了一些URP特定的选项
  • 在灯光组件上有Universal Additional Light Data组件,存储了与URP管线相关的数据。
  • 从URP12开始支持Enlighten的实时全局光照。

内建渲染管线和URP灯光之间的特性差异,能参见灯光特性对比表

相机

URP中的相机是基于Unity的标准相机的,但是也有一些明显的区别。如下:

  • Universal Additional Camera Data组件,扩展标准相机的功能,并允许URP存储相机相关的数据。
  • 渲染类型(Render Type)设置,定义了URP中的两种类型的相机:Base和Overlay。
  • 相机堆叠(Camera Stacking)系统, 允许你去层化多个相机的输出到一个合并后的输出。
  • Volume系统,允许基于场景的给定位置去应用后处理效果。
  • 相机组件,在Inspector中暴露了URP特定的选项。

后处理

URP包含了一个整合的后处理效果,如果你使用的URP,将不再需要额外安装后处理包了。URP不兼容Post Processing Stack v2包。URP使用Volume系统框架来现实后处理效果。Volumes能够覆写或扩展场景属性,依赖每个Volume相对于相机的位置。URP 为Volumes实现了专用的GameObjects:Global Volume, Box Volume, Sphere Volume, Convex Mesh Volume. 体类型

Volume组件引用了一个Volume Profile的文件。Volume Profile文件包含了每个属性的默认值并默认这些属性是隐藏的。 Volume Overrides让你改变或扩展这些在Volume Profile上的默认值。 在运行时,URP将便利那些启用了Volume组件的激活对象,比并且确定每个Volume组件的在最终场景设置中的贡献。URP使用相机位置和Volume组件属性去计算这些贡献值。URP将从所有有贡献的Volumes中插值得到最终的属性值。

URP支持的后处理效果有: - Bloom - Channel Mixed - Chromatic Aberration - Color Adjustments - Color Curves - Depth of Field - Film Grain - Lens Distortion - Lift Gamma Gain - Motion Blur - Panini Projection - Shadows Midtones Highlights - Split Toning - Tonemapping - Vignette - White Balance - Lens Flare

着色器和材质

URP提供了以下的一些着色器来满足大多数的使用场景:

  • Complex Lit (包含所有Lit着色器的共功能并添加一些高级材质特性,此着色器中的某些功能可能会占用更多资源并且需要Unity Shader Model 4.5的硬件)
  • Lit (PBS光照模型)
  • Simple Lit (Blinn-Phong光照模型)
  • Baked Lit (烘培光)
  • Unlit (无光照)
  • Terrain Lit (地形)
  • Particles Lit (粒子PBS光照模型)
  • Particles Simple Lit(粒子Blinn-Phong光照模型)
  • Particles Unlit (粒子无光照)
  • SpeedTree (SpeedTree特定着色器)
  • Decal (贴花)

选择着色器

URP提供了PBS和非PBS的光照着色器。对于PBS,使用Lit着色器。你能够使用它在所有的平台。这个Shader的质量适应性依赖平台,但保持在所有平台都是基于物理的渲染。Unity内建渲染管线的Standard和Standard(Specular)着色器都对应于URP的Lit着色器。内建渲染管线的和URP的着色器映射表参见着色器映射。 如果您的目标是功能较弱的设备,或者你的项目只有简单的着色需求,使用Simple Lit着色器,此着色器是非PBS的光照模型。 如果你不需要实时灯光,或者宁愿只使用烘焙光照和采样全局光照,选择Baked Lit着色器。 如果你不需要光照,可以使用Unlit着色器。

SRP批处理器兼容

确保SRP批处理器兼容着色器需要满足以下要求:

  • 声明所有材质属性在一个名为UnityPerMaterial的CBUFFER中。
  • 声明所有的内建引擎属性(比如:unity_ObjectToWorld 或 unity_WorldTransformParams)在一个名为UnityPerDraw的CBUFFER中。

着色器剔除

Unity从单个Shader源文件编译许多Shader变体。着色器变体的数量取决于您在着色器中包含多少关键字。 在默认着色器中,通用渲染管线 (URP) 使用一组关键字用于照明和阴影。 URP可以排除某些着色器变体,具体取决于URP资源中哪些功能处于活动状态。当您禁用 URP 资源中的某些功能时,管道会从构建中“剥离”相关的着色器变体。 剥离着色器可让您获得更小的构建尺寸和更短的构建时间。 如果您的项目永远不会使用某些功能或关键字,这将非常有用。例如,您可能有一个项目,您从不将阴影用于定向灯。 如果没有着色器剥离,具有定向阴影支持的着色器变体仍保留在构建中。 如果您知道根本不会使用这些阴影,则可以取消选中 URP 资源中的投射阴影以获得主或附加方向灯。 URP 然后从构建中去除这些着色器变体。具体的剔除方法参见剔除可编程着色器变体

URP ShaderLab Pass 标记

LightMode标记让管道确定在执行渲染管道的不同部分时使用哪个Pass。如果你在Pass中不设置LightMode标记,URP将使用SRPDefaultUnlit作为LightModede的值。在URP中, LightMode标记可以有下面的值:

属性 描述
UniversalForward Pass渲染对象几何体并评估所有光的贡献。 URP在前向渲染路径中使用此标记值。
UniversalGBuffer Pass渲染对象几何体但不评估任何光贡献。 URP在延迟渲染路径中使用此标记值。
UniversalForwardOnly Pass渲染对象几何体并评估所有光贡献,类似于LightMode为UniversalForward时。与UniversalForward的不同之处在于,URP可以将Pass用于前向和延迟渲染路径。如果在URP使用延迟渲染路径时某个通道必须使用前向渲染路径渲染对象,请使用此值。 例如,如果URP使用延迟渲染路径渲染场景并且场景包含具有不适合GBuffer的着色器数据的对象(例如透明涂层法线),则使用此标记。如果着色器必须同时在前向和延迟渲染路劲中渲染,请使用UniversalForward和UniversalGBuffer标签值声明两个Pass。 如果着色器必须使用前向渲染路径进行渲染,而不管URP渲染器使用的渲染路径,请仅声明一个将LightMode标记设置为UniversalForwardOnly的Pass。
Universal2D Pass渲染对象并评估2D光贡献。 URP在2D渲染器中使用此标记值。
ShadowCaster Pass将物体深度从灯光的角度渲染到阴影贴图或深度纹理中。
DepthOnly Pass仅将来自相机视角的深度信息渲染到深度纹理中。
Meta Unity仅在Unity编辑器中烘焙光照贴图时执行此Pass。 Unity在构建Player时会从着色器中剥离此通道。
SRPDefaultUnlit 渲染对象时使用此LightMode标记值绘制额外的Pass。 应用示例:绘制对象轮廓。 此标记值对前向和延迟渲染路径均有效。当Pass没有LightMode标记时,URP使用此标记值作为默认值。

2D图形特性

URP包含的2D功能是2D Lighting图形管道,可让您创建2D灯光和2D照明效果;并且用于在您的项目中实现像素化视觉风格的 2D 像素完美相机。 以下是包的 Light 2D 组件中包含的不同 2D 灯光类型:

  • Freeform
  • Sprite
  • Spot
  • Global

URP附带的2D光照系统由一组艺术家友好的工具和运行时组件组成,可帮助您通过核心Unity组件(例如 Sprite Renderer)和2D光照组件快速创建光照2D场景,这些组件充当熟悉的3D光照的2D对应物。这些工具旨在与2D渲染器无缝集成,例如 Sprite Renderer、Tilemap Renderer 和 Sprite Shape Renderer。 该工具和组件系统针对移动系统和在多个平台上运行进行了优化。

2D灯光实现细节

2D Lighting图形管线渲染过程可以分为2个不同的阶段: 1. 绘制光照渲染纹理 2. 绘制渲染器

灯光渲染纹理是包含有关屏幕空间中灯光颜色和形状信息的渲染纹理。这两个阶段仅针对每组明显照明的 Light Layers 重复。 换句话说,如果 Sorting Layers 1 到 4 具有完全相同的一组 Lights,则它只会执行上述一组操作。默认设置允许在绘制渲染器之前提前绘制多个批次,以减少目标切换。 理想的设置将允许管道为所有批次渲染光渲染纹理,然后才继续绘制渲染器。 这可以防止加载和卸载颜色目标。 有关更多详细信息,请参阅优化

前期:计算排序层批处理

在继续渲染阶段之前,2D Lighting 图形管道首先分析场景,以评估哪些图层可以在单个绘制操作中批量处理。 以下是确定图层是否一起批处理的标准:

  1. 它们是连续的层。
  2. 他们共享完全相同的一组灯光。

强烈建议批处理尽可能多的图层,以最大限度地减少光渲染纹理绘制操作的数量并提高性能。

阶段 1:绘制光渲染纹理

在预阶段批处理之后,管道然后为该批处理绘制光照纹理。这基本上将灯光的形状绘制到渲染纹理上。 根据灯光的设置,可以使用Additive或Alpha Blended将灯光的颜色和形状混合到目标灯管的渲染纹理上。

阶段一

值得注意的是,仅当至少有一个2D光以它为目标时才会创建光渲染纹理。 例如,如果一个图层的所有灯光只使用 Blendstyle #1,那么只会创建一个灯光渲染纹理。

第 2 阶段:绘制渲染器

绘制完所有光照渲染纹理后,管道将继续绘制渲染器。 系统将跟踪哪组渲染器是由哪组光渲染纹理绘制的。 它们在前期阶段的批处理过程中关联。绘制渲染器时,它将可以访问所有(每种混合样式一个)可用的光渲染纹理。 在着色器中,通过使用指定操作将输入颜色与来自灯光的渲染纹理的颜色组合来计算最终颜色。

阶段二

具有四种活动混合样式的设置示例,说明了多种混合样式如何组合在一起。 在大多数情况下,您通常只需要两种混合样式即可获得所需的效果。

2D Pixel Perfect

2D Pixel Perfect套件包含 Pixel Perfect相机组件,可确保您的像素艺术在不同分辨率下保持清晰,并且在运动情况下也能保持清晰。它是一个单一的组件,可以进行Unity所需的所有计算,以随分辨率变化缩放视口,因此您无需手动进行。您可以使用此组件设置来调整相机视口中渲染像素艺术的定义,并且可以使用“在编辑模式下运行”功能在游戏视图中立即预览任何更改。

URP管线的具体的实现细节,打算后续对URP的源码进行阅读,待续...

HDRP

现阶段手游开发不会用到此渲染管线。

原文

[1] Render pipelines https://docs.unity3d.com/Manual/render-pipelines.html

[2] Universal Render Pipeline https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@14.0/manual/index.html

[3] High Definition Render Pipeline https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@14.0/manual/index.html

[4] unity shader 实现延迟渲染代码加注释 https://blog.csdn.net/lengyoumo/article/details/104489830