Scriptable Render Pipeline

Created by miccall (转载请注明出处 miccall.tech)

1. Cull 剔除检查

在unity中,每个可以被渲染的物体,都带有一个Mesh Render组件
但是我们并不是渲染场景中每个物体,而是只渲染相机可以看到的那些物体。
所以我们从要剔除那些不在摄像机视锥之外的物体。

if (!CullResults.GetCullingParameters(camera, out var cullparamet)) return;

CullResults.GetCullingParameters 方法可以检测camera相机是否有效的剔除,
如果相机无效渲染(空视口矩形,无效剪裁平面设置等)那么我们直接return
否则,他会把剔除信息记录到 cullparamet 。


CullResults.Cull(ref cullparamet, context , ref _cull);

之后,调用 Cull 方法把物体剔除

然后,我们需要更新camera的一些view, projection and clipping planes等信息


context.SetupCameraProperties(camera);

此外 ,UI 层时unity为我们在render时增加的一层overlay ,我们在编辑器时需要对这一层做剔除


        #if UNITY_EDITOR
            if(camera.cameraType == CameraType.SceneView)
                ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
        #endif 

2. Clear 清屏

清空颜色缓存和深度缓存


        var clearFlags = camera.clearFlags;

        _cameraBuffer.ClearRenderTarget(
                (clearFlags & CameraClearFlags.Depth) != 0,
                (clearFlags & CameraClearFlags.Color) != 0,
                camera.backgroundColor
        );

ExecuteCommandBuffer 开始执行 command buffer


            _cameraBuffer.BeginSample("Render Camera");
            context.ExecuteCommandBuffer(_cameraBuffer);
            _cameraBuffer.Clear();


3. Draw Opaque 渲染不透明物体

渲染物体 ,首先根据渲染对象的类型 ,设置其 drawRendererSettings 的值
.sorting.flags 的值表示他渲染的物体排序方式
不透明物体的渲染时从前向后 ,而透明物体的渲染是从后向前
filterRendererSettings 表示渲染物体的种类

            var drawRendererSettings = new DrawRendererSettings(camera, new ShaderPassName("SRPDefaultUnlit"))
            {
                sorting = {flags = SortFlags.CommonOpaque},
                // 开启动态批处理
                flags = _drawFlags
            };

            var filterRendererSettings = new FilterRenderersSettings(true)
            {
                renderQueueRange = RenderQueueRange.opaque
            };

            context.DrawRenderers(_cull.visibleRenderers,ref drawRendererSettings,filterRendererSettings);

context.DrawRenderers 来执行渲染操作

4. Sky box 渲染天空盒


context.DrawSkybox(camera);

5. DrawTransparent 渲染透明物体

同理,我们上面说到了不透明物体的渲染时从前向后 ,而透明物体的渲染是从后向前 。
这次我们同样修改 drawRendererSettings 和 filterRendererSettings ,
并且context.DrawRenderers来执行


            drawRendererSettings.sorting.flags = SortFlags.CommonTransparent ;
            filterRendererSettings.renderQueueRange = RenderQueueRange.transparent;
            context.DrawRenderers(_cull.visibleRenderers,ref drawRendererSettings,filterRendererSettings);

6. error 错误材质

由于我们的管道仅支持我们自己的着色器,
而其他的却不会被渲染 。所有我们需要对其他进行统一处理,
最方便的方法就是使用错误的着色器。

让我们为此添加一个专用的DrawDefaultPipeline方法,


        [Conditional("DEVELOPMENT_BUILD"), Conditional("UNITY_EDITOR")]
        private  void DrawDefaultPipeline(ScriptableRenderContext context, Camera camera)
        {
            if (_errorMaterial == null) {
                var errorShader = Shader.Find("Hidden/InternalErrorShader");
                _errorMaterial = new Material(errorShader) {
                    hideFlags = HideFlags.HideAndDontSave
                };
            }

            var drawSettings = new DrawRendererSettings(
                camera, new ShaderPassName("ForwardBase")
            );

            drawSettings.SetShaderPassName(1, new ShaderPassName("PrepassBase"));
            drawSettings.SetShaderPassName(2, new ShaderPassName("Always"));
            drawSettings.SetShaderPassName(3, new ShaderPassName("Vertex"));
            drawSettings.SetShaderPassName(4, new ShaderPassName("VertexLMRGBM"));
            drawSettings.SetShaderPassName(5, new ShaderPassName("VertexLM"));
            drawSettings.SetOverrideMaterial(_errorMaterial,0);
            var filterSettings = new FilterRenderersSettings(true);

            context.DrawRenderers(
                _cull.visibleRenderers, ref drawSettings, filterSettings
            );
        }

