Unity CRP学习笔记(一)
主要参考:
https://catlikecoding.com/unity/tutorials/custom-srp/
https://docs.unity.cn/cn/2022.3/ScriptReference/index.html
中文教程部分参考(可选):
https://tuncle.blog/custom_render_pipeline/index.html
https://edu.uwa4d.com/lesson-detail/282/1308/0(依照Unity 2019版的内容翻译,不太适用于Unity 2022)
本文主要是,在参考以上内容学习的过程中,对一些基于已有内容但还是不太能理解的部分进行了一些额外的补充。
Custom Render Pipeline
涉及的C#代码如下,对我自己不太理解的部分做了尽量详细的注释。对于无法在代码中简单说明的部分,在代码部分之后尝试进行了较为详细的解释。
/*CustomRenderPipelineAsset.cs*/
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
[CreateAssetMenu(menuName = "Rendering/CustomRenderPipeline")]
public class CustomRenderPipelineAsset : RenderPipelineAsset
{
protected override RenderPipeline CreatePipeline()
{
return new CustomRenderPipeline();
}
}
/*CustomRenderPipeline*/
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
public class CustomRenderPipeline : RenderPipeline {
CameraRenderer renderer = new CameraRenderer();
protected override void Render(ScriptableRenderContext context, Camera[] cameras){ }
protected override void Render(ScriptableRenderContext context, List<Camera> cameras)
{
for (int i = 0; i < cameras.Count; i++)
{
renderer.Render(context, cameras[i]);
}
}
}
/*CameraRender.cs*/
using System;
using UnityEngine;
using UnityEngine.Rendering;
public partial class CameraRenderer
{
ScriptableRenderContext context;
Camera camera;
CullingResults cullingRes;
static ShaderTagId unlitShaderTagId = new ShaderTagId("SRPDefaultUnlit");
CommandBuffer buffer = new CommandBuffer();
public void Render(ScriptableRenderContext context, Camera camera)
{
this.context = context;
this.camera = camera;
buffer.name = camera.name;
//setup
//设置物体渲染的参数
SortingSettings sortingOpaqueSettings = new SortingSettings(camera)
{
criteria = SortingCriteria.CommonOpaque
};
SortingSettings sortingTransparentSettings = new SortingSettings(camera)
{
criteria = SortingCriteria.CommonTransparent
};
DrawingSettings drawingOpaqueSettings = new DrawingSettings(unlitShaderTagId, sortingOpaqueSettings);
DrawingSettings drawingTransparentSettings = new DrawingSettings(unlitShaderTagId, sortingTransparentSettings);
//根据RenderQueueRange设定的范围筛选物体,RenderQueueRange可以在shader中设置(通过 Tags { "RenderType"="Opaque" })也可以在C#脚本中设置
FilteringSettings filteringOpaqueSettings = new FilteringSettings(RenderQueueRange.opaque);
FilteringSettings filteringTransparentSettings = new FilteringSettings(RenderQueueRange.transparent);
//向命令缓存区中增加开启采样的命令,其实就是启用对缓冲区中指令执行的性能分析,其中name参数只标识该采样,在Profiler中采样域名称为buffer.name
buffer.BeginSample(camera.name);
//传入当前相机的view和projection矩阵,否则unity_MatrixVP为固定的默认值
context.SetupCameraProperties(camera);
//压入清空缓冲区的指令
CameraClearFlags flags = camera.clearFlags;
buffer.ClearRenderTarget(flags <= CameraClearFlags.Depth, flags == CameraClearFlags.Color,
flags == CameraClearFlags.Color ? camera.backgroundColor.linear : Color.clear);
//需要在渲染前执行缓冲区指令,否则渲染结果就被直接清空了
context.ExecuteCommandBuffer(buffer);
buffer.Clear();
//在scene相机中渲染UI
DrawSceneViewUnityUI();
//剔除视野外的物体
if (!Cull()) return;
//render
//先渲染DrawRender
context.DrawRenderers(cullingRes, ref drawingOpaqueSettings, ref filteringOpaqueSettings);
DrawUnsupportShader();
context.DrawSkybox(camera);
context.DrawRenderers(cullingRes, ref drawingTransparentSettings, ref filteringTransparentSettings);
DrawGizmos();
//关闭采样
buffer.EndSample(camera.name);
context.ExecuteCommandBuffer(buffer);
buffer.Clear();
//submit(end)
//context发送的渲染命令都是缓冲的,需要提交执行
context.Submit();
}
bool Cull()
{
ScriptableCullingParameters p;
if(camera.TryGetCullingParameters(out p))
{
cullingRes = context.Cull(ref p);
return true;
}
return false;
}
}
/*CameraRender.editor.cs*/
using System;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEditor;
public partial class CameraRenderer
{
partial void DrawUnsupportShader();
partial void DrawGizmos();
partial void DrawSceneViewUnityUI();
#if UNITY_EDITOR
static Material errorMaterial = new Material(Shader.Find("Hidden/InternalErrorShader"));
//内置渲染管线材质,ShaderTagId的参数是在Shader Pass中的LightMode设定的
static ShaderTagId[] legacyShaderTagIds = {
new ShaderTagId("Always"),
new ShaderTagId("ForwardBase"),
new ShaderTagId("PrepassBase"),
new ShaderTagId("Vertex"),
new ShaderTagId("VertexLMRGBM"),
new ShaderTagId("VertexLM")
};
partial void DrawUnsupportShader()
{
//设置所有需要渲染的对象的材质为errorMaterial
var drawingSettings = new DrawingSettings(legacyShaderTagIds[0], new SortingSettings(camera)) { overrideMaterial = errorMaterial };
for(int i = 1; i < legacyShaderTagIds.Length; ++i)
{
//添加其他legacy的Shader Pass Name
drawingSettings.SetShaderPassName(i, legacyShaderTagIds[i]);
}
FilteringSettings filteringSettings = FilteringSettings.defaultValue;
context.DrawRenderers(cullingRes, ref drawingSettings, ref filteringSettings);
}
partial void DrawGizmos()
{
if (Handles.ShouldRenderGizmos())
{
//GizmoSubset.PreImageEffects表示受后处理影响的Gizmos,GizmoSubset.PostImageEffects表示不受后处理影响的部分
context.DrawGizmos(camera, GizmoSubset.PreImageEffects);
context.DrawGizmos(camera, GizmoSubset.PostImageEffects);
}
}
partial void DrawSceneViewUnityUI()
{
if(camera.cameraType == CameraType.SceneView)
{
ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
}
}
#endif
}
Render Mode
Unity RenderMode有三种。
根据unity doc:
ScreenSpace-Overlay 使用 2D Canvas 在场景末端渲染。
ScreenSpace-Camera 使用在 Canvas上配置的 Camera 渲染。
WorldSpace 使用场景中可渲染该层的任意 Camera 渲染。
ScreenSpace-Overlay效果是在屏幕空间中最后渲染UI,UI会覆盖所有物体。
ScreenSpace-Camera则Canvas会有一个深度,根据深度值渲染,所以物体可能覆盖UI。
在游戏运行时的摄像机中(CameraType为Game),ScreenSpace-Overlay和ScreenSpace-Camera都是由Unity UI package实现渲染的,而WorldSpace则会经过SRP渲染。
而在场景窗口中(CameraType为SceneView),三种RenderMode都是在WorldSpace下渲染的,所以在Scene中会看到Canvas和其他物体一样,固定在世界空间中不随相机运动改变位置。
注:根据custom-render-pipeline实现的PrepareForSceneWindow函数依然不能在Game窗口中在WorldSpace下渲染出UI,如需要可以自行实现。
多相机
custom-render-pipeline中实现的多相机,只是更改了性能分析时的作用域名,且代码不涉及缓冲区。在没有明确选择缓冲区的情况下,渲染将默认到屏幕,所以两个相机会相继渲染到同一个缓冲区,但多相机通常应该是每个相机都渲染到自己的缓冲区。此时,如果去改变RenderMode为ScreenSpace-Camera则会出现一些问题。
RenderMode为ScreenSpace-Overlay时
在所有渲染完成后绘制GUI,所以GUI会在Game窗口中正常显示。
RenderMode为ScreenSpace-Camera时
GUI的渲染被放到了第一次渲染半透明物体的部分中且先与半透明物体进行渲染,而当再次渲染SecondCamera时GUI就不会再次进行渲染了,导致GUI不会显示。猜测ScreenSpace-Camera的GUI渲染机制是当渲染RenderQueueRange为transparent时(或者渲染队列值超过某个特定值),会回调某个函数开始渲染GUI。
Clear Flags
Clear Flags是Camera内置的参数,共有四个参数,分别为Skybox,Solid Color,Depth only,Don’t Clear。如果使用SRP渲染则该参数基本失效(这里用基本是因为context.DrawSkybox会判断camera.clearFlags是否为Skybox,判断通过才会绘制天空盒,否则即使用了context.DrawSkybox也不会绘制天空盒),为了让其依然能达到在URP中原有的效果,所以需要修改ClearRenderTarget的实现方式。
C#相关内容(按出现顺序)
C# {} 对象初始化器
在CRP中,经常能看到使用 {} 而不是 () 来进行对象的初始化,使用 {} 的原因是如果想要使用 (),需要有对应支持的构造函数,而 {} 可以直接对对象的公开属性或字段进行初始化,非常灵活。
SortingSettings sortingSettings = new SortingSettings(camera)
{
criteria = SortingCriteria.CommonOpaque
};
例如在上述代码块中,可以只创建一个构造函数,通过 {} 根据需要来对camera以外的其他属性或字段进行初始化。
C#函数参数传递方式
1.按值传递(不必多言)
2.out 关键字
out 关键字的主要作用是允许方法在返回时修改该参数的值,并将结果传递回调用者,而不需要通过返回值来传递结果。
例如camera.TryGetCullingParameters(out p),返回值是bool值,同时还可以传递赋值后的变量p。
3.ref 关键字
同样是允许修改参数并传递回调用者,区别必须是已经初始化的变量(传递 out 参数时,调用方不需要预先初始化参数)。
C# partial关键字
1.partial类
partial 类允许将一个类的定义拆分为多个部分(文件或代码块)进行编写。
2.partial函数
partial 函数允许开发者将函数的定义和具体实现分开。使用 partial 关键字的方法可以没有定义全部实现,如果某个 partial 方法没有在其他地方定义实现,编译器不会报错,并且该方法调用将被忽略,没有任何运行时开销。