双叶高光皮肤渲染:从UE4到Unity URP的移植复刻与踩坑实录
2026/9/17 5:42:58 网站建设 项目流程

做角色皮肤渲染的人,应该都遇到过同一个问题:UE4或Unity的默认PBR高光打上去,皮肤质感总是偏“塑料”。参数调到一定程度,要么高光太窄像抹了油,要么高光太宽像灯罩。后来我们在UE4项目里用了一套叫“双叶高光”的技巧,把高光拆成两片:一片负责锐利的皮肤表面油脂反射,一片负责柔和的次表面散射感,组合之后皮肤看起来会通透很多。最近项目需要把这套效果从UE4移植到Unity,我把完整过程捋了一遍,包括原理、节点拆解、Shader复刻、常见坑位,整理成这篇文章。适合正在做角色皮肤渲染、不想继续在PBR参数里挣扎的TA和图形程序员参考。

1. 双叶高光原理拆解与移植前准备

1.1 双叶高光是怎么回事

双叶高光这个叫法,不同团队里解释略有不同,我们沿用最常见的一种理解:“双叶”形容的是两层高光,一层像植物叶片表面那种硬朗的镜面反射,一层像叶片背面透光时那种柔和的光晕。对应到皮肤渲染上,就是物理上真实存在的两种反射行为。

皮肤并不是单一反射表面。最外层是角质层,表面还有一层薄薄的油脂和水分混合膜,这层东西会把光线集中反射出去,形成比较锐利的高光。光线穿过表皮之后,在真皮层里会被胶原纤维和血管散射,一部分光线又从皮肤内部“透”出来,形成比较宽泛的次级反射光。常规PBR用一个GGX高光加一个粗糙度,很难同时模拟这两个现象。粗糙度调低了,高光整体锐利,皮肤缺少那种“白里透光”的柔和感;粗糙度调高了,整体高光过宽,又没有皮肤表面那一点点润泽的反光。

双叶高光的思路就是各管各的。两片高光都基于半程向量和法线点乘,但一个用很高的指数压缩出锋利小亮点,另一个用很低的指数铺开成柔和大光斑,最后用权重混合。公式大致是:

  • 锐利层高光 = pow(saturate(dot(N, H)), 高指数) × 高光强度
  • 柔光层高光 = pow(saturate(dot(N, H)), 低指数) × 高光强度
  • 最终高光 = lerp(柔光层, 锐利层, 混合权重)

有一点必须说明:双叶高光不是次表面散射(SSS),它只是用高光造型在视觉上模拟皮肤的通透感。真正的SSS需要额外的透射计算和采样,双叶高光胜在成本低、可控性强,尤其适合日式风格化角色和移动端项目。

1.2 UE4与Unity的渲染差异对照

移植这件事,最怕的是拿着UE4的材质节点在Unity里找一个同名节点直接连。两个引擎的渲染模型看似接近,实际上从光源获取方式到输出节点的语义都有差异。我把核心差异整理成一张对照表,后面所有复刻工作都以这张表为基础。

对比项UE4Unity(URP / Built-in)
材质编辑器Material节点编辑器(蓝图式)Shader Graph(节点式)或手写Shader
光源方向LightVector节点Shader Graph的Main Light Direction节点;手写Shader用_WorldSpaceLightPos0或GetMainLight
视角方向CameraVector节点View Direction节点;手写Shader用GetWorldSpaceNormalizeViewDir
半程向量需要自行Add + Normalize同样需要自行计算,没有现成HalfVector节点
法线贴图空间默认切线空间默认切线空间,导入设置需标记为Normal Map
高光工作流Metallic / Specular两套URP有Metallic / Specular两套,Built-in也有
阴影信息材质节点里容易取到Shader Graph需接入Main Light Shadow,手写Shader要自己处理shadowAttenuation

从这张表能看出来,双叶高光的核心数学没有变,但“在哪拿方向、在哪输出颜色”完全不一样。所以移植的关键不是逐节点翻译,而是把整个材质行为拆出来,用Unity的方式重写一遍。

1.3 移植前先想清楚这4个问题

动工之前,最好先问自己几个问题,不然很容易做到一半发现方案选错了。

