要了解什么是URP,首先得先了解什么是SRP.而要了解什么是SRP,又得先了解什么是RenderPipeline.我们先来看看RenderPipeline究竟是什么.

RenderPipeline

在游戏场景中,有很多的模型效果需要绘制,如有不透明的(Opaque Object)、透明的(Transparencies)物体,有时候又需要使用屏幕深度贴图,后处理效果等.那么这些东西在什么时候来绘制,哪个阶段绘制什么东西,这就是由渲染管线来决定的.

默认渲染管线示意图

Unity默认的渲染管线如上图所示.我们来简单解释一下Forward Rendering的渲染管线.

在前向渲染中,相机开始绘制一帧图像时,如果你在代码中对一个摄像机设置了设置了

cam.depthTextureMode = DepthTextureMode.Depth;

那么该相机的渲染管线就会执行Depth Texture这一步操作.会把场景中所有RenderType=Opaque的Shader里面Tags{"LightMode" = "ShadowCaster"}的Pass全部执行一遍,把运行结果画到深度贴图中.所以如果相机开起了深度贴图,那么该相机的drawcall就会增加(所有Opaque的DrawCall翻了一倍).要是没有打开深度贴图,那么渲染管线就会略过这一步.

所以Shader 的Tags { "LightMode" "RenderType" "Queue"}等标签其实是和渲染管线息息相关的.Unity在进行绘制的时候,会根据这些标签,把Shader中对应的Pass放进上图中对应的步骤去执行.所以就有了先画不透明物体,再画透明物体这些绘制顺序.

总结来说,渲染管线就是定义好了一种绘制的顺序,相机在绘制每一帧的时候就是按这个定义的顺序去画场景中的一个个物体.

Scriptable Renderer Pipeline(SRP 可编程渲染管线)

SRP简单来说,就是之前定死的渲染管线现在可以自己来组织了.只要你乐意,就可以把不透明物体放到透明物体之后来画,或者在上面的步骤中可以插入很多的渲染步骤来满足你需要的效果.

SRP除了提供定义管线的自由度,还和很多更新的优化有关,比如SRP Batcher的使用等.

URP/LWRP

URP其实就是Unity定义出来的,适合大多数设备的一个SRP.它的具体实现细节可以通过代码来看到.

文件结构

在你添加完URP后,会看到如下的结构

URP的入口就是Universal RP/Runtime/UniversalRenderPipeline.Render函数

protected override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
{BeginFrameRendering(renderContext, cameras);GraphicsSettings.lightsUseLinearIntensity = (QualitySettings.activeColorSpace == ColorSpace.Linear);GraphicsSettings.useScriptableRenderPipelineBatching = asset.useSRPBatcher;SetupPerFrameShaderConstants();SortCameras(cameras);//根据相机深度把相机排好序foreach (Camera camera in cameras){BeginCameraRendering(renderContext, camera);
#if VISUAL_EFFECT_GRAPH_0_0_1_OR_NEWER//It should be called before culling to prepare material. When there isn't any VisualEffect component, this method has no effect.VFX.VFXManager.PrepareCamera(camera);
#endifRenderSingleCamera(renderContext, camera);//渲染一个个相机EndCameraRendering(renderContext, camera);}EndFrameRendering(renderContext, cameras);
}

关键的几步就是对摄像机根据深度排好序,然后调用RenderSingleCamera逐个去绘制每个摄像机.

RenderSingleCamera

