拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

风格化渲染系统设计与实践:从三渲二到NPR卡通着色

风格化渲染系统设计与实践:从三渲二到NPR卡通着色

风格化渲染系统写了大半年,从最早只想做个“三渲二”的Demo,到后来演变成一个独立的渲染模块,我踩了不少坑,也整理出一套能实际落地的方案。这篇文章不聊虚幻和Unity编辑器怎么点按钮,就讲这个渲染系统本身的设计思路、核心Shader的实现细节、资产规范、以及我在实际项目中遇到的典型问题和排查过程。如果你也想把场景做成卡通、手绘、或者带着明显“风格化”味道的画面,这篇文章应该能帮你避开我走过的弯路。

1. 风格化渲染系统的整体设计思路

1.1 为什么要做风格化,而不是写实

先说结论:风格化渲染系统的核心不是“渲染”,而是“风格化”。PBR、光线追踪、全局光照这些写实管线追求的是物理正确,而风格化渲染追求的是信息筛选和视觉表达。同一个场景,写实渲染要把木头的纹理、反射、次表面散射全算出来,风格化渲染却只需要保留“它是木头”的认知,然后把画面压缩到几层色阶、几条轮廓线、几个高光团块里。

我做这个系统的初衷其实很简单,项目里的美术资源风格偏手绘,如果直接用标准PBR渲染,贴图里的手绘笔触和颜色倾向会丢掉大半,模型看起来又塑料又平。那时我意识到,渲染管线必须反过来适配美术资源,而不是让美术资源去迁就一套写实管线。所以这个系统的目标被定成三条:

  • 保留贴图的笔触、纹理和手绘感,不让PBR的物理参数磨平它们。
  • 用简化的光照模型还原“卡通着色”的视觉逻辑,也就是色阶、描边、高光团块。
  • 支持程序化微调,美术不用改贴图也能调风格。

整个系统围绕这三个目标展开,所有技术选型都是这三条的衍生物。

1.2 从写实管线改到风格化管线,关键取舍在哪

写实管线里的很多步骤在风格化渲染里反而是噪音。最典型的就是反射探针、SSAO、体积雾,这些在写实渲染里提升真实感的东西,一旦套到风格化画面里,轻则让画面发灰,重则直接破坏手绘的平面感。我一开始也犯过这个错,以为保留全局光照能增加质感,结果画面变成“带GI的模型”,既不写实也不卡通,非常尴尬。

所以我在设计管线时做了几个取舍:

  • 砍掉物理光照模型,改成自定义的NPR光照函数。
  • 保留后处理,但只保留对风格化有用的部分(描边增强、色差、内部分色)。
  • 大量使用顶点色和贴图通道来驱动风格变化,而不是依赖参数堆砌。
  • 渲染顺序和对象分类直接跟风格绑定,比如哪些物体要描边、哪些不描边、哪些用全色阶、哪些用四色阶。

这里有个值得细说的思路:我最终没有做成一个全屏后处理的风格化滤镜,而是基于Shader变体做逐物体风格控制。原因是全屏滤镜只能做色度调节和边缘检测,没法做真正的卡通光照和描边膨胀,而且对物体边界的判断容易出锯齿。逐物体的方案虽然要管理Shader变体和材质参数,但对风格的控制精度高得多。一个角色和一个石块在同一个画面里完全可以走不同的色阶数和轮廓强度,这是滤镜方案做不到的。

2. 核心风格化特征的实现细节

2.1 轮廓描边:最出效果也最翻车的环节

轮廓描边是风格化渲染的标志性特征,但实现方式不同,效果天差地远。我最终采用的是“反向面膨胀描边+边缘光补充”的组合方案,而不是全屏边缘检测。

反向面膨胀的原理不复杂:渲染物体时,先把正面按正常方式输出一遍,再多渲染一次“背面”,把背面顶点沿着法线方向向外膨胀,然后输出纯色。因为背面在几何上比原模型大一圈,从摄像机角度看,它就像给正面物体镶了一圈轮廓。

