URP渲染管线研究

URP渲染管线研究

相关类

  • UniversalRenderPipeline 渲染管线
  • ScriptableRenderer 渲染器,主要抽象不同的渲染路径
    • ForwordRenderer urp 只有一个前向渲染器
  • ScriptableRenderPass 渲染某个通道
    • MainLightShadowCasterPass 主光源阴影
    • DrawObjectsPass 普通物体渲染(包括透明和不透明)
    • DrawSkyboxPass 天空盒
    • DepthOnlyPass 深度
    • PostProcessPass 后期处理

大概流程

  • 为了区分,把 RenderPipeline 称为 渲染管线, ShaderPipeline 称为着色管线,RenderPass 称为渲染通道,ShaderPass 称为着色通道

  • 渲染管线根据需要加入各种渲染通道,对渲染通道排序后依次调用,每个渲染通道根据需要加入各种渲染指令到指令队列中,
    最后在 ScriptableRenderContext.Submit 时执行所有渲染指令
    在渲染通道中有个渲染物体的通道,该通道遍历物体队列把调用Shader中的某个Pass的指令加入指令队列->Pass中通过Shader执行物体渲染过程

  • 渲染管线 -> 渲染单个相机 -> 加入各种RenderPass -> RenderPass排序后依次调用 -> RenderPass执行时添加各种 Command 到 CommandBuffer-> submit 时执行 CommandBuffer
    -> 执行渲染网格指令时会执行着色管线 -> 调用Shader中的某个Pass->Pass中通过Shader执行物体渲染过程

渲染目标

  • RenderTargetIdentifier
    用来统一三种渲染对象:

    • RenderTexture 自己创建的渲染纹理
    • CommandBuffer.GetTemporaryRT 通过该函数创建的临时纹理
    • BuiltinRenderTextureType 内置纹理
      • CurrentActive 当前目标纹理,就是通过 CommandBuffer.SetRenderTarget 设置的纹理
      • CameraTarget 当前显示器纹理,渲染给它就等于在显示器显示,如果是双缓冲区也有可能是后备缓冲区
  • RenderTargetHandle
    用来统一三种渲染目标:

    • RenderTargetIdentifier
    • CameraTarget 指的就是离屏表面 m_ColorBufferSystem.GetBackBuffer();
    • Shader.PropertyToID(shaderProperty); 显卡上的命名纹理
  • RenderTargetHandle.CameraTarget
    相机如果是直接上屏的话,就要渲染给它

  • ScriptableRenderer.m_CameraColorTarget
    当前相机渲染的目标
    可以是纹理或BuiltinRenderTextureType.CameraTarget
    这个是 ScriptableRenderer 执行渲染流程时使用
    在 RenderPass 中通过 renderingData.cameraData.renderer.cameraColorTarget 获取的就是该目标
    或直接通过 ScriptableRender.current.cameraColorTarget; 获取
    该值只在渲染过程获取才有效,并且在渲染过程是会改变的

    • 渲染开始时,赋值为离屏表面
      m_ActiveCameraColorAttachment = m_ColorBufferSystem.GetBackBuffer();
      ConfigureCameraColorTarget(m_ActiveCameraColorAttachment.Identifier());

    • 渲染Base类型相机时,如果有需要(比如做后期效果),会创建一个临时纹理
      if (cameraData.renderType == CameraRenderType.Base)
      if (intermediateRenderTexture)
      CreateCameraRenderTarget(context, ref cameraTargetDescriptor, useDepthPriming);

    • 渲染结束时,会调用 ScriptableRender.SwapColorBuffer 切换离屏表面(似乎没调用到)

  • ScriptableRenderer.m_ActiveColorAttachments
    当前渲染激活的目标
    ScriptableRenderer 内部维护的当前激活目标,要保证跟 BuiltinRenderTextureType.CurrentActive 一致
    直接通过 CommandBuffer.SetRenderTarget 或 CoreUtils.SetRenderTarget 设置并不会改变这个目标
    要改变这个目标需要在 RenderPass 中重载 Configure 函数,然后通过 ConfigureTarget 设置
    当 RenderPass 执行时会激活设置的目标,没有设置则激活 ScriptableRenderer.m_CameraColorTarget

    • 如何使用激活的渲染目标
      只要用 BuiltinRenderTextureType.CurrentActive 就能代表当前激活的渲染目标
      当你使用 CommandBuffer.SetRenderTarget 后当前激活的渲染目标将会改变
      如果你用 RTHandle handle = RTHandles.Alloc(BuiltinRenderTextureType.CurrentActive); 绑定了激活的渲染目标
      则使用 CommandBuffer.SetRenderTarget 后不影响已有的 handle 对象,它仍然指向原来的目标
  • ForwardRenderer.m_CameraColorAttachment
    一个临时的渲染目标,当相机要渲染到纹理或抓图时就会创建这个
    这个值一般是 ForwardRenderer 内部创建 RenderPass 使用

  • ForwardRenderer.m_ActiveCameraColorAttachment
    当前激活的渲染目标,根据需要可以指向 m_CameraColorAttachment 或 CameraTarget
    在最开始时设置给 ScriptableRender.m_CameraColorTarget
    这个值一般是 ForwardRenderer 内部创建 RenderPass 使用

