Unity SRP自定义渲染管线学习1.2:初步绘制

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

绘制物体

绘制物体包括不透明的物体透明物体再加上之前的天空盒
Camera

void DrawVisibleGeometry()
    {
        //我们需要将不透明物体和透明物体分开绘制
        //如果我们直接先绘制所有的物体然后再绘制天空盒我们就会看到对于透明的物体如果其对于相机视线后面没有不透明的物体的话就会被天空球给遮挡住
        //我们的透明shader没有写入深度缓冲绘制天空盒时在这个片元上如果没有不透明的物体写入过深度的话天空盒的绘制就会直接覆盖掉
        //所以我们的绘制顺序是先不透明物体再绘制天空盒最后绘制透明物体
        //先进行不透明物体的绘制
        var sortingSettings = new SortingSettings(camera) //相机的透明排序取决于使用的是正交还是基于距离的排序
        {
            // 不给定排序规则的话绘制排序是随机混乱的
            // 这个排序是从前往后的因为对于后面的不透明物体当检测到其的深度大于缓冲区的深度时说明被挡住了可以直接丢弃不绘制
            // 而如果从后往前的话就会需要不断的覆盖绘制性能不如从前往后
            criteria = SortingCriteria.CommonOpaque,
        };
        var drawSettings = new DrawingSettings(unlitShaderTagId, sortingSettings);  //DrawingSettings主要用于对物体的渲染顺序和使用哪个Shader Pass
        var filteringSettings = new FilteringSettings(RenderQueueRange.opaque);  //FilteringSettings主要决定哪些物体渲染哪些不渲染根据LayerMaskRenderQueuesortingLayer等
        //在Cull中我们得到了cullingResults因此我们知道了哪些物体是需要绘制的
        context.DrawRenderers(cullingResults, ref drawSettings, ref filteringSettings);
        context.DrawSkybox(camera);  //添加绘制Skybox的命令
        // 再进行透明物体的绘制
        // 这个绘制顺序是从后往前的与不透明的绘制顺序是相反的
        // 不透明的物体并不写入深度缓冲因此从前往后并没有性能更优
        // 而且透明物体之间需要进行混合从后往前才能得到正确的混合
        // 但事实上并不能完全保证正确因为排序是基于整个物体的位置的但是对于复杂的大型物体间可能会有部分穿插有部分在前有部分在后这样子就会得到错误的效果
        sortingSettings.criteria = SortingCriteria.CommonTransparent;
        drawSettings.sortingSettings = sortingSettings;
        filteringSettings.renderQueueRange = RenderQueueRange.transparent;
        context.DrawRenderers(cullingResults, ref drawSettings, ref filteringSettings);
    }


    //使用Unity默认的一些Shader绘制出使用了不支持的Shader的物体
    partial void DrawUnsupportedShaders()  //如果不改成分部方法的定义和实现声明打包就会出错因为这部分代码是仅在编辑器下使用的
    {
        if (errorMaterial == null)
        {
            errorMaterial = new Material(Shader.Find("Hidden/InternalErrorShader"));
        }
        //我们将不支持的Shader用Unity默认的设置直接绘制出来
        //没有设置好Shader的属性之前绘制出来的物体是黑色的
        var drawSettings = new DrawingSettings(legacyShaderTagIds[0], new SortingSettings(camera))
        {
            overrideMaterial = errorMaterial,  //使用内置错误材质来绘制所有不支持的Shader的物体
        }
        ;
        for (int i = 1; i < legacyShaderTagIds.Length; i++)
        {
            drawSettings.SetShaderPassName(i, legacyShaderTagIds[i]);
        }
        var filteringSettings = FilteringSettings.defaultValue;
        context.DrawRenderers(cullingResults, ref drawSettings, ref filteringSettings);
    }

没有对绘制排序前
在这里插入图片描述

排序后
在这里插入图片描述

将不透明物体和透明物体分开渲染
在这里插入图片描述

先用默认Shader渲染不支持的Shader
在这里插入图片描述

用内置的错误材质在编辑器下绘制
在这里插入图片描述

分部代码让代码更清晰且可以让Editor代码不会出现打包错误
在这里插入图片描述

绘制Gizmos

    partial void DrawGizmos()
    {
        if (Handles.ShouldRenderGizmos())  //获取Gizmos是否开启
        {
            context.DrawGizmos(camera, GizmoSubset.PreImageEffects);
            context.DrawGizmos(camera, GizmoSubset.PostImageEffects);
        }
    }

