
第一次把公司的一个展厅项目从 Built-in 切到 URP是在一个周五晚上。美术同事在群里发了一张截图整个场景的模型全部变成了刺眼的粉红色灯光像被人拔了电源地面只剩下一片死灰。那天我们折腾到凌晨两点才意识到问题不在模型、不在贴图而在于渲染管线本身就是另一套完全不同的运行时——材质的 Shader、贴图的通道约定、灯光的单位、后处理的挂载方式没有一样是可以原地不动带过来的。这篇笔记就是从那几次翻车开始整理的。它面向的是已经在 Unity 里写过脚本、做过 UI、跑通过一个小 Demo但还没认真区分过Built-in RP、URP、HDRP这三条渲染管线的开发者也适合那些正在给项目做技术选型、纠结到底上哪条管线的人。内容会围绕三条管线的本质差异、选型逻辑、工程配置、光照阴影、Shader 迁移、性能优化以及多平台打包时的实际约束展开尽量把每个选择的为什么说清楚让你少走我当年走过的弯路。1. 三条渲染管线并存的真正原因1.1 Built-in 的硬编码路径为什么越来越难改Built-in 渲染管线是 Unity 最早的一套渲染流程它的核心问题不在于不好而在于它被写死在引擎的 C 层。你在项目里能改的东西基本集中在几个固定的入口Camera 的 Rendering PathForward / Deferred / Legacy Vertex Lit、Quality Settings 里的阴影参数、Lighting 面板里的烘焙设置。想在里面插一个自定义的渲染步骤只能靠OnRenderImage抓屏再处理或者写CommandBuffer挂在相机事件上。我做过一个需求要在不透明物体画完之后、透明物体画之前插一次全屏描边。在 Built-in 里这件事极其别扭因为你无法精确控制渲染队列的插入点只能靠 Queue 标签去蹭位置稍微复杂一点的后处理就会和内置的雾效、后处理栈打架。这种能改但改不干净的状态是 Unity 决定把渲染逻辑开放出来的直接动机。1.2 URP 与 HDRP 的代码组织差异与可扩展点URP 和 HDRP 都建立在 SRPScriptable Render Pipeline之上也就是说它们的主体逻辑是用 C# 写的躺在 Packages 目录里你能打开、能读、能继承、能改写。这才是它们和 Built-in 最本质的区别——不是画质高低而是控制权在谁手上。但两者的组织方式差别很大。URP 的结构相对扁平一个UniversalRenderPipelineAsset管全局参数挂若干个UniversalRendererData2D 项目则是Renderer2DData每个 Renderer Data 上可以挂Renderer Feature这是 URP 里最常用的扩展点。HDRP 的结构要复杂得多HDRenderPipelineAsset管全局HDRenderPipelineGlobalSettings存项目级配置扩展点则是Custom Pass通过CustomPassVolume挂在场景里还能精确指定注入时机Before Opaque、After Opaque、Before Transparent 等。我个人的体会是URP 的扩展像插件HDRP 的扩展像手术。前者上手快后者精度高但门槛明显更高。1.3 一个常被忽略的事实管线不是画质开关很多人的第一反应是URP 是低配版HDRP 是高配版Built-in 是旧版。这个理解会直接导致选型错误。实际情况是三条管线各有支持范围HDRP 在移动端和 WebGL 上根本无法运行URP 在高端 PC 上做不到 HDRP 那种物理光照和体积雾但它能让一个移动端项目稳定跑到 60 帧Built-in 虽然老但大量存量资源和第三方插件都是围绕它写的。所以选管线的第一问不是哪个画质好而是我的目标平台是什么、美术资产是什么规格、团队能不能承接迁移成本。这三个问题答不上来选什么都是赌。2. 选型决策平台、美术规模与管线能力的三角关系2.1 用一张表把平台支持范围钉死选型讨论最容易跑偏的地方是大家都在聊画质却没人先把平台约束列出来。我现在的习惯是先拿这张表对一遍能一下子砍掉一半的无效讨论平台 / 场景Built-in RPURPHDRPAndroid / iOS支持官方主推不支持WebGL / 小游戏支持支持但需注意变体与包体不支持XR 一体机如 PICO 系列支持官方推荐不支持Windows / Mac 独立应用支持支持支持且效果上限最高主机平台支持支持官方主推2D 项目支持但无 2D 光照有专门的 2D Renderer不适用这张表里最需要记住的一条是HDRP 的运行平台是封闭的。只要你的项目有移动端、网页端、一体机 XR 的任何一条发布需求HDRP 就不在候选名单里无论它的画面多好看。2.2 移动端与 XR 场景为什么几乎只能是 URP移动端和 XR 一体机的硬件特征很明确GPU 是 Tile-based 架构带宽极度宝贵不支持或部分不支持现代 GPU 的一些高级特性发热和续航是硬约束。这种环境下URP 的设计取向刚好对得上——单 Pass 前向渲染 少量逐像素光源 可控的阴影级联。以 PICO 这类一体机为例立体渲染本身就要把场景渲染两遍或通过单通道立体渲染一次提交两个视角渲染开销天然翻倍。如果在这个基础上再叠加 HDRP 级别的物理光照和体积效果帧率会直接崩掉。所以一体机项目的标准配置基本是Unity 2022 LTS URP XR Plugin Management 单通道立体渲染阴影级联压到 1~2 级附加光源用 Per Vertex 甚至 Disabled。我踩过的一个坑是一开始为了画面好看把 Additional Lights 设成 Per Pixel 且数量上限拉到 8结果一体机上开了几个点光源之后帧率从 72 掉到 40 多。后来改成仅主光逐像素、其余光源走光照贴图和顶点光照画面几乎没变化帧率回来了。2.3 数字孪生与建筑可视化这类大场景高画质怎么选数字孪生、展厅大屏、建筑可视化这一类的需求很特殊场景极大几平方公里、模型极多几万个构件、但相机运动通常很慢甚至固定而且几乎全部跑在 PC 上不需要考虑移动端。这个时候 HDRP 的优势才真正体现出来物理光照单位让日照模拟有据可依体积雾让远景有空气感屏幕空间反射和接触阴影让金属和玻璃有质感。但即便如此我也不建议一上来就选 HDRP。原因很现实这类项目的模型来源往往是 BIM 或 CAD 导出材质乱、面数高、法线朝向混乱。HDRP 的 Lit Shader 对这些问题的包容度比 URP 低得多一个法线反了的构件在 HDRP 里会黑得特别明显。我的做法是先用 URP 把场景跑通、把模型规范化和材质映射做完确认了资产质量之后再评估要不要迁 HDRP。2.4 小游戏与 WebGL 发布路径上的硬约束小游戏和 WebGL 的约束是另一套逻辑。它不是渲染能力不够而是运行时环境受限没有计算着色器、纹理压缩格式选择有限、内存上限严格、首次加载包体大小直接影响留存。这些约束会反过来影响渲染方案的选择。在这种场景下我通常会做三件事第一关掉一切非必要的后处理只保留最基础的色彩调整第二严格控制 Shader 变体数量把用不到的关键字剥离掉第三把渲染缩放降下来宁可 0.8 的渲染分辨率换稳定帧率也不要用 1.0 跑出一个忽高忽低的帧数。至于管线小游戏项目用 Built-in 和 URP 都有成功案例URP 的优势在于 Shader Graph 和后续维护性代价是要花时间做变体裁剪。3. 工程初始化与配置从空项目到能跑的管线3.1 模板创建与手动升级两条路搭管线有两条路新建项目时选 URP 或 HDRP 模板或者在已有项目上手动升级。前者省事后者是现实中最常遇到的情况。手动升级的正确顺序是先备份这一步永远不要省然后通过 Package Manager 安装Universal RP或High Definition RP接着用Edit Rendering Materials Convert Selected Built-in Materials to URP之类的转换工具批量处理材质最后把 Quality Settings 和 Graphics Settings 里的管线资产指过去。顺序错了会很难受。我见过有人先把 Graphics Settings 指向了 URP Asset再去转换材质结果整个项目瞬间全粉连编辑器界面里的预览都看不清只能靠命令行回滚。先转材质、后切管线资产这个顺序能让你在出问题时还有退路。3.2 URP Asset 与 Renderer Data 每一栏到底在控制什么URP 的配置集中在两处UniversalRenderPipelineAsset全局和挂在它下面的UniversalRendererData每个渲染器。很多人把参数改来改去却不知道改的是哪一层我把常用的几项列一下参数所在位置实际影响Shadow DistanceURP Asset超过这个距离的物体不投阴影是移动端最重要的性能旋钮之一Cascade CountURP Asset阴影级联数1 级省性能但近处阴影锯齿明显Additional LightsURP Asset附加光源的渲染方式直接决定多光源场景的开销Render ScaleURP Asset渲染分辨率缩放0.8 通常肉眼几乎无感但省 30% 以上像素开销Depth Texture / Opaque TextureRenderer Data开了才能用需要深度或屏幕颜色的效果代价是一次额外的全屏拷贝Post-processingRenderer Data这是总开关没打开的话 Camera 上勾了后处理也没用这里有个特别容易忽略的点Camera 上也有一个 Post Processing 勾选框。Renderer Data 里开了总开关Camera 上没勾效果同样不出现。这个双重开关让无数人排查了半天。3.3 HDRP 的三板斧Asset、Global Settings 与 VolumeHDRP 的初始化配置比 URP 多一层我把它总结成三板斧第一HDRP Asset决定整体质量档位、阴影分辨率、光照贴图尺寸等。注意 HDRP 的 Asset 分了 Quality 等级可以在不同等级下配不同的 Asset这点比 URP 更灵活。第二HDRP Global Settings存的是项目级的默认值和渲染管线资源比如默认的 Volume Profile、光照贴图相关设置。这个资产如果丢了整个 HDRP 项目会表现得很奇怪。第三VolumeHDRP 里几乎所有画面表现都由 Volume 驱动曝光、色调映射、雾、泛光、景深。这带来一个副作用——空场景默认啥都没有看起来还不如 Built-in 好看。这是正常的。我第一周用 HDRP 的时候特别崩溃觉得画面灰得像没做完的工程。后来才明白HDRP 默认的光照单位是物理量级勒克斯、EV相机默认曝光是个固定值不配 Volume 的话室内场景会死黑、室外场景会惨白。3.4 分辨率、渲染缩放与画质等级的多档位设计多平台项目一定要做画质档位而不是一套参数打天下。我的做法是建三套 URP AssetLow 给低端移动设备Render Scale 0.8、级联 1、附加光源 Per Vertex、Medium 给主流设备Render Scale 1.0、级联 2、High 给 PC 端级联 4、开 Opaque Texture 和更多后处理。切换的时候用QualitySettings.SetQualityLevel主机的职责是选档渲染的职责是按档执行两者不要互相污染。有个细节值得注意分辨率设置和渲染缩放是两回事前者决定最终输出到屏幕的像素数后者决定内部渲染的分辨率。在移动端为了省电很多人会同时降两者但降分辨率会导致 UI 变糊所以更稳的做法是保持屏幕分辨率、只降渲染缩放。4. 光照与阴影管线切换后第一个翻车点4.1 HDRP 物理曝光不配 Exposure 就一片死黑HDRP 最劝退新手的地方就是曝光。它的光照是按物理单位来的一个平行光代表太阳强度单位是勒克斯数值量级在十万上下一盏室内灯可能是几百到几千。这些数值经过相机的曝光计算之后才会映射到 0~1 的显示范围。如果 Volume 里没有 Exposure 覆盖相机就用默认的固定曝光值那个值对大多数场景都不合适。解决办法是在场景里放一个 Global Volume加一个 Exposure 覆盖模式选 Automatic 并设好 EV 上下限或者干脆用 Fixed 手动调。我的习惯是先用 Fixed 调到画面正常然后再考虑要不要切 Automatic。另外 Tonemapping 也要一起配HDRP 默认的 ACES 会压高光如果不开色调映射高亮区域会直接糊成一片白。顺带说一句HDRP 的高光溢出和白点问题八成是曝光和色调映射没配好而不是灯打错了。4.2 烘焙光照与光照贴图的迁移坑从 Built-in 迁到 URP最容易出问题的是烘焙数据。Lighting Settings 在两者之间不通用光照贴图也不会自动带过来。正确做法是迁移完成后重新烘焙并且检查三件事——光照贴图的编码方式URP 下需要确认使用的是合适的编码、Lightmap 的静态标记Contribute GI 有没有丢、Light Probe 的分组动态物体是否还能正确接收间接光。还有一个更隐蔽的坑如果原项目用了 Enlighten 的实时 GI迁移到 URP 后就没这个选项了得改成烘焙方案或者用别的替代思路。我遇到过一次展厅项目的动态展品原本靠实时 GI 得到柔和的间接光迁移之后整个变平了最后是靠 Light Probe 加一块模拟的补光解决的。4.3 阴影级联、距离与接触阴影的取舍阴影是性能的重灾区也是画面质感的关键。URP 这边的调节旋钮主要有四个阴影距离、级联数、阴影分辨率、软阴影开关。这四个参数的组合决定了阴影好不好看和帧率掉不掉。我的经验值是这样移动端阴影距离设 20~30 米、级联 1~2、分辨率 1024、关软阴影PC 端阴影距离 80~150 米、级联 4、分辨率 2048~4096、开软阴影。如果场景里有很多小物件栏杆、装饰线条可以在 HDRP 里开Contact Shadow它能给出很近的细节阴影比拉高整体分辨率划算得多。还有一个常见的误判影子边缘有锯齿很多人第一反应是调高阴影分辨率但真正的瓶颈往往是级联分布。级联的切分比例如果不合理近处那一级覆盖范围太大会导致精度全浪费在远处。URP Asset 里的 Cascade 分割比例是可以调的把近处那级压小一点效果往往比拉高分辨率更明显。4.4 后处理 Volume 的作用域与优先级URP 和 HDRP 都用 Volume 系统做后处理但作用域规则要搞清楚。Volume 分 Global 和 Local 两种Local 需要 Collider 触发多个 Volume 之间按 Priority 排序高优先级的覆盖低优先级的同名参数。这个机制很好用——比如你想让主角进入某个房间时色调变冷就在房间里放一个 Local Volume。但有个坑不同 Volume 之间的参数是逐项覆盖不是整体替换。意味着如果 Global 里配了 BloomLocal 里只配了 Color Adjustments那么进入 Local 之后 Bloom 依然生效。这个特性容易造成我在 Local 里明明没开某个效果它却还在的困惑。另外提醒一句改 Volume Profile 是运行时修改资产在编辑器里调试会导致资产被永久改掉。调试阶段建议用volume.profile的实例副本或者干脆新建一个专供调试的 Profile。5. Shader 与材质迁移从 Built-in 到 Shader Graph5.1 内置 Shader 变粉红的真实原因粉色是 Unity 最经典的出事了信号。它的本质是材质的 Shader 在当前管线里编译失败或找不到引擎用错误 Shader 兜底。迁移过程中出现粉色通常是两类原因一是材质还在用Standard、Diffuse这类内置 Shader二是自定义 Shader 里引用了当前管线不存在的头文件或函数。第一类好办用官方的材质转换工具批量替换。第二类就得逐个改了因为 URP 和 HDRP 的 Shader 库完全是另一套。举个最直观的例子贴图和颜色的命名约定就不一样// Built-in 的常见写法 half4 c tex2D(_MainTex, uv) * _Color; // URP 的约定 half4 c SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, uv) * _BaseColor;_MainTex变成_BaseMap_Color变成_BaseColor这不是改个名字那么随意而是因为 URP 内部依赖这套命名来做 SRP Batcher 的兼容性检查。名字对不上批处理就失效。5.2 纹理通道约定HDRP 的 Mask Map 是个坎URP 和 HDRP 在金属度贴图的通道打包上思路不同这是从 URP 迁到 HDRP 时最容易被忽略的一点。HDRP 的 Mask Map 把四种信息塞进一张图的四个通道通道内容RMetallic金属度GAmbient Occlusion环境光遮蔽BDetail Mask细节遮罩ASmoothness光滑度这张图是 HDRP 的标准输入之一如果沿用 URP 的项目直接迁过去而没有重新打包金属和粗糙度会完全错乱看起来像材质发油或者发灰。我一般会用脚本在导入时自动完成通道合成避免美术手动处理出错。5.3 Shader Graph 与手写 HLSL 的边界Shader Graph 是 URP 和 HDRP 的官方可视化 Shader 工具对大多数效果来说够用了溶解、描边、水面、卡通渲染的常见需求都能用节点拼出来而且它天然兼容两条管线切换目标管线时只需要在 Graph 设置里改一下。这是它最大的价值。但 Shader Graph 有几个明确边界复杂的自定义光照模型、需要精细控制编译分支的场合、以及需要和 C# 侧深度联动的效果手写 HLSL 更合适。我自己的划分标准很简单——如果一个 Shader 要做三件以上互相影响的事或者需要控制变体数量我就直接写 HLSL如果只是材质表现就用 Graph。还有一点Shader Graph 生成的代码可读性一般调试时如果遇到问题最好的办法是把 Graph 编译出来的 HLSL 打开看一眼往往一眼就能发现是精度问题还是分支问题。5.4 变体、关键字与宏定义包体和卡顿的源头Shader 变体是很多项目上线前才发现的定时炸弹。每多一个multi_compile关键字变体数量就翻倍包体和加载时间跟着涨。一个带十个关键字的 Shader 可能产生上千个变体而实际用到的可能只有几十个。我的做法是三步第一用Shader Variant Collection或者构建日志统计实际用到哪些变体第二把确定不用的关键字从 Shader 里删掉或者用shader_feature替代multi_compile第三如果项目里有多条管线共存的代码用宏定义隔离别让另一条管线的代码被编译进来。// C# 侧判断当前跑的是哪条管线避免逻辑走错分支 using UnityEngine; using UnityEngine.Rendering; public class PipelineProbe : MonoBehaviour { void Start() { var rp GraphicsSettings.currentRenderPipeline; Debug.Log(rp null ? Built-in : rp.GetType().FullName); } }这段代码看着简单但它在写跨管线工具脚本时非常有用尤其是做编辑器扩展的时候。热词里常有人问Unity 怎么扩展Unity 工具链其实工具链的第一步就是先搞清楚当前项目跑的是哪条管线。6. 性能优化把 GPU 和 CPU 的账算清楚6.1 SRP Batcher 到底帮你省了什么SRP Batcher 是 URP 和 HDRP 的默认批处理机制它的原理和传统的动态合批完全不同。传统动态合批是把多个小网格合并成一个大网格顶点数有限制SRP Batcher 是把材质属性放进常量缓冲区只要 Shader 变体一致就能在不合并网格的情况下减少 CPU 侧的 SetPass 开销。这意味着两件事第一它省的是CPU 侧对 GPU 几乎没帮助第二它要求材质的 Shader 兼容属性声明在统一的UnityPerMaterialCBUFFER 里。手写 Shader 如果没按这个规则写SRP Batcher 就会失效这时候你在 Frame Debugger 里会看到每个物体一个批次Draw Call 爆表。6.2 Frame Debugger 与 Profiler 的排查路径排查性能问题我有一套固定顺序从不跳步先用Profiler看是 CPU 还是 GPU 瓶颈。CPU 满、GPU 闲通常是脚本或批处理问题GPU 满就是渲染负载问题。用Frame Debugger逐帧看绘制顺序找出哪些物件在重复绘制、哪些 Shader 关键字变了导致批次被打断。如果是移动端用厂商的 GPU 工具看带宽和 Overdraw很多移动端的性能问题其实是半透明 Overdraw 造成的跟光源数关系不大。我遇到过一个典型案例一个全是玻璃幕墙的场景在 PC 上跑得好好的到一体机上就卡。最后定位到是几十层叠加的半透明面片造成的 Overdraw解决方案是把远处的幕墙换成不透明的简化材质近处的保留透明帧率立刻回来了。6.3 移动端的带宽与光源数量红线移动端优化跟 PC 是两套思路。PC 可以堆算力移动端要省带宽。几个我总结出来的红线逐像素光源不要超过 4 个URP 默认上限是 8但那不是建议值阴影级联不超过 2 级不要在移动端用高分辨率的屏幕空间效果SSAO、SSR 这类纹理尽量用压缩格式并控制尺寸。还有一个热词里经常有人问的点限定数据块大小。这其实说的是常量缓冲区的布局优化把同一帧内频繁更新的数据集中在一起减少 CPU 到 GPU 的数据传输次数。这个优化对 draw call 密集的场景收益明显但需要你有比较清楚的渲染流程理解才动手盲目改反而容易出错。6.4 大场景的分层裁剪与流式加载做数字孪生或大型场景时最有效的手段不是优化渲染而是不渲染不该渲染的东西。分层裁剪的三个层次是视锥剔除自动的、距离剔除、以及按业务逻辑的显隐控制。Unity 的 LOD Group 和 Culling Group 是标准工具但在超大场景里我更常用的是自己按区块分包。把场景切成若干区块相机移动时按距离动态加载卸载区块每个区块内部再按视角做显隐。这套方案的前提是资源结构要提前规划好不能等模型都堆进场景了再想办法拆。顺便说一句热词里不用脚本隐藏组件的讨论——实际上在运行时隐藏渲染开销最高效的方式还是SetActive(false)因为它同时停掉了渲染、物理和脚本更新改localScale只是让渲染体积归零物理和脚本还在跑。这个取舍要看你隐藏的目的到底是省性能还是保留逻辑。7. 多平台发布与二次踩坑7.1 XR 一体机的管线配置要点一体机平台的管线配置有几个固定动作在 Package Manager 里装 XR Plugin Management启用对应的 XR 插件然后在 Player Settings 里打开立体渲染模式。URP 下的关键是把 XR 的系统配置指对同时确认 Renderer 支持单通道立体渲染。这里最容易踩的坑是阴影和后期在一体机上的开销。立体渲染把一切都乘了两次或通过单通道立体渲染降低一部分所以任何在 PC 上看着还行的后期效果在一体机上都会被放大成性能问题。我的建议是把后处理压到最少保留色彩调整关掉景深和运动模糊泛光降分辨率。7.2 小游戏与 WebGL 的渲染能力上限WebGL 运行时没有计算着色器纹理压缩格式的选择也很有限内存上限严格。这些限制直接决定了渲染方案不能用依赖计算着色器的大规模 GPU 剔除不能用重量级的屏幕空间效果纹理要提前转成适合的格式。小游戏还有一个额外约束是包体。Shader 变体数量会直接影响包体大小和首次加载时间所以在 WebGL 目标下我通常会把质量档位砍到只剩一套并且用 Shader 变体剥离工具做一轮精简。宁可牺牲一点画质也不要让玩家等半分钟才进游戏。7.3 摄像机与 UI 这些看起来无关的小细节渲染管线调通了往往还有一堆小问题跳出来。比如 UI 的点击区域太小本质是 Canvas 的 Raycaster 精度和 UI 元素的 RectTransform 尺寸问题分辨率变化导致画面变形多半是 Canvas Scaler 的模式没选对。这些跟管线没直接关系但会让人误以为是渲染出了问题。有个经验值得分享URP 下用 Camera Stacking 叠加 UI 相机的时候Base Camera 和 Overlay Camera 的 Render Type 必须配对否则 UI 会不显示。这个问题不会报错只会静默失效排查起来很费时间。我的做法是搭一个最小可复现场景把相机关系理清楚再往主工程里搬。7.4 几个我踩过之后会主动检查的点第一每换一次管线资产就把所有材质的 Shader 过一遍。用AssetDatabase.FindAssets(t:Material)写个小脚本统计一下有多少材质还在用内置 Shader比肉眼找靠谱得多。第二烘焙设置和光照贴图要跟管线绑定管理。切管线之后光照贴图就是废的不要让旧数据留在工程里容易造成改了参数没反应的错觉。第三变体统计要纳入构建流程。上线前跑一次构建把 Shader 变体数量、包体大小、首次加载耗时三项拉出来对比任何一项异常增长都要查清楚原因。第四移动端测试一定要真机跑。编辑器里的性能表现和真机差得很远尤其是发热降频之后的表现编辑器里永远看不到。我做这类迁移的时候有个习惯先在纸上或者文档里把管线—平台—美术规格—性能目标这四个维度写清楚再动手改工程。这几年下来凡是跳过这一步直接开干的基本都会在中后期返工而且返工的成本远高于前期多花的那两天。还有一个我自己一直在用的检查清单贴在显示器边上场景里逐像素光源是不是超过四个有没有半透明物体叠了五层以上烘焙贴图尺寸是不是超了后处理里有没有一个当时为了好看加的效果一直没删。这四个问题答完项目基本不会出大问题。