CommandBuffer详解

  • CommandBuffer.GetTemporaryRT获取的RT会在Graphics.ExecuteCommandBuffer或者CameraEvent渲染完成后被回收,
    这就可能导致在不同CameraEvent获取RT时,取得之前创建的RT,覆盖原有的效果。
    如果是持续化显示,通过RenderTexture.GetTemporary接口可以避免这类问题。

  • RenderBufferLoadAction枚举类型(SetRenderTarget额外的参数)
    RenderBufferLoadAction描述渲染目标激活(加载)时应对其执行的操作。
    其中有三个值,在SetRenderTarget函数中,根据自己的RenderTexture实际的使用情况选择不同的Load策略:

    1. Load:当RenderBuffer被激活时,载入原本已保存的内容,这个加载读取的操作对Tile base框架的GPU比较占用宽带。
    2. Clear:当RenderBuffer被激活时,会清理掉原本buffer里的内容。目前需要用CommandBuffer.CleanRenderTarget做为代替。
    3. DontCare:当RenderBuffer被激活时,GPU不会把RenderBuffer的内容加载进内存中,从而减少宽带传输压力。

RenderPass说明

  • 相关代码
    执行 RenderPass 逻辑: ScriptableRender.ExecuteRenderPass
    抓取相机纹理的Pass: CapturePass

  • 激活的目标纹理问题

    • URP 在渲染每个 ScriptableRenderPass 前都会调用 cmd.SetRenderTarget 重设目标纹理为 ScriptableRenderPass 需要的目标纹理
    • 由于 ScriptableRenderer 不是直接使用 BuiltinRenderTextureType.CurrentActive 来判断当前目标,而是另外在 ScriptableRenderer 维护了一套变量,而这套变量只能通过 ScriptableRenderer.SetRenderTarget 进行修改,该函数是 internal ,意味着外部不能调用,底层也是调用 CoreUtils.SetRenderTarget
    • 因此我们不能在 RenderPass 中调用 cmd.SetRenderTarget 或 cmd.Blit 之类的会改变当前目标的函数,如果用了就要在用完后还原回来,但更好的作法是使用 RenderPass.Blit 代替 cmd.Blit ,重载 Configure 函数,调用 ConfigureTarget 设置需要的目标纹理来代替 cmd.SetRenderTarget
    • ScriptableRenderPass 的 ConfigureTarget 会设置 overrideCameraTarget=true,这样才会在 RenderPass 执行前重设目标纹理,而且 ScriptableRenderPass 的 ConfigureTarget 和 Blit 函数才会调用 ScriptableRenderer.SetRenderTarget 修改自已内部维护的当前激活的纹理目标
      总结一下
    • 使用 ScriptableRenderPass.Blit 替代 cmd.Blit
    • 使用 ScriptableRenderPass.ConfigureTarget 替代 cmd.SetRenderTarget
    • 实在要用 cmd.Blit 或 cmd.SetRenderTarget ,请在使用后调用 cmd.SetRenderTarget 还原成 ScriptableRenderer.m_ActiveColorAttachments[0],要用反射

