这篇文章主要记录学习URP的知识点。主要搞清楚这几件事:
- 什么是URP,以及URP能干啥。
- 相比于传统的Built-In,URP有哪些优势。与此同时在URP管线下需要注意哪些点。
- 最实在的,URP管线下的Shader编写规则。
- 关于自定义管线,这一块暂时先放下。
Unity-URP
一、什么是URP
想要了解什么是URP,那么得先了解什么是SRP。
SRP全称为Scriptable Render Pipeline(可编程渲染管线/脚本化渲染管线),是Unity提供的新渲染系统,可以在Unity通过C#脚本调用一系列API配置和执行渲染命令的方式来实现渲染流程,SRP将这些命令传递给Unity底层图形体系结构,然后再将指令发送给图形API。
说白了就是我们可以用SRP的API来创建自定义的渲染管线,可用来调整渲染流程或修改或增加功能。
URP的全称为Universal Render Pipeline(通用渲染管线), 它是Unity官方基于SRP提供的功能而专门开发出的一个模板。它的前身是LWRP, 在2019.3开始改名为URP,它涵盖了范围广泛的不同平台,性能比内置管线要好,另外可以进行自定义,实现不同风格的渲染。
二、URP三大优势
1. 光照处理
URP是单Pass前向渲染管线,而内置管线是多Pass,可选前向渲染管线和延迟渲染管线。
所谓的前向渲染,就是在渲染物体受点光光照的时候,分别对每个点光对该物体产生的影响进行计算,最后将所有光的渲染结果相加得到最终物体的颜色。
内置渲染管线是多pass的,简单来讲,当场景中有多个光源时,会产生多个pass来渲染光照,在移动端如果有多个光源的话,将会产生巨大的性能消耗。
而URP使用单个pass,这个物体收到的所有光照都只会产生一个pass,所有的光源处理都可以在一个DrawCall中完成,对于性能的消耗就小得多。
2. SRP Bacher
批处理是性能优化的一大法宝,传统内置渲染管线中的批处理有着诸多限制。特别是动态批处理,这在SRP Batcher 中得到了彻底优化。
SRP Batcher 是一个底层渲染优化机制,可通过许多使用同一着色器变体的材质来加快场景中的 CPU 渲染速度。即使是不同的材质球,只要是用一个着色器变体的物体都可以批处理到一起。
3. 可扩展性
利用通用渲染管线提供的接口,技术美术可以轻松实现以往很难实现的效果
二、 URP和传统Built-In最大的不同
1. SRP Batcher
要想使用SRP Batcher,需要在URP Asset中勾选启用,并且要有对应支持的Shder。具体做法是需要将需缓存到CBuffer的变量写到CBUFFER_START(UnityPerMaterial)和CBUFFER_END之间:
CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
float4 _BaseColor;
CBUFFER_END
2. 单Pass
URP管线不再支持多个渲染Pass。在URP中采用的是单Pass的光照计算,在一个Pass中遍历光源进行光照计算:
现在URP解决了这个问题。实现了一个单PASS的正向渲染。可以支持多盏动态光,但是全部动态灯光都会放在一个Pass里渲染,这样带来的问题是要限制灯光的数量,因为每次Draw Call去画的时候,传给GPU的参数是有限的。目前是支持1盏平行光,每个对象可能只能接受4个动态光。
在Built-In管线里面,我们可以使用多Pass实现描边、毛发等等效果,但是在URP中就不行了。一般来说 Render Feature可以解决大部分问题,可以通过它实现大部分多Pass的效果。
对于毛发这种Pass数量多的,就可以考虑使用多物体单Pass的方式实现:https://github.com/Acshy/FurShaderUnity
接下来看看内置渲染管线和URP各种情况下的光照处理实验对比
以下是分别在四种情况下对比所得出的结论
- 无光源。 (没区别)
- 一个平行光,无阴影。(没区别)
- 一个平行光,一个点光源,无阴影。
结论:内置渲染管线跟只有一个平行光时比起来Batches将近增加了一倍,而URP的Batches和SetPass calls跟一个平行光时一样,一点都没有增加。
一个动态光,有阴影。
结论:在阴影的处理方面URP性能比内置渲染管线好很多。
URP光照处理最终结论:
- 性能上阴影处理方面比内置渲染管线好很多。
- URP平行光基础上添加动态光没有带来额外的Batches和SetPass calls性能开销。
三、Shader
基于URP管线下不同的渲染策略,需要改变开发出对应的Shader包。
URP的Shader开发仍然使用的是ShaderLab的语法结构,开发语言和普通的Shader开发不一样在于它使用的是HLSL编程语言。
- 渲染Pass:
“LightMode”=“UniversalForward”
最终输出到帧缓存中,只能有一个,也就是我们说的URP只支持一个Pass - 投影Pass:
“LightMode”=“ShadowCaster”
用于计算投影 - 深度Pass:
“LightMode”=”DepthOnly”
如果管线设置了生成深度图,会通过这个Pass渲染 - 其他Pass:
专门用于烘焙的Pass,专门用于2D渲染的Pass等
四、SRP Batcher
Unity 2018引入了可编程渲染管线SRP,其中包含新的底层渲染循环SRP Batcher批处理器,它可以大幅提高CPU在渲染时的处理速度,根据场景内容的不同,提升效果为原来的1.2~4倍不等。
SRP Batcher:SRP Batcher 是一个底层渲染循环,可通过许多使用同一着色器变体的材质来加快场景中的 CPU 渲染速度。也就是说就算是不同的材质,只要使用同一个shader,都能批处理。
五、URP Shader 对比
“RenderPipeline” = “UniversalPipeline”
名称为 RenderPipeline 的子着色器标签会向 Unity 显示,此子着色器应与哪个渲染管线搭配使用。UniversalPipeline 的值表明 Unity 应将此子着色器与 URP 搭配使用。
目前不建议使用 CGPROGRAM / ENDCG 宏。使用这些宏意味着使用 UnityCG.cginc。如此混合 SRP 和内置渲染管线着色器库,会导致很多问题。
简单URP模板:
Shader "URPCustom/Unlit"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_BaseColor("Base Color",Color)=(1,1,1,1)
}
SubShader //子着色器1,针对显卡A的着色器,这里是ShaderLab着色器的主要内容
{
Tags
{
"RenderPipeline"="UniversalPipeline"//这是一个URP Shader!
"Queue"="Geometry"
"RenderType"="Opaque"
}
HLSLINCLUDE
//CG中核心代码库 #include "UnityCG.cginc"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
//除了贴图外,要暴露在Inspector面板上的变量都需要缓存到CBUFFER中
CBUFFER_START(UnityPerMaterial)
float4 _MainTex_ST;
half4 _BaseColor;
CBUFFER_END
ENDHLSL
Pass
{
Tags{"LightMode"="UniversalForward"}//这个Pass最终会输出到颜色缓冲里
HLSLPROGRAM //CGPROGRAM
#pragma vertex vert
#pragma fragment frag
TEXTURE2D(_MainTex);//在CG中会写成sampler2D _MainTex;
SAMPLER(sampler_MainTex);
struct Attributes//这就是a2v
{
float4 positionOS : POSITION;
float2 uv : TEXCOORD;
};
struct Varings//这就是v2f
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD;
};
Varings vert(Attributes IN)
{
Varings OUT;
//在CG里面,我们这样转换空间坐标 o.vertex = UnityObjectToClipPos(v.vertex);
VertexPositionInputs positionInputs = GetVertexPositionInputs(IN.positionOS.xyz);
OUT.positionCS = positionInputs.positionCS;
OUT.uv=TRANSFORM_TEX(IN.uv,_MainTex);
return OUT;
}
float4 frag(Varings IN):SV_Target
{
//在CG里,我们这样对贴图采样 fixed4 col = tex2D(_MainTex, i.uv);
half4 baseMap = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, IN.uv);
return baseMap * _BaseColor;
}
ENDHLSL //ENDCG
}
}
}
一个基础的光照模型代码:
Shader "zxz/URPSimpleLit"
{
Properties
{
_BaseMap ("Base Texture",2D) = "white"{}
_BaseColor("Base Color",Color) = (1,1,1,1)
_SpecularColor("SpecularColor",Color)=(1,1,1,1)
_Smoothness("Smoothness",float)=10
_Cutoff("Cutoff",float)=0.5
}
SubShader
{
Tags
{
"RenderPipeline"="UniversalPipeline"
"Queue"="Geometry"
"RenderType"="Opaque"
}
HLSLINCLUDE
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
CBUFFER_START(UnityPerMaterial)
float4 _BaseMap_ST;
float4 _BaseColor;
float4 _SpecularColor;
float _Smoothness;
float _Cutoff;
CBUFFER_END
ENDHLSL
Pass
{
Name "URPSimpleLit"
Tags{"LightMode"="UniversalForward"}
HLSLPROGRAM
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
#pragma vertex vert
#pragma fragment frag
struct Attributes
{
float4 positionOS : POSITION;
float4 normalOS : NORMAL;
float2 uv : TEXCOORD0;
};
struct Varings
{
float4 positionCS : SV_POSITION;
float2 uv : TEXCOORD0;
float3 positionWS : TEXCOORD1;
float3 viewDirWS : TEXCOORD2;
float3 normalWS : TEXCOORD3;
};
TEXTURE2D(_BaseMap);
SAMPLER(sampler_BaseMap);
Varings vert(Attributes IN)
{
Varings OUT = (Varings)0;
VertexPositionInputs positionInputs = GetVertexPositionInputs(IN.positionOS.xyz);
VertexNormalInputs normalInputs = GetVertexNormalInputs(IN.normalOS.xyz);
OUT.positionCS = positionInputs.positionCS;
OUT.positionWS = positionInputs.positionWS;
OUT.viewDirWS = GetCameraPositionWS() - positionInputs.positionWS;
OUT.normalWS = normalInputs.normalWS;
OUT.uv=TRANSFORM_TEX(IN.uv,_BaseMap);
return OUT;
}
float4 frag(Varings IN):SV_Target
{
half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv);
//计算主光
Light light = GetMainLight();
half3 diffuse = LightingLambert(light.color, light.direction, IN.normalWS);
half3 specular = LightingSpecular(light.color, light.direction, normalize(IN.normalWS), normalize(IN.viewDirWS), _SpecularColor, _Smoothness);
//计算附加光照
uint pixelLightCount = GetAdditionalLightsCount();
for (uint lightIndex = 0; lightIndex < pixelLightCount; ++lightIndex)
{
Light light = GetAdditionalLight(lightIndex, IN.positionWS);
diffuse += LightingLambert(light.color, light.direction, IN.normalWS);
specular += LightingSpecular(light.color, light.direction, normalize(IN.normalWS), normalize(IN.viewDirWS), _SpecularColor, _Smoothness);
}
half3 color=baseMap.xyz*diffuse*_BaseColor+specular;
clip(baseMap.a-_Cutoff);
return float4(color,1);
}
ENDHLSL
}
Pass
{
Name "ShadowCaster"
Tags{"LightMode" = "ShadowCaster"}
ZWrite On
ZTest LEqual
Cull[_Cull]
HLSLPROGRAM
// Required to compile gles 2.0 with standard srp library
#pragma prefer_hlslcc gles
#pragma exclude_renderers d3d11_9x
#pragma target 2.0
// -------------------------------------
// Material Keywords
#pragma shader_feature _ALPHATEST_ON
#pragma shader_feature _GLOSSINESS_FROM_BASE_ALPHA
//--------------------------------------
// GPU Instancing
#pragma multi_compile_instancing
#pragma vertex ShadowPassVertex
#pragma fragment ShadowPassFragment
//由于这段代码中声明了自己的CBUFFER,与我们需要的不一样,所以我们注释掉他
//#include "Packages/com.unity.render-pipelines.universal/Shaders/SimpleLitInput.hlsl"
//它还引入了下面2个hlsl文件
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl"
#include "Packages/com.unity.render-pipelines.universal/Shaders/ShadowCasterPass.hlsl"
ENDHLSL
}
}
}
URP Shaders默认提供PBR光照(Lit)、简单兰伯特光照(SimpleLit)和无光照(Unlit)等常用Shader
处于性能考虑,URP默认参数中,只有平行光进行逐像素计算,其他光进行顶点计算(我们可以在URP Asset中更改这一设置)。
阴影:
一般在Buit-In管线里,我们只需要最后FallBack返回到系统的Diffuse Shader,管线就会去里面找到他处理阴影的Pass。但是在URP中,一个Shader中的所有Pass需要有一致的CBuffer,否则便会打破SRP Batcher,影响效率。
而系统默认SimpleLit的Shader中的CBuffer内容和我的写的并不一致,所以我们需要把它阴影处理的Pass复制一份,并且删掉其中引用的SimpleLitInput.hlsl(相关CBuffer的声明在这里面)。
CBUFFER_START和CBUFFER_END,对于变量是单个材质独有的时候建议放在这里面,以提高性能。CBUFFER(常量缓冲区)的空间较小,不适合存放纹理贴图这种大量数据的数据类型。HLSL贴图的采样函数和采样器函数,TEXTURE2D (_MainTex)和SAMPLER(sampler_MainTex)。
Shader迁移具体细节
1. LightMode
LightMode里的值改成UniversalForward。如下:
"LightMode"="UniversalForward"
删掉以下内容:
#define UNITY_PASS_FORWARDBASE
#pragma multi_compile_fwdbase
可以使用的灯光模式,有三种:
“UniversalForward”
“LightweightForward”
“SRPDefaultUnlit"
2. 一些CGInclude常用的接口
1、把CGPROGRAM改成HLSLPROGRAM,
把ENDCG改成ENDHLSL。
把CGINCLUDE改成HLSLINCLUDE。
2、把#include “UnityCG.cginc”
改成
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
3、o.pos = UnityObjectToClipPos( v.vertex );
改成
VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex.xyz);
o.pos = vertexInput.positionCS;
4、o.normalDir = UnityObjectToWorldNormal(v.normal);
改成
VertexNormalInputs vertexNormalInput = GetVertexNormalInputs(v.normal);
o.normalDir = vertexNormalInput.normalWS;
5、灯光方向和灯光颜色
_WorldSpaceLightPos0.xyz改成
在vert函数里:
o.shadowCoord = GetShadowCoord(vertexInput);
frag函数里:
Light mainLight = mainLight = GetMainLight(i.shadowCoord);
float3 lightDirection = mainLight.direction;
_LightColor0 改成 mainLight.color
6、把fixed 定义的变量都改成float 的。
比如 fixed4 (finalColor,1)改成float4 (finalColor,1)
7、阴影:https://blog.csdn.net/zakerhero/article/details/106274331?spm=1001.2014.3001.5501
8、 如何获取深度图:https://blog.csdn.net/zakerhero/article/details/115693888?spm=1001.2014.3001.5501
9、_GrabTexture
URP管线下,截图不再使用GrabTexture,而是使用_CameraOpaqueTexture或_CameraColorTexture
删掉GrabPass{ }
sampler2D _GrabTexture; 改成 SAMPLER(_CameraOpaqueTexture);
同时,还得开启URP管线设置的Opaque Texture。
具体参考:Unity URP管线如何截屏,及热扰动(热扭曲)效果的实现
10、 如何使用多Pass:如何在Unity的URP下使用多pass(multi pass)
NN 自定义管线资料:
最重要的资料:官方文档