|
概念
SRP全称为:Scriptable Render Pipeline,也就是可编程的渲染管线,它属于一种轻量的API,允许开发者使用C#脚本来设置渲染命令,Unity将这些命令传递给它的底层图形架构(low-level graphics architecture),它会将这些指令会被发送到graphics API当中,最终由GPU进行处理。
SRP的官方介绍如下:
如今常见的URP以及HDRP都是在SRP的基础上拓展的,当然我们也可以用SPR实现自定义的渲染管线。想要实现自定义的渲染管线,必须要有 Render Pipeline Asset 和 Render Pipeline Instance这两个东西。
Render Pipeline Instance
我们先来看看什么是Render Pipeline Instance,它实际上就是一个继承了RenderPipeline的类,如下:
public class CustomRenderPipeline : RenderPipeline {
protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
}
}
然后我们可以通过重写Render方法,在里面实现我们自定义的渲染效果。其中我们可以看见有一个 ScriptableRenderContext对象,它充当着C#代码与Unity底层图形代码的接口。SRP的渲染使用的是延迟执行,我们可以用ScriptableRenderContext构建一系列的渲染命令,然后告诉Unity去执行这些命令。
我们通过下面两种方法来设置渲染命令:
- 将一系列的CommandBuffer传递给ScriptableRenderContext,然后使用ScriptableRenderContext.ExecuteCommandBuffer方法执行这些命令。
- 直接调用ScriptableRenderContext的API,例如 ScriptableRenderContext.Cull 和 ScriptableRenderContext.DrawRenderers。
然后我们可以通过调用ScriptableRenderContext.Submit方法来告诉Unity去执行我们设置好的命令。在调用Submit方法之前,Unity不会去执行我们前面所设置的命令。
下面例子,我们使用红色来进行清屏:
protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, true, Color.red);
context.ExecuteCommandBuffer(cmd);
cmd.Release();
context.Submit();
}
Render Pipeline Asset
那么怎么让我们上面的代码生效呢?这里就需要我们的Render Pipeline Asset出场了。从Asset关键字可以看出,它属于一种资源文件,例如在URP中,我们就可以用下面方法来创建一个属于URP的Render Pipeline Asset。
URP的Render Pipeline Asset
那么我们怎么创建上面自定义的Render Pipeline Instance对应的Asset文件呢?只需要新建一个脚本继承RenderPipelineAsset类,并且重写里面的 CreatePipeline() 方法,返回我们的Render Pipeline Instance即可,如下:
[CreateAssetMenu(menuName = "Rendering/CustomRenderPipelineAsset")]
public class CustomRenderPipelineAsset : RenderPipelineAsset {
protected override RenderPipeline CreatePipeline() {
return new CustomRenderPipeline();
}
}通常情况下,我们会把Render Pipeline Instance中需要用于渲染的一些信息存储在Asset文件中。例如前面我们用了红色来清屏,我们可以利用Asset来配置这个颜色,然后传递给Instance,代码如下:
[CreateAssetMenu(menuName = "Rendering/CustomRenderPipelineAsset")]
public class CustomRenderPipelineAsset : RenderPipelineAsset {
public Color clearColor;
protected override RenderPipeline CreatePipeline() {
return new CustomRenderPipeline(this);
}
}
public class CustomRenderPipeline : RenderPipeline {
CustomRenderPipelineAsset m_asset;
public CustomRenderPipeline(CustomRenderPipelineAsset asset) {
m_asset = asset;
}
protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
var cmd = new CommandBuffer();
cmd.ClearRenderTarget(true, true, m_asset.clearColor);
context.ExecuteCommandBuffer(cmd);
cmd.Release();
context.Submit();
}
}
除此之外我们还可以根据不同的设备配置来指定不同的Asset,来达到不同硬件条件下的适配效果。
接着我们就可以创建我们自定义的Render Pipeline Asset了:
在Asset的Inspector界面就可以设置清屏的颜色,如图我设置一个黄色:
最后我们要在Project Settings的Graphics中,关联上我们的Asset文件,如图:
此时我们的画面就变得一片黄色,说明我们Instance中的代码生效了:
<hr/>接着我们来想办法在这一片屎黄色的屏幕上加点什么,ScriptableRenderContext为我们提供了DrawRenderers方法来绘制可见的物体,具体函数如下:
public void DrawRenderers(CullingResults cullingResults,ref DrawingSettings drawingSettings,
ref FilteringSettings filteringSettings);
其中参数有CullingResults,DrawingSettings和FilteringSettings对象,我们依次来了解一下。
CullingResults
CullingResults,顾名思义就是剔除后的结果。在SRP的每个渲染循环里(Render Loop,每帧执行的所有渲染操作我们称之为一个渲染循环),渲染过程中通常会对每个Camera做剔除操作留下可见的物体,然后再渲染它们以及处理可见光。
我们可以通过ScriptableRenderContext.Cull方法来执行剔除操作,如下:
public CullingResults Cull(ref ScriptableCullingParameters parameters);
其中ScriptableCullingParameters参数决定了剔除的规则,该参数通常从当前渲染的Camera中获得,即Camera.TryGetCullingParameters方法,如下:
public bool TryGetCullingParameters(out ScriptableCullingParameters cullingParameters);
此外我们还可以手动的修改得到的ScriptableCullingParameters结构体,来更新剔除的规则,例如:
//增加遮挡剔除
cullingParameters.cullingOptions |= CullingOptions.OcclusionCull;
//剔除除了default layer之外的layer
cullingParameters.cullingMask = 1 << 0;
执行Cull方法后,得到的结果即存储在CullingResults对象中,并且在每次渲染循环完成后,CullingResults所占的内存都会被释放掉。
DrawingSettings
DrawingSettings用来描述可见物体的排序方式,以及绘制它们时使用的Shader Pass,其构造函数如下:
public DrawingSettings(ShaderTagId shaderPassName, SortingSettings sortingSettings);ShaderTagId 用于关联Shader中的Tag id,在SRP中,我们可以使用 LightMode 这个Pass块里的Tag来决定我们的绘制方式。在内置的渲染管线中,我们的LightMode可以选择诸如Always,ForwardBase,Deferred等等值,但是现在我们要自定义渲染管线,因此LightMode的值就需要我们自定义,例如我们可以在Shader的Pass中添加如下代码:
Tags { &#34;LightMode&#34; = &#34;CustomLightModeTag&#34;}那么我们DrawingSettings中需要的ShaderTagId 即为:
ShaderTagId shaderTagId = new ShaderTagId(&#34;CustomLightModeTag&#34;);
这样就会找到Shader中LightMode为CustomLightModeTag的Pass进行渲染。
接着是SortingSettings结构图,对应着排序方式,我们可以通过下面方法获得:
var sortingSettings = new SortingSettings(camera);
怎么理解排序方式呢?可以参考Camera.transparencySortMode。简单来说,默认情况下正交摄像机的话,排序只考虑物体与摄像机在Camera.forward方向上的距离。而透视摄像机直接根据物体到摄像机的距离进行排序。
最终我们的DrawingSettings 即为:
DrawingSettings drawingSettings = new DrawingSettings(shaderTagId, sortingSettings);
FilteringSettings
FilteringSettings用来描述渲染时如何过滤可见物体,可通过如下方法获得:
FilteringSettings filteringSettings = FilteringSettings.defaultValue;
filteringSettings.layerMask = 1 << 0;
defaultValue即表示不进行任何的过滤,设置layerMask,即只显示属于该layer的物体。
完整RenderPipeline代码
通过上面,DrawRenderers需要的参数就都可以获得了,我们就可以绘制物体了,完整代码如下:
protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
var cmd = new CommandBuffer();
//清除上一帧绘制的东西
cmd.ClearRenderTarget(true, true, Color.black);
context.ExecuteCommandBuffer(cmd);
cmd.Release();
// 会有显示Scene视图的SceneCamera,点击Camera时显示Preview视图的PreviewCamera,以及场景中我们添加的Camera
foreach(Camera camera in cameras) {
//获取当前相机的剔除规则,进行剔除
camera.TryGetCullingParameters(out var cullingParameters);
var cullingResults = context.Cull(ref cullingParameters);
//根据当前Camera,更新内置Shader的变量
context.SetupCameraProperties(camera);
//生成DrawingSettings
ShaderTagId shaderTagId = new ShaderTagId(&#34;CustomLightModeTag&#34;);
var sortingSettings = new SortingSettings(camera);
DrawingSettings drawingSettings = new DrawingSettings(shaderTagId, sortingSettings);
//生成FilteringSettings
FilteringSettings filteringSettings = FilteringSettings.defaultValue;
context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);
if(camera.clearFlags == CameraClearFlags.Skybox && RenderSettings.skybox != null) {
//绘制天空盒
context.DrawSkybox(camera);
}
context.Submit();
}
}
自定义Shader
别忘了,在自定义管线里,我们用的是 Tags { &#34;LightMode&#34; = &#34;CustomLightModeTag&#34;} 的Shader,因此我们要新建一个Shader,然后Pass里添加我们指定的Tag。
一个简单的无光照Shader如下:
Shader &#34;Custom/UnlitColor&#34;
{
SubShader
{
Pass
{
Tags { &#34;LightMode&#34; = &#34;CustomLightModeTag&#34;}
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
float4x4 unity_MatrixVP;
float4x4 unity_ObjectToWorld;
struct appdata
{
float4 vertex : POSITION;
};
struct v2f
{
float4 vertex : SV_POSITION;
};
v2f vert (appdata v)
{
v2f o;
float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
o.vertex = mul(unity_MatrixVP, worldPos);
return o;
}
float4 frag (v2f i) : SV_TARGET
{
return float4(0.5,1,0.5,1);
}
ENDHLSL
}
}
}最后我们只需要创建一个Material来关联这个Shader,并且在场景中创建Cube,Sphere等GameObject,并且使用我们的Material即可。
得到的效果如下:
本文是一个最简单的例子,如果我们要在SRP里添加更多复杂的功能,可以在Package Manager中安装Core RP Liberary。它里面包含有一些核心的shader库,使用它们可以让我们的shader兼容SRP Batcher,此外还有很多工具函数适用于一些常见操作。 |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|