
1. 项目概述为什么要在URP里折腾卡通皮肤的SSS做角色渲染尤其是二次元、卡通风格的角色皮肤质感一直是个老大难问题。你可能会觉得卡通风格不就是平涂色块加描边吗但真做起来尤其是想做出那种有生命力、通透感的角色纯色块就显得特别“纸片人”缺乏体积和深度。这就是为什么很多高水准的卡通渲染项目都会在皮肤上加入次表面散射Subsurface Scattering 简称SSS效果。简单来说SSS模拟的是光线穿透半透明物体比如皮肤、玉石、蜡并在内部散射后从另一点射出的物理现象。对于皮肤它最直观的表现就是耳朵在逆光下的透红感鼻翼和关节处因为皮肤薄而透出的血色以及脸颊在特定光照下那种柔和的、仿佛从内部透出来的光泽。没有SSS你的角色在强背光下边缘就是死黑一片毫无生气有了它角色立刻就“活”了质感提升不止一个档次。传统上在Unity内置管线或早期的SRP里实现SSS要么写复杂的表面着色器要么依赖一些后处理方案门槛不低。但现在我们有了URPUniversal Render Pipeline和两大可视化着色器编辑利器Unity官方的Shader Graph和社区强大的Amplify Shader EditorASE。这个项目的核心目标就是利用URP的现代渲染框架结合这两款工具我会以Shader Graph为主并对比ASE的关键节点思路快速搭建一个专为卡通风格优化的皮肤SSS着色器。我们不止要“实现”更要“快速”和“风格化”——这意味着效果要明显、可控并且能完美融入你的卡通渲染管线。2. 核心思路与方案选型卡通SSS的简化之道在开始连连看之前我们必须想清楚卡通风格的SSS和写实风格的SSS本质区别在哪。写实SSS追求物理精确计算复杂涉及厚度图、多次散射模拟等。而卡通SSS是风格化的我们追求的是“感觉对”而不是“物理对”。因此核心思路是做大幅度的简化与艺术化控制。2.1 核心算法Wrap Lighting 与 Rim Light 的融合我们不会去模拟复杂的次表面散射方程。对于卡通皮肤一个经过验证且效果出色的简化模型是“边缘透光” “漫反射柔化”。边缘透光Rim SSS这是表现皮肤通透感最直接的方式。在视线方向与光照方向夹角很大的地方即轮廓边缘让光线“透”过来。计算时我们通常使用NdotV法线方向与视线方向的点积。NdotV越小说明该点越靠近边缘。我们用一个幂函数Power来控制边缘光的衰减和宽度再乘以光照颜色和强度就能得到基础的边缘透光效果。这常用于表现耳朵、鼻尖等薄皮肤区域。漫反射柔化Wrap Lighting普通的兰伯特漫反射是max(0, NdotL)。这会在背光面产生清晰的明暗分界线。对于皮肤这种柔软、有散射的材质我们需要让暗部也有一点微光。Wrap Lighting的公式通常是max(0, (NdotL wrap) / (1 wrap))。这里的wrap是一个0到1之间的参数wrap值越大光线“包裹”物体的程度越高暗部就越亮、越柔和。这模拟了光线在皮肤内部散射后从各个方向补充了暗部光照的效果。我们的方案就是将这两种效果结合起来。基础色由经过Wrap Lighting柔化后的漫反射决定然后在边缘叠加上基于NdotV计算的透光色。这样皮肤既有柔和的整体明暗过渡又在轮廓处有生动的透光感。2.2 工具选型Shader Graph vs. ASE为什么同时提这两个因为它们各有拥趸且在一些细节处理上思路不同。Shader Graph (SG)Unity官方亲儿子与URP集成度最高节点清晰规范支持SRP Batcher对于URP项目是首选。它的Subgraph功能对于封装复用逻辑非常方便。但有时节点功能比较“基础”实现一些特定效果需要绕点路。Amplify Shader Editor (ASE)第三方插件功能极其强大且灵活节点库丰富有很多“黑科技”节点和预设社区资源多。很多复杂的着色器教程都基于ASE。它生成的代码效率通常也很高。在这个项目中我会以Shader Graph为主要构建工具进行讲解因为这是URP下的标准工作流。但对于ASE用户我会在关键步骤指出对应的节点或不同实现思路确保你无论用哪个工具都能跟上。核心的算法思想是完全相通的只是节点连线方式略有差异。2.3 风格化控制参数化设计我们不能只有一个固定的SSS效果。为了适配不同风格的角色从日系卡通到美式动画着色器必须是高度可调的。我们需要暴露至少以下参数SSS颜色通常是与皮肤底色互补或相近的暖色如红色、橙色用于控制透光的颜色。SSS强度控制边缘透光的整体亮度。SSS宽度/衰减控制透光效果从边缘向内延伸的范围。Wrap强度控制漫反射柔化的程度让暗部变亮。纹理控制一张额外的贴图比如保存在顶点色或一张遮罩贴图的某个通道中用来控制不同部位的SSS强度。例如让耳朵、脸颊的SSS更强而额头、下巴则弱一些。3. 在Shader Graph中构建核心节点网络现在我们进入实操环节。在URP项目中创建一个Lit Shader Graph因为我们需要基于PBR光照模型进行修改并将其命名为“ToonSkin_SSS”。3.1 准备基础输入与属性首先创建并暴露我们需要的属性Properties它们会在材质球面板上显示_BaseColor(Color)皮肤基础色。_BaseMap(Texture2D)皮肤基础色贴图。_SSSColor(Color)次表面散射颜色默认设为偏红的颜色如RGB(1.0, 0.4, 0.3)。_SSSIntensity(Float)SSS强度默认0.5。_SSSPower(Float)用于控制边缘光衰减的幂值默认2.0。值越大边缘光越窄、越锐利。_Wrap(Float)Wrap Lighting强度默认0.3。范围0无柔化到1完全柔化。_SSSMaskMap(Texture2D)SSS强度遮罩贴图可选。可以使用它的R通道。在Graph中通过Sample Texture 2D节点采样_BaseMap然后与_BaseColor相乘得到最终的Albedo。3.2 实现Wrap Lighting柔化漫反射在Shader Graph的Fragment阶段我们可以通过Custom Function节点或直接计算来修改光照模型。更直接的方式是我们复制URP Lit的照明逻辑并进行修改。获取关键向量使用Normal Vector节点获取世界空间法线使用Light Direction节点获取主光源方向。使用View Direction节点获取视线方向需要归一化。计算NdotL使用Dot Product节点计算法线与光源方向的点积。应用Wrap使用公式(NdotL _Wrap) / (1 _Wrap)。在Shader Graph中可以用Add节点将NdotL与_Wrap相加再用Divide节点除以(1 _Wrap)。钳制到[0,1]使用Saturate节点确保结果在0到1之间。这个结果我们称之为wrappedNdotL它比原始的NdotL更亮暗部区域原本NdotL为负或接近0也有了数值。注意这里有一个重要的取舍。标准的Wrap Lighting公式会让背光面NdotL为负也产生亮度这在物理上不准确但对于卡通风格是可接受的风格化处理。如果你希望更物理一些可以先用Max节点将NdotL和0取最大值再进行Wrap计算但这会削弱柔化效果。我个人的经验是直接对原始NdotL进行Wrap计算风格化效果更好。3.3 实现边缘透光Rim SSS计算NdotV使用Dot Product节点计算归一化的法线与视线方向的点积。反转与幂运算皮肤SSS发生在边缘即NdotV小的地方。所以我们将1 - NdotV作为基础因子。然后使用Power节点将(1 - NdotV)作为底数_SSSPower作为指数。这样我们可以用_SSSPower精确控制边缘光的衰减曲线。值越大边缘光越集中在真正的轮廓线上。应用颜色与强度将上一步的结果与_SSSColor和_SSSIntensity相乘得到边缘光的颜色贡献值rimSSS。遮罩控制可选如果使用了_SSSMaskMap采样其R通道并与rimSSS相乘实现区域化控制。3.4 合成最终颜色这是最关键的一步决定了SSS效果如何与基础光照融合。基础漫反射照明将之前计算好的wrappedNdotL作为主光源的漫反射强度系数。在Shader Graph中你可以将其连接到Custom Lighting模型的自定义漫反射部分或者更简单直接地将其与主光源颜色相乘再与Albedo颜色相乘作为基础的漫反射颜色baseDiffuse。叠加SSS效果SSS效果应该作为附加光Additive叠加到最终颜色上而不是相乘。这是因为它模拟的是内部散射出来的光是额外的亮度。所以最终的片段颜色可以近似为finalColor baseDiffuse rimSSS环境光与高光别忘了加上环境光Ambient和可能的高光Specular。对于卡通皮肤高光可以非常克制或完全不用。在URP Lit框架下你可以在主光照节点后通过Add节点将rimSSS加到最终结果上。一个重要的技巧SSS颜色与光源颜色的互动。为了让效果更自然rimSSS最好也受光源颜色影响。你可以将rimSSS与主光源颜色Main Light Color进行某种混合比如相乘或屏幕混合这样在暖色光源下SSS会更暖冷色光源下会偏冷增强场景统一性。3.5 ASE用户的对应思路在ASE中整体流程完全一致但节点名称和获取方式不同向量获取使用World Normal、View Dir、Light Direction节点。计算节点Dot Product、Power、Add、Divide等节点与SG类似。关键差异ASE有现成的Wrap Lighting节点在Lighting分类下你可以直接使用并调整Wrap参数比自己连线更方便。同样ASE也有丰富的Rim Light节点但其计算方式可能默认是菲涅尔效应。我们需要修改其输入将其基于NdotV而非VdotH或NdotH来计算以符合我们的“边缘透光”模型。合成输出在ASE的Master Node如PBR Master或Custom Lighting Master中通常有Emission自发光端口。将计算好的rimSSS连接到Emission端口是一个绝佳的选择。因为Emission是纯加法、不受光照影响的这完美符合SSS作为“内部发光”的物理直觉而且能确保SSS效果在暗部也能清晰可见。这是ASE实现此效果的一个非常直观和高效的方法。4. 高级优化与效果打磨基础网络搭好了但要让效果真正可用、好看还需要不少打磨。4.1 厚度模拟与区域化增强真正的SSS效果强度与皮肤厚度相关。我们可以用一张厚度贴图Thickness Map来模拟。通常由美术在建模软件中生成越薄的地方如耳朵、鼻翼值越小或越大取决于你的计算习惯越厚的地方值越大。在着色器中我们采样这张厚度图并用它来影响_SSSIntensity或rimSSS的计算结果。例如finalSSS rimSSS * (1.0 - thickness)假设厚度图白色代表厚黑色代表薄 这样薄的地方SSS效果更强更符合物理。如果没有厚度图一个取巧的办法是利用模型的顶点颜色。让美术在建模时将需要强SSS的区域耳廓、鼻头、指尖涂上红色。然后在Shader Graph中通过Vertex Color节点读取并用其R通道作为遮罩。这种方法灵活且无需额外纹理。4.2 与卡通阴影Ramp贴图的融合很多卡通渲染会使用Ramp贴图来风格化阴影色阶。我们的Wrap Lighting已经柔化了阴影如何与Ramp结合方法一先Wrap后Ramp。将计算得到的wrappedNdotL值域仍在[0,1]作为UV的U坐标去采样一张一维的Ramp贴图。这样柔化后的光照信息再经过Ramp的色调映射能得到既有柔和过渡又有卡通色阶的效果。这是最推荐的方式因为它保持了SSS对光照的柔化作用。方法二分开处理。将SSS效果rimSSS作为完全独立于漫反射的附加层。基础漫反射颜色baseDiffuse由原始的NdotL采样Ramp贴图得到然后直接加上rimSSS。这种方式SSS效果会更独立、更明显但可能和基础阴影的融合不够自然。你可以根据项目风格选择。4.3 性能考量与移动端适配我们的方案已经非常简化主要计算是几个点积、加减乘除和一次幂运算开销很低。但在移动端仍需注意纹理采样如果用了基础色贴图、SSS遮罩贴图、厚度图、Ramp图采样次数会增多。尽量合并贴图。例如将SSS遮罩R通道和厚度信息G通道打包到同一张贴图。精度在Fragment Shader中使用half精度在SG中通常是默认进行计算足以满足效果。实时灯光我们的计算默认针对主方向光。如果场景有多个实时光需要循环累加每个光的贡献开销会成倍增加。对于卡通渲染通常建议只使用一个主方向光环境光其他光源用烘焙或假光源比如用额外的加法Pass模拟的点光来实现。ASE的Emission技巧在移动端将SSS连接到Emission要小心因为Emission通常意味着物体自发光可能会错误地参与全局光照GI计算。确保在URP的材质或ASE的Master Node设置中关闭了全局光照对自发光的影响。5. 常见问题排查与实战心得在实际应用这个着色器时你肯定会遇到一些问题。这里记录几个我踩过的坑和解决方案。5.1 问题速查表问题现象可能原因排查与解决SSS效果完全看不见1._SSSIntensity设置为0。2.rimSSS计算错误未连接到最终颜色。3. 在ASE中rimSSS连接到了错误的端口如Albedo应连接到Emission或Additive通道。1. 检查材质球参数。2. 在Shader Graph中用Preview节点逐步检查rimSSS的计算结果。确保Power节点输入正确。3. 在ASE中检查Master Node的连线确保附加颜色输出到了正确的通道。SSS颜色不自然像外发光1._SSSColor太亮或饱和度太高。2.rimSSS直接叠加没有与光源颜色混合。3. 边缘光范围_SSSPower太小导致效果过于锐利。1. 将_SSSColor调暗、降低饱和度通常使用偏暗的红色/橙色如RGB(0.8, 0.2, 0.1)作为起点。2. 将rimSSS与Main Light Color相乘后再叠加。3. 适当减小_SSSPower如从2.0调到1.5让边缘光更柔和、扩散。背光面整体太亮像飘在空中Wrap参数值过大。虽然Wrap Lighting是为了柔化暗部但过大的值会完全消除阴影破坏体积感。将_Wrap参数降低通常0.2到0.4之间是安全范围。可以尝试一个非线性的Wrap公式如(NdotL _Wrap) / (1 _Wrap)的变体或者对Wrap后的结果再进行一次轻微的压暗处理。角色转动时SSS效果闪烁或突变计算NdotV或NdotL的向量没有归一化Normalize。在模型边缘这些向量的变化很快未归一化的点积值不稳定。确保所有参与点积计算的向量法线、视线、光源方向都是归一化后的。在Shader Graph中使用Normalize节点。这是最常见也最容易被忽略的错误之一。在特定角度下SSS出现在不该出现的地方如额头仅依赖NdotV判断边缘不够精确。任何法线朝向与视线接近90度的位置都会被计算包括一些非轮廓的褶皱。引入菲涅尔Fresnel因子进行约束。经典的菲涅尔公式pow(1.0 - NdotV, _FresnelPower)和我们用的边缘光公式很像但我们可以将其作为一个遮罩与原来的rimSSS相乘让效果更集中在真正的轮廓周围。或者更简单地结合NdotL只在与光源方向相反NdotL 0的背光面才显示强SSS。5.2 实战心得与技巧迭代与观察不要只在默认光照下调试。创建一个简单的场景包含一个方向光并让角色缓慢旋转。同时创建一个纯黑的背景这能让你最清晰地观察到SSS效果的形状和颜色。再创建一个纯白的背景检查SSS效果是否过于突兀。参考真实照片找一些逆光拍摄的人像照片尤其是耳朵、手指部位。观察透光的颜色、范围和强度。你会发现真实的皮肤SSS颜色是一种非常微妙的、饱和度不高的暖色而不是明亮的红色。你的_SSSColor应该向这个感觉靠拢。分层渲染思维将SSS视为独立于漫反射和高光的“透光层”。在后期调整时可以单独调整这一层的强度、颜色和模糊度通过修改_SSSPower而不会影响基础的阴影关系。这种思维让美术调整起来更直观。与后处理配合单一的Shader SSS有时会显得生硬。可以结合轻度的屏幕空间泛光Bloom。将SSS效果输出到自发光Emission通道ASE天然支持SG需要设置Emission属性然后开启URP的Bloom后处理。Bloom会对高亮区域包括我们的SSS光晕进行模糊和扩散能极大地增强皮肤通透、柔和的质感让光效更加自然融合。这是提升最终画面表现力的一个关键技巧。性能与质量的平衡如果项目对性能极其敏感可以尝试将SSS计算从逐片段Fragment移到逐顶点Vertex阶段。在顶点着色器中计算NdotV和NdotL然后通过顶点颜色或纹理坐标插值到片元。这会有一些精度损失效果稍显粗糙但对于低模卡通角色在移动端可能是一个可以接受的折中方案。最后附上本方案核心的Shader Graph节点图思路示意文字描述整个网络从左到右分为三列。第一列是属性输入和纹理采样。第二列是核心计算区上方分支计算wrappedNdotLDot Product-Add-Divide-Saturate下方分支计算rimSSSDot Product(NdotV) -One Minus-Power-Multiply(Color, Intensity)。第三列是合成输出wrappedNdotL与光源颜色、Albedo相乘得到基础色再与rimSSS通过Add节点相加最后输出到Base Color和Emission如需。整个流程清晰体现了“柔化漫反射 边缘附加光”的核心思想。