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

来源:

https://edu.uwa4d.com/lesson-detail/282/1308/0?isPreview=false
Unity SRP 管线【第一讲:自定义渲染管线】_第1张图片

文章目录

  • 来源:
  • 自定义渲染管线
    • 前置工作
    • 渲染管线资产
    • 渲染管线实例
  • 正式渲染
    • CommandBuffer
    • 清除渲染目标
    • 剔除(Culling)
    • 绘制
      • 绘制集合体
    • 透明和不透明物体分开绘制
  • 编辑器优化
    • 绘制SRP不支持的着色器类型
      • 使用Unity的ErrorShader来绘制不支持的着色器
    • 将Unity编辑器中使用的代码单独放在一个局部类中管理
    • 绘制辅助线框
    • UI绘制(在Scene视图中把UI绘制出来)
  • 多摄像机
    • 多个摄像机渲染不同类型的物体Culling Mask
    • 多个摄像机渲染结果的混合Clear Flags

自定义渲染管线

前置工作

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

渲染管线资产

using UnityEngine.Rendering;

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

    protected override RenderPipeline CreatePipeline()
    {
        return new CustomRenderPipeline();
    }

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

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

之后在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);
        }
    }
}

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

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

正式渲染

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

        // 设置相机view属性
        context.SetupCameraProperties(camera);
        context.DrawSkybox(camera);
        context.Submit();

CommandBuffer

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

新建CommanfBuffer

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

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

commandBuffer.BeginSample(bufferName);
context.ExecuteCommandBuffer(commandBuffer);//执行缓存区命令
commandBuffer.Clear();

渲染设置...
渲染命令...

commandBuffer.EndSample(bufferName);
context.ExecuteCommandBuffer(commandBuffer);
commandBuffer.Clear();

命令提交...

清除渲染目标

buffer.BeginSample();

//*************************************
//buffer.ClearRenderTarget(是否清除深度,是否清除颜色,清除颜色数据的颜色);
buffer.ClearRenderTarget(true,true,Color.clear);
//*************************************

context.ExecuteCommandBuffer(commandBuffer);
commandBuffer.Clear();
......

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

上段代码修改为

//*************************************
buffer.ClearRenderTarget(true,true,Color.clear);
//*************************************

buffer.BeginSample();
context.ExecuteCommandBuffer(commandBuffer);
commandBuffer.Clear();
......

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

Unity SRP 管线【第一讲:自定义渲染管线】_第2张图片

剔除(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;
}

绘制

当剔除完毕后,我们就知道需要渲染那些可见物体了。
正是渲染需要调用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);
   }

Unity SRP 管线【第一讲:自定义渲染管线】_第3张图片

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

先绘制不透明物体,首先将过滤设置设置为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);

    }

渲染透明物体要注意渲染顺序,这里设置渲染顺序为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);
}

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

public void Render(ScriptableRenderContext context, Camera camera)
{
	...
	
    SetUp();
    DrawVisibleGeometry();
    DrawUnsupportedShaders();
	
	...
}

使用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
	};
}

将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
}

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

绘制辅助线框

未绘制辅助线框
Unity SRP 管线【第一讲:自定义渲染管线】_第4张图片
绘制辅助线框
Unity SRP 管线【第一讲:自定义渲染管线】_第5张图片
绘制辅助线框代码

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

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);
    }
}

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

多摄像机

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

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

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

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

将第二个 Camera 的 clearFlag 设置为Depth Only,然后根据该设置写代码:
Unity SRP 管线【第一讲:自定义渲染管线】_第6张图片

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);
}

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

  • 若设置为Depth Only,则只重置深度,颜色不变,这样就可以将上一个摄像机的渲染作为这个摄像机的背景。
  • 若设置为Don't Clear,则深度,颜色都不重置,这样相当于在原有帧数据的基础上继续渲染【只修改了摄像机数据】。

你可能感兴趣的:(Unity,unity,单一职责原则,游戏引擎)