卡通渲染(TOON)核心原理与角色制作实战全解析
发布时间:2026/10/20 14:20:36 锦皓数字建站
核心原理与角色制作实战全解析`)
前几天有位做独立游戏的朋友发来一张截图问我为什么画面看起来这么“动画”明明模型和贴图都是3D的。我扫了一眼就告诉他这是Toon Shading也就是卡通渲染大家在二次元风格项目里一般直接叫它TOON。现在不管是热门手游、独立动画还是三维短片TOON几乎是绕不开的词但真正能解释清楚它背后原理的人并不多。我会把做这类渲染的完整经验摊开讲TOON到底指什么核心原理在哪生产环节会踩哪些坑美术和程序之间怎么配合。1. TOON圈内到底在说什么它不是“加个描边”这么简单1.1 行业里TOON的三层意思很多人第一次接触TOON是从“描边”或者“赛璐璐上色”这种美术效果开始的但真正从业内角度去理解TOON其实是一个覆盖三层内容的系统。第一层是Shading也就是光照模型本身。写实渲染会把光线、粗糙度、菲涅尔全部揉进一个连续的物理模型输出接近真实相机的亮度TOON则完全不一样它故意把光照结果离散化不让亮度平滑变化而是让画面出现清晰的明暗分界。这个分界不是偷懒是一种审美选择模仿的是二维手绘动画里赛璐璐片上色的方式——只有几层有限的颜色表达光影。第二层是Linework通常就是我们说的描边。手绘动画里人物有黑色的轮廓线3D模型本身没有线条所以要用专门的Pass或后处理把轮廓找出来。描边的粗细、颜色、在转角处的变化都直接影响TOON的气质这一层在写实渲染里基本不需要考虑。第三层是后处理与合成。二维动画的画面有色差、景深、叠色这些手工痕迹做TOON时经常需要把边缘检测、色板化、颗粒叠加等后处理手段带上才能让3D画面真正“像手绘”而不只是像一个上了色的塑料模型。我之所以要掰开这三层是因为很多刚入门的朋友把TOON简单理解为“卡通贴图加描边”结果做出来的东西永远差一口气原因就是Shading层没做对后续叠再多的后期也救不回来。1.2 为什么写实容易“藏拙”TOON却很难这里举个实际体会的例子。写实渲染里你给材质设错了粗糙度通常只会让高光偏软或偏硬一点普通人肉眼不一定能第一时间看出毛病因为人脑对真实世界的材质判断本身就有宽容度。但TOON没有这种宽容度。TOON里的明暗边界是锐利的颜色是成块状的任何一个阈值选得不对、颜色配得不舒服画面里就会立刻出现一条“脏兮兮”的灰带或者一块突兀的阴影。看的人不一定能说出来哪里不对劲但一定会觉得“这画面好廉价”。我在几个项目里反复体会到TOON对审美和参数调校的要求远高于写实。写实有物理定律兜底TOON完全靠艺术判断来定规则。所以如果你想做好TOON第一个要接受的事实是——它不是一条偷懒捷径而是一条更需要抠细节的路。2. 核心原理拆解把平滑光线切成几个“挡位”2.1 Lambert的平滑是怎么被“砍”出风格的先说光照。传统漫反射最常用的模型是Lambert取法线方向N和光方向L的点积得到0到1之间连续变化的亮度值。物理上这个结果是平滑的所以物体受光面到背光面会有细腻的渐变。TOON的第一步就是把这个渐变“切”成几个台阶。你可以理解成普通旋钮灯光能连续调节亮度而卡通渲染的灯只有“开”“关”或者“低亮”“中亮”“高亮”几个挡位。人眼看到这种不连续的色阶就会产生“手绘感”因为二维上色本来就不追求连续物理光影。标准的做法是使用step函数或者smoothstep我写一段最简化的核心逻辑// 传统漫反射 float ndotl max(0, dot(normalWS, lightDirWS)); // 最简单的卡通化一刀切成两档 float toonBand step(0.3, ndotl); // 带过渡带的版本两档之间留一点点渐变 float softBand smoothstep(0.28, 0.32, ndotl);直接使用step的问题是边界会产生非常锐利的锯齿一帧帧播放时还能看到边缘跳变。生产环境中我更推荐smoothstep过渡带宽通常控制在0.02到0.05之间既能保留清晰的卡通边界又能避免过于生硬的像素级切换。2.2 Ramp贴图把明暗装进一张纹理step函数只能切出两三个挡位一旦美术想要更多层次比如四层阴影、五层高光代码里写一大堆if和step又难维护又难调。行业里更通用的做法是使用Ramp贴图。Ramp贴图本质上是一张一维渐变纹理横轴是ndotl从0到1纵轴可以放更多维度比如粗糙度或者视角夹角。Shading时直接采样这张图float3 toonColor tex2D(_RampTex, float2(ndotl, 0.5)).rgb;美术在Photoshop里把明暗色阶画出来程序在线性空间里采样。每个色条之间的边界就是卡通明暗线边界附近留几个像素的过渡羽化用来消除硬边锯齿。二维Ramp还可以根据材质类型分别定义不同的明暗配色。比如皮肤用低反差、带一点透红的渐变布料用高反差、干净利落的色块金属用带高光压缩的曲线。这样做的好处是材质差异从贴图层就分开了不需要在Shader里写大量分支。2.3 一个能跑通的核心Shader框架这里我给出一个去掉平台专属代码的HLSL风格框架纯讲结构你在不同引擎里稍微改造就能用half ndotl saturate(dot(N, L)); half3 rampColor tex2D(_RampTex, half2(ndotl, 0.5)).rgb; half ndoth saturate(dot(N, H)); half spec pow(ndoth, _Smoothness); half specBand smoothstep(_SpecThreshold, _SpecThreshold _SpecSoft, spec); half rim 1.0 - saturate(dot(N, V)); half rimBand smoothstep(_RimStart, _RimEnd, rim); half3 color _BaseColor.rgb * rampColor * mainLightColor; color _SpecColor.rgb * specBand; color _RimColor.rgb * rimBand;这只是用于演示原理的极简版本真正的角色Shader还会叠加阴影贴图、间接光、球谐环境光、深度雾等。但核心骨架就是这几行漫反射走Ramp高光走阈值边缘光走菲涅尔。3. 从“能跑”到“好看”角色TOON调参实战笔记3.1 高光从一条白线到一层色块很多人在漫反射处理完之后直接用Blinn-Phong的pow值来推高光。结果做出来的高光要么是一条硬邦邦的白线要么是很大一块过曝。之所以翻车是因为卡通高光本质上应该是一块有边界的色块而不是物理高光。我的做法是先算Blinn-Phong的spec值然后用阈值把它量化成一个开关量再乘上一个高光色half3 specColor smoothstep(_SpecThreshold, _SpecThreshold _SpecSoft, spec) * _SpecColor.rgb;阈值的控制逻辑很微妙。阈值太低高光面积过大像满屏油光阈值太高高光只剩一两个像素像干裂的墙皮。一般角色脸上我会把阈值控制在0.5左右soft控制在0.1以内同时用专门的高光贴图来控制哪些区域允许出现高光。头发的高光处理也有讲究。真实头发的各向异性高光在卡通渲染里可以简化成一层或两层条状色块。我会先采样一张高光偏移贴图把高光沿发丝方向拉长再叠加一层弱高光在发梢模拟手绘的笔触感。这一条建议值得记下来很多新手在头发上直接套用皮肤的高光参数效果总是不对。3.2 描边法线外扩为何在头发上翻车描边是TOON最有辨识度的特征但也最能暴露问题。基础做法是背面外扩渲染模型正面之后再渲染一次背面把背面顶点沿法线方向稍微推开一点并输出黑色。代码大致是v2f vert(appdata v) { v2f o; float3 pos v.vertex.xyz v.normal * _OutlineWidth; o.pos TransformObjectToHClip(float4(pos, 1)); return o; }硬表面物体用这个方法效果很好但一旦碰到头发、裙子、手指这类薄片结构就很容易穿模。因为头发是大量片状面片拼起来的法线方向非常混乱外扩时每个面片都往各自方向膨胀正面看不到问题背面却会露出各种三角面破绽。通用的解决办法有两个方向。第一个是顶点色控制美术在建模时把描边宽度需求刷进顶点色程序用顶点色的红色通道乘以基础宽度达到头发根部细、发梢更细、脸边保持正常的效果。第二个是曲率检测利用法线和视线夹角计算边缘强度在描边Pass里动态压低宽度这个方案对程序更友好但美术需要额外理解算法逻辑。3.3 阴影、暗部与色带治理TOON的阴影边界通常比写实更锐利。阴影贴图带来的自阴影、接触阴影、平行光级联阴影会让暗部出现很多杂乱边界和Ramp里手工画的干净色阶冲突。我的习惯是给TOON材质单独一个ShadowMap采样版本把PCF软阴影的范围调小或者直接用自定义的阴影衰减去贴合Ramp的阈值。暗部色带问题也很值得单独说。当Ramp阶梯跨度特别大时屏幕上会出现明显的色阶断层尤其在暗部8bit显示下更容易看到一条一条的“等高线”。这是一个典型的量化误差问题。我项目里常用的解决方式是给ndotl加微量的抖动float noise (Random(uv) - 0.5) * 0.01; ndotl saturate(ndotl noise);这个抖动量必须控制得非常小。太小没作用太大会让明暗边界出现噪点颗粒。开了TAA的项目里可以配合时域抖动把色粒度分散到多帧视觉上会更干净。还有一点是暗部不能直接压成死黑。手绘动画的阴影色其实是偏冷或者偏环境色的颜色而不是纯黑。我一般会在Ramp最暗色阶上叠一层环境球谐光或菲涅尔反射让暗部保留一点体积感画面整体会耐看很多。4. 美术与程序的生产协作Ramp贴图和参数约定4.1 Ramp贴图到底怎么画Ramp贴图不是随手拉一条渐变就行。美术在绘制时要注意几个生产规范否则到程序那边会有一堆沟通成本。首先贴图分辨率不需要很大256x16或者512x32完全足够重点是横轴要代表光照方向左暗右亮色阶边界的位置必须和程序里约定好的ndotl阈值对应。想让暗部宽一些就把左边色阶拉长想让高光范围尖锐就把右边边界压缩。其次色阶之间的过渡羽化要克制。我见过很多美术喜欢把边界羽化得很柔和觉得这样过渡很舒服但结果就是一个跑不出来的伪TOON画面灰扑扑的。羽化宽度建议控制在贴图宽度的2%到4%也就是256宽度的图上大概5到10个像素。这个宽度可以在渲染时配合smoothstep消除锯齿又不会把锐利边界弄丢。第三Ramp要在线性空间里处理。如果美术给的图是在sRGB空间里画的程序采样时需要转成线性否则明暗深浅会和预期不一致。这个坑在跨平台项目里坑过我好几次后来约定做成规范所有Ramp贴图必须勾选线性采样或者在有需要时由程序统一转换。4.2 材质参数怎么暴露给美术程序写Shader时最容易犯的毛病是把所有细节都钉死成常量。这样做调起来非常不方便。我会把以下参数固定在材质面板参数名作用典型范围_BaseColor基础色贴图或颜色拾色器_ShadowColor阴影色拾色器避免纯黑_RampTex明暗渐变默认给一张标准三阶Ramp_SpecThreshold高光阈值0.4-0.6_SpecSoft高光过渡软硬0.02-0.1_OutlineWidth描边基础宽度0.01-0.05模型单位相关_RimStart / _RimEnd边缘光起止0.0-1.0这些参数不仅是为了给美术调节更是为了让同一个Shader能适配不同角色。比如主角的脸部高光阈值和配角的可以完全不同但材质球里统一调节可比每个模型单独维护一套Shader省事多了。如果团队用可视化Shader工具我建议把上面这套整理成自定义节点端口画清楚再给美术写一份简短的使用文档。美术最怕的不是参数多而是不知道每个参数控制什么。4.3 和PBR资产共存的过渡方案很多二次元项目的美术资产实际上是以PBR规范制作的Albedo、Normal、Metallic、Roughness、AO都有。直接放弃这些资产重做TOON专用贴图成本非常高。更务实的做法是让TOON Shader保留PBR贴图通道用Roughness和Metallic来决定Ramp的采样维度和高光参数。比如Roughness高的布料Ramp采样靠近暗色色阶Roughness低的金属采样靠近高光压缩段。这样贴图资产不用重新绘制切换写实和TOON只换Shader材质球里的参数照用。Normal贴图在TOON里同样有效它能让明暗边界沿着法线变化增加细节。AO则可以直接混合到阴影色里让缝隙处更暗。这套过渡方案能显著降低制作成本是我个人最推荐的生产路线。5. 真实项目踩坑实录色带、描边与平台差异5.1 暗部色带的完整排查链路有次在项目里角色裙摆暗部出现非常明显的色带一张一合的动态下尤其让人难受。一开始以为是Ramp没画好但换了一张全新建模的Ramp后问题依旧。后来我做了几个对照实验静态单帧没色带动态播放有色带开了TAA有色带不开TAA反而轻压缩贴图格式后色带变重。最后定位到是明暗色阶量化加上光照变化时暗部产生了步进式跳变。解决方式分三步第一步把Ramp暗部色阶的过渡羽化加宽一点第二步给ndotl加上基于屏幕坐标的微抖动态第三步也是最重要的把暗部色阶的数量从四档减少到三档同时让每档之间的宽度接近人眼感知的均匀程度。最终色带肉眼几乎消失。我想说的是排查这种问题不要一上来就怀疑工具先分清是不是量化精度问题、是不是贴图格式问题、是不是显示位深问题做一个变量控制实验很快就能定位。5.2 描边粗细不均的排查链路另一个典型问题是同一条裙子正面描边清晰侧面描边特别粗背面直接糊成一团。一开始认为是模型法线有问题重新平滑法线后改善有限。后来才想明白描边是沿法线外扩的而裙摆模型有很多褶皱不同位置法线方向差异大外扩时每个顶点的偏移方向不一致视觉上就会粗细不均。解决方法是引入曲率因子来抑制描边宽度。我用顶点法线与视线方向夹角做判断夹角接近90度的边缘处把描边宽度乘以一个小于1的系数同时把顶点色里预刷的权重与这个系数相乘。这样正面、侧面、背面的描边宽度就基本一致了。核心经验是描边宽度的控制要放在顶点层做而不是在片段层做片段里调整只会越描越脏。5.3 雾效、多光源与移动端精度TOON对雾效极其敏感。场景里一旦有距离雾明暗边界就会被雾的插值蒙上一层灰色整个画面立刻失去干净感。我在项目里的做法是关闭TOON材质默认的雾效接收改成手动的距离混合让雾混合时也按TOON的明暗边界去分档。多光源也是常见问题。主光之外再射来一盏补光时每一盏光都会跑一遍Ramp结果模型身上出现好几层互不搭调的明暗带观感非常碎。我的处理方式是只让主光源跑完整TOON光照其他光源只做少量颜色增益不做新的明暗分界。移动端上还有半精度浮点的坑。低端GPU的fp16精度不够Ramp贴图采样后会出现更明显的色阶条纹。我会在移动端版本里把Ramp采样结果整体提高精度或者对Ramp贴图做一次预过滤把邻域色阶平滑掉。这点在Shader代码里加上平台宏判断即可成本不高但效果明显。6. 做完几年TOON项目后的几句心里话最后分享一点个人体会。做了几年TOON相关项目之后我最大的感受是它并没有比写实渲染简单反而在审美层面更苛刻。写实渲染出问题可以通过物理参数一点点调回来TOON一旦色阶、阈值或描边不对整个画面都会显得假和廉价。如果给新人一个学习路径我会建议先把Lambert、Smoothstep、Ramp这三个概念彻底揉进自己的直觉里再去找一个人体或头部模型亲手调一版包含漫反射分阶、高光色块、描边外扩、边缘光的最简Shader。这一套做完你对TOON的理解会比看十篇教程都快。至于工具选型Unity、虚幻引擎、Blender甚至WebGL都能做关键是先把“量化明暗”这套思维建立起来。跨过这道门槛你会发现TOON真正的乐趣在于用极少的光照信息去表达极强的画面气质。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。