绘制Gizmos前
在这里插入图片描述
绘制Gizmos后
在这里插入图片描述

UI

如果是Overlay的UI则不受我们的管线影响
在这里插入图片描述
改成ScreenCamera或者WorldSpaceCamera后
在这里插入图片描述
但是Scene中没有UI
在这里插入图片描述

添加绘制Scene视图的代码后

    partial void PrepareForSceneWindow()
    {
        if (camera.cameraType == CameraType.SceneView)
        {
            //绘制Scene视图中的UI
            ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
        }
    }

在这里插入图片描述

多相机

添加新的相机
在这里插入图片描述

两个相机的绘制在FrameDebuger中都混在了一起
在这里插入图片描述
我们对每个相机单独进行Sample

   partial void PrepareBuffer()
    {
        //我们将Buffer的名称修改为相机名称这样子就可以在FrameDebuger中看到相机对应的渲染在自己的组中查看会比较清晰
        Profiler.BeginSample("Editor Only");  //这里会产生GC会混下我们性能分析所以直接标明是Editor Only
        buffer.name = SampleName = camera.name;
        Profiler.EndSample();
    }

在这里插入图片描述
有多余的GC
在这里插入图片描述
在这里插入图片描述
把Editor下的GC单独分开
在这里插入图片描述

相机的ClearFlags

根据相机的ClearFlags的设置去调用buffer.ClearRenderTarget主要就是决定是否清除颜色缓冲深度缓冲
在这里插入图片描述

var flags = camera.clearFlags;
        //ClearRenderTarget需要在SetupCameraProperties之后进行Execute不然Clear的方式会变成Draw GL可以在FrameDebuger中看到
        //Draw GL是使用Hidden/InternalClear Shader绘制了一个全屏的Quad来达到清除的效果这种方式不是性能最好的
        //ClearRenderTarget需要在BeginSample之前不然FrameDebuger中会被多一次嵌套在"Render Camera"中
        buffer.ClearRenderTarget(
            flags != CameraClearFlags.Nothing,
            flags == CameraClearFlags.Color,   //为什么可以不用管Skybox时的清理颜色缓冲因为当选择Skybox时会清理深度缓冲且需要绘制Skybox没有深度缓冲此时一定会覆盖掉之前的颜色相当于清理了颜色缓冲
            flags == CameraClearFlags.Color ? camera.backgroundColor.linear : Color.clear  //我们用的是线性空间所以当要使用背景颜色时需要将其转化为线性空间的颜色
        );

Solid Color
在这里插入图片描述
Don’t Clear
在这里插入图片描述
改变Viewport后的Skybox
在这里插入图片描述
改变Viewport后的SolidColor
在这里插入图片描述
改变Viewport后的Depth Only
在这里插入图片描述
改变Viewport后的Don’t Clear
在这里插入图片描述
没有改变Viewport之前是直接用Clear的方式
在这里插入图片描述
改变Viewport后由于需要将清理的范围限定在Viewport中并非整个屏幕因此使用Shader绘制的方式清理将范围限定
在这里插入图片描述

参考

本文主要学习自https://catlikecoding.com/unity/tutorials/custom-srp/custom-render-pipeline/
catlikecoding是大神的博客里面有很多教程膜拜大神感恩大神。

具体代码

CustomRenderPipeline.cs

using UnityEngine;
using UnityEngine.Rendering;
public class CustomRenderPipeline : RenderPipeline
{
    CameraRenderer cameraRenderer = new CameraRenderer();
    //每一帧渲染调用
    protected override void Render(ScriptableRenderContext context, Camera[] cameras)
    {
        foreach(var camera in cameras)
        {
            cameraRenderer.Render(context, camera);
        }
    }
}

CustomRenderPipelineAsset.cs

using UnityEngine;
using UnityEngine.Rendering;  //需要使用UnityEngine.Rendering命名空间
//用于存储自定义渲染管线的设置
[CreateAssetMenu(menuName = "Rendering/Custom Render Pipeline")]
public class CustomRenderPipelineAsset : RenderPipelineAsset  //需要继承自RenderPipelineAsset
{
    protected override RenderPipeline CreatePipeline()
    {
        return new CustomRenderPipeline();
    }
}

