找回密码
 立即注册
查看: 515|回复: 1

[笔记] 【Unity】SRP简单入门

[复制链接]
发表于 2021-12-30 20:21 | 显示全部楼层 |阅读模式
概念

SRP全称为:Scriptable Render Pipeline,也就是可编程的渲染管线,它属于一种轻量的API,允许开发者使用C#脚本来设置渲染命令,Unity将这些命令传递给它的底层图形架构(low-level graphics architecture),它会将这些指令会被发送到graphics API当中,最终由GPU进行处理。
SRP的官方介绍如下:
如今常见的URP以及HDRP都是在SRP的基础上拓展的,当然我们也可以用SPR实现自定义的渲染管线。想要实现自定义的渲染管线,必须要有 Render Pipeline Asset 和 Render Pipeline Instance这两个东西。

Render Pipeline Instance

我们先来看看什么是Render Pipeline Instance,它实际上就是一个继承了RenderPipeline的类,如下:
public class CustomRenderPipeline : RenderPipeline {
    protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
    }
}
然后我们可以通过重写Render方法,在里面实现我们自定义的渲染效果。其中我们可以看见有一个 ScriptableRenderContext对象,它充当着C#代码与Unity底层图形代码的接口。SRP的渲染使用的是延迟执行,我们可以用ScriptableRenderContext构建一系列的渲染命令,然后告诉Unity去执行这些命令。
我们通过下面两种方法来设置渲染命令:

  • 将一系列的CommandBuffer传递给ScriptableRenderContext,然后使用ScriptableRenderContext.ExecuteCommandBuffer方法执行这些命令。
  • 直接调用ScriptableRenderContext的API,例如 ScriptableRenderContext.Cull 和 ScriptableRenderContext.DrawRenderers。
然后我们可以通过调用ScriptableRenderContext.Submit方法来告诉Unity去执行我们设置好的命令。在调用Submit方法之前,Unity不会去执行我们前面所设置的命令。
下面例子,我们使用红色来进行清屏:
protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
    var cmd = new CommandBuffer();
    cmd.ClearRenderTarget(true, true, Color.red);
    context.ExecuteCommandBuffer(cmd);
    cmd.Release();
    context.Submit();
}

Render Pipeline Asset

那么怎么让我们上面的代码生效呢?这里就需要我们的Render Pipeline Asset出场了。从Asset关键字可以看出,它属于一种资源文件,例如在URP中,我们就可以用下面方法来创建一个属于URP的Render Pipeline Asset。



URP的Render Pipeline Asset

那么我们怎么创建上面自定义的Render Pipeline Instance对应的Asset文件呢?只需要新建一个脚本继承RenderPipelineAsset类,并且重写里面的 CreatePipeline() 方法,返回我们的Render Pipeline Instance即可,如下:
[CreateAssetMenu(menuName = "Rendering/CustomRenderPipelineAsset")]
public class CustomRenderPipelineAsset : RenderPipelineAsset {
    protected override RenderPipeline CreatePipeline() {
        return new CustomRenderPipeline();
    }
}通常情况下,我们会把Render Pipeline Instance中需要用于渲染的一些信息存储在Asset文件中。例如前面我们用了红色来清屏,我们可以利用Asset来配置这个颜色,然后传递给Instance,代码如下:
[CreateAssetMenu(menuName = "Rendering/CustomRenderPipelineAsset")]
public class CustomRenderPipelineAsset : RenderPipelineAsset {
    public Color clearColor;
    protected override RenderPipeline CreatePipeline() {
        return new CustomRenderPipeline(this);
    }
}

public class CustomRenderPipeline : RenderPipeline {
    CustomRenderPipelineAsset m_asset;
    public CustomRenderPipeline(CustomRenderPipelineAsset asset) {
        m_asset = asset;
    }
    protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
        var cmd = new CommandBuffer();
        cmd.ClearRenderTarget(true, true, m_asset.clearColor);
        context.ExecuteCommandBuffer(cmd);
        cmd.Release();
        context.Submit();
    }
}
除此之外我们还可以根据不同的设备配置来指定不同的Asset,来达到不同硬件条件下的适配效果。
接着我们就可以创建我们自定义的Render Pipeline Asset了:


在Asset的Inspector界面就可以设置清屏的颜色,如图我设置一个黄色:


最后我们要在Project Settings的Graphics中,关联上我们的Asset文件,如图:


此时我们的画面就变得一片黄色,说明我们Instance中的代码生效了:


<hr/>接着我们来想办法在这一片屎黄色的屏幕上加点什么,ScriptableRenderContext为我们提供了DrawRenderers方法来绘制可见的物体,具体函数如下:
public void DrawRenderers(CullingResults cullingResults,ref DrawingSettings drawingSettings,
ref FilteringSettings filteringSettings);
其中参数有CullingResults,DrawingSettings和FilteringSettings对象,我们依次来了解一下。

CullingResults

