Unity SRP(一) Create a pipeline


Scriptable Render Pipeline

回顾一下渲染管线,渲染时需要决定画哪些物体,画在哪里,然后在什么时候画,物体之间的遮挡关系等等,渲染管线就是一条工作流水线,去安排这一切的操作。unity2018之前有两种内置的渲染管线:Deferred、Froward。但内置的管线的内容都是固定的,我会记录一下自己搭建一条完整的srp的过程,然后实现各种效果。

Pipeline Asset

首先要创建一个Asset,RenderPipelineAsset的主要目的是帮助Unity创建一个渲染管线实例,RenderPipelineAsset自身其实就是存储各渲染管线的配置。通过override RenderPipeline CreatePipeline()方法来创建一个渲染管线实例,因为现在还没定义自己的渲染管线,所以现在先返回Null。

1
2
3
4
5
6
7
8
[CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline")]
public class CustomRenderPipelineAsset : RenderPipelineAsset
{
protected override RenderPipeline CreatePipeline()
{
return null;
}
}

然后创建我们的asset:

这个时候就会变得一切漆黑,因为此时引擎没有渲染出来任何物体,而且graphics里面的一些选项也不见了。

Render Pipeline Instance

创建pipeline实例,新建一个C#脚本,继承自RenderPipeline,在这里面需要去复写Render方法去创建一个具体的Pipeline,然后在CustomRenderPipelineAsset.createPipeline里去返回这个实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class CustomRenderPipeline : RenderPipeline
{
protected override void Render(ScriptableRenderContext context, Camera[] cameras)
{
throw new System.NotImplementedException();
}
}

public class CustomRenderPipelineAsset : RenderPipelineAsset
{
protected override RenderPipeline CreatePipeline()
{
return new CustomRenderPipeline();
}
}

这个时候已经有了我们创建的具体的pipeline了,但是它还没有效果,因为他还没有去渲染任何东西。

Rendering

Unity就是使用context和active状态的camera作为参数,调用pipeline中的render函数,进行渲染。这个过程对game 窗口,scene窗口和材质预览窗口都一样。我们现在要做的就是正确的设置好各参数,用正确的顺序绘制出应该被渲染的东西。

因为每个Camera是单独渲染的,再专门为摄像机新建一个类:

1
2
3
4
5
6
7
8
9
10
11
12
public class CameraRenderer 
{
ScriptableRenderContext context;

Camera _camera;

public void Render(ScriptableRenderContext context, Camera camera)
{
this.context = context;
this._camera = camera;
}
}

然后在CustomRenderPipeline创建出一个pipeline实例时,用它去渲染所有的相机,所以在CustomRenderPipeline的实例里面:

1
2
3
4
5
6
7
protected override void Render(ScriptableRenderContext context, Camera[] cameras)
{
foreach (Camera camera in cameras)
{
renderer.Render(context, camera);
}
}

这个loop在后面可以去实现不同的渲染方式,比如有的采用延迟渲染,有的渲染第一人称视角等等。

srp最重要的也就是这个RenderPipeline实例,它在已经定义好的Render函数里面调用每个摄像机进行渲染,人为控制管线的流程也就是在每个摄像机类里面去实现的。

Draw SkyBox

CameraRenderer.Render里面去画摄像机能看到的物体,我们在Render里面的每个command都是存在buffer里的,它们是排着队的,必须使用submit命令才能执行这个command。要正确的渲染Skybox和整个场景,还需要通过Camera的位置和视角设置MVP矩阵,要将Camera中的配置信息通过SetupCameraProperties函数写入Context中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void Render(ScriptableRenderContext context, Camera camera)
{
this.context = context;
this.camera = camera;

Setup();
DrawVisibleGeometry();
Submit();
}

void Setup()
{
context.SetupCameraProperties(camera);
}

void DrawVisibleGeometry()
{
context.DrawSkybox(camera);
}

void Submit()
{
context.Submit();
}

Command Buffers

Context 直到Submit才会进行实际的渲染,在这之前,我们需要配置它并且将comands加入其中等之后执行。一些任务比如绘制Skybox有专门的函数实现,但是画其它物体或者做其它操作的时候就没有这种内置函数了,所以要专门的一个buffer。

设置Command Buffer:

1
2
3
4
5
const string bufferName = "Render Camera";
CommandBuffer buffer = new CommandBuffer
{
name = bufferName
};

使用时,需要用到BeginSample,EndSample,这个范围内就属于Render Camera这个Command Buffer的范围。为了得到正确的渲染图像,还需要clearrendertarget:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void Setup()
{
//先设置好摄像机的matrix,再clear
buffer.ClearRenderTarget(true, true, Color.clear);
buffer.BeginSample(bufferName);
ExecuteBuffer();
context.SetupCameraProperties(camera);
}

void Submit()
{
buffer.EndSample(bufferName);
ExecuteBuffer();
context.Submit();
}

void ExecuteBuffer()
{
context.ExecuteCommandBuffer(buffer);
buffer.Clear();
}

可以看到成功清除掉color和depth Buffer以及stencil

Culling

在渲染物体之前,需要先通过摄像机的视椎体对物体进行裁剪剔除,只渲染那些能被看见的物体。

通过ScriptableCullingParameters结构体和 CullResults.GetCullingParameters 函数,可以针对某个Camera得到裁剪的配置信息:

1
2
3
4
5
6
7
8
9
10
bool Cull()
{
ScriptableCullingParameters p;//存摄像机设置和信息的结构
if (camera.TryGetCullingParameters(out p))//是否能得到摄像机的属性信息和matrix
{
cullingResults = context.Cull(ref p);//cull之后的结果
return true;
}
return false;
}

因为是剔除,所以在draw之前进行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void Render(ScriptableRenderContext context, Camera camera)
{
this.context = context;
this.camera = camera;

if (!Cull())
{
return;
}

Setup();
DrawVisibleGeometry();
Submit();
}

Draw Geometry

通过culling得到可视化信息后,就可以用DrawRenderer去进行绘制,它需要三个参数,cullingresult以及drawingSettings、filteringSettings:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static ShaderTagId unlitShaderTagId = new ShaderTagId("SRPDefaultUnlit");
void DrawVisibleGeometry()
{
var sortingSettings = new SortingSettings(camera)
{
criteria = SortingCriteria.CommonOpaque//从前向后的顺序渲染不透明物体
};
//SortingCriteria 决定渲染时物体的顺序

var drawingSettings = new DrawingSettings(
unlitShaderTagId, sortingSettings
);
var filteringSettings = new FilteringSettings(RenderQueueRange.all);

context.DrawRenderers(
cullingResults, ref drawingSettings, ref filteringSettings
);

context.DrawSkybox(camera);
}

这样就可以画出所有Unlit Shader:

可以看到这里还有个问题,透明物体被天空覆盖了,所以要修改一下渲染的顺序。

先渲染不透明的物体,然后是天空盒,最后是透明物体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var sortingSettings = new SortingSettings(camera)
{
criteria = SortingCriteria.CommonOpaque
};

var drawingSettings = new DrawingSettings(unlitShaderTagId, sortingSettings);
var filteringSettings = new FilteringSettings(RenderQueueRange.opaque);

context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);

context.DrawSkybox(camera);

sortingSettings.criteria = SortingCriteria.CommonTransparent;
drawingSettings.sortingSettings = sortingSettings;
filteringSettings.renderQueueRange = RenderQueueRange.transparent;

context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);

Draw Gizmos

1
2
3
4
5
6
7
8
partial void DrawGizmos()
{
if (Handles.ShouldRenderGizmos())
{
context.DrawGizmos(camera, GizmoSubset.PreImageEffects);
context.DrawGizmos(camera, GizmoSubset.PostImageEffects);
}
}

Draw Unity UI

添加一个UI物件时,它能成功在Game窗口被渲染出来,因为它是独立渲染的,不依靠我们的自定义管线,它的默认Render Mode是Screen Space - OverLay。UI在Scene里面是使用World Space Mode渲染的,所以它会显得很大。现在把它在Scene里面画出来:

1
2
3
4
5
6
7
partial void PrepareForSceneWindow()
{
if (camera.cameraType == CameraType.SceneView)
{
ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
}
}

Multiple Cameras

多相机的渲染会比较麻烦,需要小心地设置渲染顺序,也就是对ClearFlag的设置。


Author: cdc
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source cdc !
  TOC