7. Dynamic Batching 动态合批处理

private readonly DrawRendererFlags _drawFlags;
// 开启 动态合批处理 
if (dynamicBatching) {
    _drawFlags = DrawRendererFlags.EnableDynamicBatching;
}

8. GPU Instance GPU 实例化

// 开启 GPU 实例化 
if (instancing) {
    _drawFlags |= DrawRendererFlags.EnableInstancing;
}

9. 材质

Shader "MyPipeLine/Unlit"
{
    Properties
    {
        _Color("Color",Color) = (1,1,1,1)
    }
    SubShader
    {
        Pass
        {    
            HLSLPROGRAM
            #pragma target 3.5
            #pragma multi_compile_instancing
            #pragma instancing_options assumeuniformscaling
            #pragma vertex UnlitPassVertex
            #pragma fragment UnlitPassFragment
            #include "../ShaderLibrary/Unlit.hlsl"
            ENDHLSL
        }
    }
}

shader 包含了 vert 和 fragment shader
以及支持 instance 的 multi compile
此外 , 为支持instance 的矩阵变换 ,我们添加了 instancing_options

其余内容,添加到 hlsl 的include 文件


struct VertexInput {
    float4 pos:POSITION;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct VertexOutput {
    float4 clipPos:SV_POSITION;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

输入输出结构体
UNITY_VERTEX_INPUT_INSTANCE_ID 为支持instance 定义在 include 文件中


VertexOutput UnlitPassVertex (VertexInput input) {
    VertexOutput output;
    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_TRANSFER_INSTANCE_ID(input, output);
    float4 worldPos = mul(UNITY_MATRIX_M, float4(input.pos.xyz, 1.0));
    output.clipPos = mul(unity_MatrixVP, worldPos);
    return output;
}
float4 UnlitPassFragment (VertexOutput input) : SV_TARGET {
    UNITY_SETUP_INSTANCE_ID(input);
    return UNITY_ACCESS_INSTANCED_PROP(PerInstance, _Color);
}

顶点着色器中,我们除了支持instace的定义外 ,
我们计算了 worldPos 和 clipPos
其中用到了 MVP 矩阵

在 片元着色器中 ,我们用color输出了 instance

矩阵的定义和赋值:

CBUFFER_START(UnityPerFrame)
    float4x4 unity_MatrixVP;
CBUFFER_END

CBUFFER_START(UnityPerDraw)
    float4x4 unity_ObjectToWorld;
CBUFFER_END
#define UNITY_MATRIX_M unity_ObjectToWorld

CBUFFER_START 是引用unity的常量缓存区,Constant buffer。
unity把矩阵计算好,存入其中

为了支持instace ,我们还需引入一个


#include "Packages/com.unity.render-pipelines.core/ShaderLibrary/UnityInstancing.hlsl"

UNITY_INSTANCING_BUFFER_START(PerInstance)
UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
UNITY_INSTANCING_BUFFER_END(PerInstance)

10. 灯光

平行光
点光
聚光

范围
衰减


    // 最大支持的灯光数量 
    private const int maxVisibleLights = 16;

    // 颜色id
    private static  int visibleLightColorsId = Shader.PropertyToID("_VisibleLightColors");
    // 灯光的方向或者位置 (平行光忽略位置)
    private static  int visibleLightDirectionsOrPositionsId = Shader.PropertyToID("_VisibleLightDirectionsOrPositions");
    // 灯光衰减
    private static  int visibleLightAttenuationsId = Shader.PropertyToID("_VisibleLightAttenuations");
    // 聚光灯方向
    private static  int visibleLightSpotDirectionsId = Shader.PropertyToID("_VisibleLightSpotDirections");
    //索引
    private static  int lightIndicesOffsetAndCountID = Shader.PropertyToID("unity_LightIndicesOffsetAndCount");

    //buffer 
    private Vector4[] visibleLightColors = new Vector4[maxVisibleLights];
    private Vector4[] visibleLightDirectionsOrPositions = new Vector4[maxVisibleLights];
    private Vector4[] visibleLightAttenuations = new Vector4[maxVisibleLights];
    private Vector4[] visibleLightSpotDirections = new Vector4[maxVisibleLights];


我们需要对这些赋值然后传入shader


        // 可见性
        if (_cull.visibleLights.Count > 0) ConfigureLights();
        else _cameraBuffer.SetGlobalVector(lightIndicesOffsetAndCountID, Vector4.zero);

        // 传入shader
         _cameraBuffer.BeginSample("Render Camera");

        // 设置 灯光 缓冲区  
        _cameraBuffer.SetGlobalVectorArray(
            visibleLightColorsId, visibleLightColors
        );
        _cameraBuffer.SetGlobalVectorArray(
            visibleLightDirectionsOrPositionsId, visibleLightDirectionsOrPositions
        );
        _cameraBuffer.SetGlobalVectorArray(
            visibleLightAttenuationsId, visibleLightAttenuations
        );
        _cameraBuffer.SetGlobalVectorArray(
            visibleLightSpotDirectionsId, visibleLightSpotDirections
        );

        context.ExecuteCommandBuffer(_cameraBuffer);
        _cameraBuffer.Clear();

在 ConfigureLights(); 中 ,我们遍历场景中的灯光

private void ConfigureLights()
    {
        for (var i = 0; i < _cull.visibleLights.Count; i++)
        {
            // 灯光数量限制
            if (i == maxVisibleLights) break;

            // 可用灯光 
            var light = _cull.visibleLights[i];
            //finalColor : 灯光的颜色乘以其强度
            visibleLightColors[i] = light.finalColor;
            // 衰减值 
            var attenuation = Vector4.zero;
            attenuation.w = 1f;

            // 平行光 : 计算灯光方向 ,位置无关 
            if (light.lightType == LightType.Directional)
            {
                var v = light.localToWorld.GetColumn(2);
                v.x = -v.x;
                v.y = -v.y;
                v.z = -v.z;
                visibleLightDirectionsOrPositions[i] = v;
            }
            else
            {
                // point light

                visibleLightDirectionsOrPositions[i] = light.localToWorld.GetColumn(3);
                attenuation.x = 1f / Mathf.Max(light.range * light.range, 0.00001f);
                if (light.lightType == LightType.Spot)
                {
                    var v = light.localToWorld.GetColumn(2);
                    v.x = -v.x;
                    v.y = -v.y;
                    v.z = -v.z;
                    visibleLightSpotDirections[i] = v;

                    var outerRad = Mathf.Deg2Rad * 0.5f * light.spotAngle;
                    var outerCos = Mathf.Cos(outerRad);
                    var outerTan = Mathf.Tan(outerRad);
                    var innerCos =
                        Mathf.Cos(Mathf.Atan((46f / 64f) * outerTan));
                    var angleRange = Mathf.Max(innerCos - outerCos, 0.001f);
                    attenuation.z = 1f / angleRange;
                    attenuation.w = -outerCos * attenuation.z;
                }
            }

            visibleLightAttenuations[i] = attenuation;
        }

        if (_cull.visibleLights.Count <= maxVisibleLights) return;
        {
            var lightIndices = _cull.GetLightIndexMap();
            for (var i = maxVisibleLights; i < _cull.visibleLights.Count; i++)
            {
                lightIndices[i] = -1;
            }

            _cull.SetLightIndexMap(lightIndices);
        }
    }