做得漂不漂亮全在细节。第一个坑是法线精度,模型如果经过顶点合并或顶点色混合,法线方向可能不平滑,膨胀之后轮廓会断成一段段的。我的解决方案是把法线先压到切线空间的贴图里,在顶点Shader里用压缩法线替代模型原生法线。这个操作虽然会丢失一些细节,但换来的是稳定连续的轮廓线,划算。

第二个坑是深度冲突。膨胀后的背面会和地面、墙壁这些平面穿插,产生闪烁。我的处理方式是在膨胀Pass里偏移深度,把背面的深度往摄像机方向拉,大约偏移0.0001到0.001的归一化深度值。这个数值不能太大,否则轮廓会穿透模型自己,在闭合区域形成“镂空”效果。

描边最终的颜色和厚度也不是固定值,我是用模型的漫反射贴图作为基础色,乘一个描边强度系数,再叠一个基于摄像机距离的衰减。近处描边是深色细线,远处描边随距离变浅变粗,模拟手绘中远景线条被空气透视稀释的感觉。

2.2 色阶光照的整体思路与参数

风格化着色最大的特征是把连续的光照变化压缩成多个色阶。我之前也纠结过是直接用软件动态色调映射还是预烘焙一张色阶渐变纹理,最终选了后者,因为可控性更强,而且美术调起风格来直观。

核心逻辑是这样的:

  • 先把法线方向和主光源方向的点积算出来,得到的值从0到1连续变化。
  • 然后拿这个值去采样一张1D色阶纹理,纹理由几段纯色组成,段数就是色阶数。
  • 采样结果就是最终的光照颜色。本质上,色阶纹理就是一个查找表。

色阶纹理的段数设置我建议从二段开始试。二段效果就是经典的日系二值卡渲,亮部一个颜色,暗部一个颜色,中间有个硬切口。三段会更柔和,保留中间过渡的同时仍能看到明确的颜色分层。到四段以上,画面就开始接近写实了,卡通感会明显下降。

另一个关键参数是“色阶切割阈值”。点积值不是均匀切割的,而是要偏暗部切割更密,亮部更松。因为暗部的颜色过渡是画面情绪的关键,如果暗部切割太粗,物体看起来像被刀切过,太细又会失去卡通感。我常用的阈值区间是0.15、0.35、0.65,但这个数值不固定,要根据主光源和物体形状微调。

我还额外加了方向光调制的支持。美术可以给模型设置一个自定义光源方向,用来控制暗部的形状。这个看起来是小事,但实际作用很大:当主光源从侧面照过来时,角色的脸会因为色阶切割产生难看的阴影,把光源方向稍微拉高或者旋转一点,就可以让脸部光照更干净,又不影响整体场景的光照逻辑。

2.3 贴图风格化与边缘光、高光的配合

风格化渲染不只是光照模型的问题,贴图本身也要做处理。我的方案是给漫反射贴图额外加一条“内部阴影”通道,这个通道的用途和常规AO不同。常规AO是把遮蔽信息烘焙到模型表面,风格化阴影通道却是美术手动绘制的一块颜色,专门用于暗部的倾向性。比如一个角色的头发,暗部不是简单变黑,而是偏蓝或者偏棕的颜色倾向,这个信息就存在阴影通道里,色阶光照采样之后再把阴影通道颜色叠上去。

这样做的视觉效果非常明显,物体暗部不再是单纯的亮度下降,而是冷色倾向的暗面,非常像传统手绘中对阴影的处理方式。代价也很直接,美术得花时间整理贴图通道,不能只丢一张漫反射贴图就完事。我在项目里把阴影通道统一放在Alpha通道里,这样一张贴图同时携带明度和色调信息。

