
开头如果你最近在逛 Unity 社区应该能明显感觉到纯写实 PBR 的赛道越来越挤反而是《原神》《崩坏星穹铁道》这类三渲二风格、以及大量独立游戏里带手绘质感的卡通渲染玩法成了美术和玩家都买单的主流。而这一切落到 Unity 里核心就两个字——卡通材质。它不只是一张贴图也不只是一个带描边的 Shader它本质上是把物理正确的光照模型替换成一套“艺术优先”的简化光照模型。这篇文章我会从原理讲到实际操作给你一套可以直接抄作业的 Unity 卡通材质实现方案包含完整 Shader 代码、材质参数、描边方案对比和常见坑。适合正在做风格化项目、独立游戏、或者刚开始接触 Shader 想搞懂“为什么别人的卡通材质那么干净”的 Unity 开发者。很多人一开始写卡通材质第一反应是“亮部暗部砍成两档再加个描边就完事”。真上手之后才发现事情没那么简单。Lighting 模型怎么改描边用倒转法线还是屏幕后处理为什么拉远看描边变粗Ramp 贴图总是一段黑一段白衔接处还全是噪点这些问题如果没在前期想清楚后面返工的效率会非常低。这篇文章不会只给一个 Shader 文件让你复制而是把整条链路拆开讲明白从光照模型选型、Ramp 工作流、描边的三种主流实现到参数调优和移动端性能验证最后附上我自己踩过的一些坑和排查思路。你不用是图形学大佬跟着思路走就能在项目里做出观感干净、风格统一、性能可控的卡通材质。1. 内容整体设计与思路拆解1.1 卡通材质解决的根本问题做写实 PBR 时我们追求的是光子被物体表面反射、折射之后的物理近似核心关键词是 BRDF、法线微表面、能量守恒。但卡通材质的目标恰好相反它要的是“不那么真实”要的是画面里的人一眼就能辨认出“这是手绘插入 3D 画面”的质感。所以卡通材质要解决的根本问题其实有两个。第一是把光照信息从连续的实数域压缩成离散的色阶。现实里一束光照下来球体表面的明暗是平滑过渡的但在卡通风格里我们通常希望亮面和暗面之间有明显分界像动画里上色的那种“赛璐璐”效果。第二是简化材质模型的同时还得保留足够清晰的轮廓。动画里的人物、道具为什么能在复杂场景中一眼被认出来很大程度上靠的是描线也就是 Outline。这两个需求加在一起就推导出了卡通材质最底层的两套实现路径一套负责 Toon Diffuse 光照另一套负责边缘轮廓提取。很多新手写卡通 Shader 时会直接把 Standard Shader 的颜色输出改两笔结果就是亮部灰蒙蒙、暗部全黑看起来既不卡通也不写实。原因就是你没有把光照模型本身替换掉只在表面刷了一层风格化的漆。1.2 方案选型手写Shader、后处理还是Shader Graph确定要做卡通渲染之后第一个要选的路口是技术方案用什么目前 Unity 生态里主流有三条路。第一条是手写 ShaderLab 的代码可控性最好可以做到单个物体级优化配合 SRP Batcher 和 GPU Instancing 也很方便。缺点是学习门槛高一点但说实话对于卡通渲染这种复杂度不算特别高的着色器手写一套也就在四五百行以内。第二条是 Unity Shader Graph节点式操作对美术友好做 Ramp 和描边都能拉节点调试直观但一遇到自定义 Pass、前向渲染深度写入这种细节节点式表达会有点别扭尤其是做 Outline 的内置 Pass 时Shader Graph 需要开 Custom Function反而绕。第三条是纯屏幕后处理全屏做边缘检测加色阶化优点是所有材质自动生效缺点是没有真正的光照参与立体感和方向感很容易丢。我自己的项目里如果是独立 Demo 或中小型游戏我会选手写 ShaderLab 适量后处理的结构模型表面的卡通光照全部靠自定义 Shader 解决全局的描边、晕影、色彩校正这种统一效果再交给后处理。这样两个部分各管各的排查问题也快。1.3 卡通光照模型对比传统PBR的关键差异传统 PBR 光照模型。方向光进来之后漫反射按 Lambert 余弦计算高光按 GGX 或者 BlinnPhong 分布还要叠环境反射、AO、阴影遮罩。卡通渲染里我们把这些全部做了“量化”处理。最典型的做法是把漫反射系数从连续的 dot(N, L) 值用 step 函数或 Ramp 曲线映射成三段、四段离散值。比如 dot(N, L) 小于 0.3 时输出暗部颜色0.3 到 0.7 时输出中间过渡色大于 0.7 时输出亮部颜色。这样的光照就不再“物理正确”了它变成了美术控制的调色盘。高光部分同理卡通材质里的高光与其说是一团“光斑”不如说是一块“色块”。通常用 smoothstep 把高光收敛成边界清晰的亮斑模拟动画眼睛里的那种高光片。反射和环境光也一样不再是全场景实时反射而是用 cubemap 或者干脆直接忽略。把这套思路整理成对比表格会更直观维度传统PBR卡通渲染漫反射dot(N, L) 连续过渡step/smoothstep 量化成色阶高光GGX/BlinnPhong 能量守恒高光形状可控的色块环境反射基于物理的IBL可选通常弱化或取消描边无依赖轮廓阴影核心要素宽度可控目标接近真实风格统一手绘感你可能会问那卡通渲染是不是就不需要法线贴图了当然需要只是法线细节的作用方式不同。写实材质里法线贴图影响的是微表面高光的形状卡通里法线贴图通常只参与漫反射计算用来让色阶分界的位置产生起伏这样能保留模型表面的一些细节又不至于破坏整体风格。2. 核心细节解析与实操要点2.1 漫反射的阶梯化处理step函数与Ramp贴图漫反射阶梯化是卡通材质的灵魂。落实下来常用有两条路。第一条是纯代码方案用 step 函数把 dot(N, L) 划分成几档。比如这句代码half diff max(0, dot(Normal, LightDir)); half toonDiff step(_DiffuseStep, diff);step(a, b) 的意思是如果 b 大于等于 a返回 1否则返回 0。所以当光照力度超过你设定的 _DiffuseStep 时漫反射直接输出亮部否则输出暗部。这个方案最狠效果最“赛璐璐”但问题是硬切边界会显得很脆物体一转角度明暗交界线就一跳一跳地动。美术经常会觉得太生硬。所以更普遍的做法是引入Ramp 贴图。Ramp 贴图本质上是一张一维渐变纹理横轴从左到右代表光照强度从 0 到 1纵轴就是你想要输出的颜色。在 Shader 里做一次 uv 坐标映射以 dot(N, L) 为 u 坐标去采样这张贴图。关键点在于这张贴图不是非得是平滑渐变它可以是 2 格、3 格分段色块。美术在 Photoshop 里手动画出“暗部颜色—中间色—亮部颜色”的分段就能得到非常稳定的色阶。好处很明显色阶位置、颜色、过渡软硬全部由贴图决定不用在代码里调一堆 step 阈值迭代速度极快。但 Ramp 贴图也有一个隐藏问题就是采样精度。如果贴图分辨率只有 128x128 甚至 64 宽色带之间的硬边缘可能出现锯齿或采样偏移反之如果你把渐变过渡做得特别平滑渲染出来又会出现一层一层的条带噪点banding。我一般用 256 宽度、高度随意1 像素甚至 8 像素都行并且把 Texture 的 Wrap Mode 设置为 Clamp避免光照强度超过 1 或低于 0 时产生多余的拉伸采样。2.2 描边的三条技术路线对比与选型描边Outline是卡通渲染逃不开的存在。没有描边的卡通材质就像画师画完线稿忘记勾线上色。描边实现得干净利落画面的“手绘感”立刻翻倍。主流方案有三条模型空间法线外扩、屏幕空间后处理描边、以及基于视角的边缘光描边。我分开说一下。模型空间法线外扩是最老牌的做法。思路是额外渲染一个 PassCull Front把顶点沿法线方向往外推一点点把背面的角色渲染成黑色再让它略大于前面正常着色的模型看起来就像勾了一圈边。难点在推的幅度怎么控制如果推得太少距离一远描边会消失推得太多模型本来就低模的话边线会穿进身体里出现“面条状”的破洞。还需要做顶点色或顶点法线的修正否则硬边模型的描边角度会很奇怪。屏幕空间后处理描边是另一条路。它不操作模型顶点而是渲完场景之后通过深度图和法线图检测边缘再叠加黑线。传统意义上的 Sobel 边缘检测或者更精细一点的 Roberts Cross都能出效果。优点是所有模型统一描边、不用担心描边穿模缺点是没法按物体控制描边颜色和粗细而且对深度边界敏感的场合比如角色头发很容易出现描边时断时续的问题。边缘光描边用法线点乘视角方向在边缘区域输出黑色或暗色。实现代码最短但这不是真正的描边更像“轮廓压暗”适合辅助增强轮廓感不适合做标准动画描线。我给的选型建议是主角和重要道具这类需要棱角分明、描边粗细可控的物体直接用模型空间法线外扩方案场景物体、远景小物件用后处理描边统一带一笔这样底层的性能和视觉都能平衡。2.3 过渡带与色带断层的质量控制色阶量化之后最让人头疼的就是色带断层banding。动画片里颜色是一块一块铺的但人家没有噪点也没有彩色的纹路只有安静的色块。三维渲染时如果色阶之间的过渡区域里有梯度振荡就会产生一圈一圈的条状噪点尤其出现在暗部渐变到中间调的地方。这里我通常做三件事。第一给 Ramp 贴图加一点噪声扰动。在采样 Ramp 前把 u 坐标加上一个很小的噪声偏移打散由于 8bit 量化导致的条纹原理类似视频降噪里的 dithering。注意噪声幅度不能大否则色阶边缘会糊。我习惯的做法是用一张 2D 蓝噪声贴图把采样的结果做轻微偏移效果比单纯在 Photoshop 里拉渐变好很多。第二推荐把经过梯度和色阶处理的 Shader 放在 Linear 色彩空间下使用。Gamma 空间下暗部对比度会被拉高色带断层更容易暴露。如果你项目还在用 Gamma 空间至少要保证 Ramp 贴图是 Linear 采样不然贴图被二次提亮颜色就完全不是美术调出来的那一套了。第三控制色阶的“台阶数”。色阶太多会失去卡通感色阶太少会脏。我实测下来包含暗部阴影、中间过渡、主亮部这三个颜色区间是比较安全的选择其中过渡区可以在 Ramp 里控制成 30% 左右的软过渡其他都做成硬边。这样做既保留了动画的“块感”又不像纯 step 一样生硬。3. 实操过程与核心环节实现3.1 一个可直接复用的卡通材质Shader说再多理论不如直接看代码。下面这套卡通材质 Shader 是我在项目里用的一个基础版包含基于 Ramp 的漫反射、可控高光、边缘光以及一个翻转法线的描边 Pass。代码用的是 Unity 内置渲染管线的 Surface Shader 思路改写的 Vertex/Fragment方便你直接对照理解。Shader Custom/ToonBasic { Properties { _BaseMap (漫反射贴图, 2D) white {} _RampMap (Ramp 色带, 2D) white {} _ShadowColor (暗部颜色, Color) (0.5, 0.5, 0.5, 1) _LightStep (色阶阈值, Range(0, 1)) 0.4 _OutlineColor (描边颜色, Color) (0, 0, 0, 1) _OutlineWidth (描边宽度, Range(0, 0.05)) 0.01 _RimColor (边缘光颜色, Color) (1, 1, 1, 1) _RimPower (边缘光强度, Range(0, 5)) 2 } SubShader { Tags { RenderTypeOpaque QueueGeometry } Pass { Name OUTLINE Cull Front ZWrite On CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc struct appdata { float4 vertex : POSITION; float3 normal : NORMAL; }; struct v2f { float4 pos : SV_POSITION; }; float _OutlineWidth; fixed4 _OutlineColor; v2f vert (appdata v) { v2f o; // 关键把顶点沿法线方向往外推注意先转到观察空间再推 float3 normalOS v.normal; float3 posOS v.vertex.xyz normalOS * _OutlineWidth; o.pos UnityObjectToClipPos(float4(posOS, 1.0)); return o; } fixed4 frag (v2f i) : SV_Target { return _OutlineColor; } ENDCG } Pass { Name FORWARD Tags { LightModeForwardBase } Cull Back CGPROGRAM #pragma vertex vert #pragma fragment frag #pragma multi_compile_fwdbase #include UnityCG.cginc #include Lighting.cginc struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; float3 normal : NORMAL; }; struct v2f { float2 uv : TEXCOORD0; float3 worldNormal : TEXCOORD1; float3 worldPos : TEXCOORD2; float4 pos : SV_POSITION; }; sampler2D _BaseMap; float4 _BaseMap_ST; sampler2D _RampMap; fixed4 _ShadowColor; float _LightStep; fixed4 _RimColor; float _RimPower; v2f vert (appdata v) { v2f o; o.pos UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _BaseMap); o.worldNormal UnityObjectToWorldNormal(v.normal); o.worldPos mul(unity_ObjectToWorld, v.vertex).xyz; return o; } fixed4 frag (v2f i) : SV_Target { fixed3 albedo tex2D(_BaseMap, i.uv).rgb; float3 N normalize(i.worldNormal); float3 L normalize(_WorldSpaceLightPos0.xyz); float3 V normalize(_WorldSpaceCameraPos.xyz - i.worldPos); // 漫反射光照dot 之后直接用 Ramp 贴图映射 float diff dot(N, L) * 0.5 0.5; fixed3 rampColor tex2D(_RampMap, float2(diff, 0.5)).rgb; fixed3 diffuse albedo * rampColor; // 边缘光 float rim 1.0 - saturate(dot(N, V)); rim pow(rim, _RimPower); fixed3 rimTerm rim * _RimColor.rgb; // 主方向光颜色乘上去 fixed3 finalColor diffuse * _LightColor0.rgb rimTerm; // 补一个环境光 finalColor albedo * unity_AmbientSky.rgb * 0.3; return fixed4(finalColor, 1.0); } ENDCG } } FallBack Diffuse }这个版本我做了一些刻意的取舍。第一描边 Pass 用的是 Cull Front 加顶点外扩这是最可控的描边方案第二漫反射没有用 step 硬切而是走 Ramp 贴图这样你可以随时在 Photoshop 里改色带的过渡软硬第三边缘光独立成一个通道方便你按场景开关。要特别留意的是顶点外扩那行坐标变换。如果直接在对象空间把法线乘宽度有些引擎会因为非等比缩放的模型导致描边粗细不均匀所以更稳妥的写法是在观察空间里外扩或者对法线做归一化处理后再乘宽度。上面代码为了保持简洁用了对象空间外扩实际项目建议改成float3 normalOS normalize(v.normal); float3 posOS v.vertex.xyz normalOS * _OutlineWidth;如果你的模型有非等比缩放就在外扩前把法线乘上模型缩放矩阵的逆转置或者干脆用 Unity 提供的变换函数来规避。3.2 Unity材质创建与Shader挂载的完整流程代码拿过来之后怎么在 Unity 里跑起来我按步骤说一遍。第一步创建一个新的 Shader 文件。在 Project 窗口右键Create - Shader - Unlit Shader然后把刚才的代码整体替换掉保存命名为 ToonBasic。第二步创建材质。右键 Create - Material命名 ToonCharMat。把 Shader 从 Project 拖到材质面板的 Shader 下拉里选 Custom/ToonBasic。第三步准备贴图。你需要准备一张主贴图和一张 Ramp 贴图。主贴图就是模型的颜色贴图Ramp 贴图我建议用 256x1 像素的 PNG在 PS 里画好了导入导入设置里务必把 Wrap Mode 改成 ClampFilter Mode 保持 BilinearsRGB 选项看你项目的色彩空间设置。第四步给模型挂材质。直接把材质拖到场景里物体的 MeshRenderer 的 Material 槽位上或者拖到模型的预制体上。第五步在材质面板上调整参数。 _OutlineWidth 建议从 0.01 开始试模型越大这个值可以适当调大 _LightStep 在 0.4 到 0.5 之间比较舒服 _RimPower 调高会让边缘光更收窄调低则更宽泛。这里有个坑要提醒你如果你用 URP 渲染管线需要在 Shader 的 SubShader 标签里把 RenderPipeline 的环境变量做一下兼容或者直接把项目切到内置渲染管线来验证。URP 下上面代码里的 _WorldSpaceLightPos0 和 forward base 光照宏会不生效建议使用 URP 的 HLSL 库。这块内容适合单独开一篇我先不展开。如果你项目已经上了 URP可以直接用相同思路在 Shader Graph 里复刻一套光照映射逻辑。3.3 关键参数调优与最终输出效果验证材质能在场景里正常显示之后你需要花时间调参数。不要直接相信美术在 PS 里看到的颜色因为渲染结果跟灯光颜色、环境光强度都有关系。我一般会按一个固定步骤来调第一步先关掉所有环境光和实时光只留一盏方向光。保证你能看清 Ramp 色带的真实输出。把 _LightStep 和 Ramp 贴图配合调出清晰的三档色阶暗部、中间调、亮部。第二步打开环境光看暗部有没有被环境光冲淡。卡通风格里暗部颜色通常要稍微发灰或者偏冷色调环境光太强会让暗部发蓝发紫。调内置的 Ambient Intensity 或 lightmap 相关参数把环境光压到 0.1~0.3 之间比较稳。第三步开一个自定义 Skybox 或者在 Scene 窗口的 Light 里设置一个灰蓝色的环境色这样模型表面的暗部颜色就不会太脏。第四步调描边。拉远摄像头看描边粗细是否合适。如果你发现拉远之后描边几乎看不见说明 _OutlineWidth 对当前模型比例太小可以按 0.02 再试试如果拉近发现描边粗到覆盖了模型正面说明数值太大降回 0.005 级别再测。调参的过程别只看 Scene 一个视角我建议你在 Game 视图下把分辨率设为目标分辨率比如 1920x1080旋转模型一圈重点检查描边在头发交叉处、手指缝隙、裙摆边缘这些位置是否穿模。穿模的原因一般是模型本身带尖锐凸起或法线方向不连续最简单的修复方法是在建模软件里把模型做一次“统一法线”处理同时在描边 Pass 里把宽度设为一个很保守的值比如 0.008 以内。4. 常见问题与排查技巧实录4.1 描边断裂、穿模与法线修正描边断裂是我被问得最多的问题。具体表现就是模型一圈描边看起来有一截是断开的尤其在某些部位像是被人拿橡皮擦掉了一笔。大多数情况下问题出在模型法线上。模型空间法线外扩的方案本质上依赖法线方向如果模型某些面的法线方向和相邻面不连续比如硬边模型或者没有做顶点焊接外扩的方向就不一致描边会有肉眼可见的裂痕。处理方法有几个层级。第一级是建模阶段要保证模型的硬边和软边的法线设置合理。卡通渲染里的描边物体我通常建议把需要描边的边缘做适量的倒角或 weld不要让两个面直接公用一个顶点。第二级是在 Shader 里做法线平滑外扩用顶点色或额外的平滑法线贴图来替代原始法线。这个办法我在一些日系游戏的做法里见过他们会额外存一份把硬边平滑掉的法线用来做描边外扩这样线条不会断。第三级是干脆换屏幕后处理描边避开模型法线问题代价是轮廓粗细不再是物空间的固定值。遇到模型穿模时先检查模型的骨骼权重或者顶点位置是否有多余重叠。如果模型本身没问题再把描边 Pass 的 ZWrite 改成 On避免描边被其他物体的深度覆盖掉。另外为了减少穿模可以把描边宽度从常数改成基于视觉距离的衰减比如在顶点 Shader 里除以视角距离这样近距离描边不会糊成一团远距离也不会消失。4.2 色阶区域出现噪点、摩尔纹或带状条纹这个问题我在调 Ramp 时踩过很多次。一开始我以为是自己贴图画得不对后来发现是采样精度和色彩空间导致的。现象通常在暗部区域最明显一块很均匀的深色区域却出现了一圈圈细密的彩色条纹或者颗粒噪点。根源是有限的 8bit 颜色精度在渲染时产生量化条带。解决方案我在 2.3 节里已经提了加噪声扰动打散条带以及在 Linear 空间下工作。这里再补充一个关键点Ramp 贴图的颜色格式尽量用 HDR不要用 PNG 的 LDR 格式。如果贴图里暗部只有极少数色阶值渲染时就会缺色块。我通常会把 Ramp 贴图的最低亮度和最高亮度分别拉到靠近 0 和靠近 1 的位置避免没有值能表示的区间。如果摩尔纹出现在角色脸部或衣服大块颜色区域还有一种可能是边缘光或者高光太锐利配合相机采样发生了空间混叠。解决办法是给高光边缘加一个轻度的 smoothstep 过渡别从 0 瞬间跳到 1。4.3 Ramp贴图采样不生效这个问题很常见尤其你是第一次用 Ramp 贴图时。明明在材质里贴了一张 Ramp但模型看起来完全没变化还是普通光照的样子或者就是一团黑。我遇到过的原因有几个一是贴图 Wrap Mode 不是 Clamp。Ramp 的 u 坐标是光照强度理论上只会落在 0 到 1 之间但如果 Wrap Mode 是 Repeat采样点超过 1 时就会回绕导致颜色跳变到贴图的最左边去出现异常。第二个是 Ramp 贴图的高光方向搞反了。很多人习惯从左到右画亮部到暗部但 Shader 里我们用 diff 值做 u 坐标diff 从 0 到 1 分别代表暗到亮。如果贴图画反了出来的效果就是光照越强颜色越暗特别像自发光贴图出错了。第三个是顶点着色器里忘了给 uv 做 TRANSFORM_TEX 变换导致贴图平铺尺度错乱采样到 Ramp 图上一个奇怪的区域。排查方法特别简单在材质面板上把 Ramp 贴图临时换成一张从黑到白的纯渐变看模型的暗亮面是否正常。如果正常说明问题出在贴图本身或者方向如果还是不正常去检查 Shader 代码里 uv 坐标的采样逻辑。4.4 半透明物体、双面材质的处理技巧游戏里不可能全是实心物体。头发、翅膀、衣物飘带、叶子都会有半透明或者双面可见的需求。卡通材质里处理这几类有几个容易踩的坑。双面渲染最简单把 Cull Off 就好。但开了双面之后原来的描边 Pass 也会变成双面导致模型内部也画出描边看起来很脏。所以我通常会给双面物体的描边 Pass 单独设 Cull Front而主 Pass 设 Cull Off这样背面正常显示但不会额外描边。半透明物体要麻烦一点。卡通渲染里半透明头发很多是“Opaque 渲染只是贴图里画了透明区域”也就是用 Cutout 透明。这是性能和效果最平衡的方案。但如果要做真正的半透明比如玻璃、魔法护盾就需要关闭深度写入ZWrite Off并按从远到近排序渲染。卡通风格里的半透明材质我建议不要用标准玻璃那一套 Fresnel 反射而是用边缘光配合透明度渐变让边缘更亮更厚、中心更透手绘感会更强。4.5 渲染性能监测与优化建议卡通材质的 Shader 代码本身不难性能问题往往出在高光计算、后处理和 Ramp 采样次数上。我在移动端调试时喜欢开 Frame Debugger 和 Profiler逐个 Pass 看 draw call 和 overdraw。一个卡通角色如果只有一个前向渲染 Pass 加一个 Outline Passdraw call 是 2这是很理想的数值。如果你在角色头发、眼睛、衣服上各挂了不同材质draw call 会翻倍所以要尽量合并材质用贴图区分不同部位而不是用多套材质。Ramp 采样不算贵但如果你在 Fragment Shader 里采样多张 Ramp 或者做多次四维向量运算对移动端还是有压力。建议把 Ramp 相关的计算保持在 half 精度并且尽量复用同一张贴图。高级一点的优化是把色阶化后的光照缓存到一张低分辨率纹理里然后采样出来但这属于进一步方案项目没到瓶颈时不必急着上。5. 性能与风格化扩展锦上添花的几个方向5.1 移动端渲染开销分析卡通材质在移动端其实很占优势因为相比 PBR它通常不需要复杂的漫反射积分、反射探针和阴影贴图采样。Shadow 这块要稍微注意默认的实时阴影贴图在移动端精度有限角色脸上的阴影经常出现锯齿或抖动。我习惯给角色关掉实时阴影用假阴影一个平面阴影 一张软边贴图替代或者用“描边阴影”的形式把阴影也做成色阶化。这样既省性能风格还统一。Shadows 质量不高的另一个原因是项目里可能有多个实时灯光。卡通渲染特别忌讳多灯混合因为每个灯都会单独算一次漫反射和高光颜色会迅速变脏。如果你要在角色身后加补光别用方向光改用假的 emissive 贴图或 light probe 的间接光。5.2 保留法线细节与PBR混合的进阶思路有的项目不想要纯赛璐璐那么“平”它想保留手绘贴图里的笔触和细节又不想完全丧失物理光照的方向感。这种需求我用“过渡带法线增强”来处理。思路是在算完 Ramp 色阶之后不要直接输出最终颜色而是把原始法线取回来用它的 dot(N, L) 结果做一次非常弱的二次渐变叠加。这样物体转动时法线细节会微调色阶边界的位置就像笔刷在纸上留下了一点厚度。听起来简单效果却立竿见影暗部不再是死黑而是有微弱的体积感。PBR 混合则更大胆一点可以在 Ramp 采样结果上再叠一个白模 AO 或者环境光遮蔽贴图。卡通渲染里 AO 是非常好用的工具但它不能是标准 PBR 里那种纯灰度遮蔽最好是美术专门画的分块色 AO用来压暗交界线附近的区域让角色更“立得住”。5.3 配合后期特效如何叠加描边、晕影和半色调卡通材质跑通后画面可能还是缺一层“统一感”。这时候后处理就派上用场了。我会建议叠加三层第一层是色彩分级把画面的对比度抽到一个舒适区间暗部不会被提得太灰第二层是一个很轻的暗角Vignette让视线聚焦在角色脸部第三层是可选的效果游戏里比较常见的是“网点纸”或“半色调”效果。直接在屏幕空间采样亮度高于阈值的区域叠一层均匀白低于阈值的区域叠黑色网点这样画面会突然多出一股老漫画的感觉。技术实现也不难在后期脚本里加一个自定义 Image Effect Shader输入主纹理和 uv用亮度阈值做 step 就能完成。需要注意的是后处理不能开得太重。卡通渲染的本质是简洁如果叠太多滤镜画面会显得脏。我一般会把后处理的 opacity 控制在 20% 以内让效果若隐若现就好。结尾卡通材质最大的魅力在于它不是一成不变的公式而是一套“艺术规则”的可编程表达。同一套 Shader换一张 Ramap、换一组描边参数就能从日系赛璐璐切换成美式漫画风格。我自己实际用下来的体会是不要一上来就追求把 Shader 写得又多又全先搭好一个最基础的光照模型在场景里反复看、反复调 Ramp 和描边的边界参数等手感建立起来再往里面加边缘光、半透明、后处理这些扩展功能。整个流程走通之后你会发现自己对 Unity 渲染管线的理解也上了一个台阶看美术提的需求时脑子里会直接浮现出对应的 Pass 该怎么写、参数放哪里调。最后再分享一个小技巧每次调完一套满意的卡通材质参数记得把材质和贴图存成一个完整的 prefab顺便写一行注释记下灯光角度和环境光强度。这样可以避免你隔几天再打开项目时面对着一堆调好的参数却忘了当时是在什么光照条件下调的。这个习惯能帮你省掉很多反复试错的返工时间。