CullingResults,顾名思义就是剔除后的结果。在SRP的每个渲染循环里(Render Loop,每帧执行的所有渲染操作我们称之为一个渲染循环),渲染过程中通常会对每个Camera做剔除操作留下可见的物体,然后再渲染它们以及处理可见光。
我们可以通过ScriptableRenderContext.Cull方法来执行剔除操作,如下:
public CullingResults Cull(ref ScriptableCullingParameters parameters);
其中ScriptableCullingParameters参数决定了剔除的规则,该参数通常从当前渲染的Camera中获得,即Camera.TryGetCullingParameters方法,如下:
public bool TryGetCullingParameters(out ScriptableCullingParameters cullingParameters);
此外我们还可以手动的修改得到的ScriptableCullingParameters结构体,来更新剔除的规则,例如:
//增加遮挡剔除
cullingParameters.cullingOptions |= CullingOptions.OcclusionCull;
//剔除除了default layer之外的layer
cullingParameters.cullingMask = 1 << 0;
执行Cull方法后,得到的结果即存储在CullingResults对象中,并且在每次渲染循环完成后,CullingResults所占的内存都会被释放掉。

DrawingSettings

DrawingSettings用来描述可见物体的排序方式,以及绘制它们时使用的Shader Pass,其构造函数如下:
public DrawingSettings(ShaderTagId shaderPassName, SortingSettings sortingSettings);ShaderTagId 用于关联Shader中的Tag id,在SRP中,我们可以使用 LightMode 这个Pass块里的Tag来决定我们的绘制方式。在内置的渲染管线中,我们的LightMode可以选择诸如Always,ForwardBase,Deferred等等值,但是现在我们要自定义渲染管线,因此LightMode的值就需要我们自定义,例如我们可以在Shader的Pass中添加如下代码:
Tags { "LightMode" = "CustomLightModeTag"}那么我们DrawingSettings中需要的ShaderTagId 即为:
ShaderTagId shaderTagId = new ShaderTagId("CustomLightModeTag");
这样就会找到Shader中LightMode为CustomLightModeTag的Pass进行渲染。
接着是SortingSettings结构图,对应着排序方式,我们可以通过下面方法获得:
var sortingSettings = new SortingSettings(camera);
怎么理解排序方式呢?可以参考Camera.transparencySortMode。简单来说,默认情况下正交摄像机的话,排序只考虑物体与摄像机在Camera.forward方向上的距离。而透视摄像机直接根据物体到摄像机的距离进行排序。
最终我们的DrawingSettings 即为:
DrawingSettings drawingSettings = new DrawingSettings(shaderTagId, sortingSettings);

FilteringSettings

FilteringSettings用来描述渲染时如何过滤可见物体,可通过如下方法获得:
FilteringSettings filteringSettings = FilteringSettings.defaultValue;
filteringSettings.layerMask = 1 << 0;
defaultValue即表示不进行任何的过滤,设置layerMask,即只显示属于该layer的物体。

完整RenderPipeline代码

通过上面,DrawRenderers需要的参数就都可以获得了,我们就可以绘制物体了,完整代码如下:
protected override void Render(ScriptableRenderContext context, Camera[] cameras) {
    var cmd = new CommandBuffer();
    //清除上一帧绘制的东西
    cmd.ClearRenderTarget(true, true, Color.black);
    context.ExecuteCommandBuffer(cmd);
    cmd.Release();

    // 会有显示Scene视图的SceneCamera,点击Camera时显示Preview视图的PreviewCamera,以及场景中我们添加的Camera
    foreach(Camera camera in cameras) {
        //获取当前相机的剔除规则,进行剔除
        camera.TryGetCullingParameters(out var cullingParameters);
        var cullingResults = context.Cull(ref cullingParameters);

        //根据当前Camera,更新内置Shader的变量
        context.SetupCameraProperties(camera);

        //生成DrawingSettings
        ShaderTagId shaderTagId = new ShaderTagId("CustomLightModeTag");
        var sortingSettings = new SortingSettings(camera);
        DrawingSettings drawingSettings = new DrawingSettings(shaderTagId, sortingSettings);

        //生成FilteringSettings
        FilteringSettings filteringSettings = FilteringSettings.defaultValue;

        context.DrawRenderers(cullingResults, ref drawingSettings, ref filteringSettings);

        if(camera.clearFlags == CameraClearFlags.Skybox && RenderSettings.skybox != null) {
            //绘制天空盒
            context.DrawSkybox(camera);
        }

        context.Submit();
    }
}

自定义Shader

别忘了,在自定义管线里,我们用的是 Tags { "LightMode" = "CustomLightModeTag"} 的Shader,因此我们要新建一个Shader,然后Pass里添加我们指定的Tag。
一个简单的无光照Shader如下:
Shader "Custom/UnlitColor"
{
    SubShader
    {
        Pass
        {
            Tags { "LightMode" = "CustomLightModeTag"}

            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            float4x4 unity_MatrixVP;
            float4x4 unity_ObjectToWorld;

            struct appdata
            {
                float4 vertex : POSITION;
            };

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v)
            {
                v2f o;
                                float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
                o.vertex = mul(unity_MatrixVP, worldPos);
                return o;
            }

            float4 frag (v2f i) : SV_TARGET
            {
                return float4(0.5,1,0.5,1);
            }
            ENDHLSL
        }
    }
}最后我们只需要创建一个Material来关联这个Shader,并且在场景中创建Cube,Sphere等GameObject,并且使用我们的Material即可。
得到的效果如下:


本文是一个最简单的例子,如果我们要在SRP里添加更多复杂的功能,可以在Package Manager中安装Core RP Liberary。它里面包含有一些核心的shader库,使用它们可以让我们的shader兼容SRP Batcher,此外还有很多工具函数适用于一些常见操作。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2021-12-30 20:30 | 显示全部楼层
简单易懂 [赞同]
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-24 10:15 , Processed in 0.249920 second(s), 27 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表