第一个问题:目标渲染管线是什么?如果是比较老的项目,还在用Built-in管线,Shader Graph和URP Shader都无法直接用;如果项目已经切到URP,就要按URP的API来写。我们这次是URP项目,所以下面所有代码和节点操作都以URP为例。HDRP又是另一套API,不建议在HDRP里直接用URP Shader。

第二个问题:双叶高光要不要保留PBR参数?我的建议是保留。皮肤本质是绝缘体,Metallic固定为0,Smoothness还承担了基础高光形状控制。双叶高光应该作为高光层的“增强”,而不是把PBR高光完全扔掉。否则遇到多光源、环境反射和阴影时,效果会非常不自然。

第三个问题:需要支持多光源还是主光源就够?很多风格化角色只吃主光,双叶高光只用主光方向就够了,但遇到切换光源数量容易出问题。如果希望严谨一点,就要把双叶高光放到光照循环里,或者走引擎的Specular通道。

第四个问题:是否需要移动端兼容?高指数的pow在移动端half精度下容易出现色带和闪烁,这个问题在下面“踩坑实录”里会详细说。如果目标是移动端,参数上限、精度关键字都要提前规划。

2. UE4侧材质节点拆解:把原Shader的“配方”挖出来

2.1 用节点复现双叶高光

如果你手上还有UE4工程,最直接的办法就是把原材质截屏存档,再逐步拆解。我描述一下典型节点图长什么样,方便没有原始工程的读者理解。

以Lit材质为例,材质域选Surface,光照模型选Default Lit或Subsurface Profile都可以。在材质图里,我们通常这样搭:

  1. 用Normal Vector节点取世界空间法线。
  2. 用Light Vector节点取的是场景主光方向,用Camera Vector节点取视角方向。
  3. 把Light Vector和Camera Vector相加,再接Normalize节点,得到半程向量H。
  4. 用Dot节点计算法线和H的点乘,接Saturate。
  5. 复制这个结果,一个接Power节点,指数设到64到256之间,作为锐利层;另一个接Power节点,指数设到4到16之间,作为柔光层。
  6. 两层分别乘强度系数,再用Lerp按权重混合。

如果觉得节点图太占面积,直接在Custom节点里写几行HLSL也可以,效果一样:

float3 H = normalize(LightVector + CameraVector); float nh = saturate(dot(VertexNormalWS, H)); float sharp = pow(nh, Leaf1Power); float broad = pow(nh, Leaf2Power); return lerp(broad, sharp, LeafBlend);

在UE4里,这个返回值一般有两种接法:一种是接到Specular引脚,让引擎按高光流程走;另一种是接到Emissive Color,作为发光项直接叠加。前者物理响应更正确,后者风格化控制更强。我们最终选择了接到Specular引脚,并且通过一个参数开关控制是否启用双叶高光,方便A/B对比。

2.2 UE4皮肤材质中双叶高光与PBR的搭配

原项目里,角色皮肤并不是一个纯PBR材质,也不是一个纯风格化材质,而是两者混搭。基色贴图驱动Base Color,Smoothness用贴图控制,Metallic直接给0。双叶高光作为高光层的“附加贡献”,在一个Lerp里和默认PBR高光做混合,混合系数由一张角色专属遮罩贴图的R通道提供。

为什么要这么搭?因为PBR高光在高光点上是准确的,但皮肤那种“润而不油”的中间过渡区域,PBR很难单独表现。双叶高光补的正是这部分视觉信息。注意不要让两层高光都独立直接输出,那样高光强度会叠加失控,尤其是脸颊、鼻尖这些高光集中区域,特别容易过曝。建议在UE4里先把两层高光的原始强度调到0.3到0.8之间,再通过材质实例微调,而不是图省事直接加到2.0。

这里必须提一下UE4的Smoothness工作流。UE4里很多皮肤贴图习惯把Smoothness直接压在Alpha通道,然后用Constant节点读取。移植到Unity时,如果贴图本身没有变,UE4的Smoothness和Unity的Smoothness数值不能直接画等号,因为两边的粗糙度到高光宽度的映射有细微差别。别嫌麻烦,一定要在Unity里重新目测校准。

2.3 移植前需要记录的最小参数集

