由于我们的管线只支持unlit着色器pass,因此使用不同pass的对象不会被渲染,所以不可见。虽然理论上这是正确的,但会和场景中绘制错误shader的物件混淆。所以,我们需要分开对他们进行渲染。
如果有人从默认的Unity项目开始,然后切换到我们的渲染管线,那么场景中可能会有使用错误着色器的对象。为了涵盖所有Unity的默认着色器,我们需要使用Always、ForwardBase、PrepassBase、Vertex、VertexLMRGBM和VertexLM的着色器标签ID。我们将这些放在一个静态数组中。
static ShaderTagId[] legacyShaderTagIds = {
newShaderTagId("Always"),newShaderTagId("ForwardBase"),newShaderTagId("PrepassBase"),newShaderTagId("Vertex"),newShaderTagId("VertexLMRGBM"),newShaderTagId("VertexLM")};
在绘制完可见几何体之后,使用一个单独的方法绘制所有不受支持的着色器,首先绘制第一个pass。由于这些pass是无效的,结果将无论如何都是错误的,因此我们不关心其他设置。我们可以通过 FilteringSettings.defaultValue 属性获取默认的过滤设置。
public void Render (ScriptableRenderContext context, Camera camera) {
Setup();DrawVisibleGeometry();DrawUnsupportedShaders();Submit();}void DrawUnsupportedShaders () {var drawingSettings = newDrawingSettings(legacyShaderTagIds[0], newSortingSettings(camera));var filteringSettings = FilteringSettings.defaultValue;context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);}
我们可以通过在绘制设置中使用 draw order 索引和标签作为参数,调用 SetShaderPassName 来绘制多个pass。对于数组中的所有pass,从第二个开始进行此操作,因为在构造绘制设置时已经设置了第一个pass。
var drawingSettings = newDrawingSettings(
legacyShaderTagIds[0], newSortingSettings(camera));for (int i = 1; i < legacyShaderTagIds.Length; i++) {drawingSettings.SetShaderPassName(i, legacyShaderTagIds[i]);}

standard shader呈现黑色
为了明确指示哪些对象使用了不支持的着色器,我们将使用Unity的错误着色器来绘制它们。通过将 Hidden/InternalErrorShader 字符串作为参数调用 Shader.Find 来查找该着色器。通过静态字段缓存材质,这样我们就不会在每一帧创建一个新的材质。然后将它分配给绘制设置的 overrideMaterial 属性。
staticMaterial errorMaterial;
void DrawUnsupportedShaders () {if (errorMaterial == null) {errorMaterial =new Material(Shader.Find("Hidden/InternalErrorShader"));}var drawingSettings = new DrawingSettings(legacyShaderTagIds[0], newSortingSettings(camera)) {overrideMaterial = errorMaterial};}

用紫色来绘制错误的shader
现在就可以直观看到所有错误的对象了。
绘制无效对象对于开发很有用,但不适用于发布的应用程序。因此,让我们将相机渲染器中所有仅用于编辑器的代码放在一个单独的部分类文件中。首先,复制原始的相机渲染器脚本资源,并将其重命名为 CameraRenderer.Editor