public static void RenderSingleCamera(ScriptableRenderContext context, Camera camera)
{//获取相机的裁剪参数if (!camera.TryGetCullingParameters(IsStereoEnabled(camera), out var cullingParameters))return;var settings = asset;UniversalAdditionalCameraData additionalCameraData = null;if (camera.cameraType == CameraType.Game || camera.cameraType == CameraType.VR)camera.gameObject.TryGetComponent(out additionalCameraData);//根据PipelineAsset和UniversalAdditionalCameraData(相机上的设置)设置好摄像机参数InitializeCameraData(settings, camera, additionalCameraData, out var cameraData);//把相机的参数设置进shader共用变量,即shader中使用的一些内置shader变量SetupPerCameraShaderConstants(cameraData);//获取到该相机使用的ScriptableRenderer,即自己定义的SRP管线.//该管线如果相机有自己定义好,就用相机定义的,没有则用asset设置的管线.//URP中默认使用的是ForwardRenderer,就是在这里使用上了重新定义的渲染管线.ScriptableRenderer renderer = (additionalCameraData != null) ? additionalCameraData.scriptableRenderer : settings.scriptableRenderer;if (renderer == null){Debug.LogWarning(string.Format("Trying to render {0} with an invalid renderer. Camera rendering will be skipped.", camera.name));return;}//设置好FrameDebug中的标签名字string tag = (asset.debugLevel >= PipelineDebugLevel.Profiling) ? camera.name: k_RenderCameraTag;CommandBuffer cmd = CommandBufferPool.Get(tag);using (new ProfilingSample(cmd, tag)){renderer.Clear();//根据cameraData设置好cullingParametersrenderer.SetupCullingParameters(ref cullingParameters, ref cameraData);context.ExecuteCommandBuffer(cmd);cmd.Clear();#if UNITY_EDITOR// Emit scene view UIif (cameraData.isSceneViewCamera)ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
#endif//根据裁剪参数计算出裁剪结果,管线中的所有渲染步骤都是从这个裁剪结果中筛选出自己要渲染的元素var cullResults = context.Cull(ref cullingParameters);//把前面获取到的所有参数都设置进renderingDataInitializeRenderingData(settings, ref cameraData, ref cullResults, out var renderingData);//设置好管线中的所有设置renderer.Setup(context, ref renderingData);//执行管线中的一个个步骤renderer.Execute(context, ref renderingData);}context.ExecuteCommandBuffer(cmd);CommandBufferPool.Release(cmd);context.Submit();
}

在这个函数中有几个函数可以说明一下

SetupPerCameraShaderConstants中把shader常用的几个内置变量给设置了

PerCameraBuffer._InvCameraViewProj = Shader.PropertyToID("_InvCameraViewProj");
PerCameraBuffer._ScreenParams = Shader.PropertyToID("_ScreenParams");
PerCameraBuffer._ScaledScreenParams = Shader.PropertyToID("_ScaledScreenParams");
PerCameraBuffer._WorldSpaceCameraPos = Shader.PropertyToID("_WorldSpaceCameraPos");

之前这些都是unity在背后给做了,现在这些都可以看到.所以现在Shader中使用的所有内置变量都可以找到出处,用的是那些数据进行设置的.

string tag = (asset.debugLevel >= PipelineDebugLevel.Profiling) ? camera.name: k_RenderCameraTag;

这个tag决定了Frame Debug中显示的最顶层的名字.如果你设置了DebugLevel

那么你在FrameDebug中就可以看到相应摄像机的渲染步骤.

其实这个函数最关键的两步就是

        //设置好管线中的所有设置renderer.Setup(context, ref renderingData);//执行管线中的一个个步骤renderer.Execute(context, ref renderingData);

从这里进入了ScriptableRenderer,URP自定义的渲染管线ForwardRenderer.

在ForwardRenderer中,定义了一堆的ScriptableRenderPass.ScriptableRenderPass其实就相当于渲染管线中的一个个步骤.如m_DepthPrepass就相当于开始图片中Depth Texture这一步,m_RenderOpaqueForwardPass就相当于默认管线中的Opaque Object这一步.

而SetUp函数做的就是把这些Pass组织起来,按什么顺序来定义这个渲染管线.下面截取一些来说明一下.

