首页 > 其他分享 >自定义SRP(一)

自定义SRP(一)

时间:2022-12-07 20:13:07浏览次数:56  
标签:自定义 渲染 private 相机 SRP Camera Context new

自定义SRP管线(一)

创建RenderPipelineAsset

创建自定义SRP管线,我们首先需要一个RenderPipelineAsset,这可以通过使用脚本继承RenderPipelineAsset这个抽象类来创建自己的RenderPipelineAsset。

具体代码如下:

//拓展编辑器,这样可以使用右键创建一个Asset
[CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline Asset")]
//RenderPipelineAsset继承自ScriptableObject,是一个Unity的资产文件,类似XML,JSON等可以保存数据,但是我们可以通过Inspector面板看到里面的数据。运行时可以通过引用获取到里面的数据,相比传统Monobehavior的值拷贝传递,节省空间。存储不变数据最好使用ScriptableObject
public class CustomRenderPipelineAsset : RenderPipelineAsset
{
    //继承自RenderPipelineAsset这个抽象类的类必须实现这个抽象方法,返回一个自定义的RenderPipeline
    protected override RenderPipeline CreatePipeline()
    {
        return new CustomRenderPipeline();
    }
}

这个脚本编译之后,可以在Asset下右键依次点击Create/Rendering/Custom Render Pipeline Asset创建一个Asset,之后可以在Edit/Project Settings/Graphics下指定需要使用的RenderPipelineAsset,image-20221114193847751

指定之后,Scene窗口和Game窗口都会完全变成黑色,这是因为我们只是创建了一个Pipeline,但是还没有实现任何渲染流程。

创建RenderPipeline

上面的这个Asset可以和引擎核心进行交流,确定使用的RenderPipeline,因此必须返回一个RenderPipeline。RenderPipeline里我们可以手动实现每一步的渲染流程,这就很像OpenGL的编程了,区别是Unity给我们封装好了很多函数,比OpenGL方便很多。

创建一个自定义的RenderPipeline,我们需要继承自RenderPipeline这个抽象类。

//自定义RenderPipeline,需要继承自RenderPipeline抽象类
public class CustomRenderPipeline : RenderPipeline
{
    //这个RenderPipeline可以持有很多Renderer,首先我们需要一个摄像机Renderer,渲染出所有相机观察到的东西
    private readonly CameraRenderer m_CameraRenderer = new();
    //引擎每一帧都会调用这个自己实现的抽象方法Render,里面有一个context和所有的摄像机
    protected override void Render(ScriptableRenderContext context, Camera[] cameras)
    {
        //对每个摄像机,使用摄像机渲染场景
        foreach (var camera in cameras)
        {
            m_CameraRenderer.Render(context,camera);
        }
    }
}

上面的ScriptableRenderContext是当前使用的图形API的抽象,可使用 ScriptableRenderContext 向 GPU 调度和提交状态更新和绘制命令。

实现CameraRenderer

public class CameraRenderer
{
    private ScriptableRenderContext m_Context;
    private Camera m_Camera;
    private const string BufferName = "Render Camera";
    //在 Scriptable Render Pipeline 中,当 Unity 执行剔除操作时,它会将结果存储在 CullingResults 结构中。此数据包括有关可见对象、灯光和反射探测器的信息。Unity 使用此数据来渲染对象和处理灯光。 CullingResults 结构还提供了几个函数来帮助阴影渲染。
   	private CullingResults m_CullingResults;
    //支持的SRP ShaderID
    private static readonly ShaderTagId UnlitShaderTagId = new("SRPDefaultUnlit");
    private readonly CommandBuffer m_Buffer = new()
        {
            name = BufferName
        };
}

CameraRenderer肯定需要持有当前的ScriptableRenderContext,和所使用的相机。CommandBuffer则包含一组图形命令,比如设置渲染目标,采样渲染命令显示到Frame Debugger中。目前主要是把渲染的所有Draw Call显示出来。

public void Render(ScriptableRenderContext context, Camera camera)
{
    m_Context = context;
    m_Camera = camera;
    if (!Cull())
    {
        return;
    }

    SetUp();
    DrawVisibleGeometry();
    DrawUnsupportedShaders();
    Submit();
}

在Render函数里,接收RenderPipeline中传来的context和相机。

private void SetUp()
{
    m_Context.SetupCameraProperties(m_Camera);
    m_Buffer.ClearRenderTarget(true, true, Color.clear);
    //开始采样
    m_Buffer.BeginSample(BufferName);
    ExecuteBuffer();
}

SetUp主要是设置摄像机的视图和投影矩阵,清屏(类似glClear(),glClearColor())防止上一帧的画面影响当前帧。然后使用CommandBuffer开始采样,这主要是想让FrameDebugger显示渲染的所有DrawCall信息。

private void DrawVisibleGeometry()
{
    //渲染不透明物体
    var sortingSettings = new SortingSettings(m_Camera)
    {
        criteria = SortingCriteria.CommonOpaque
    };
    var drawingSettings = new DrawingSettings(UnlitShaderTagId, sortingSettings);
    var filteringSettings = new FilteringSettings(RenderQueueRange.opaque);
    m_Context.DrawRenderers(m_CullingResults, ref drawingSettings, ref filteringSettings);
    //渲染天空盒
    m_Context.DrawSkybox(m_Camera);
    //渲染透明物体
    sortingSettings.criteria = SortingCriteria.CommonTransparent;
    drawingSettings.sortingSettings = sortingSettings;
    filteringSettings.renderQueueRange = RenderQueueRange.transparent;
    m_Context.DrawRenderers(m_CullingResults, ref drawingSettings, ref filteringSettings);
}

Submit则是提交自己在Context中配置的所有DrawCall

private void Submit()
{
    //结束采样
    m_Buffer.EndSample(BufferName);
    ExecuteBuffer();
    m_Context.Submit();
}

在上面DrawVisibleGeometry()渲染各种物体的时候,需要一些参数,比如渲染不透明物体的时候,我们一般由近到远渲染物体,因为如果后面的物体被挡到,我们就可以不花费时间去渲染它了。

这就需要我们设置

var sortingSettings = new SortingSettings(m_Camera)
    {
        criteria = SortingCriteria.CommonOpaque
    };

而在渲染透明物体的时候,我们又需要从远到近渲染,这是因为我们最终可能要混合各种不透明物体的颜色

因此设置

sortingSettings.criteria = SortingCriteria.CommonTransparent;

filteringSettings则是设置的过滤条件,Unity对于不透明和透明物体,分别设置了两条渲染队列,把两类物体分在两个队列里,依次渲染。

//只渲染不透明物体队列中的物体
var filteringSettings = new FilteringSettings(RenderQueueRange.opaque);
//只渲染透明物体队列中的物体
filteringSettings.renderQueueRange = RenderQueueRange.transparent;

在设置好这些参数之后,就可以使用下列代码让context渲染了

ref关键字表示按引用传递,类似C++中的const-to-references,减少拷贝带来的性能消耗。

 m_Context.DrawRenderers(m_CullingResults, ref drawingSettings, ref filteringSettings);

这里的m_CullingResults则是相机的剔除结果,获得 CullingResults 结构,调用 ScriptableRenderContext.Cull。

private bool Cull()
{
    //获取相机剔除参数
    if (!m_Camera.TryGetCullingParameters(out var p)) return false;
    //使用参数进行剔除
    m_CullingResults = m_Context.Cull(ref p);
    return true;
}

渲染Unity UI物体

上面的这些代码并不会在Scene窗口下渲染各种Unity 内部的UI,如果我们想在scene下看到UI,我们需要显式告诉Unity,通过下列代码:

partial void PrepareForSceneWindow()
{
    //如果相机是Scene窗口的相机,那么渲染UI物体
    if (m_Camera.cameraType == CameraType.SceneView)
    {
        ScriptableRenderContext.EmitWorldGeometryForSceneView(m_Camera);
    }
}

渲染Gizmoz

partial void DrawGizmos()
{
    if (!Handles.ShouldRenderGizmos()) return;
        //Gizmoz有两种,一起渲染
    m_Context.DrawGizmos(m_Camera, GizmoSubset.PreImageEffects);
    m_Context.DrawGizmos(m_Camera, GizmoSubset.PostImageEffects);
}

渲染不支持的Shader

在URP中,我们可以看到普通的shader呈现出一种粉色,表示不支持这个shader,给开发者一个提示,换成支持的SRP shader

我们可以把不支持的shader列出来

private static readonly ShaderTagId[] LegacyShaderTagIds =
{
    new("Always"),
    new("ForwardBase"),
    new("PrepassBase"),
    new("Vertex"),
    new("VertexLMRGBM"),
    new("VertexLM")
};

然后渲染这些不支持的shader

private static Material _errorMaterial;
partial void DrawUnsupportedShaders()
{
    if (_errorMaterial == null)
    {
        //把_errorMaterial设置为粉色的shader
        _errorMaterial = new Material(Shader.Find("Hidden/InternalErrorShader"));
    }

    var drawingSettings = new DrawingSettings(
        LegacyShaderTagIds[0], new SortingSettings(m_Camera)
    )
    {
        overrideMaterial = _errorMaterial
    };
    for (var i = 1; i < LegacyShaderTagIds.Length; i++)
    {
        //配置所有不支持的shader
        drawingSettings.SetShaderPassName(i, LegacyShaderTagIds[i]);
    }

    var filteringSettings = FilteringSettings.defaultValue;
    m_Context.DrawRenderers(
        //使用粉色shader渲染
        m_CullingResults, ref drawingSettings, ref filteringSettings
    );
}

image-20221115143259495

粉色是使用不支持的shader的物体

完整Render代码

public void Render(ScriptableRenderContext context, Camera camera)
{
    //设置使用的context和camera
    m_Context = context;
    m_Camera = camera;
    //配置buffer名称,让Frame Debugger显示
    PrepareBuffer();
    //渲染UI
    PrepareForSceneWindow();
    //剔除
    if (!Cull())
    {
        return;
    }
	//设置相机参数,清空屏幕缓存
    SetUp();
    //渲染可见物体
    DrawVisibleGeometry();
    //渲染不支持的shader
    DrawUnsupportedShaders();
    //渲染Gizmoz
    DrawGizmos();
    //提交上面的所有渲染命令
    Submit();
}

ClearFlags

每个相机都有一个ClearFlags参数

image-20221115143426694

从上到下依次为Skybox,Solid Color,Depth Only,Not Clear。

依次表示清空所有,只清空天空盒并保留颜色和深度缓存,清空天空盒和背景颜色并只保留深度缓存,和什么都不清空。

我们创建两个相机,放在相同位置,第一个选择天空盒子。

在SetUp中,设置当CameraClearFlags<=CameraClearFlags.Depth清空颜色缓冲,这表示当我们设置flag为Skybox和Solid Color和Depth Only的时候清空深度缓冲。

然后设置 flags == CameraClearFlags.Color的时候清空颜色缓冲,这表示只有当我们设置相机flag为Solid Color的时候清空颜色缓冲。

private void SetUp()
{
    m_Context.SetupCameraProperties(m_Camera);
    var flags = m_Camera.clearFlags;
    m_Buffer.ClearRenderTarget(flags <= CameraClearFlags.Depth,
        flags == CameraClearFlags.Color,
        flags == CameraClearFlags.Color ? m_Camera.backgroundColor.linear : Color.clear);
    //开始采样
    m_Buffer.BeginSample(sampleName);
    ExecuteBuffer();
}

如果第二个选择天空盒,表示第二个相机全部重新渲染,因此第一个相机的所有画面都看不到了。

image-20221115143804723

如果第二个选择Solid Color,表示清空颜色缓冲,并且使用自己相机的背景颜色重新渲染,因此第一个相机的天空盒和渲染结果,被第二个相机的背景颜色完全覆盖了,表现为

image-20221115143915750

当我们设置第二个相机为Depth Only,表示只清除第一个相机的深度缓冲,因此第二个相机的画面都会在第一个相机画面的前方。

image-20221115144238574

当我们设置第二个相机为Not Clear,我们对于第一个相机的渲染结果不做任何处理,

image-20221115144452435

主要区别在这个地方,紫色方块是在绿色方块后方的,如果我们清空深度缓冲,第二张图的渲染结果会覆盖第一张图,因此粉色方块反而出现在绿色方块的前方了,不清空深度缓冲的话,渲染结果就正确了。

image-20221115144645977

参考

Custom Render Pipeline

标签:自定义,渲染,private,相机,SRP,Camera,Context,new
From: https://www.cnblogs.com/luoxiaozhu1998/p/16964383.html

相关文章

  • ES自定义评分机制:function_score查询详解
    一、function_score介绍主要用于让用户自定义查询相关性得分,实现精细化控制评分的目的。在ES的常规查询中,只有参与了匹配查询的字段才会参与记录的相关性得分score的计算。......
  • 开源web自定义表单有哪几个特点?
    目前,市场正在蓬勃发展中,作为企业,采用传统表单的弊端也逐渐显现出来,面临比较大的问题就是办公效率得不到提升。在各行各业正积极筹备朝着数字化方向转型的背景下,采用开源web......
  • 自定义指令(全局和局部作用)
    自定义指令分为两个字情景1.在某一个vue文件里,而不是全局2.在main.ts文件里注册全局的自定义指令vue文件<scriptsetup>import{ref,onMounted}from'vue'letn=ref(......
  • PHP 自定义 数组根据键去重
    去重前$result=$this->unique_array_by_key($result,"id");functionunique_array_by_key($array,$unique_key){$tmp_key[]=array();forea......
  • leaflet 用自定义pane实现图层顺序调整
    在Leaflet中,mappanes隐式地将图层组合在一起,而开发者并不知道这一点。这种分组允许Web浏览器以比单独处理图层更有效的方式同时处理多个图层。Mappanes使用z-ind......
  • Vue3必会技巧-自定义Hooks
    Vue3自定义Hooks定义:个人理解:一些可复用的方法像钩子一样挂着,可以随时被引入和调用以实现高内聚低耦合的目标,应该都能算是hook;为什么Vue3要用自定义Hook?:结论:就是为了......
  • 使用自定义类加载器打破双亲委派机制实现自定义String类
    今天我和大家如何用自定义加载器打破双亲委派机制。在介绍双亲委派机制前,我先来聊聊Java里面有几种类加载器。引导类加载器:Bootstrapclassloader扩展类加载器:ExtClassLoade......
  • 随笔(三)『SpringBoot自定义异常类』
    1、定义异常类,继承RuntimeExceptionpackagecom.baihua.common.exception;importlombok.Data;/***自定义异常*/@DatapublicclassMyExceptionextendsRunti......
  • gym——1自定义Gym环境并注册
    gym1——自定义Gym环境并注册目录gym1——自定义Gym环境并注册感谢开始Step0新建文件夹Step1:新建环境文件xxEnv.pyStep2:在env下的__init__.py下注册**Step3**......
  • 自定义RBAC(2)
    您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~ 上一次把DAC、MAC、PBAC这三个都一口气介绍完了,本想一口气把RBAC也说完。想了想,饭还是要一口口吃,一次吃太多会消化不良,......