
各位做渲染的同学、Unity转UE4的朋友、或者刚被“URP和HDRP到底选谁”困扰过的新手今天我直接把一个很多人问过的问题摊开聊同一盏灯、同一块金属材质在 Unity URP、Unity HDRP、UE4 三套渲染代码里到底差在哪。这篇主要聚焦 PBR 的“局部光照”部分也就是直接光、光源衰减、材质表面参数、直接光阴影这几块暂不碰全局光照、反射探针、后处理这些大坑。原因是Part1就把这一层讲透后面的间接光、GI、大量辅助光再单独开一篇。只要你写过自定义Shader或者被光照调色逼疯过这篇一定有用。1. 三个引擎在局部光照上到底比的是什么1.1 局部光照的定义镜头里最先看到的那一层光在渲染领域光照大体分两类一类是只考虑光源直接照射到物体表面的能量也就是“局部光照”另一类要考虑光线在物体之间的反弹、天空环境、间接反射等也就是全局光照。直接光这部分包含了方向光、点光源、聚光灯、矩形光等直接打在表面的光以及对应的阴影。PBR材质模型处理的是“表面如何响应这些光”而不是“光从哪里来”。所以三个引擎在局部光照的对比本质上是BRDF近似、光源衰减函数、法线/粗糙度/金属度参数如何参与计算以及阴影采样方式这四件事的对比。1.2 为什么Unity分裂成两个管线而UE4只有一个Unity从Built-in管线扩展到URP和HDRP这个我们都很熟了。URP主要照顾移动端和VRChat类轻量项目HDRP则面向PC和主机的高保真渲染。UE4从4.x到现在一直沿用一套以Deferred为主、Forward为辅的架构光照模型选择更多但底层一直都相对统一。很多新手会问UE4的光照比Unity好是不是因为代码更“先进”不是。大家底层都在用GGX分布、Smith遮挡近似、Schlick菲涅尔核心PBR公式没有本质区别。真正拉开差距的是实现精度、光照单位、默认参数、以及各管线的性能取舍。直接说结论如果你在URP里用默认Lit材质、在HDRP里用Lit材质、在UE4里用DefaultLit材质同一个材质球放到三个引擎里截图颜色大概率不一样甚至差很多。这不是引擎谁对谁错而是它们对PBR参数的默认解释不同、光照单位不同、贴图打包方式不同。1.3 三端共用的PBR模型Cook-Torrance的局部版本在局部光照层面三端用的都是Cook-Torrance微表面模型的一个简化实现。一个表面的直接光照颜色可以拆成漫反射加高光L (Diffuse Specular) * LightAttenuation * Shadow * LightColor其中Specular项大致是Specular NDF * G * F / (4 * dot(N, L) * dot(N, V))NDF项管高光的形状主要受粗糙度影响G项管阴影遮罩和几何遮挡F项管菲涅尔反射受视角和金属度影响。Unity URP的Lit、HDRP的Lit、UE4的DefaultLit都建立在这个公式上。区别在于三项近似的实现细节NDFUnity URP/UE4用GGXTrowbridge-ReitzHDRP也以GGX为默认但在各向异性和布料材质上有额外扩展。G项常见的是Smith配合Schlick-GGX。F项用Schlick近似金属度直接决定F0基准值。所以你可以把三个引擎的PBR着色器理解为“同一套菜谱三个厨师用的火候和调料比例不一样”。后面所有对比其实都是在拆火候和调料。2. 方向光同一盏灯三套代码里的不同算法方向光是最常见的“太阳光”。在三个引擎里方向光在局部光照计算中承担的作用差不多提供一个稳定的L向量和光颜色。但Shader里怎么组织这段计算差别很大。2.1 URPUniversalFragmentPBR一把梭URP里最核心的PBR入口是UniversalFragmentPBR函数它接收InputData和SurfaceData两个结构体输出最终颜色。看起来很简单但URP为了兼容Forward和GBuffer、主光和附加光内部代码其实很繁杂。我们在URP写Custom Shader时一般这么用#include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float3 normalWS : TEXCOORD0; float2 uv : TEXCOORD1; }; Varyings vert(Attributes input) { Varyings output; output.positionCS TransformObjectToHClip(input.positionOS.xyz); output.normalWS TransformObjectToWorldNormal(input.normalOS); output.uv input.uv; return output; } half4 frag(Varyings input) : SV_Target { InputData inputData (InputData)0; inputData.positionWS GetVertexPositionInputs(input.positionOS).positionWS; inputData.normalWS normalize(input.normalWS); inputData.viewDirectionWS GetWorldSpaceNormalizeViewDir(inputData.positionWS); inputData.shadowCoord TransformWorldToShadowCoord(inputData.positionWS); SurfaceData surfaceData (SurfaceData)0; surfaceData.albedo half4(0.9, 0.6, 0.2, 1).rgb; surfaceData.metallic 0.0; surfaceData.smoothness 0.5; surfaceData.occlusion 1.0; surfaceData.emission 0.0; surfaceData.alpha 1.0; half4 color UniversalFragmentPBR(inputData, surfaceData); return color; }这是URP做局部光照最标准的路径。注意InputData里shadowCoord的获取方式——Unity封装了TransformWorldToShadowCoordShadow Cascade的坐标计算都在里面。你如果自己写光照不调用UniversalFragmentPBR那要自己处理Light.hlsl里GetMainLight等函数。URP容易被忽略的一个点Main Light只在Forward Pass里逐像素渲染一次如果场景里有多个方向光只有最亮的那个当主光其他方向光会被当成附加光源处理而且不一定走PBR。这在局部光照调试中非常容易踩坑。2.2 HDRP物理单位驱动一切HDRP针对高保真场景方向光组件默认强度是10万lux模拟真实太阳。移动端出来的项目一到HDRP经常过曝就是没意识到单位变了。URP默认方向光强度填1数值本身是“相对能量”HDRP的方向光强度是勒克斯lux跟真实物理亮度挂钩。HDRP的Lit Shader复杂程度比URP高好几个量级通常我不建议纯手写HDRP Shader而是用ShaderGraph配合Custom Function。但从代码层面看HDRP方向光计算的核心思路和URP一致都需要采样ShadowMap、计算NDF/G/F项只是在InputData上增加了很多物理量比如#include Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/Lighting.hlsl #include Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/Lit.hlsl // HDRP中获取直接光 DirectLighting directLighting (DirectLighting)0; directLighting.diffuse lightColor * brdfData.diffuse * NoL * lightAttenuation; directLighting.specular lightColor * brdfData.specular * NoL * lightAttenuation;这只是一个概念示意。实际上HDRP的Lit分支极多HandleAreaLights、SSS、Transmission都在里面。HDRP的优势在于光照单位物理化、阴影种类多能做Contact Shadow、Color Shadow、PCSS软阴影代价是性能开销大、Shader复杂度高、移动端基本跑不动。在HDRP里调方向光我建议遵循物理逻辑户外晴天方向光强度 100000 lux阴天方向光强度 10000-20000 lux室内主光窗口方向光2000-5000 lux这个数值不是绝对真理但比瞎试强多了。你按这个基准调颜色再通过Exposure、ToneMapping去修正画面明暗思路会清楚得多。2.3 UE4Deferred管线下的一体化光照UE4默认Deferred渲染所有灯光最终在DeferredLightingCommon.ush里统一计算。方向光在UE4里有一个很有意思的设定Intensity单位是lux默认强度3.14。很多人第一次点开UE4的DirectionalLight会被这个3.14搞懵其实它已经按物理单位缩放过了。UE4的材质输出节点中Metallic、Roughness、Specular三个值进入Shading Model再经过BxDFCommon.ush计算高光。从局部光照来看UE4的DirectLighting会比Unity多一个“材质指定ShadingModel”的阶段比如默认Lit、Subsurface、ClearCoat等。同一个方向光打在一个ClearCoat材质和DefaultLit材质上算法不一样。如果要在UE4里写自定义光照通常是在Custom节点里实现BRDF。下面是一段简化的方向光局部光照示意// UE4 Custom节点或.usf中 float3 L normalize(LightVector); // 方向光无衰减L固定 float3 V normalize(CameraVector); float3 H normalize(L V); float3 N Parameters.TangentToWorld[2]; float NoL saturate(dot(N, L)); float NoV saturate(dot(N, V)); float NoH saturate(dot(N, H)); float VoH saturate(dot(V, H)); float rough Parameters.Roughness; float a rough * rough; // GGX NDF float d (NoH * a - NoH) * NoH 1.0; float D a / (PI * d * d); // Schlick-GGX近似遮蔽 float k (a 1.0); float G NoL / (NoL * (1 - k) k) * NoV / (NoV * (1 - k) k); // 简化菲涅尔 float3 F0 lerp(0.04, Parameters.BaseColor, Parameters.Metallic); float3 F F0 (1 - F0) * pow(1 - VoH, 5); float3 spec D * G * F / (4 * NoL * NoV 0.001); float3 direct (Parameters.BaseColor / PI * (1 - F) spec) * NoL * LightColor;这段代码里明显能看到局部分量NoL只有正数才有意义分母加0.001防止除零BRDF的每一项独立可控。UE4默认材质比Unity多一个Specular参数用来调整非金属的F0基准这在调材质时很关键。2.4 三端方向光性能特点对比引擎/管线方向光渲染方式单位默认强度参考常见问题URPForward逐像素Shadow Cascades相对能量1多主光切换不直观附加光忽略HDRPForward或Deferred物理单位lux100000单位不熟导致过曝UE4Deferred/Forward物理单位lux3.14新手对lux无感参数语义抽象从实测经验看URP最省心的是调参快适合快速验证HDRP最物理但需要学习光照单位和曝光系统UE4介于两者之间但因为它默认带Auto Exposure光线体验比较“电影化”其实不等同于物理真实。3. 点光和聚光灯衰减函数不只是数学是取舍局部光照里点光和聚光灯的衰减往往决定场景质感。三个引擎的衰减函数都来自平方反比定律但如何让光照在某个距离内平滑降到0实现方式不同。3.1 平方反比衰减与Range窗函数的差异物理上点光强度应该严格按距离平方反比衰减强度 intensity / (d^2)。但直接这么用会有一个问题光永远不会到0性能上无法确定覆盖范围。所以引擎都会加一个Range参数和一个窗函数让光在Range边缘平滑过渡到0。URP点光源衰减大致可以看成distanceAtten 1.0 / saturate(distanceSqr) smoothFactor saturate(1.0 - distanceSqr / (range * range)) attenuation distanceAtten * smoothFactor也就是说URP会强行让光在range内平滑消失避免硬截止。这个方案对性能很友好但物理上并不严格。HDRP的物理光衰减更偏向纯平方反比同时配合Light Range进行限制。如果你在HDRP里把点光Range拉得很大但强度不够远处看起来非常微弱强度和Range是两个独立维度不像URP里Range直接决定可见范围。UE4的点光衰减也遵循平方反比但它对“光源半径”有一个参数叫Source Radius。这个参数会影响高光高光形状也会让点光源变成有体积的光源局部高光效果更真实。3.2 实际调参时三端强烈推荐的数值基准参数URPHDRPUE4点光强度单位相对能量CandelaCandela点光默认强度11000左右才比较明显若干cd聚光灯强度单位相对能量CandelaCandela方向光单位相对能量LuxLuxRange作用直接决定可见范围限制范围但强度主导限制范围且影响窗函数我在做一个小型室内场景时给了一个0.3米高的小台灯URP里点光强度填1.5刚好放到HDRP里同样场景要填600-1200才有一点点台灯漏光的感觉。这不是Bug是因为HDRP按单位立体角算能量。反过来把HDRP的1000强度拿到URP里画面直接白成纸。所以跨引擎搬灯光数值千万别直接复制。我建议做法先把场景主方向光调好再逐个加辅助点光以肉眼看高光点不过曝、暗部不漆黑为标准别迷信数值。3.3 聚光灯的内外角与锥形衰减代码差异聚光灯比点光多一个锥形范围控制。URP聚光灯在Shader里用SpotDirection与LightVector的夹角来判断是否在锥内再用内角外角做平滑过渡UE4聚光灯衰减在DeferredLightingCommon.ush里也会计算SpotAngularFalloff同样有内锥和外锥。HDRP类似但光照强度单位不同导致最终亮度差异很大。用大白话说三个引擎的聚光灯“内外角软化”思路完全一致预设一个内角一个外角内角以内全亮外角以外全黑中间斜着插值。只是URP很多参数暴露在Light组件面板HDRP暴露在材质和Light结合的系统里UE4则在Attenuation Radius和Inner/Outer Cone Angle里调。实操建议做舞台光效果时把内角设小一点、外角比内角大6-10度边缘过渡会自然很多。拿UE4来说默认Spot的Inner Cone Angle是20度Outer Cone Angle是25度这个默认组合已经比较柔和原因是差异5度刚好让衰减带平滑。4. PBR材质参数映射同一张贴图在三端怎么读局部光照最终呈现颜色一半靠灯光一半靠材质。很多美术资产在Unity里看起来合理导入UE4后“像塑料”反过来UE4的材质到Unity里容易“过黑”这都跟参数映射有关。4.1 Metallic与Smoothness/Roughness的换算Unity URP/HDRP的Lit材质使用Metallic SmoothnessUE4使用Metallic Roughness。它们的核心对应关系是Roughness 1 - Smoothness注意这是在线性空间下做。很多人直接在Gamma空间里把Smoothness反相当Roughness用结果高光范围错到家。如果你的贴图在Photoshop里直接反相一定要先确认色彩空间否则UE4里金属粗糙度会整体偏暗。另外Unity里Metallic贴图的R通道存金属度A通道存光滑度UE4里Roughness通常是一个独立贴图或者打包在ORM贴图RAO、GRoughness、BMetallic里。所以“同一张贴图”不能直接换用必须做通道重映射。4.2 HDRP的MaskMap通道设计与URP完全不同这是跨引擎最容易翻车的地方。URP的标准PBR流程中Metallic工作流的贴图是一张RGBAR金属度、G无、B无、A光滑度 一张单独的AO。HDRP里则喜欢用MaskMap把AO、Metallic、Detail Mask、Smoothness打包在一张贴图里具体通道R通道AOG通道MetallicB通道Detail MaskA通道Smoothness如果你把一个URP的Metallic贴图直接塞给HDRP的MaskMap结果必然灾难。HDRP的AO前提是放在R通道而URP里AO是单独一张图你把单独AO图参数抄过去的时候通道顺序错一下金属会掉得七零八落。UE4的ORM贴图也类似但顺序普遍是RAO、GRoughness、BMetallic。如果你从Unity导出Smoothness图到UE4先在Substance Painter里把Roughness按1-Smoothness烘焙好再决定打包方式不要直接拖到UE4里用。4.3 UE4的Specular参数不是摆设UE4材质面板里有Metallic、Roughness、Specular三个核心标量很多人忽略Specular。UE4中非金属的F0基准并不是固定0.04而是由Specular参数控制默认0.5对应F0约0.08。这意味着UE4的“非金属”在暗部会比Unity默认的0.04更亮一点。如果从Unity迁移材质到UE4Unity默认的PBR非金属F0基本等于0.04对应UE4的Specular大约0.33。所以我在非金属材质上常常把UE4 Specular从0.5降到0.3-0.35画面才和Unity里接近。如果你追求的是“跨引擎资产统一”不要只在材质编辑器里调颜色要理解每个引擎非金属高光基准值的含义。这个细节很冷门但渲染效果差异一下子就出来了。4.4 贴图通道对照速查材质参数URPHDRPUE4Albedo/BaseColorBaseMap(RGB)BaseColorMap(RGB)BaseColor(RGB)金属度Metallic贴图R通道MaskMap G通道Metallic贴图/ORM B通道粗糙度由Smoothness派生由Smoothness派生Roughness贴图/ORM G通道光滑度Metallic贴图A通道MaskMap A通道不直接使用AO独立贴图MaskMap R通道ORM R通道/独立贴图每次从Substance Painter导出贴图我都建议按目标引擎做两组预设一组是Unity URP/HDRP的Smoothness打包一组是UE4的Roughness打包。别偷懒导出一张通用贴图靠Shader转换虽然可行但效果容易打折扣。5. 直接光阴影三端实现与联调的实用经验局部光照里阴影是最影响空间感的一层。三个引擎的阴影系统差别很大尤其是在默认阴影质量、Cascade级数、接触阴影、软阴影上的表现。5.1 URP的Main Light Shadow与CascadeURP的主光阴影很简单在Universal Render Pipeline Asset里开启Shadow再在光源组件勾选Cast Shadows即可。URP支持Cascade划分默认4级还是2级取决于版本和设置。Cascade级数越多近处阴影越清晰但性能和显存占用越高。做URP项目时我强烈建议把关卡里近景地面和远景地面的Cascade偏移调一下。如果地形很大默认Cascade分配不均会出现“近处清晰/远处糊成一团”的断层感。直接把Shadow Distance缩小到50-80米很多阴影闪烁立刻消失。URP一个常见问题多个点光源Cast Shadow会导致阴影贴图被打爆或者有些点光源阴影不生效。这是因为URP对Additional Lights默认不开启阴影或者光强与衰减Range太大导致超出Shadow Distance。排查时先看Shadow Distance再考虑综合阴影预算。5.2 HDRP的HD Shadow与Contact ShadowHDRP阴影质量上限高但配置复杂。它支持方向光Cascade阴影、点光/聚光Cube阴影、Contact Shadow近距离接触阴影、PCSS软阴影等。HDRP里我常用的设置方向光勾选Contact Shadow可以把物体和地面接触的位置补上细碎阴影点光源Range小一点Cubemap阴影分辨率才能保证PCSS会大幅增加开销非主机平台慎开阴影Bias在HDRP里默认很微小调大可能直接造成漏光HDRP的Contact Shadow是我最喜欢的功能。在URP里要做出同一个效果往往得加planeshadow或者手动放半透明黑平面非常麻烦。HDRP可以在方向光上开Contact Shadow局部光照的“落地感”立刻强一个级别。5.3 UE4的动态阴影方法与距离场UE4默认方向光阴影走CSM点光和聚光灯默认也都会投射阴影。在UE4里最有意思的是Distance Field Shadow它可以让小物体、或者远处物体的阴影也用降级方式快速出现。局部阴影的采样质量受Light Shadow Resolution影响很大默认2048在复杂场景放大看全是锯齿。UE4调阴影套路阴影整体偏软、偏淡增大Light Source Angle阴影边缘闪烁提高Shadow Bias或增大Light Source Angle阴影末端突然消失调大Dynamic Shadow Distance近处有黑块减小Shadow Bias如果UE4里金属高光和阴影一起出现时画面很脏基本是Roughness贴图没调对导致高光分布奇怪阴影末端和暗部糊在一起。5.4 跨引擎阴影调试的联调经验把同一场景从Unity搬到UE4我通常会先把阴影距离统一定到50米Cascade统一4级开Contact ShadowHDRP/UE4可用URP用Plane Shadow替代。这样三端阴影视觉上先趋近再回头调光照强度。如果要让阴影整体偏柔URP要调主光角度或增大SoftShadow参数HDRP用PCSSUE4用Light Source Angle。它们本质都是在“增大光源发光面积”从而让本影和半影过渡更宽。6. 把同一个场景在三端调到“看起来一样”的实用清单6.1 先从方向光开始再做辅助光我强烈建议布光顺序固定住先方向光再天光/环境光最后点光和聚光灯。局部光照层面尤其如此因为方向光决定了整个画面的色调基调和阴影方向。实操步骤三端分别创建一个平面和几个标准球体材质全部用默认PBR材质Metallic设0.5、Smoothness/Roughness设0.5。只开一盏方向光调整强度/色温让标准球的高光形态和明暗层次接近。再加一个点光作为补光调节衰减范围观察球体高光边缘的柔和度。开启阴影比较地面阴影轮廓和接触点黑度。最后贴材质贴图确认贴图通道映射正确。这一步走完三端画面大方向就差不多了。千万不要一上来就堆几十盏灯再调材质你会分不清是光不对还是材质不对。6.2 三端容易翻车的几个点我整理了最近踩过的一些坑按频率排序现象原因解决URP转HDRP画面瞬间过曝光照单位不同HDRP按lux设置强度重建灯光同一贴图在UE4偏黑RoughnessSmoothness反相没做线性处理在线性空间反相或用ORM重打包阴影边缘狂闪Cascade距离/Shadow Bias不合适缩短Shadow Distance微调Bias金属材质“脏乱差”MaskMap通道顺序错误按HDRP/UE4通道定义重新打包非金属材质亮度过高UE4默认Specular0.5F0偏高把Specular调到0.3左右聚光灯边缘生硬内外角差值太小外角比内角大6-10度6.3 一套贴图走三端的通道适配建议在Substance Painter里我目前常用的导出预设是这样的导出BaseColor通用无需改导出Normal通用无需改但URP HDRP UE4的法线格式差异不大OpenGL和DirectX差异要先确认导出MetallicUnity直接用R通道UE4放到ORM的B通道导出RoughnessUnity里需要1-Roughness转成Smoothness存A通道UE4直接存G通道导出AOUnity单独存一张UE4放ORM R通道如果项目量非常大也可以把所有贴图塞进一张ARMPackedTexture让Shader去取通道。但实际体验是引擎和版本一升级通道顺序很容易出错不如老老实实给每个引擎导出专用贴图。最后分享一个小技巧。我在做三端对比时会固定一个标准球材质Metallic分别取0、0.25、0.5、0.75、1.0Smoothness/Roughness也按0.1、0.5、0.9各存一个版本然后截图放在PS里比对。每次调光或改材质先用这个标准球验证不要直接看角色或场景。因为复杂模型的高光分布很难判断是材质问题还是光照问题标准球能一眼暴露出BRDF参数偏差。局部光照这块三端框架相仿但参数语义差异巨大。掌握这一层后面再碰间接光和GI你会明显轻松一些。我自己的体会是与其死记三个引擎的默认数值不如先记住“单位、通道、衰减、阴影距离”这四个变量然后养成用标准球验光的习惯。这套方法论在任何引擎版本迭代里都不会过时。