CameraRenderer.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
//用于进行单个相机的画面渲染
public partial class CameraRenderer
{
    static ShaderTagId unlitShaderTagId = new ShaderTagId("SRPDefaultUnlit");
    ScriptableRenderContext context;
    Camera camera;
    CullingResults cullingResults;
    const string bufferName = "Render Camera";
    CommandBuffer buffer = new CommandBuffer
    {  //创建实例时可以这样直接初始化
        name = bufferName,
    };
    public void Render(ScriptableRenderContext context, Camera camera)
    {
        this.context = context;
        this.camera = camera;
        PrepareBuffer();
        PrepareForSceneWindow();
        if (!Cull())
            return;
        Setup();
        DrawVisibleGeometry();
        DrawUnsupportedShaders();
        DrawGizmos();
        Submit();
    }
    bool Cull()
    {
        //返回值表示cullingParameters是否有效比如当相机viewport rectangle宽高被设置为0,0无效的裁剪平面设置等
        //因此返回值能代表这个相机是否需要绘制
        if (camera.TryGetCullingParameters(out var cullingParameters))
        {
            //我们会发现在这里对ScriptableCullingParameters的操作基本都是out和ref这是因为ScriptableCullingParameters比较大这样子可以优化
            cullingResults = context.Cull(ref cullingParameters);
            return true;
        }
        return false;
    }
    void Setup()
    {
        //将相机的属性设置到绘制命令中比如相机的位置和旋转透视还是正交投影等这会决定视图矩阵和投影矩阵
        //就是Shader中所使用的unity_MatrixVP, View Matrix, Projection Matrix
        //我们可以在FrameDebuger中的ShaderProperties中看到unity_MatrixVP
        //如果我们不进行这项设置unity_MatrixVP都是一样的所以当我们旋转相机时相机看到的东西不会发生变化
        context.SetupCameraProperties(camera);
        var flags = camera.clearFlags;
        //ClearRenderTarget需要在SetupCameraProperties之后进行Execute不然Clear的方式会变成Draw GL可以在FrameDebuger中看到
        //Draw GL是使用Hidden/InternalClear Shader绘制了一个全屏的Quad来达到清除的效果这种方式不是性能最好的
        //ClearRenderTarget需要在BeginSample之前不然FrameDebuger中会被多一次嵌套在"Render Camera"中
        buffer.ClearRenderTarget(
            flags != CameraClearFlags.Nothing,
            flags == CameraClearFlags.Color,   //我们的Skybox是在最后绘制的
            flags == CameraClearFlags.Color ? camera.backgroundColor.linear : Color.clear  //我们用的是线性空间所以当要使用背景颜色时需要将其转化为线性空间的颜色
        );
        buffer.BeginSample(SampleName);
        ExecuteBuffer();  //Execute后Buffer中的命令才会加入到context中
    }
    void DrawVisibleGeometry()
    {
        //我们需要将不透明物体和透明物体分开绘制
        //如果我们直接先绘制所有的物体然后再绘制天空盒我们就会看到对于透明的物体如果其对于相机视线后面没有不透明的物体的话就会被天空球给遮挡住
        //我们的透明shader没有写入深度缓冲绘制天空盒时在这个片元上如果没有不透明的物体写入过深度的话天空盒的绘制就会直接覆盖掉
        //所以我们的绘制顺序是先不透明物体再绘制天空盒最后绘制透明物体
        //先进行不透明物体的绘制
        var sortingSettings = new SortingSettings(camera) //相机的透明排序取决于使用的是正交还是基于距离的排序
        {
            // 不给定排序规则的话绘制排序是随机混乱的
            // 这个排序是从前往后的因为对于后面的不透明物体当检测到其的深度大于缓冲区的深度时说明被挡住了可以直接丢弃不绘制
            // 而如果从后往前的话就会需要不断的覆盖绘制性能不如从前往后
            criteria = SortingCriteria.CommonOpaque,
        };
        var drawSettings = new DrawingSettings(unlitShaderTagId, sortingSettings);  //DrawingSettings主要用于对物体的渲染顺序和使用哪个Shader Pass
        var filteringSettings = new FilteringSettings(RenderQueueRange.opaque);  //FilteringSettings主要决定哪些物体渲染哪些不渲染根据LayerMaskRenderQueuesortingLayer等
        //在Cull中我们得到了cullingResults因此我们知道了哪些物体是需要绘制的
        context.DrawRenderers(cullingResults, ref drawSettings, ref filteringSettings);
        context.DrawSkybox(camera);  //添加绘制Skybox的命令
        // 再进行透明物体的绘制
        // 这个绘制顺序是从后往前的与不透明的绘制顺序是相反的
        // 不透明的物体并不写入深度缓冲因此从前往后并没有性能更优
        // 而且透明物体之间需要进行混合从后往前才能得到正确的混合
        // 但事实上并不能完全保证正确因为排序是基于整个物体的位置的但是对于复杂的大型物体间可能会有部分穿插有部分在前有部分在后这样子就会得到错误的效果
        sortingSettings.criteria = SortingCriteria.CommonTransparent;
        drawSettings.sortingSettings = sortingSettings;
        filteringSettings.renderQueueRange = RenderQueueRange.transparent;
        context.DrawRenderers(cullingResults, ref drawSettings, ref filteringSettings);
    }
    void Submit()
    {
        buffer.EndSample(SampleName);
        ExecuteBuffer();
        context.Submit();  //提交绘制命令进行绘制
    }
    void ExecuteBuffer()
    {
        context.ExecuteCommandBuffer(buffer);
        buffer.Clear();
    }
}