高光和边缘光也有类似的做法。高光我不再采用Blinn-Phong的圆形高光,而是用一个近似形状的贴图采样来模拟手绘高光团块。美术可以画一条高光带、一块高光点,通过参数控制贴图采样的缩放和平滑度。边缘光则用菲涅尔近似实现,但要注意别把边缘光做成“发光描边”,否则会和轮廓线混在一起。我的习惯是边缘光的强度控制在0.2以下,颜色偏粉或偏暖,只负责把模型的体积边缘从背景里轻轻托出来。

3. 工具链、参数预设与工作流设计

3.1 材质模板与参数预设

做风格化渲染,纯靠Shader是不够的,必须有完整的材质模板和参数预设体系。如果每个模型都要从头调一遍光照参数,项目会直接失控。

我把材质模板分成四类:

  • 标准风格化材质:适用于角色、道具、植被,有完整的色阶、描边、高光和阴影通道支持。
  • 轻量化材质:适用于地形、建筑这类需要控制DrawCall的对象,砍掉高光和描边,只保留色阶和阴影通道。
  • 特效专属材质:适用于粒子、光效,走全透明渲染,色阶调整为随特效颜色变化。
  • 无光照材质:适用于背景板、远景,直接用贴图色,不参与光照计算。

每个模板里的核心参数我都做成了一组“预设档位”,比如“硬朗二值”、“柔和三色”、“手绘混合”三档。美术先用预设档位找到整体状态,再微调单个参数。这样既保证了全场景风格的统一,又给了局部调优的空间。

这里有个容易被忽略的点:参数预设不只是调数字,还要绑定到物体的Shader变体上。同一个物体在不同状态(比如被火光照亮、被技能特效覆盖)需要自动切换色阶逻辑和光照函数,不能一个变体走到黑。我最后给渲染系统加了一层“风格态”管理,美术或逻辑层按需切换变体组合,这也是系统能在大场景里稳定工作的关键。

3.2 资产规范与DCC工具配合

有了渲染系统之后,模型的制作规范也得跟着调整。传统建模强调高模细节、法线贴图的丰富度,风格化模型却要把“结构感”做在模型本身而不是贴图细节里。因为色阶光照会把表面细小的法线变化压缩掉,法线贴图在风格化渲染里的作用会大打折扣。

基于这个原因,我对入模规范做了几条硬性要求:

  • 硬表面模型优先用光滑组切出结构边,而不是靠法线贴图模拟转折。
  • 模型顶部或侧面的顶点尽量保持平整,方便描边法线向外膨胀时保持均匀。
  • 阴影通道和漫反射贴图必须在引擎内可见,不可见就无法调风格。
  • 模型不能有过长的三角形面片,否则描边膨胀后轮廓会出现直线化,看起来像折纸。

DCC工具侧我用的是Blender,配合自定义导出脚本,把阴影通道、色阶标记、描边系数这些信息统一打包成一个自定义Shader属性集合。导出后直接对应到引擎材质参数的命名空间,省去大量手动赋值。主流的建模工具都有类似方案,核心逻辑一样:要么定义一套资产的元数据规范,要么在导出环节把数据烘焙成引擎识别的自定义通道,不要指望纯靠人工维护。

3.3 性能预算与硬件适配

风格化渲染听起来比写实渲染轻量,实际跑起来未必。反向面膨胀描边等于每个需要描边的物体额外增加一次顶点Shader开销和一个背面渲染Pass,部分低端设备上这部分开销占到整体渲染时间的15%到25%。我在项目里做了性能预算控制:

  • 描边物体的数量不超过屏幕内总物体数量的60%。
  • 远景物体自动切换为无描边变体,描边距离阈值默认是20米,可按场景规模调整。
  • 色阶纹理采样限制为每像素一次,不做额外模糊。
  • 半透明物体不参与描边,描边只对不透明集合生效。

硬件适配方面,我的Shader最低目标是OpenGL ES 3.0等级的GPU能流畅跑。因此我没有依赖硬件无关的着色率优化,也没用动态分支去处理光照,所有风格化逻辑全部用纹理采样和线性运算完成。实测在中端手机上能达到稳定的60帧,在低端设备上降到30帧也可以接受。这个取舍很明确:风格化画面不能靠动态性能保帧率,要在Shader复杂度层面就把预算卡死。