一个class两个scripts
然后,将原始的CameraRenderer转换为Partial Class,并从中删除标记数组、错误材质和 DrawUnsupportedShaders 方法。
publicpartialclassCameraRenderer { … }
清理其他的partial class文件只留下我们从其他地方移动过来的文件
using UnityEngine;
using UnityEngine.Rendering;partialclassCameraRenderer {static ShaderTagId[] legacyShaderTagIds = {… };staticMaterial errorMaterial;void DrawUnsupportedShaders () { … }}
编辑器的Content部分只存在编辑器中,所以给一个宏UNITY_EDITOR
partialclassCameraRenderer {#if UNITY_EDITORstaticShaderTagId[] legacyShaderTagIds = { … }};staticMaterial errorMaterial;void DrawUnsupportedShaders () { … }#endif}
然而,此时进行构建将会失败,因为另一部分始终包含对 DrawUnsupportedShaders 的调用,而它现在仅在编辑器中存在。为了解决这个问题,我们也将这个方法设置为部分方法。我们可以在类定义的任何部分中进行声明,就像抽象方法声明一样,在方法签名前面添加 partial 关键字。让我们将其放在编辑器部分。完整的方法声明也必须标记为 partial。
partialvoid DrawUnsupportedShaders ();#if UNITY_EDITOR…partialvoid DrawUnsupportedShaders () { … }#endif
现在编译构建成功。编译器将会剥离掉所有没有完整声明的部分方法的调用。

没有Gizmos的场景
我们可以通过调用 UnityEditor.Handles.ShouldRenderGizmos 来检查是否应该绘制 Gizmos。如果是,则需要使用相机作为参数,在上下文中调用 DrawGizmos 方法,并传入第二个参数来指示应该绘制哪个 Gizmo 子集。有两个子集,用于图像特效之前和之后。由于我们当前不支持图像特效,我们将同时调用这两个子集。请在一个新的仅限于编辑器的 DrawGizmos 方法中完成这个操作。
using UnityEditor;using UnityEngine;using UnityEngine.Rendering;partialclassCameraRenderer {partialvoid DrawGizmos ();partialvoid DrawUnsupportedShaders ();#if UNITY_EDITOR…partialvoid DrawGizmos () {if (Handles.ShouldRenderGizmos()) {context.DrawGizmos(camera, GizmoSubset.PreImageEffects);context.DrawGizmos(camera, GizmoSubset.PostImageEffects);}}partialvoid DrawUnsupportedShaders () { … }#endif}
Gizmo应该在所有物件之后绘制
publicvoid Render (ScriptableRenderContext context, Camera camera) {…Setup();DrawVisibleGeometry();DrawUnsupportedShaders();DrawGizmos();Submit();}

有了gizmos的场景
还有一件需要我们关注的事情是Unity的游戏内用户界面。例如,通过GameObject / UI / Button来添加一个简单的按钮来创建一个UI。它会在游戏窗口中显示出来,但不会在场景窗口中显示。

game窗口的ui按钮
帧调试器显示ui是单独渲染的,而不是我们的RP渲染的

帧调试器中的ui
至少,在画布组件的渲染模式设置为屏幕空间 - 覆盖时是这样的,这也是默认设置。将其更改为屏幕空间 - 摄像机,并使用主摄像机作为其渲染摄像机,将使其成为透明几何体的一部分。

帧调试器中的屏幕空间相机ui
当 UI 在场景窗口中渲染时,它始终使用世界空间坐标,这就是为什么它通常会变得非常大的原因。但是虽然我们可以通过场景窗口编辑 UI,但它不会被绘制出来。

ui在场景窗口中不可见
当渲染场景窗口时,我们必须通过调用ScriptableRenderContext.EmitWorldGeometryForSceneView 方法将 UI 添加到世界几何体中,并将相机作为参数传入。在一个新的仅限编辑器使用的 PrepareForSceneWindow 方法中进行这个操作。我们在场景摄像机的 cameraType 属性等于 CameraType.SceneView 时进行渲染。
partialvoid PrepareForSceneWindow ();
#if UNITY_EDITOR…partialvoid PrepareForSceneWindow () {if (camera.cameraType == CameraType.SceneView) {ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);}}
由于这可能会向场景添加几何图形,因此必须在剔除之前完成。
PrepareForSceneWindow();if (!Cull()) {return;}

ui在场景窗口中可见
场景中可能不止一台摄像机,所以我们需要保证都一起工作。
每个相机都有一个深度值,对于默认的主相机来说,这个值是 -1。它们按照深度递增的顺序进行渲染。为了验证这一点,可以复制主相机,将其重命名为 Secondary Camera,并将其深度值设置为 0。另外,给它一个不同的标签也可以,因为 MainCamera 应该只被单个相机使用。

两台相机都分组在一个标签类中
现在场景被渲染了两次。由于渲染目标在之间被清除,所以得到的图像仍然是相同的。帧调试器显示了这一点,但是由于具有相同名称的相邻采样范围被合并,我们最终得到了一个单独的 Render Camera 范围。
如果每个相机都有自己的范围,那就更清晰了。为了实现这一点,添加一个仅用于编辑器的 PrepareBuffer 方法,使缓冲区的名称与相机的名称相同。
partialvoid PrepareBuffer ();
#if UNITY_EDITOR…partialvoid PrepareBuffer () {buffer.name = camera.name;}#endif
在我们准备场景窗口之前调用它。
PrepareBuffer();PrepareForSceneWindow();

每个相机单独标签类
尽管帧调试器现在显示了每个相机的单独采样层次结构,但当我们进入播放模式时,Unity 的控制台会充斥着警告消息,告诉我们 BeginSample 和 EndSample 的计数必须匹配。它会混淆因为我们为采样和缓冲区使用了不同的名称。除此之外,每次访问相机的名称属性时,我们还会分配内存,所以我们不希望在构建中这样做。
为了解决这两个问题,我们将添加一个 SampleName 字符串属性。如果我们在编辑器中,我们会在 PrepareBuffer 方法中设置它,同时设置缓冲区的名称;否则,它只是一个 Render Camera 字符串的常量别名。
#if UNITY_EDITOR… string SampleName { get; set; }…partialvoid PrepareBuffer () {buffer.name = SampleName = camera.name;}#elseconststring SampleName = bufferName;#endif
为Setup和Submit标签类使用SamplerName
void Setup () {
context.SetupCameraProperties(camera);buffer.ClearRenderTarget(true, true, Color.clear);buffer.BeginSample(SampleName);ExecuteBuffer();}void Submit () {buffer.EndSample(SampleName);ExecuteBuffer();context.Submit();}
我们可以通过检查性能分析器(通过窗口/分析/性能分析器打开)来看到差异,并首先在编辑器中播放。切换到层级模式并按 GC Alloc 列进行排序。您会看到两个 GC.Alloc 的调用,共分配了 100 字节,这是由于检索相机名称所引起的。在更下面,你将看到这些名称显示为样本:Main Camera 和 Secondary Camera。

具有单独的标签类和100b allocations的分析器
接下来,制作一个启用了“Development Build”和“Autoconnect Profiler”的构建。点击build,并确保分析器已连接并记录。在这种情况下,我们不会有100字节的分配,而是只会得到单个的 Render Camera 标签类。

profiling build
我们可以通过在一个名为“Editor Only”的性能采样中包装相机名称检索来清楚地表示我们只在编辑器中分配内存,而不在构建中分配内存。在这种情况下,我们需要从 UnityEngine.Profiling 命名空间调用 Profiler.BeginSample 和 Profiler.EndSample。只需要将名称传递给 BeginSample。
using UnityEditor;
using UnityEngine; using UnityEngine.Profiling;using UnityEngine.Rendering;partialclassCameraRenderer {…#if UNITY_EDITOR…partialvoid PrepareBuffer () {Profiler.BeginSample("Editor Only");buffer.name = SampleName = camera.name;Profiler.EndSample();}#elsestring SampleName => bufferName;#endif}

Editor Only
相机还可以配置为只能看到特定层上的物体。这是通过调整它们的剔除掩码(Culling Mask)来实现的。为了看到它的效果,我们将使用标准着色器的所有对象移动到“Ignore Raycast”层。

层切换到Ignore Raycast
从主摄像机的Culling Mask中取消勾选Ignore Raycast。

剔除掉Ignore Raycast
并让他在第二个camera中唯一可见

只显示ignore raycast
因为第二个摄像机最后渲染,所以我们只能看到无效的对象。

只渲染ignore raycast
我们可以通过调整第二个相机的清除标志(clearFlags)来合并两个相机的结果。它们由 CameraClearFlags 枚举定义,我们可以通过相机的 clearFlags 属性获取它们。在清除之前,在 Setup 中进行此操作。
void Setup () {
context.SetupCameraProperties(camera);CameraClearFlags flags = camera.clearFlags;buffer.ClearRenderTarget(true, true, Color.clear);buffer.BeginSample(SampleName);ExecuteBuffer();}
CameraClearFlags 枚举定义了四个值。从 1 到 4,它们分别是 Skybox、Color、Depth 和 Nothing。实际上,这些不是独立的标志值,而是表示清除量递减的。在所有情况下(除了最后一个),都必须清除深度缓冲区,因此很多标志值都为 Depth 。
buffer.ClearRenderTarget(flags true, Color.clear);
当标志设置为 Color 时,我们实际上只需要清除颜色缓冲区,因为在 Skybox 的情况下,我们最终会替换所有先前的颜色数据。
buffer.ClearRenderTarget(flags flags == CameraClearFlags.Color,Color.clear);
在Unity 2022中,我更改为始终清除颜色,除非明确告知不清除,因为渲染目标可能包含非数字和无穷大的值,这可能会导致混合伪影。此外,帧调试器可能显示随机数据,这会使调试变得困难。
如果我们要清除为纯色,我们必须使用相机的背景颜色。但是,因为我们在线性颜色空间中进行渲染,所以我们必须将该颜色转换为线性空间,因此我们最终需要使用 camera.backgroundColor.linear。在所有其他情况下,颜色并不重要,因此我们可以使用 Color.clear。
buffer.ClearRenderTarget(
flags <= CameraClearFlags.Depth,flags == CameraClearFlags.Color,flags == CameraClearFlags.Color ?camera.backgroundColor.linear :Color.clear);
因为主相机是第一个进行渲染的,所以它的清除标志应设置为 Skybox 或 Color。当启用帧调试器时,我们始终从清除缓冲区开始,但一般情况下不能保证这一点。
第二个相机的清除标志决定了两个相机的渲染结果如何合并。在使用天空盒或颜色进行清除时,先前的结果将完全被替换。当仅清除深度时,次要相机以正常方式进行渲染,只是不绘制天空盒,所以先前的结果会显示为背景。当不进行任何清除时,深度缓冲区将被保留,因此未照亮的对象会遮挡无效对象,就好像它们是由同一相机绘制的一样。然而,由上一个相机绘制的透明对象没有深度信息,因此会被绘制在上面,就像之前的天空盒一样。
依次是清除颜色,depth-only以及都不清除
通过调整相机的 Viewport Rect,还可以将渲染区域缩小为整个渲染目标的一部分。渲染目标的其余部分不受影响。在这种情况下,清除操作使用了 Hidden/InternalClear 着色器。模板缓冲区用于限制渲染到视口区域。

缩小第二个相机的视口,清除color
注意每帧渲染多个相机意味着需要进行多次裁剪、设置、排序等操作。通常,每个独特视角使用一个相机是最高效的方法。
原文:
https://catlikecoding.com/unity/tutorials/custom-srp/custom-render-pipeline/
文章转载自
Thepoly