很多人移植经验少,容易犯一个错误:盯着节点一个个复制,结果漏了某个贴图通道或者参数。我的建议是,在动Unity之前,先写一份“移植信息采集表”,把原材质的输入输出全部抽出来归档。

  • 基础色贴图、法线贴图路径及导入设置。
  • Smoothness来源:是贴在Alpha通道还是单独一张图?
  • 第一层高光强度、指数。
  • 第二层高光强度、指数。
  • 混合权重来源:是纯数值还是贴图控制?如果用贴图,是哪张贴图的哪个通道?
  • 高光颜色:是纯白还是乘了肤色?如果乘了肤色,是乘BaseColor还是单独一张高光颜色贴图?
  • 是否接入了SSS项,SSS的强度和宽度参数是多少?
  • 是否和Rim Light边缘光混在一起?

把这些参数整理成表之后,在Unity里重建Properties就非常快了。我们最后只花了半天就把参数全部对应上,剩下时间都在调视觉差异。

3. Unity侧复刻:从Shader Graph到手写Shader

3.1 用Shader Graph快速验证效果

进入Unity之后,我建议先不要直接上手写代码,先用Shader Graph快速搭一个原型,确认思路没有跑偏。URP下创建一个PBR Shader Graph,然后按下面这些步骤连节点。

第一步,把BaseColor和Normal贴图接入PBR Master节点的Base Color和Normal引脚。法线贴图的导入设置必须改成Normal Map,否则着色器里看到的效果是错的。

第二步,拿光照方向。在Shader Graph里,URP环境用Main Light Direction节点,Space选World。如果项目还在Built-in管线,就没有这个节点,需要自己通过Light Data子图或者手写Custom Function节点获取_WorldSpaceLightPos0。

第三步,拿视角方向。直接用View Direction节点,Space选World。

第四步,计算半程向量。Shader Graph里没有HalfVector节点,需要Add之后接Normalize。这里要注意Add的两个输入都是三维向量,别把精度设置得太低。

第五步,计算NdotH。用Dot节点,输入法线和半程向量,再接Saturate。

第六步,复制这个NdotH,接两个Power节点,一个指数设高,一个指数设低,各自乘强度,再用Lerp混合。混合权重可以先用一个Slider节点占位,后面再换贴图。

第七步,把这个混合结果接到PBR Master节点的Specular引脚,或者接到Emission作为风格化输出。接到Specular更符合物理,遇到后期处理不会出大问题。

Shader Graph的好处是迭代快,适合验证两层高光的比例和整体感觉。但项目落地的时候,我还是推荐手写Shader,原因很简单:Shader Graph生成的代码开销不可控,而且跨版本升级经常出现节点行为变化,手写HLSL虽然麻烦一点,胜在稳定可控。

3.2 手写URP Shader完整代码与关键函数解析

下面这份代码是在URP下可用的基础版本,我们在项目中一直沿用,只是把参数名换成了项目规范。代码里省去了一些宏定义,保留了双叶高光最核心的计算逻辑,适合作为复刻的起步模板。