CameraRenderer.Editor.cs

using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine.Profiling;
using UnityEngine;
using UnityEngine.Rendering;
//用于进行单个相机的画面渲染
partial class CameraRenderer
{
    //为分部方法定义声明这样子编译时如果没有找到实现声明的方法的话就会剔除掉对这个方法的调用
    //使用这种方法可以比较好的避免Build出错再来查错
    partial void PrepareBuffer();
    partial void PrepareForSceneWindow();
    partial void DrawUnsupportedShaders();
    partial void DrawGizmos();
#if UNITY_EDITOR
    string SampleName { get; set; }
    static Material errorMaterial; //用于绘制不支持的shader
    //Unity默认的所有的Shader Pass的Tag
    static ShaderTagId[] legacyShaderTagIds = {
        new ShaderTagId("Always"),
        new ShaderTagId("ForwardBase"),
        new ShaderTagId("PrepassBase"),
        new ShaderTagId("Vertex"),
        new ShaderTagId("VertexLMRGBM"),
        new ShaderTagId("VertexLM"),
    };
    partial void PrepareBuffer()
    {
        //我们将Buffer的名称修改为相机名称这样子就可以在FrameDebuger中看到相机对应的渲染在自己的组中查看会比较清晰
        Profiler.BeginSample("Editor Only");  //这里会产生GC会混下我们性能分析所以直接标明是Editor Only
        buffer.name = SampleName = camera.name;
        Profiler.EndSample();
    }
    partial void PrepareForSceneWindow()
    {
        if (camera.cameraType == CameraType.SceneView)
        {
            //绘制Scene视图中的UI
            ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
        }
    }
    //使用Unity默认的一些Shader绘制出使用了不支持的Shader的物体
    partial void DrawUnsupportedShaders()  //如果不改成分部方法的定义和实现声明打包就会出错因为这部分代码是仅在编辑器下使用的
    {
        if (errorMaterial == null)
        {
            errorMaterial = new Material(Shader.Find("Hidden/InternalErrorShader"));
        }
        //我们将不支持的Shader用Unity默认的设置直接绘制出来
        //没有设置好Shader的属性之前绘制出来的物体是黑色的
        var drawSettings = new DrawingSettings(legacyShaderTagIds[0], new SortingSettings(camera))
        {
            overrideMaterial = errorMaterial,  //使用内置错误材质来绘制所有不支持的Shader的物体
        }
        ;
        for (int i = 1; i < legacyShaderTagIds.Length; i++)
        {
            drawSettings.SetShaderPassName(i, legacyShaderTagIds[i]);
        }
        var filteringSettings = FilteringSettings.defaultValue;
        context.DrawRenderers(cullingResults, ref drawSettings, ref filteringSettings);
    }
    partial void DrawGizmos()
    {
        if (Handles.ShouldRenderGizmos())  //获取Gizmos是否开启
        {
            context.DrawGizmos(camera, GizmoSubset.PreImageEffects);
            context.DrawGizmos(camera, GizmoSubset.PostImageEffects);
        }
    }
#else
    const string SampleName = bufferName;
#endif
}

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6