0%

Unity SRP Misc

渲染指定物体

在SRP里面一个非常重要的函数是ScriptableRenderContext.DrawRenderers,通过它可以帮我们完成整个场景的渲染。但是这个函数用起来很方便,因为它封装了很多细节。但是在游戏UI中经常会用RenderTexture来混合UI和模型,就需要将个别物体渲染到RenderTexture。如果我们只想渲染某一两个物体时,操作起来就比较麻烦,因为首先我们需要将需要渲染的物体ScriptableRenderContext.Cull出来,这个剔除操作是针对整个场景的,必然有额外的消耗。特别是我们已经知道要渲染哪个物体的情况下,我们可能希望跳过剔除这一步。但是跳过剔除,ScriptableRenderContext.DrawRenderers函数就用不了了。

这时候我们可以使用CommandBuffer.DrawRanderer函数,针对单个物体进行渲染。具体操作就是获取所有物体的Render组件、材质,使用CommandBuffer渲染。示例代码如下:

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
public class RenderData{
public Renderer renderer;
public Material material;
public float sortingFudge;
public int materialIndex;
}
int Comparison(RenderData a, RenderData b){
if(a.material.renderQueue == b.material.renderQueue){
if(a.sortingFudge == b.sortingFudge){
string aBlendMode = a.material.GetTag("BlendMode", false);
string bBlendMode = b.material.GetTag("BlendMode", false);
if(aBlendMode == bBlendMode)
return a.materialIndex - b.materialIndex;
else
return aBlendMode == "AlphaBlend" ? -1 : 1;
}
else
{
return a.sortingFudge >b.sortingFudge ? -1 : 1;
}
}
else
{
return a.material.renderQueue - b.material.renderQueue;
}
}
List<RenderData> renderList = new List<RenderData>();
List<Renderer> tempRenders = new List<Renderer>();
void CalculateRenderData(GameObject go){
tempRenders.Clear();
renderList.Clear();
go.GetComponentsInChildren<Renderer>(tempRenders);
foreach(var render in tempRenders){
float sortingFudge = 0;
if(render is SkinnedMeshRenderer sRender){
sRender.updateWhenOffscreen = true;
}
else if(render is ParticleSystemRenderer pRender){
sortingFudge = pRender.sortingFudge;
}
var materials = render.sharedMaterials;
int index = -1;
foreach(var material in materials){
index = index + 1;
if(material == null) continue;
RenderData renderData = new RenderData{
renderer = render,
material = material,
materialIndex = index,
sortingFudge = sortingFudge,
};
renderList.Add(renderData);
}
}
}
ScriptableRenderContex context;
Camera camera;
void RenderSingleObject(GameObject go, RenderTexture rt){
CalculateRenderData(go);
Camera.SetupCurrent(camera);
CommandBuffer cmd = CommandBufferPool.Get();
context.SetupCameraProperties(camera);
cmd.SetRenderTarget(rt);
cmd.ClearRenderTarget(true, true, Color.clear);
foreach(var data in renderList){
if(data.material.passCount > 0){
for(int i = 0; i< data.material.passCount; ++i){
cmd.DrawRenderer(data.renderer, data.material, data.materialIndex, i);
}
}
}
context.ExecuteCommandBuffer(cmd);
CommandBufferPool.Release(cmd);
}

这里有几个点需要注意:

  • 首先是Camera.SetupCurrent(camera),如果没有这行代码的话,粒子系统是不会渲染的参考
  • ScriptableRenderContext.DrawRenderers支持SRPBatch,但是其他绘制函数不一定支持,比如这里的CommandBuffer.DrawRanderer,这是当前方法的一个弊端。但是Unity2022版本增加了BatchRendererGroup据说是可以实现SRPBatch,参考这里,但是WebGL目前还不支持。BatchRendererGroup的原理是在SRPBatch上扩展的自定义合批方法。
  • 这里也有对剔除的探讨,但是好像并没有什么有价值的东西,暂且留个入口。
  • 另外,Animator组件和ParticleSystem组件上都带有CullingMode属性,一般来说当物体不可见时,我们希望动画和粒子系统都停止,从而减少性能消耗。CullingMode就是用来控制物体不可见时的行为。当使用ScriptableRenderContext.DrawRenderers绘制时,会自动标记物体是否可见的状态,所以通常设置为Automatic,也就是说不可见时停止,可见时照常运行。但是使用CommandBuffer.DrawRanderer绘制时,并不会标记这些可见状态,所以需要强制设置为Always Simulate,当然有些粒子系统设置为Automatic也能正常运行,这个应该是Unity的bug。
  • 当然,我非要用ScriptableRenderContext.DrawRenderers渲染某一个物体也不是不行,就是要把剔除流程走一遍。如果我们需要将多个物体分别渲染到多个纹理上呢?是不是就的把整个流程走多遍?有一种做法,可以只走一边剔除流程,就可以渲染多个模型到多个贴图上。FilteringSettings.renderingLayerMask这个参数就是可以用来控制本次渲染渲染指定渲染层的模型,我们可以为不同的模型设置不同的层,渲染多张纹理的时候把FilteringSettings.renderingLayerMask设置到对应的层。渲染层总共有32层,也就是最多可分32个独立渲染的物体。我之前想过渲染一次设置一次模型的渲染层,这样看起来就可以渲染无数多个独立渲染的物体,但是实际上这是行不通的,因为整个指令是CommandBuffer缓存执行,也就是说动态修改物体的渲染层是无效的,CommandBuffer指令只知道最后一次设置的渲染层。