• Unity SRP 管线【第一讲:自定义渲染管线】


    来源:

    https://edu.uwa4d.com/lesson-detail/282/1308/0?isPreview=false
    在这里插入图片描述

    自定义渲染管线

    前置工作

    下载 Core RP Library 库。SRP、URP、HDRP都是依据该包进行拓展的,它是Unity开放出来供我们使用调用的C#接口,通过调用更底层的C++提供的渲染接口。包中还包括一些基本的着色器文件。

    渲染管线资产

    using UnityEngine.Rendering;
    
    • 1

    继承RenderPipelineAsset,即可成为一个资产管理资源文件的定义文件
    继承后需要重写CreatePipeline()函数,返回一个RenderPipeline 【渲染管线实例】

        protected override RenderPipeline CreatePipeline()
        {
            return new CustomRenderPipeline();
        }
    
    • 1
    • 2
    • 3
    • 4

    记得将资产管理资源添加到菜单栏

    //该类作为一个管线资产,可通过如下菜单创建
    [CreateAssetMenu(menuName = "Rendering/CreateCustomRenderPipeline")]
    
    • 1
    • 2

    之后在unity中创建该资源文件,并将该资源文件添加到Editor->Project Setting->Graphics->Scriptable Render Pipeline Setting中即可创建成功。

    渲染管线实例

    新建渲染管线脚本文件,继承RenderPipeline,实现方法Render。

    public class CustomRenderPipeline : RenderPipeline
    {
    
        CameraRenderer renderer = new CameraRenderer();
        /// 
        /// Unity每帧都会调用CustomRenderPipeline实例的Render()方法进行画面渲染
        /// 是SRP的入口
        /// 
        ///  传入的当前上下文 
        ///  参与这一帧渲染的所有对象 
        protected override void Render(ScriptableRenderContext context, Camera[] cameras)
        {
            foreach (var camera in cameras)
            {
                renderer.Render(context, camera);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    在渲染管线资产中中返回该渲染管线实例类的一个实例即可。

    Unity每帧都会调用实例的Render方法进行画面渲染,Render()方法是SRP的入口,进行渲染时底层接口会调用它并传递两个参数,一个是ScriptableRenderContext渲染上下文,一个是Camera[]对象,储存了参与这一帧渲染的所有相机对象

    正式渲染

    调用ScriptableRenderContext.DrawSkybox来绘制一个天空盒
    因为通过context发送的渲染命令都是缓存,所以需要通过调用Submit()方法来正式提交渲染命令。

            // 设置相机view属性
            context.SetupCameraProperties(camera);
            context.DrawSkybox(camera);
            context.Submit();
    
    • 1
    • 2
    • 3
    • 4

    CommandBuffer

    控制Unity渲染流程的一种手段。一些命令需要单独的命令缓冲区简介发出,CommandBuffer是一个容器,保存了这些将要执行的渲染命令。

    新建CommanfBuffer

        const string bufferName = "Render Camera";
        CommandBuffer commandBuffer = new CommandBuffer//是一个容器,保存将要执行的渲染命令
        {
            //给缓冲区起个名字,用于在Frame Debugger中识别他
            name = bufferName
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    开启采样过程,这样就可以在Frame Debugger中显示,通常放在整个渲染过程的开始和结束。

    commandBuffer.BeginSample(bufferName);
    context.ExecuteCommandBuffer(commandBuffer);//执行缓存区命令
    commandBuffer.Clear();
    
    渲染设置...
    渲染命令...
    
    commandBuffer.EndSample(bufferName);
    context.ExecuteCommandBuffer(commandBuffer);
    commandBuffer.Clear();
    
    命令提交...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    清除渲染目标

    buffer.BeginSample();
    
    //*************************************
    //buffer.ClearRenderTarget(是否清除深度,是否清除颜色,清除颜色数据的颜色);
    buffer.ClearRenderTarget(true,true,Color.clear);
    //*************************************
    
    context.ExecuteCommandBuffer(commandBuffer);
    commandBuffer.Clear();
    ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    ClearRenderTarget操作会自动包裹在一个使用命名缓冲区名字的样本条目中,因此一般不包含在其他命名缓冲区之内。

    上段代码修改为

    //*************************************
    buffer.ClearRenderTarget(true,true,Color.clear);
    //*************************************
    
    buffer.BeginSample();
    context.ExecuteCommandBuffer(commandBuffer);
    commandBuffer.Clear();
    ......
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    然而,在帧调试中,Clear操作显示为Draw GL条目,而不是Clear。原因是我们需要提前设置Camera的属性,才能让Unity知道要调用Clear的硬件方法,而不是Hidden/InternalClear,SubShader #0着色器。

    在这里插入图片描述

    剔除(Culling)

    剔除在相机渲染 Render() 最开始执行。

    通过camera.TryGetCullingParameters(out p)得到需要进行剔除检查的所有物体,正式的剔除通过context.Cull()实现,最后会返回一个CullingResults结构,里面存储了我们相机剔除后所有视野内可见的物体的数据信息。

    CullingResults cullingResults;
    bool Cull(){
    	ScriptableCullingParameters p;
    	if(camera.TryGetCullingParameters(out p)){
    		cullingResults = context.Cull.cull(ref p);
    		return true;
    	}
    	return false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    绘制

    当剔除完毕后,我们就知道需要渲染那些可见物体了。
    正是渲染需要调用context.DrawRenderers方法实现。

    绘制集合体

       static ShaderTagId unlitShaderTagID = new ShaderTagId("SRPDefaultUnlit");//渲染使用的Shader
       private void DrawVisibleGeometry()
       {
           // 设置绘制顺序和指定渲染相机
           var sortingSettings = new SortingSettings(camera)
           {
               criteria = SortingCriteria.CommonOpaque//不透明对象的典型排序模式
           };
           // 设置渲染的 Shader Pass 和排序模式
           var drawingSettings = new DrawingSettings(unlitShaderTagID,sortingSettings);//使用哪个ShaderID,以什么一定顺序渲染的设定
           // 设置哪些类型的渲染队列可以被绘制
           var filteringSettig = new FilteringSettings(RenderQueueRange.all);//过滤给定的渲染对象,这里使用all渲染所有对象
           //图像绘制
           context.DrawRenderers(cullingResults,ref drawingSettings,ref filteringSettig);
    
           context.DrawSkybox(camera);
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    透明和不透明物体分开绘制

    先绘制不透明物体,首先将过滤设置设置为Opaque,渲染不透明物体。
    在渲染天空盒
    最后渲染透明物体

        private void DrawVisibleGeometry()
        {
            // 设置绘制顺序和指定渲染相机
            var sortingSettings = new SortingSettings(camera)
            {
                criteria = SortingCriteria.CommonOpaque//不透明对象的典型排序模式
            };
            // 设置渲染的 Shader Pass 和排序模式
            var drawingSettings = new DrawingSettings(unlitShaderTagID,sortingSettings);//使用哪个ShaderID,以什么一定顺序渲染的设定
            // 设置哪些类型的渲染队列可以被绘制
            //var filteringSettig = new FilteringSettings(RenderQueueRange.all);//过滤给定的渲染对象,这里使用all渲染所有对象
            var filteringSetting = new FilteringSettings(RenderQueueRange.opaque);//过滤出不透明物体
            //图像绘制(不透明物体)
            context.DrawRenderers(cullingResults,ref drawingSettings,ref filteringSetting);
    
            context.DrawSkybox(camera);
    
            // 绘制透明物体
            sortingSettings.criteria = SortingCriteria.CommonTransparent; //透明对象的典型排序模式
            drawingSettings.sortingSettings = sortingSettings;
            // 过滤出透明物体
            filteringSetting.renderQueueRange = RenderQueueRange.transparent;
            // 绘制
            context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSetting);
    
        }
    
    • 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

    渲染透明物体要注意渲染顺序,这里设置渲染顺序为SortingCriteria.CommonTransparent

    编辑器优化

    绘制SRP不支持的着色器类型

    SRP不支持的着色器类型

    static ShaderTagId[] legacyShaderTagId = 
    {
    	new ShaderTagId("Always"),
    	new ShaderTagId("ForwardBase"),
    	new ShaderTagId("PrepassBase"),
    	new ShaderTagId("Vertex"),
    	new ShaderTagId("VertexMRGBM"),
    	new ShaderTagId("VertexLM"),
    };
    
    private void DrawUnsupportedShaders()
    {
        var drawingSettings = new DrawingSettings(legacyShaderTagId[0], new SortingSettings(camera));
        for (int i = 1; i < legacyShaderTagId.Length; i++)
        {
            drawingSettings.SetShaderPassName(i, legacyShaderTagId[i]);
        }
    
        var filteringSettings = FilteringSettings.defaultValue;
        context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    使用函数DrawUnsupportedShaders();绘制不支持的材质渲染

    public void Render(ScriptableRenderContext context, Camera camera)
    {
    	...
    	
        SetUp();
        DrawVisibleGeometry();
        DrawUnsupportedShaders();
    	
    	...
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    使用Unity的ErrorShader来绘制不支持的着色器

    static Material errorMaterial;
    void DrawUnsupportedShaders(){
    	if(errorMaterial == null)
    	{
    		errorMaterial = new Material(Shader.Find("Hidden/InternalErrorShader"));
    	}
    	
    	var drawingSettings = new DrawingSettings(legacyShaderTagIds[0], new SortingSettings(camera))
    	{
    		overrideMaterial = errorMaterial
    	};
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    将Unity编辑器中使用的代码单独放在一个局部类中管理

    一些代码只适合调试时使用,但不会在发行版中运行,因此使用 动静代码分离:局部类 将代码进行分离管理。

    使用partial分离class,将编辑器部分分离出来。

    using UnityEngine;
    using UnityEngine.Rendering;
    
    public partial class CameraRenderer
    {
        partial void DrawUnsupportedShaders();
    
    // build and run 时 不运行这部分
    #if UNITY_EDITOR
        static ShaderTagId[] legacyShaderTagIds =
        {
            new ShaderTagId("Always"),
            new ShaderTagId("ForwardBase"),
            new ShaderTagId("PrepassBase"),
            new ShaderTagId("Vertex"),
            new ShaderTagId("VertexMRGBM"),
            new ShaderTagId("VertexLM"),
        };
    
        static Material errorMaterial;
        /// 
        /// 使用Unity内置ErrorShader。
        /// 
        partial 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);
        }
    #endif
    }
    
    
    • 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

    对编辑器内使用的函数代码,使用#if UNITY_EDITOR #endif将其包入其中。注意,若将函数定义在宏编译内,在编译条件不通过时,该函数如果在其他函数内调用,则会报错。使用partial添加函数定义,即可。
    如:partial void DrawUnsupportedShaders();

    绘制辅助线框

    未绘制辅助线框
    在这里插入图片描述
    绘制辅助线框
    在这里插入图片描述
    绘制辅助线框代码

    #if UNITY_EDITOR
    	...
    	
        partial void DrawGizmos()
        {
            if (Handles.ShouldRenderGizmos())
            {
                context.DrawGizmos(camera, GizmoSubset.PreImageEffects);
                context.DrawGizmos(camera, GizmoSubset.PostImageEffects);
            }
            
        }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    UI绘制(在Scene视图中把UI绘制出来)

    如果将 CanvasRenderMode 设置为 ScreenSpace Overlay
    在这里插入图片描述
    UI不是由我们管线进行绘制的,而是单独绘制的。

    如果我们将 CanvasRenderMode 设置为 ScreenSpace Camera
    UI绘制由我们管线管理。

    调用如下函数,即可将Games视图下的UI绘制到Scene视图下。

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

    注意:调用PrepareForSceneWindow函数需要在剔除之前,因为UI会在场景中添加几何体,这个集合体可能会被剔除操作剔除。

    多摄像机

    多摄像机绘制。绘制顺序按照深度递增渲染。【先绘制小数,再绘制大数】。
    在这里插入图片描述

    多个摄像机渲染不同类型的物体Culling Mask

    需要使用Layer将物体进行分类。使用摄像机的Culling Mask剔除不需要渲染的可见物。

    多个摄像机渲染结果的混合Clear Flags

    将第二个 Camera 的 clearFlag 设置为Depth Only,然后根据该设置写代码:
    在这里插入图片描述

    private void SetUp()
    {
        // 设置相机属性
        context.SetupCameraProperties(camera);
        // 得到Camera的CameraClearFlags对象
        CameraClearFlags flags = camera.clearFlags;
        //设置相机清除状态
        commandBuffer.ClearRenderTarget(flags <= CameraClearFlags.Depth,//Skybox,Color,Depth 无论哪一个都要清理深度缓存
                                        flags == CameraClearFlags.Color,
                                        flags == CameraClearFlags.Color ? camera.backgroundColor.linear : Color.clear);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    根据camera的Clear Flags修改帧缓存的深度缓存,颜色缓存是否重置。

    • 若设置为Depth Only,则只重置深度,颜色不变,这样就可以将上一个摄像机的渲染作为这个摄像机的背景。
    • 若设置为Don't Clear,则深度,颜色都不重置,这样相当于在原有帧数据的基础上继续渲染【只修改了摄像机数据】。
  • 相关阅读:
    奇思妙想-可以通过图片闻见味道的设计
    深度思考ES面经
    Nanoprobes丨GoldiBlot 用于 His-tag 检测方案
    docker、docker-compose安装教程,很详细
    【MYSQL】 三大范式 表的关系 外键 ER图
    安卓开发——安卓界面布局笔记
    svg学习 路由跳转方式以及传(获取)参 路由获取参数 懒加载
    桥接模式(Bridge)
    嵌入式Linux应用开发基础知识(二)——GCC总体选项
    适合中小企业的项目管理系统有哪些?
  • 原文地址:https://blog.csdn.net/weixin_44518102/article/details/133769319