public override void Setup(ScriptableRenderContext context, ref RenderingData renderingData)
{...//这些rendererFeatures是在RenderData的配置文件中设置的,相当于我们自己如果要在管线中做一些添加//就自己写上renderPass,添加到这些feature里.Forward管线在这里把我们自己的pass加进来.for (int i = 0; i < rendererFeatures.Count; ++i){rendererFeatures[i].AddRenderPasses(this, ref renderingData);}...//这里就是把一个个的步骤加进管线,在FrameDebug中可以看到一个个的步骤if (mainLightShadows)EnqueuePass(m_MainLightShadowCasterPass);if (additionalLightShadows)EnqueuePass(m_AdditionalLightsShadowCasterPass);if (requiresDepthPrepass){m_DepthPrepass.Setup(cameraTargetDescriptor, m_DepthTexture);EnqueuePass(m_DepthPrepass);}if (resolveShadowsInScreenSpace){m_ScreenSpaceShadowResolvePass.Setup(cameraTargetDescriptor);EnqueuePass(m_ScreenSpaceShadowResolvePass);}if (postProcessEnabled){m_ColorGradingLutPass.Setup(m_ColorGradingLut);EnqueuePass(m_ColorGradingLutPass);}EnqueuePass(m_RenderOpaqueForwardPass);if (camera.clearFlags == CameraClearFlags.Skybox && RenderSettings.skybox != null)EnqueuePass(m_DrawSkyboxPass);// If a depth texture was created we necessarily need to copy it, otherwise we could have render it to a renderbufferif (createDepthTexture){m_CopyDepthPass.Setup(m_ActiveCameraDepthAttachment, m_DepthTexture);EnqueuePass(m_CopyDepthPass);}if (renderingData.cameraData.requiresOpaqueTexture){// TODO: Downsampling method should be store in the renderer isntead of in the asset.// We need to migrate this data to renderer. For now, we query the method in the active asset.Downsampling downsamplingMethod = UniversalRenderPipeline.asset.opaqueDownsampling;m_CopyColorPass.Setup(m_ActiveCameraColorAttachment.Identifier(), m_OpaqueColor, downsamplingMethod);EnqueuePass(m_CopyColorPass);}EnqueuePass(m_RenderTransparentForwardPass);EnqueuePass(m_OnRenderObjectCallbackPass);bool afterRenderExists = renderingData.cameraData.captureActions != null ||hasAfterRendering;bool requiresFinalPostProcessPass = postProcessEnabled &&renderingData.cameraData.antialiasing == AntialiasingMode.FastApproximateAntialiasing;// if we have additional filters// we need to stay in a RTif (afterRenderExists){bool willRenderFinalPass = (m_ActiveCameraColorAttachment != RenderTargetHandle.CameraTarget);// perform post with src / dest the sameif (postProcessEnabled){m_PostProcessPass.Setup(cameraTargetDescriptor, m_ActiveCameraColorAttachment, m_AfterPostProcessColor, m_ActiveCameraDepthAttachment, m_ColorGradingLut, requiresFinalPostProcessPass, !willRenderFinalPass);EnqueuePass(m_PostProcessPass);}//now blit into the final targetif (m_ActiveCameraColorAttachment != RenderTargetHandle.CameraTarget){if (renderingData.cameraData.captureActions != null){m_CapturePass.Setup(m_ActiveCameraColorAttachment);EnqueuePass(m_CapturePass);}if (requiresFinalPostProcessPass){m_FinalPostProcessPass.SetupFinalPass(m_ActiveCameraColorAttachment);EnqueuePass(m_FinalPostProcessPass);}else{m_FinalBlitPass.Setup(cameraTargetDescriptor, m_ActiveCameraColorAttachment);EnqueuePass(m_FinalBlitPass);}}}else{if (postProcessEnabled){if (requiresFinalPostProcessPass){m_PostProcessPass.Setup(cameraTargetDescriptor, m_ActiveCameraColorAttachment, m_AfterPostProcessColor, m_ActiveCameraDepthAttachment, m_ColorGradingLut, true, false);EnqueuePass(m_PostProcessPass);m_FinalPostProcessPass.SetupFinalPass(m_AfterPostProcessColor);EnqueuePass(m_FinalPostProcessPass);}else{m_PostProcessPass.Setup(cameraTargetDescriptor, m_ActiveCameraColorAttachment, RenderTargetHandle.CameraTarget, m_ActiveCameraDepthAttachment, m_ColorGradingLut, false, true);EnqueuePass(m_PostProcessPass);}}else if (m_ActiveCameraColorAttachment != RenderTargetHandle.CameraTarget){m_FinalBlitPass.Setup(cameraTargetDescriptor, m_ActiveCameraColorAttachment);EnqueuePass(m_FinalBlitPass);}}#if UNITY_EDITORif (renderingData.cameraData.isSceneViewCamera){m_SceneViewDepthCopyPass.Setup(m_DepthTexture);EnqueuePass(m_SceneViewDepthCopyPass);}
#endif
}

