Custom SRP - Custom Render Pipeline

https://catlikecoding.com/unity/tutorials/custom-srp/custom-render-pipeline/

    1. 新建 Render Pipeline

    任何内容的渲染,最终都是要由 unity 决定在哪里,什么时候,以哪些参数进行渲染。根据目标效果的复杂程度,决定渲染的过程也很复杂。灯光,阴影,透明,图像效果,体积效果等,必须以特定的顺序渲染到最终的图像。

    实际项目中,建议从URP定制管线。本教程依然是从头定制管线。

    本篇教程展示基于前向渲染最简单的 unlit 对象。之后会逐步加入光照,阴影等其它高级效果。

    1.1 项目设置

    创建3D项目。注意不要创建URP/HDRP项目。之后,可以到 Package Manager 中移除我们不需要的 package。我们只需要 Unity UI package 。

    我们的项目要使用 linear color space,在 Edit/Project Settings/Player,平台设置区域,Other Settings中,找到 Rendering,检查并确保切换到 linear color space。

    在场景中创建几个对象,并为其指定材质:

    • 红色立方体:Standard shader

    • 绿色,黄色立方体:Unlit/Color

    • 蓝色球:Standard shader,并切换到透明模式,指定贴图

    • 白色球:Unlit/Transparent

    1.2 Pipeline Asset

    我们按照URP的目录组织方式,创建我们的目录,并创建我们的 Pipeline Asset

    创建 CustomeRenderPipelineAsset.cs

    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Rendering;[CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline")]
    public class CustomRenderPipelineAsset : RenderPipelineAsset 
    {protected override RenderPipeline CreatePipeline(){return null;}
    }
    • [CreateAssetMenu] 语义,向资产右键创建菜单中添加菜单项

    • RP Asset 必须派生自 RenderPipelineAsset

    • 必须实现 CreatePipeline 接口。Unity 通过调用该方法创建 RP 实例

    在Asset窗口,右键 Create/Rendering/Custom Render Pipeline,创建 CustomeRenderPipeline.asset

    在 Project Settings/Graphics 窗口,指定我们的管线:

    由于我们目前没有创建管线实例,因此,整个 Unity 的渲染窗口,都不会执行任何渲染。

    1.3 Render Pipeline Instance

    创建 CustomRenderPipeline.cs

    using System.Collections.Generic;
    using UnityEngine;
    using UnityEngine.Rendering;public class CustomRenderPipeline : RenderPipeline 
    {// RenderPipeline 定义的抽象接口,必须实现。但是由于 cameras 每帧分配内存,因此废弃了。// 保持该方法为空即可protected override void Render(ScriptableRenderContext context, Camera[] cameras) { }// RenderPipeline 渲染入口protected override void Render(ScriptableRenderContext context, List<Camera> cameras){ }
    }

      2. 渲染

      Unity 每帧调用 RP 实例的 Render 来执行渲染:

      • ScriptableRenderContext 提供引擎渲染接口,连接 Native Engine,我们将用该对象完成渲染。

      • cameras 场景中可能会使用多个对象,Unity 根据顺序,用该参数传入。

      2.1 Camera Renderer

      每个 Camera 都需要独立渲染,我们可以直接在 CustomRenderPipeline.Render 中实现渲染逻辑,但是渲染逻辑代码量会很大,为了代码结构更易维护,更清晰,我们专门建立一个类,来渲染每个摄像机。为了方便,缓存下渲染参数。

      using UnityEngine;
      using UnityEngine.Rendering;public class CameraRenderer
      {ScriptableRenderContext context;Camera camera;public void Render(ScriptableRenderContext context, Camera camera){this.context = context;this.camera = camera;}
      }

      基于 CameraRenderer,RP中的渲染代码看起来是这样的:

      public class CustomRenderPipeline : RenderPipeline 
      {CameraRenderer cameraRenderer = new CameraRenderer();// RenderPipeline 渲染入口protected override void Render(ScriptableRenderContext context, List<Camera> cameras){ for(int i = 0; i < cameras.Count; i++){// 用 CameraRenderer 渲染每个摄像机cameraRenderer.Render(context, cameras[i]);}}
      }

      URP 中也是定义了 CameraRenderer 来执行渲染。用这种方法,如果未来希望每个摄像机用不同的方式渲染,扩展起来会很方便,例如一个摄像机是 first-person 视口,而另一个用来渲染3D地图,或者使用 forward / deferred 渲染。

      2.2 Draw the Skybox

      CameraRenderer 渲染指定摄像机可以“看到”的对象。为了代码的清晰,把这些任务实现到独立的方法 DrawVisibleGeometry 中,同时先把 Skybox 绘制出来。

      渲染时,通过方法 SetupCameraProperties 设置摄像机的VP矩阵,该矩阵可以在 shader 中,以 unity_MatrixVP 来访问。

      public class CustomRenderPipeline : RenderPipeline 
      {CameraRenderer cameraRenderer = new CameraRenderer();// RenderPipeline 渲染入口protected override void Render(ScriptableRenderContext context, List<Camera> cameras){ for(int i = 0; i < cameras.Count; i++){// 用 CameraRenderer 渲染每个摄像机cameraRenderer.Render(context, cameras[i]);}}
      }

      现在,渲染视口将正常渲染 Skybox,并且可以旋转摄像机看到天空盒的不同角度。

      2.3 Command Buffers

      只有我们 Submit 之后,Context 才会渲染。在这之前,我们可以进行配置,以及添加我们的渲染指令。像绘制天空这种,有专门的接口来提交渲染,但是其它的渲染,则需要通过另外的 CommandBuffer 来进行渲染。场景中其它几何体的渲染,就是用 CommandBuffer 来渲染的。

      创建 CommandBuffer 我们可以直接创建一个 CommandBuffer,同时可以给它起名字,以在 Frame Debugger 中看到。

      分析 CommandBuffer CommandBuffer 可以注入分析,通过调用 BeginSampleEndSample 实现。分析数据可以显示在 Profiler 和 Frame Debugger 中。

      执行 CommandBuffer CommandBuffer 执行通过调用 ExecuteCommandBuffer 。该方法将拷贝指令,不会清空它。我们后面要继续复用该 CommandBuffer,因此我们要手动 Clear。我们把该流程定义成 ExecuteBuffer 方法。

      现在,代码看起来是这样

      public class CameraRenderer
      {ScriptableRenderContext context;Camera camera;const string bufferName = "Render Camera";CommandBuffer buffer = new CommandBuffer{name = bufferName};public void Render(ScriptableRenderContext context, Camera camera){this.context = context;this.camera = camera;Setup();DrawVisibleGeometry();Submit();}void Setup(){buffer.BeginSample(bufferName);ExecuteBuffer();context.SetupCameraProperties(camera);}void DrawVisibleGeometry(){context.DrawSkybox(camera);}void Submit(){buffer.EndSample(bufferName);ExecuteBuffer();context.Submit();}void ExecuteBuffer(){context.ExecuteCommandBuffer(buffer);buffer.Clear();}
      }

      2.4 Clearing the Render Target

      渲染结果最终体现在 Render Target 上,为了避免上一帧(也可能是上上帧)的图像对当前帧产生影响,每次渲染,我们都要清理 Render Target,通过调用 CommandBuffer.ClearRenderTarget 完成。

      ClearRenderTarget 会自动封装一个以 CommandBuffer 的名字的采样,因此在 FrameDebugger 中会出现嵌套

      先执行 Clear,再启用我们的 Sample ,可以避免。

      如果执行 Clear 时,还没有执行 SetupCameraProperties,Unity 会用 Hidden/InternalClear Shader 来渲染一个矩形的方式来“清理”(Draw GL),这种方式相对很慢。我们可以先执行 SetupCameraProperties,再 Clear,这样 Unity 会通过API层的 Clear 调用来完成清理,效率高得多。

      现在,代码是这样

          void Setup(){context.SetupCameraProperties(camera);buffer.ClearRenderTarget(true, true, Color.clear);buffer.BeginSample(bufferName);ExecuteBuffer();}

      2.5 Culling

      根据当前摄像机,裁剪出所有在视锥体内的 Renderer Component。

      • camera.TryGetCullingParameters(out ScriptableCullingParameters p)

      • CullingResults cullingResults = context.Cull(ref p);

      定义一个 Cull 方法实现裁剪,如果成功,则获取裁剪结果:

          CullingResults cullingResults;bool Cull(){if(camera.TryGetCullingParameters(out ScriptableCullingParameters p)){cullingResults = context.Cull(ref p);return true;}return false;}

      在渲染中,执行裁剪,如果失败,则中止渲染,直接返回。

          CullingResults cullingResults;bool Cull(){if(camera.TryGetCullingParameters(out ScriptableCullingParameters p)){cullingResults = context.Cull(ref p);return true;}return false;}

      2.6 Draw Geometry 分别绘制不透明和透明物体

      得到裁剪结果后,就可以通过 context.DrawRenderers 来渲染他们了。在调用该接口前,需要进行设置:

      • DrawingSettings

        • 通过 ShaderTagId 指定绘制 shader 的哪个 pass. 目前我们只绘制 Pass SRPDefaultUnlit

        • 通过 SortingSetings 指定如何排序    指定排序策略为 SortingCriteria.CommonOpaque,从前到后的顺序

      • FilteringSettings 指示渲染哪些队列 通过为 filteringSettings 传入参数 RenderQueueRange,指示渲染哪些内容。

      代码是这样的:

          void DrawVisibleGeometry(){// 渲染不透明物体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);}

      现在渲染结果是这样的:

        3. Editor Rendering 编辑器渲染

        现在我们的 RP 正确的渲染了 unlit 的材质,但是对于 standard 材质不能正确渲染。在编辑器中,我们要以特殊方式将无法渲染的材质渲染出来,并告诉用户出错了,这对用户体验很重要。

        3.1 Drawing Legacy Shaders

        如果项目过程中切到我们的 RP,场景中可能会使用一些我们不支持的 Shader。把不支持的 Shader 记录下来,并在最后用特殊的材质将他们渲染出来,以向用户提示这些材质需要更换。

            void DrawVisibleGeometry(){// 渲染不透明物体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);}

        错误材质

        通过调用 new Material(Shader.Find("Hidden/InternalErrorShader")); 来创建一个材质,用来渲染材质错误的情况。

        定义 DrawUnsupportedShaders 接口来渲染他们:

            void DrawUnsupportedShaders(){if(errorMaterial == null){errorMaterial = new Material(Shader.Find("Hidden/InternalErrorShader"));}var drawingSettings = new DrawingSettings(legacyShaderTagIds[0], new SortingSettings(camera))// 指示错误的材质{ overrideMaterial = errorMaterial };for (int i = 1; i < legacyShaderTagIds.Length; i++){drawingSettings.SetShaderPassName(i, legacyShaderTagIds[i]);}var filteringSettings = FilteringSettings.defaultValue;context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);}

        不支持的 Standard Shader 将会以紫色显示:

        3.2 Partial Class

        渲染错误材质,仅在编辑器下是有用的,在 Release 时是不需要被渲染的。得益于C# 的 partial 机制,可以让我们将一个类的定义分散到多个文件中。因此我们把这部分代码定义到 CameraRender.Editor.cs 中,同时用 UNITY_EDITOR 宏让这部分代码仅在编辑器时有效.

        3.3 Draw Gizmos

        可以通过 UnityEditor.Handles.ShouldRenderGizmos 判断是否需要渲染 Gizmos,如果需要,则调用 context.DrawGizmos

        • 第一个参数是摄像机

        • 第二个参数指定要渲染的 Gizmos 的子集:

          • image effect 阶段之前

          • image effect 阶段之后

        目前我们还没有 image effect,因此直接渲染两者:

            partial void DrawGizmos();
        #if UNITY_EDITORpartial void DrawGizmos(){if (Handles.ShouldRenderGizmos()){context.DrawGizmos(camera, GizmoSubset.PreImageEffects);context.DrawGizmos(camera, GizmoSubset.PostImageEffects);}}
        #endif

        3.4 Draw Unity UI

        渲染 Scene 窗口 中的 UI

        如果当前摄像机是 CameraType.SceneViewn 类型,通过调用 ScriptableRenderContext.EmitWorldGeometryForSceneView(camera) 提交UI的渲染。

            partial void PrepareForSceneWindow();
        #if UNITY_EDITOR    partial void PrepareForSceneWindow(){if (camera.cameraType == CameraType.SceneView){ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);}}
        #endif

        在绘制中调用该接口:

        public void Render(ScriptableRenderContext context, Camera camera)
        {...PrepareForSceneWindow();if (!Cull())return;...
        }

          4. 多摄像机

          场景中可能有多个摄像机,需要我们正确的处理

          4.1 两个摄像机

          每个摄像机都有Depth 属性,默认著摄像机的 depth =-1,多个摄像机以 depth 升序进行渲染。

          我们之前为 CommandBuffer 设置的剖析时的名字,是用的固定的字符串。当有多个摄像机时,由于名字一致,导致无法将两个摄像机的渲染区分开来。

          因此我们需要根据摄像机的名字来设置剖析的名字

          同时,在调用 BeginSample/EndSample 时,需要指定同样的名字,否则编辑器会报 BeginSample and EndSample counts 不匹配的错误信息。

          由于获取摄像机名字,会导致内存分配,因此将其包裹在 "EditorOnly" 中,以做区分

          #if UNITY_EDITOR    string SampleName { get; set; }partial void PrepareBuffer(){// 由于获取摄像机名字,会导致内存分配,因此将其包裹在 "EditorOnly" 中,以做区分Profiler.BeginSample("Editor Only");buffer.name = SampleName = camera.name;Profiler.EndSample();}
          #elseconst string SampleName = bufferName;
          #endif
              void Setup(){context.SetupCameraProperties(camera);buffer.ClearRenderTarget(true, true, Color.clear);buffer.BeginSample(SampleName);ExecuteBuffer();}...void Submit(){buffer.EndSample(SampleName);ExecuteBuffer();context.Submit();}

          4.2 Layers

          可以在编辑器中设置对象的 Layer,并设置摄像机的 Culling Mask,使摄像机只能看到我们想让它看到的东西。

          4.3 Clear Flags

          我们可以通过配置后续摄像机的 Clear Flags 来合并两个摄像机的渲染结果。

          camera.clearFlags属性返回枚举类型 CameraClearFlags 。然后在 ClearRenderTarget 时,适当的使用这个属性。

          如果使用摄像机颜色进行清空,也要正确使用摄像机颜色

              void Setup(){context.SetupCameraProperties(camera);buffer.ClearRenderTarget(true, true, Color.clear);buffer.BeginSample(SampleName);ExecuteBuffer();}...void Submit(){buffer.EndSample(SampleName);ExecuteBuffer();context.Submit();}

          如果修改了 Camera.ViewRect,则 Clear 将会利用 Hidden/InternalClear shader 进行清屏,效率低。

          本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
          如若转载,请注明出处:http://www.pswp.cn/web/90064.shtml
          繁体地址,请注明出处:http://hk.pswp.cn/web/90064.shtml

          如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

          相关文章

          C语言面向对象编程

          1.内核通用链表一、什么是 list_head&#xff1f;list_head 是 Linux 内核中自己实现的一种 双向循环链表 的结构&#xff0c;定义在 <linux/list.h> 中。它设计得非常轻巧、灵活&#xff0c;广泛用于内核模块、驱动、进程调度、网络协议栈等。它的关键思想是&#xff1a…

          Spring Boot+Redis Zset:三步构建高可靠延迟队列系统

          系统设计架构图---------------- ----------------- ---------------- | | | | | | | 生产者 |------>| Redis ZSet |------>| 定时任务消费者 | | (添加延迟任务) | | (延…

          MCP vs 传统集成方案:REST API、GraphQL、gRPC的终极对比

          MCP vs 传统集成方案&#xff1a;REST API、GraphQL、gRPC的终极对比 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般绚烂的技术栈中&#xff0c;我是那个永不停歇的色彩收集者。 &#x1f98b; 每一个优化都是我培育的花朵&#xff0c;每一个特…

          SQL语句中锁的使用与优化

          一、锁机制简介1.定义在数据库中&#xff0c;除了传统的计算资源&#xff08;如CPU、RAM、I/O等&#xff09;的争用以外&#xff0c;数据也是一种供需要用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题&#xff0c;锁冲突也是影响数据库并…

          Linux笔记1——简介安装

          操作系统给用户一个操作界面&#xff0c;用户通过操作界面使用系统资源Linux内核管理控制硬件&#xff0c;和硬件打交道SCSI&#xff08;盘&#xff09;sd**;第一个*表示磁盘顺序&#xff0c;第二个*表示分区。例如&#xff1a;sda\sdb\sdc,sda1,sda2NVMe&#xff08;盘&#x…

          GoLand 部署第一个项目

          前言&#xff1a;Go环境部署分为两种模式&#xff0c;一种是基于GOPATH部署&#xff08;老版本&#xff09;&#xff0c;另一种是基于Module部署&#xff08;新版本v1.11开始&#xff09;。GOPATH&#xff1a;需要配置GOPATH路径&#xff0c;将GOPATH目录视为工作目录&#xff…

          Mosaic数据增强介绍

          1. 核心概念与目标Mosaic 是一种在计算机视觉&#xff08;尤其是目标检测任务&#xff09;中非常流行且强大的数据增强技术。它最早由 Ultralytics 的 Alexey Bochkovskiy 在 YOLOv4 中提出并推广&#xff0c;后来被广泛应用于 YOLOv5, YOLOv7, YOLOv8 等模型以及其他目标检测框…

          LINUX 722 逻辑卷快照

          逻辑卷快照 lvcreate -L 128M -s -n lv1-snap /dev/vg1/lv1 lvs lvscan mount -o ro /dev/vg1/lv1 /mmt/lv1-snap dmsetup ls --tree 测试 lvs /dev/vg1/lv1-snap dd if/dev/zero of/uc1/test bs1M count40 lvs /dev/vg1/lv1-snap 问题 [rootweb ~]# cd /mnt [rootweb mnt]# m…

          Springboot+vue个人健康管理系统的设计与实现

          文章目录前言详细视频演示具体实现截图后端框架SpringBoot前端框架Vue持久层框架MyBaits成功系统案例&#xff1a;代码参考数据库源码获取前言 博主介绍:CSDN特邀作者、985高校计算机专业毕业、现任某互联网大厂高级全栈开发工程师、Gitee/掘金/华为云/阿里云/GitHub等平台持续…

          数据结构 --栈和队链

          一.栈的概念一种特殊的线性表&#xff0c;只能从固定的一端插入和删除元素。栈中元素遵循先进后出的原则。二.模拟实现public class MyStack {public int size;public int[] array;public MyStack(){array new int[10];}private void grow(){array Arrays.copyOf(array,array…

          文档处理控件TX Text Control系列教程:使用 C# .NET 将二维码添加到 PDF 文档

          PDF 文档通常是合同、发票、证书和报告的最终格式。尽管它们在设计上是静态的&#xff0c;但用户现在希望能够与它们交互、验证信息并直接从这些文件访问数字服务。这时&#xff0c;二维码就变得至关重要。 PDF 文档中的二维码将印刷或数字内容与动态在线体验连接起来。用户只需…

          Google Chrome 谷歌浏览器全部版本集合

          Google Chrome 谷歌浏览器全部版本集合 Collection of all software versions of Google Chrome. 项目介绍 本项目为Google Chrome谷歌浏览器的全部版本集合&#xff0c;方便大家下载旧版本使用。 因为Gitee项目限制仓库1G大小&#xff0c;所以许多谷歌浏览器版本无法上传。…

          论文略读:Towards Safer Large Language Models through Machine Unlearning

          ACL 2024大型语言模型&#xff08;LLMs&#xff09;的迅猛发展展现了其在多个领域的巨大潜力&#xff0c;这主要得益于其广泛的预训练知识和出色的泛化能力。然而&#xff0c;当面对问题性提示&#xff08;problematic prompts&#xff09;时&#xff0c;LLMs 仍然容易生成有害…

          深度学习 ---参数初始化以及损失函数

          深度学习 —参数初始化以及损失函数 文章目录深度学习 ---参数初始化以及损失函数一&#xff0c;参数初始化1.1 固定值初始化1.1.1 全0初始化1.1.2 全1初始化1.3 任意常数初始化1.2 随机初始化一&#xff0c;参数初始化 神经网络的参数初始化是训练深度学习模型的关键步骤之一…

          JS--M端事件

          移动端&#xff08;Mobile 端&#xff0c;简称 M 端&#xff09;开发中&#xff0c;由于设备特性&#xff08;触摸屏、手势操作等&#xff09;&#xff0c;需要处理一些与桌面端不同的事件。这些事件主要针对触摸交互、手势识别等场景 一、触摸事件&#xff08;Touch Events&am…

          Linux网络编程-tcp

          tcp、udp对比&#xff1a;UDP1. 特点无连接&#xff1a;无需建立连接即可发送数据。不可靠&#xff1a;不保证数据顺序或完整性。低延迟&#xff1a;适合实时性要求高的场景。2. 应用场景视频/音频流传输&#xff08;如直播&#xff09;。DNS 查询、在线游戏。TCP1. 特点面向连…

          记一次flink资源使用优化

          一.现状分析 现有任务的资源配置如下&#xff0c;根据ui监控中Garbage Collection可以发现&#xff0c;此任务频繁的发生GC&#xff0c;且老年代GC时间较久二.整体memory使用分析如下Framework Heap&#xff08;框架堆内存&#xff09;用于Flink框架自身的堆内存&#xff08;如…

          Vue底层换成啥了?如何更新DOM的?

          摘要&#xff1a;之前的vue是使用虚拟 DOM的&#xff0c;但是Vue 3.6 带来了一个意义重大的更新&#xff1a; Vapor Mode 渲染模式。Vue 渲染策略的演进&#xff1a; Vue 1.x&#xff1a; 基于模板渲染策略&#xff0c;直接将模板转换为DOM元素&#xff0c;并为每个DOM元素创建…

          0722 数据结构顺序表

          Part 1.顺序表的代码一.顺序表的内存申请head.h: typedef int datatype;typedef struct sqlist {//数据元素datatype data[MAXSIZE];//顺序表长度int len;}*sqlist; //*sqlist的作用: //sqlist:struct Sqlist * sqlist create();head.c: sqlist create() {sqlist list (sqlist)…

          为何在 Vue 的 v-model 指令中不能使用可选链(Optional Chaining)?

          Vue 的 v-model 是实现组件与数据双向绑定的核心指令之一&#xff0c;它本质上是一个语法糖&#xff0c;用于简化对表单元素和组件 props 的同步更新。然而&#xff0c;在 Vue 3&#xff08;以及 Vue 2 的某些模式下&#xff09;&#xff0c;开发者尝试在 v-model 中使用 JavaS…