4. 常见问题排查与调优实录

4.1 描边问题的排查表

描边是最容易出问题的地方,我这里直接整理一张排查表,都是我实际遇过的问题和对应解法。

问题现象潜在原因处理方式
描边出现断点,模型转折处中空顶点法线不一致,膨胀方向不连续使用切线空间法线贴图替代顶点法线
描边和地面穿插闪烁背面深度未被正确偏移在膨胀Pass中增加深度偏移,偏移量约0.0001至0.001
轮廓粗度和场景比例不匹配相机距离衰减系数没接上轮廓颜色强度加距离衰减,阈值范围0.5到1.0
描边在模型自身上产生漏光背面的膨胀幅度超过了造型自身的凸起降低膨胀系数,按模型Attrs分档控制
半透明角色描边异常半透明物体的排序导致描边被前层覆盖半透明物体关闭描边,或改为全屏边缘检测补充

描边这个环节的调试思路通常是:先看纯背面输出,确认膨胀方向和幅度是否正确,再看正面物体的遮挡,确认深度偏移是否生效,最后才看颜色和距离衰减。从Shader层面逐步排除,比在场景里凭感觉调参有效得多。

4.2 光照与半透明对象乱序

光照乱序是风格化渲染里的一个隐藏坑。色阶光照会让阴影的边缘产生硬分割线,当多个物体重叠时,分割线没有物理依据,看起来就会“跳”和“抖”。

我遇到最典型的问题是人物站在树荫下,角色本身的色阶阴影和树叶投下的阴影会发生双重分层,角色脸上出现两个互相冲突的暗部边缘。后来我把阴影范围分为全局阴影和局部阴影两类,全局阴影来自场景光照,局部阴影来自物体自身采样。局部阴影的色阶数降低一段,这样至少能保证角色身上的阴影不会同时出现两个强度完全一样的硬边,观感上会舒服很多。

另一个问题是半透明对象和色阶光照叠加的乱序。半透明对象渲染顺序靠深度排序,但风格化渲染里透明物体往往要保留高光和轮廓,排序失败时会出现前景透明物体带着描边穿过身后不透明物体的现象。我的处理办法是把透明物体的描边全部移除,同时将高光改成透明度遮罩内的平面高光,不参与光照计算,只叠加颜色。这是一个妥协方案,但视觉上基本看不出差异,而且彻底解决了排序穿透问题。

4.3 制作流程复盘与避坑建议

整个系统做到后期,我最大的体会是:风格化渲染系统的技术难点其实不在Shader,而在于工作流。Shader写好只占整个工程量的三成,剩下的是材质管理、资产规范、参数联动和做项目时的团队协作。

建议后来者先做三个准备工作,再动手写Shader:

  • 先想清楚目标风格,是美宣级别的三渲二,还是游戏内的高性能卡渲,这两者技术路线完全不同。
  • 建立一套可验证的测试场景,场景里要有人物、道具、建筑、地面,光照条件固定,方便每次改动后直观对比。
  • 定好Shader变体矩阵,列出哪些物体可以共用变体、哪些要特权,避免后期为了特效不断加变体导致维护成本爆炸。

还有个小技巧:风格化渲染的调试最好直接用美术给的资产,而不是用引擎自带的原型模型。色阶光照会把模型结构上的细微信号放大,原型模型看不出问题,真实资产一上就会暴露各种法线问题和遮挡问题。我在前面不少脑内调校都是因为用了过高精度的测试模型,到了真模型上才发现方向偏了。

这个系统做出来之后,我仍在继续补两件事,一是把色阶阈值做成自动估算,让Shader根据场景光照自适应切割位置;二是把阴影通道的绘制流程进一步简化,让美术可以更快地给多个资产统一风格倾向。如果你也在做类似的东西,可以记住一条底线:风格化永远服务于表达,别让技术反过来绑架美术。

返回列表