Shader "Custom/DoubleLeafSkin" { Properties { _BaseMap ("Base Map", 2D) = "white" {} _BaseColor ("Base Color", Color) = (1,1,1,1) _NormalMap ("Normal Map", 2D) = "bump" {} _Leaf1Power ("Sharp Leaf Power", Range(1,256)) = 96 _Leaf1Strength ("Sharp Leaf Strength", Range(0,2)) = 0.8 _Leaf2Power ("Broad Leaf Power", Range(1,256)) = 8 _Leaf2Strength ("Broad Leaf Strength", Range(0,2)) = 0.6 _LeafBlend ("Leaf Blend", Range(0,1)) = 0.5 } SubShader { Tags { "RenderPipeline"="UniversalPipeline" "RenderType"="Opaque" } Pass { HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl" CBUFFER_START(UnityPerMaterial) float4 _BaseColor; float4 _BaseMap_ST; float4 _NormalMap_ST; float _Leaf1Power; float _Leaf1Strength; float _Leaf2Power; float _Leaf2Strength; float _LeafBlend; CBUFFER_END TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); TEXTURE2D(_NormalMap); SAMPLER(sampler_NormalMap); struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float4 tangentOS : TANGENT; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionCS : SV_POSITION; float2 uv : TEXCOORD0; float3 normalWS : TEXCOORD1; float4 tangentWS : TEXCOORD2; float3 viewDirWS : TEXCOORD3; float3 positionWS : TEXCOORD4; }; Varyings vert(Attributes input) { Varyings output = (Varyings)0; VertexPositionInputs positionInputs = GetVertexPositionInputs(input.positionOS.xyz); VertexNormalInputs normalInputs = GetVertexNormalInputs(input.normalOS, input.tangentOS); output.positionCS = positionInputs.positionCS; output.uv = TRANSFORM_TEX(input.uv, _BaseMap); output.normalWS = normalInputs.normalWS; output.tangentWS = float4(normalInputs.tangentWS, input.tangentOS.w); output.positionWS = positionInputs.positionWS; output.viewDirWS = GetWorldSpaceNormalizeViewDir(positionInputs.positionWS); return output; } half4 frag(Varyings input) : SV_Target { half4 baseMap = SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, input.uv) * _BaseColor; half3 normalTS = UnpackNormal(SAMPLE_TEXTURE2D(_NormalMap, sampler_NormalMap, input.uv)); half3x3 tangentToWorld = half3x3( input.tangentWS.xyz, cross(input.normalWS, input.tangentWS.xyz) * input.tangentWS.w, input.normalWS ); half3 normalWS = normalize(TransformTangentToWorld(normalTS, tangentToWorld)); Light mainLight = GetMainLight(); half3 lightDir = normalize(mainLight.direction); half3 viewDir = normalize(input.viewDirWS); half3 halfDir = normalize(lightDir + viewDir); half NdotH = saturate(dot(normalWS, halfDir)); half NdotL = saturate(dot(normalWS, lightDir)); half sharpLeaf = pow(NdotH, _Leaf1Power) * _Leaf1Strength; half broadLeaf = pow(NdotH, _Leaf2Power) * _Leaf2Strength; half specular = lerp(broadLeaf, sharpLeaf, _LeafBlend); half shadow = mainLight.shadowAttenuation; half3 lighting = mainLight.color * NdotL * shadow; half3 ambient = SampleSH(normalWS); half3 finalColor = baseMap.rgb * (ambient + lighting) + specular * mainLight.color; return half4(finalColor, 1.0); } ENDHLSL } } FallBack "Universal Render Pipeline/Lit" }

这份代码里几个关键函数的用途要说明一下。GetVertexPositionInputsGetVertexNormalInputs是URP封装好的空间变换辅助函数,分别负责把模型空间数据转成世界空间数据,省去手动拼矩阵的麻烦。GetMainLight()在URP里拿到的是主光源的模拟结构体,里面有方向、颜色和阴影衰减,前提是项目里已经配置好URP的Lighting设置。GetWorldSpaceNormalizeViewDir返回的是从顶点指向相机的归一化方向。

片段着色器里最核心的几行就是NdotH的计算和两层pow。一个是高指数,负责锐利的小亮点,一个是低指数,负责宽泛的柔光层,用Lerp混合。如果你想直接叠加而不是过渡,可以把Lerp换成加法,但那样做要注意过曝,我个人不太推荐。

最后两行是输出颜色。注意高光不是直接加到最终颜色上就完事,要乘以主光颜色,且要叠加在环境光和漫反射之上,这样看起来才是一个受光照影响的高光,而不是一块死白。

3.3 双叶高光与Unity PBR的整合策略

手写Shader的高自由度也带来了一个问题:怎么和Unity的PBR光照、阴影、多光源体系融合?我整理出三种常用方案,项目里也分别测试过。

方案A:把双叶高光直接合成到最终颜色里,类似上面代码的做法。优点是完全可控,高光形状和强度精确;缺点是多光源时要自己处理,而且阴影衰减忘乘就会出现“发光脸”。

方案B:把双叶高光结果输入到PBR Master节点的Specular通道。这种方式让高光和引擎的PBR模型绑定,多光源、阴影和环境光都会自动参与计算,风格化程度稍弱,但物理正确性最高。

方案C:把双叶高光作为Emission发光项输出。这种方案能让高光在暗部、阴影里依然很亮,适合赛璐璐风格或者三渲二效果。代价是阴影下皮肤高光也不会消失,看起来会有一点“自发光”的感觉。

整合方案多光源支持阴影响应风格化强度实现复杂度
A 直接合成最终颜色弱,需手写循环手动乘shadowAttenuation
B 走Specular通道强,引擎自动处理引擎自动处理中低
C 走Emission通道强,但不受光源影响不受阴影影响

我们最终选择的是方案B加方案A的混合:主光源下用方案A做风格化高光,其他光源使用方案B让引擎自动处理。这样既能保证角色在重要镜头下的观感,又不会在大场景里因为多光源切换而出现高光闪烁。

4. 移植过程踩坑实录:6个高频问题与解决

4.1 高光位置偏移,罪魁祸首是光源方向

第一次在Unity里复刻双叶高光时,我们遇到的最匪夷所思的问题就是高光位置和UE4里完全对不上,本该在脸颊右侧的高光跑到鼻梁中间。排查了半天,最后发现是光源方向的正负号问题。

UE4的LightVector节点在某些情况下返回的是指向光源的方向,而Unity手写Shader里_WorldSpaceLightPos0在URP下可能因为w分量不同被当成点光或平行光。在URP的GetMainLight()里,mainLight.direction已经是片元到光源的方向,所以我的代码里直接用了它。如果你的项目还在Built-in管线,记得检查_WorldSpaceLightPos0的w分量,w为0表示平行光,方向直接取xyz;w为1表示点光源,方向需要用位置相减。这一步错了,高光位置就会千奇百怪。

4.2 法线贴图颜色异常与空间不一致

另一个常见坑是法线贴图和法线空间不一致。UE4里法线贴图默认是切线空间,导入时引擎内部会自动处理。Unity也一样,右键导入贴图,Texture Type必须选Normal Map,否则UnpackNormal出来的值完全是错的。

Shader Graph里还有一个容易忽略的点:Normal Vector节点的Space如果选成了Object或者Tangent,计算出来的NdotH就有偏差,高光的形状会整体扭曲。我一般习惯把所有参与双叶高光计算的方向全部统一到World Space,包括法线、光源方向、视角方向。节省排查时间,也更直观。

4.3 高光色带与半进度精度不足

把双叶高光档位调到比较高的指数之后,某个低端Android测试机上出现了明显的等高线色带,尤其是128以上的Power值表现尤其严重。原因很直接:在部分移动GPU上,Fragment Shader里默认使用half精度,而pow函数对低精度的敏感度非常高,数值一旦逼近0再求高次方,就会被整段“压平”,形成分像素色阶。

解决办法有三个,优先级从高到低。一是把参与pow计算的关键变量改成float精度,代价是移动端性能稍微下降一点。二是限制_Leaf1Power最大值,不建议超过256。三是用遮蔽函数处理高光边缘,比如用fwidth加smoothstep做抗锯齿,虽然不能消除底层精度问题,但视觉上能大幅减轻色带。

4.4 阴影下高光泛白

这个问题在方案A里特别明显:角色走到阴影区域之后,漫反射暗下去了,但高光还是亮的,出现一种“皮肤在发光”的诡异感。原因很简单,代码里只乘了NdotL,没有乘shadowAttenuation。

修正方式就是在计算光照时把阴影衰减乘进去。上面代码里有一行half shadow = mainLight.shadowAttenuation;就是干这个的。如果你用Shader Graph,最简单的方式是接入Main Light Shadow节点,把阴影衰减和高光强度相乘。走了这一步,阴影下的皮肤高光就会自然收敛。

4.5 移动端高指数Power的性能与精度取舍

移动端对高指数pow的计算量其实没那么敏感,真正的问题是精度。以我实测的几款机器来看,_Leaf1Power在64到128之间时,画面和性能都比较平衡。一旦超过256,低端机上不仅色带明显,还可能出现高光闪烁。

如果你希望高光边缘更锐利又不想堆Power,有个技巧是把两层高光做乘法叠加。比如把sharpLeaf和broadLeaf相乘,再乘一个强度系数,等于用低指数实现类似高指数的锐利边缘。我们项目最终用的就是pow加乘法混合,既能控制精度,又保留了皮肤的透亮感。

4.6 两层高光边界不自然

两层高光混合权重的Bug比想象中更容易踩。直接Add会过曝,Lerp又容易让两层边界出现一条明显的“分界线”。原因是两层高光的宽度差异过大,在过渡区域里宽度跳变太剧烈。

解决方法是给混合权重加一个基于NdotH的平滑过渡。简单一点,可以额外计算一个NdotH_smooth = smoothstep(0.0, 1.0, NdotH),然后把它作为Lerp的混合系数的一部分。这样从柔光层过渡到锐利层时,边界会柔和很多。更进阶的做法是用贴图控制,这个放到后面调参部分说。

4.7 常见问题速查表

问题可能原因解决方法
高光位置偏移光源方向正负号或w分量判断错误检查Main Light Direction,确认是否指向光源
法线导致高光扭曲法线贴图未标记为Normal Map,或空间不一致导入设置改Normal Map,计算统一到World Space
高光滑出现色带half精度不足或Power指数过高提高精度,限制指数最大值,边缘加抗锯齿
阴影下高光泛白未乘shadowAttenuation光照计算中乘以阴影衰减
移动端高光闪烁高指数Power在低精度下不稳定用低指数乘法模拟,或限制参数范围
两层高光边界生硬混合权重跳变过快用smoothstep平滑权重,或使用贴图控制

5. 调参心得与不同风格应用

5.1 核心参数含义与调节方向

双叶高光的参数不多,但每个参数都直接影响皮肤的“性格”。在项目里我习惯按照这样一套逻辑来调。

_Leaf1Power控制锐利高光的集中程度。数值越高,高光点越小越亮,适合做出那种“湿润嘴唇”或者“鼻尖一点光”的效果。写实方向建议64到128,风格化方向可以拉到192到256之间,但超过256容易引发移动端精度问题。

_Leaf1Strength控制锐利高光的整体强度。这个值不宜过高,通常0.3到0.8之间。因为锐利层的面积小,一旦强度超过1.0,在高光核心区域会非常刺眼,后期校色都救不回来。

_Leaf2Power控制柔光层的延展范围。数值越低,柔光铺得越开,皮肤的粗糙感和厚度感越强。写实角色建议4到16,偏CG风格可以升高到24到32,太高就失去“柔光”的意义了。

_Leaf2Strength是柔光层亮度,建议0.2到0.6之间。柔光层本质上是一种皮肤内部的“透光错觉”,太亮会像皮肤被灯从下面照着,很奇怪。

_LeafBlend是两层之间的权重。0表示完全使用柔光层,1表示完全使用锐利层,中间值是两层混合过渡。写实方向建议0.4到0.6,日系风格建议0.2到0.3,因为日系皮肤强调大面积柔和的受光感,锐利高光只保留一小点。

5.2 用贴图控制混合权重

把_LeafBlend从一个Slider换成贴图之后,效果会有一个质的提升。我们项目里用了一张“皮肤遮罩贴图”,R通道存高光强度倍率,G通道存两层高光的混合权重。脸颊位置G通道值高一点,这样柔光感更强;鼻梁和额头G通道值低一点,锐利高光更明显。

这种做法的好处是可以对脸部不同区域做非常细的控制。比如下眼睑需要一点点锐利高光来提神,但面积过大的话会显得油;嘴唇需要高强度的锐利层和低强度的柔光层,才能表现唇纹和湿润感。这些细节很难用一个全局参数调出来,遮罩贴图是性价比最高的方案。

5.3 和SSS、Rim Light的后续组合

双叶高光不是皮肤渲染的终点。在原项目里,这层高光还要和次表面散射、边缘光做组合。做法是先把SSS的Wrap漫反射计算出来,作为漫反射项的输入;双叶高光的锐利层和柔光层各自独立,先混合,再乘一个由SSS强度生成的遮罩,避免在高光区域出现过度叠加。

Rim Light边缘光和双叶高光组合时尤其要注意主光照方向。如果边缘光方向来自背光侧,而双叶高光的半程向量又指向相机,两个效果会互相抢视觉注意力,看起来脸上同时有两个光源。我的习惯是把边缘光的强度做成NdotV的函数,在脸部正对相机时衰减掉大部分,只保留轮廓处的微弱光边。

最后说点个人体会。移植最怕的不是Shader本身,而是把UE4里那套“手感”丢掉。我在Unity里重建完双叶高光后,第一版拿到的画面总觉得不对劲,后来发现是UE4默认材质里Smoothness工作流和URP不完全一致,导致同一张贴图在两个引擎里看起来不是一个亮度。类似这种引擎差异,一定要在移植文档里记录清楚。如果你也在做角色皮肤渲染的跨引擎移植,建议先抽一天时间把两个引擎的材质参数整理成对照表,再动手改Shader,踩坑会少很多。后面我还会继续分享这个双叶高光在Unity里接SSS和Rim Light的进阶做法,感兴趣的话可以持续关注。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询