其实总结来说,SetUp()就是决定了渲染管线中会有哪些步骤,这些步骤的渲染顺序是什么.而每一步里面该怎么渲染,该渲染什么,都是在这一步的pass中自己定义的.

public void EnqueuePass(ScriptableRenderPass pass)
{m_ActiveRenderPassQueue.Add(pass);
}

EnqueuePass就是把这些pass都加进m_ActiveRenderPassQueue里面,后面的Execute部分则把这些pass都执行一遍,画出他们对应的东西.

public void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{...//把m_ActiveRenderPassQueue中的pass都进行排序.SortStable(m_ActiveRenderPassQueue);// Cache the time for after the call to `SetupCameraProperties` and set the time variables in shader// For now we set the time variables per camera, as we plan to remove `SetupCamearProperties`.// Setting the time per frame would take API changes to pass the variable to each camera render.// Once `SetupCameraProperties` is gone, the variable should be set higher in the call-stack.
#if UNITY_EDITORfloat time = Application.isPlaying ? Time.time : Time.realtimeSinceStartup;
#elsefloat time = Time.time;
#endiffloat deltaTime = Time.deltaTime;float smoothDeltaTime = Time.smoothDeltaTime;//shader中使用的内置time变量都在这边设置.SetShaderTimeValues(time, deltaTime, smoothDeltaTime);// Upper limits for each block. Each block will contains render passes with events below the limit.NativeArray<RenderPassEvent> blockEventLimits = new NativeArray<RenderPassEvent>(k_RenderPassBlockCount, Allocator.Temp);blockEventLimits[RenderPassBlock.BeforeRendering] = RenderPassEvent.BeforeRenderingPrepasses;blockEventLimits[RenderPassBlock.MainRendering] = RenderPassEvent.AfterRenderingPostProcessing;blockEventLimits[RenderPassBlock.AfterRendering] = (RenderPassEvent)Int32.MaxValue;NativeArray<int> blockRanges = new NativeArray<int>(blockEventLimits.Length + 1, Allocator.Temp);//把m_ActiveRenderPassQueue中的pass分成几块(BeforeRendering,MainRendering,AfterRendering)来执行.FillBlockRanges(blockEventLimits, blockRanges);blockEventLimits.Dispose();//设置好shader中使用的内置light数据SetupLights(context, ref renderingData);// Before Render Block. This render blocks always execute in mono rendering.// Camera is not setup. Lights are not setup.// Used to render input textures like shadowmaps.ExecuteBlock(RenderPassBlock.BeforeRendering, blockRanges, context, ref renderingData);...// Override time values from when `SetupCameraProperties` were called.// They might be a frame behind.// We can remove this after removing `SetupCameraProperties` as the values should be per frame, and not per camera.SetShaderTimeValues(time, deltaTime, smoothDeltaTime);...// In this block main rendering executes.ExecuteBlock(RenderPassBlock.MainRendering, blockRanges, context, ref renderingData);...// In this block after rendering drawing happens, e.g, post processing, video player capture.ExecuteBlock(RenderPassBlock.AfterRendering, blockRanges, context, ref renderingData);if (stereoEnabled)EndXRRendering(context, camera);DrawGizmos(context, camera, GizmoSubset.PostImageEffects);//if (renderingData.resolveFinalTarget)InternalFinishRendering(context);blockRanges.Dispose();
}