渲染流程

```csharp
	//	渲染所有相机
	UniversalRenderPipeline.Render(ScriptableRenderContext renderContext, Camera[] cameras)
	{
		//	相机按深度排序
		SortCameras(cameras);

		for (int i = 0; i < cameras.Length; ++i)
        {
            var camera = cameras[i];
			BeginCameraRendering(renderContext, camera);
			UpdateVolumeFramework(camera, null);
            RenderSingleCamera(renderContext, camera)
			{
				RenderSingleCamera(context, cameraData, cameraData.postProcessEnabled)
				{
					---------------------------> 接下面
				}
			}
            EndCameraRendering(renderContext, camera);
        }
	}

	//	渲染单个相机
	UniversalRenderPipeline.RenderSingleCamera(ScriptableRenderContext context, CameraData cameraData, bool anyPostProcessingEnabled)
	{
		//	剪裁
		camera.TryGetCullingParameters(IsStereoEnabled(camera), out var cullingParameters);
		renderer.SetupCullingParameters(ref cullingParameters, ref cameraData);

        //  Cull 会触发对象上的 OnWillRenderObject 调用
		var cullResults = context.Cull(ref cullingParameters);

		//	初始化渲染数据
        InitializeRenderingData(asset, ref cameraData, ref cullResults, anyPostProcessingEnabled, out var renderingData);

		//	添加 RenderPass
		renderer.Setup(context, ref renderingData)  //  UniversalRender.Setup
		{
            ScriptableRenderer.AddRenderPasses(ref renderingData)
            {
                //  调用所有 RenderFeature 来添加 Pass,所以你可以通过扩展 RenderFeature 来实现特效
                for (int i = 0; i < rendererFeatures.Count; ++i)
                {
                    rendererFeatures[i].AddRenderPasses(this, ref renderingData);
                }
            }

            //  有些情况不能直接渲染到目标缓存,而是要先渲染到临时纹理,最后再Blit到目标缓存
            //  比如 通过 CameraCaptureBridge.AddCaptureAction 捕获相机纹理数据实现截屏
            //  比如 有添加 RenderFeature 实现特殊效果
            //  比如 动态分辨率
            //  比如 开启HDR
            bool createColorTexture = RequiresIntermediateColorTexture(ref renderingData, cameraTargetDescriptor) ||
(rendererFeatures.Count != 0 && !isRunningHololens);

            if (cameraData.renderType == CameraRenderType.Base)
            {
                m_ActiveCameraColorAttachment = (createColorTexture) ? m_CameraColorAttachment : RenderTargetHandle.CameraTarget;
                m_ActiveCameraDepthAttachment = m_CameraDepthAttachment;
                if (intermediateRenderTexture)
                    //  有些情况需要创建临时纹理,在这里创建临时纹理
                    CreateCameraRenderTarget(context, ref renderingData.cameraData);
            }

            //  我们经常获取 Renderer.cameraColorTarget 来取得当前渲染缓冲,就是在这里设置的
            //  当然,有些 Pass 也会调用 ConfigureCameraTarget  来修改当前的渲染缓冲
            ConfigureCameraTarget(m_ActiveCameraColorAttachment.Identifier(), m_ActiveCameraDepthAttachment.Identifier());

            //  添加 RenderFeature 的 pass
            for (int i = 0; i < rendererFeatures.Count; ++i)
            {
                if(rendererFeatures[i].isActive)
                    rendererFeatures[i].AddRenderPasses(this, ref renderingData);
            }

            EnqueuePass(m_MainLightShadowCasterPass);       //  阴影
            EnqueuePass(m_AdditionalLightsShadowCasterPass);
            EnqueuePass(m_DepthNormalPrepass);
            EnqueuePass(m_PrimedDepthCopyPass);
            EnqueuePass(m_RenderOpaqueForwardPass);     //  不透明物体
            EnqueuePass(m_DrawSkyboxPass);              //  天空盒

            //  拷贝深度值,后续着色器可通过  _CameraDepthTexture 直接访问该纹理
            //  只有勾选 UniversalRenderPipelineAsset 中的 DepthTexture 或 在RenderPass.Setup 中调用 ConfigureInput(ScriptableRenderPassInput.Depth)
            //  并且渲染缓存有深度值时才会执行 m_CopyDepthPass
            EnqueuePass(m_CopyDepthPass);               
            //  拷贝当前渲染缓冲的颜色值,这里只有不透明物体和天空盒,还未渲染透明物体,后续着色器可通过  _CameraOpaqueTexture 直接访问该纹理
            //  只有勾选 UniversalRenderPipelineAsset 中的 OpaqueTexture 或 在RenderPass.Setup 中调用 ConfigureInput(ScriptableRenderPassInput.Color) 才执行
            EnqueuePass(m_CopyColorPass);

            EnqueuePass(m_RenderTransparentForwardPass);    //  渲染透明物体
            EnqueuePass(m_OnRenderObjectCallbackPass);      //  调用所有脚本对象上的 OnRenderObject 函数

            //  所谓相机堆叠就是一个RenderType=Base的相机和多个(0-n)RenderType=Overlay相机组成的相机组
            //      在场景中添加Base相机,该相机会渲染,添加Overlay相机,则只有该相机被关联到Base相机才会渲染
            //      关联方法:选择一个 Base 的相机,在 Inspector 的 Stack 字段中关联Overlay相机
            //  理解了相机堆叠就能明白 lastCameraInTheStack,就是指相机组中最后一个相机渲染时,lastCameraInTheStack 为 true
            //      比如 某个 Base 相机没有关联 Overlay 相机,则该相机构成一个堆叠,该相机渲染时 lastCameraInTheStack 为 true
            //      某个 Base 相机关联了 Overlay 相机,则 Base 相机和所有关联的 Overlay 相机构成一个堆叠,当最后一个相机渲染时 lastCameraInTheStack 才为 true
            if (lastCameraInTheStack)
            {
                //  这里面也是最容易出问题的地方,很多 URP 版本都有问题
                //  比如 7.3.1  抓取画面回调获得的是没有经过后处理的画面 
                //  比如 7.7.1  
                EnqueuePass(postProcessPass);               //  后处理,最后一个相机才会调用
                EnqueuePass(finalPostProcessPass);          //  后处理之后的后处理,比如 FXAA

                //  给外面抓取最终渲染画面的机会,比如 截图、录屏
                //  可以通过 CameraCaptureBridge.AddCaptureAction 来注入回调,回调的 source 就是相机渲染到的纹理
                EnqueuePass(m_CapturePass);

                EnqueuePass(m_FinalBlitPass);
            }
		}

        //  执行 RenderPass
        renderer.Execute(context, ref renderingData)    //  ScriptableRender.Execute
		{
            //  调用所有 RenderPass.OnCameraSetup
            InternalStartRendering(context, ref renderingData);

            ClearRenderingState(cmd);       //  初始化渲染状态
            //  初始化全局shader变量:_Time _SinTime _CosTime unity_DeltaTime _TimeParameters
            SetShaderTimeValues(cmd, time, deltaTime, smoothDeltaTime); 

			//	对前面加入的各种 Pass 进行排序,主要是根据 RenderPassEvent 排序
            SortStable(m_ActiveRenderPassQueue);

            using var renderBlocks = new RenderBlocks(m_ActiveRenderPassQueue)
            {
                //	对 m_ActiveRenderPassQueue 中的 Pass 按 RenderPassEvent 分组,
                //	blockRanges[i] 表示i组结束的 Pass 在 m_ActiveRenderPassQueue 中的索引
                FillBlockRanges(blockEventLimits, out NativeArray<int> blockRanges);
            }

            //	设置灯光
            SetupLights(context, ref renderingData);

            //	按各个分组分别渲染分组中的所有 Pass 
            ExecuteBlock(RenderPassBlock.BeforeRendering, in renderBlocks, context, ref renderingData);

            if (cameraData.renderType == CameraRenderType.Base)
            {
                //  设置相机相关的 shader 属性,包括  _ScaledScreenParams _WorldSpaceCameraPos _ScreenParams 
                //  _ProjectionParams _ZBufferParams  unity_OrthoParams  _GlobalMipBias _ScreenSize
                //  unity_WorldToCamera unity_CameraToWorld  unity_CameraProjection unity_CameraInvProjection
                context.SetupCameraProperties(camera);
                SetPerCameraShaderVariables(cmd, ref cameraData);
            }

            //  不透明物体
            ExecuteBlock(RenderPassBlock.MainRenderingOpaque, in renderBlocks, context, ref renderingData);
            //  透明物体
            ExecuteBlock(RenderPassBlock.MainRenderingTransparent, in renderBlocks, context, ref renderingData);
            //  后处理
            ExecuteBlock(RenderPassBlock.AfterRendering, in renderBlocks, context, ref renderingData);

            //  调用所有 RenderPass.OnCameraCleanup
            InternalFinishRendering(context, cameraData.resolveFinalTarget);
		}

        //  每个 Pass 只是把渲染指令加入到指令队列中
		context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
        //  到这里才会真正执行所有指令,而且说明是每个相机提交一次而不是所有相机提交一次
        context.Submit();
	}

	//	渲染1组Pass
	ScriptableRenderer.ExecuteBlock(int blockIndex, NativeArray<int> blockRanges,
        ScriptableRenderContext context, ref RenderingData renderingData, int eyeIndex = 0, bool submit = false)
	{
		int endIndex = blockRanges[blockIndex + 1];
		//	遍历分组中的 Pass
        for (int currIndex = blockRanges[blockIndex]; currIndex < endIndex; ++currIndex)
        {
            var renderPass = m_ActiveRenderPassQueue[currIndex];
            ExecuteRenderPass(context, renderPass, ref renderingData, eyeIndex)
			{
				//	实际执行  Pass 渲染的地方
				renderPass.Execute(context, ref renderingData)
				{
					---------------------------------> 接下面
				}
			}
        }

        if (submit)
            context.Submit();
	}

	//	重点介绍普通物体的渲染 Pass (包括透明和不透明)
	DrawObjectsPass.Execute(ScriptableRenderContext context, ref RenderingData renderingData)
	{
		//	指定排序方式,不透明物体按离摄像机从近到远排序,这样远的物体如果被覆盖,在深度测试时就被pass掉,提高效率
		//	透明物体队列物体按离摄像头从远到近排序,这样才能正确显示
		var sortFlags = (m_IsOpaque) ? renderingData.cameraData.defaultOpaqueSortFlags : SortingCriteria.CommonTransparent;

		//	渲染这3个 shader pass
		List<ShaderTagId> m_ShaderTagIdList = new List<ShaderTagId>();
		m_ShaderTagIdList.Add(new ShaderTagId("UniversalForward"));
        m_ShaderTagIdList.Add(new ShaderTagId("LightweightForward"));
        m_ShaderTagIdList.Add(new ShaderTagId("SRPDefaultUnlit"));
        var drawSettings = CreateDrawingSettings(m_ShaderTagIdList, ref renderingData, sortFlags);

		//	透明和不透明物体使用不同的 RenderQueueRange
		RenderQueueRange renderQueueRange = (m_IsOpaque) ? RenderQueueRange.opaque : RenderQueueRange.transparent;
		//	层遮罩是在 URP 配置中配置的,在 UniversalRenderPipelineAsset_Renderer 配置的 Filtering 下
		LayerMask layerMask = (m_IsOpaque) ? renderingData.opaqueLayerMask : transparentLayerMask;
        var filterSettings = new FilteringSettings(renderQueueRange, layerMask);

		//	渲染物体
		context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref filterSettings, ref m_RenderStateBlock);

		//	对于不支持urp管线的物体,渲染成红色
		RenderingUtils.RenderObjectsWithError(context, ref renderingData.cullResults, camera, filterSettings, SortingCriteria.None);
	}
```

猜你喜欢

转载自blog.csdn.net/qmladm/article/details/142371959