Execute函数中把m_ActiveRenderPassQueue中的pass进行排序,并分成几块,设置好一些共用的shader内置变量后,把这几块pass都跑一遍.

void ExecuteBlock(int blockIndex, NativeArray<int> blockRanges,ScriptableRenderContext context, ref RenderingData renderingData, bool submit = false)
{int endIndex = blockRanges[blockIndex + 1];for (int currIndex = blockRanges[blockIndex]; currIndex < endIndex; ++currIndex){//找到对应的pass去执行var renderPass = m_ActiveRenderPassQueue[currIndex];ExecuteRenderPass(context, renderPass, ref renderingData);}if (submit)context.Submit();
}void ExecuteRenderPass(ScriptableRenderContext context, ScriptableRenderPass renderPass, ref RenderingData renderingData)
{//设置好该renderPass的渲染目标,是往摄像机默认缓存里输出,还是输出到对应的renderTexture中....//去执行这个renderPass的渲染renderPass.Execute(context, ref renderingData);
}

所以我们现在已经知道了一个SRP到底是怎么把一个个渲染步骤联系起来执行的.

每一个步骤中怎么执行,到底渲染哪些东西,则是在每一个步骤里面决定的.

我们挑一个DepthOnlyPass来说明一下.

public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
{CommandBuffer cmd = CommandBufferPool.Get(m_ProfilerTag);//string m_ProfilerTag = "Depth Prepass";可以在Frame Debug中找到这一步骤using (new ProfilingSample(cmd, m_ProfilerTag)){context.ExecuteCommandBuffer(cmd);cmd.Clear();var sortFlags = renderingData.cameraData.defaultOpaqueSortFlags;//ShaderTagId m_ShaderTagId = new ShaderTagId("DepthOnly");//筛选出在裁剪结果里shader中LightMode="DepthOnly"的Pass来绘制.var drawSettings = CreateDrawingSettings(m_ShaderTagId, ref renderingData, sortFlags);drawSettings.perObjectData = PerObjectData.None;ref CameraData cameraData = ref renderingData.cameraData;Camera camera = cameraData.camera;if (cameraData.isStereoEnabled)context.StartMultiEye(camera);//根据设置好的筛选条件来绘制物体.context.DrawRenderers(renderingData.cullResults, ref drawSettings, ref m_FilteringSettings);}context.ExecuteCommandBuffer(cmd);CommandBufferPool.Release(cmd);
}

其实每一个渲染步骤中,都会设置好这一步的绘制条件.比如在DepthOnly这一步骤,就会在cullResults筛选出来的所有东西里面找到那些有写了DepthOnly的Pass来绘制.所以你打开一个shader文件,会有好多个pass,每个pass都有对应的LightMode,这就是为了让这一个东西在渲染管线中对应的步骤去做相应的绘制.

所以总结来说,SRP其实就是把原先藏起来的渲染过程暴露出来,可以让我们清楚的看到在一个渲染流程中到底经历了哪些步骤,每一步骤都对哪些东西进行绘制.这些步骤的顺序,绘制的东西也都与FrameDebug窗口中的每一个条目一一对应.相当于从FrameDebug中就可以看到这个渲染管线的具体渲染步骤细节.

URP/LWRP学习入门相关推荐

  1. URP/LWRP Shader实现描边效果

    2021.1.11 更新: 我觉得我写得比较老了,可以看看下面新整理的文章 LWRP/URP/HDRP中的描边shader:https://zhuanlan.zhihu.com/p/354190065 ...

  2. 【AI参赛经验】深度学习入门指南:从零开始TinyMind汉字书法识别——by:Link

    各位人工智能爱好者,大家好! 由TinyMind发起的#第一届汉字书法识别挑战赛#正在火热进行中,比赛才开始3周,已有数只黑马冲进榜单.目前TOP54全部为90分以上!可谓竞争激烈,高手如林.不是比赛 ...

  3. 深度学习入门,一文讲解神经网络的构成、训练和算法

    小白深度学习入门系列 神经网络的构成.训练和算法 什么是神经网络 人工神经网络(Artificial Neural Network,ANN),简称神经网络(Neural Network,NN),是一种 ...

  4. PyTorch深度学习入门与实战(案例视频精讲)

    作者:孙玉林,余本国 著 出版社:中国水利水电出版社 品牌:智博尚书 出版时间:2020-07-01 PyTorch深度学习入门与实战(案例视频精讲)

  5. PyTorch深度学习入门

    作者:曾芃壹 出版社:人民邮电出版社 品牌:iTuring 出版时间:2019-09-01 PyTorch深度学习入门

  6. 深度学习入门 基于Python的理论与实现

    作者:斋藤康毅 出版社:人民邮电出版社 品牌:iTuring 出版时间:2018-07-01 深度学习入门 基于Python的理论与实现

  7. 干货|《深度学习入门之Pytorch》资料下载

    深度学习如今已经成为了科技领域中炙手可热的技术,而很多机器学习框架也成为了研究者和业界开发者的新宠,从早期的学术框架Caffe.Theano到如今的Pytorch.TensorFlow,但是当时间线来 ...

  8. 福利丨一门面向所有人的人工智能公开课:MIT 6.S191,深度学习入门

    对初学者来说,有没有易于上手,使用流行神经网络框架进行教学的深度学习课程?近日,麻省理工学院(MIT)正式开源了在线介绍性课程「MIT 6.S191:深度学习入门」.该课程包括一系列有关神经网络及其在 ...

  9. 深度学习入门指北——从硬件到软件

    作者:隔壁王大喵 近日,Rachel Thomas在fast.ai上发布了一篇博文<What you need to do deep learning>,他希望通过这篇文章回答一些深度学习 ...

最新文章

  1. 技术人的七夕表白可以有多浪漫?
  2. 计算机基础知识题库选择题,计算机基础知识篇选择题库
  3. pandas显示全部数据内容_vue项目,当鼠标移入时文本长度超出才显示全部内容
  4. yii2 beta版 执行流程
  5. c51随机数不重复_C++中随机数和不重复的随机数
  6. C++安全方向(三):3.2 单项散列函数的应用场景
  7. c54x汇编语言程序设计,第5章 apos;C54x汇编语言程序设计.doc
  8. JSON字符串转对象集合
  9. Linux之忘记密码解决方案
  10. 案例分析 - OOM的内存分析
  11. Instsrv.exe和Srvany.exe的使用方法
  12. 为什么快捷指令无法将媒体转换为文本_小红书去水印快捷指令重磅更新,连视频封面图都可以下载了...
  13. 网络图怎么画?简单专业的网络图绘制方法
  14. 《三桃演义》第二回:返航,火星人柯里昂
  15. 这些神奇 Bug,碰到真是让人目瞪狗呆!
  16. Maya Python脚本建模之随机生成多边形并设定目标限制
  17. win7计算机任务栏过长,win7任务栏终极技巧解说
  18. 基于时间序列分析方法的零售业快消品销量预测研究
  19. Unity InputField输入框调用win10平板虚拟键盘
  20. 教育培训行业市场营销推广方案分享

热门文章

  1. C语言实现split()函数:字符串分割
  2. python:统计数字字符个数
  3. 永磁同步电机的矢量控制策略(六)一一一SPWM控制
  4. 《智百盛汽修汽配管理系统--汽修管理模块》项目研发总结
  5. 定时同步 matlab仿真,高阶QAM定时同步的MATLAB仿真及其FPGA实现
  6. 计算机二级报名照片是多少寸的,报考计算机二级时对于照片的要求有哪些
  7. 音乐相册源码php,PHP+MySQL音乐相册网站的设计与实现
  8. [PaddleSeg源码阅读] PaddleSeg 导出静态图 export.py 文件中的道道
  9. 用python输出一张九九乘法表_如何用python输出九九乘法表?有哪些方法?
  10. 20-1职业技能 Taking meeting minutes