ARTICLE DETAIL

资讯详情

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

风格化渲染系统实践:从光照、描边到后期调色与移动端优化

风格化渲染系统实践:从光照、描边到后期调色与移动端优化 风格化渲染系统这两年几乎成了游戏团队找TA的标配需求但大部分团队踩的坑也高度一致先丢过来一个画风参考让“做个卡通渲染”等做完了才发现角色像塑料、场景又闷又脏、特效和角色完全不是一个画风整体画面像几个不同游戏拼在一起。真正的问题不是某个shader写得不够好而是整个渲染链路缺一个“系统”来做统一约束。我在几个项目里反复折腾过这套东西从Unity URP到自研管线都铺过一遍这篇就把我觉得最值得沉淀的部分写出来包含架构分层、光照改造、描边工程化、后期调色以及移动端性能这五个核心环节适合正在搞风格化渲染、又不想把画面做成“一锅乱炖”的小伙伴做参考。1. 先想清楚系统要长什么样再动手写shader很多项目的风格化渲染不是从零开始的而是先有了一堆标准PBR效果再零零散散地加卡通组件。结果就是角色的皮肤用了三层高光叠加场景石头还是PBR粗糙度那一套视觉语言完全不在一个频道。我后来搭系统的第一件事不是写shader而是定架构。1.1 风格决策集中在光照与后期两个环节我习惯把整个渲染系统分成三层材质层、光照层、后期层。材质层只负责输出基础色、法线、遮罩这些“原料”光照层统一决定漫反射怎么过渡、高光怎么切边、边缘光怎么衰减后期层再对整体色彩做一次风格化收口。之所以这么分是因为风格化渲染的“风格”几乎都体现在光照过渡和色彩映射上。如果让每个材质都自己写一套光照逻辑那你的项目就有十几种“卡通”美术调完一个还得调另一个永远收不了口。把光照决策集中到一套共享代码里所有shader都调用同一个光照函数风格才可能全局统一。我遇到过一个反例项目早期角色shader里用了MatCap模拟光照场景shader用兰伯特加半色调贴图两者在相同光照方向下呈现的明暗关系完全不同玩家截图里角色像站在另一个世界。所以我把光照抽象成公共函数后第一件事就是让角色和场景的默认明暗渐变曲线完全一致后续所有风格微调都在这个光照函数里完成。1.2 光照输入必须做统一封装URP里默认的Lighting数据是多样化的主光源、附加光源、球谐、环境反射各有各的获取方式。风格化系统里我强烈建议不要到处去拿光源而是做一个自己的LightData结构体在渲染前统一填好让所有shader只认这一个结构体。LightData里至少包含这几个字段主光源方向世界空间主光源颜色和强度已经做过头尾处理环境光颜色通常烘焙成几级灰阶球谐光照是否参与混合部分场景需要用球谐补暗部这样做的另一个好处是调试特别方便。我可以在不修改任何shader的情况下把主光源方向在调试面板里强制改成某个固定值用来复现某个转角的画面问题。如果是每个shader各取各的光源数据这种统一调试根本没法做。1.3 材质参数归类能并的并能删的删风格化系统的shader参数特别容易爆炸。一个角色材质可能会出现十几张贴图加二十几个浮点参数最后美术根本不敢动全让TA一个人扛。我的原则是参数必须归成几类基础表面属性、光照响应属性、描边控制属性、特效开关属性。每一类参数都放进一个Header并且能合并到一张贴图里的就不要用三张。比如漫反射渐变宽度和边缘光强度我常常把它们合并到一张控制贴图的RG两个通道里美术只需要画一张图就能同时控制两个区域。参数少了出问题的概率自然就低这是我从多个项目里总结出的最实用经验。2. 卡通光照改造从“物理正确”里跳出来风格化光照的第一步是放弃PBR那套基于能量守恒的物理模型。不需要微表面GGX也不需要BRDF能量归一化你需要的是一个好控制的、便于美术脑补的明暗模型。但放弃物理模型不等于乱写得有章法。2.1 用渐变映射替代物理漫反射最基础也最核心的改动是把NdotL从连续值映射成离散的渐变带。做法是在漫反射计算里采样一张ramp贴图half ndl saturate(dot(N, L)); half wrap (ndl _WrapValue) / (1.0 _WrapValue); half shadow tex2D(_RampMap, float2(wrap, 0.5)).r;这里的_WrapValue是wrap light参数作用是压缩暗部范围让背光面不至于太黑。ramp贴图的横轴就是光照强度从暗到亮的分布美术画这张图的时候画出的其实是“这个材质的漫反射性格”。硬边卡通就是一条中间有陡峭台阶的曲线软边半厚涂风格就是一条平滑的缓坡曲线。我把这幅图理解成“光照等位线”它决定了整个项目的最基本视觉气质。ramp图在实际使用中有几个常见坑。一个是采样分辨率太低导致高光区域出现明显的色阶断层脸上一圈一圈的。解决方法是ramp图不要用纯硬边在台阶处留两三像素的过渡带或者用单通道贴图加lerp做平滑。另一个坑是不同贴图之间采样坐标不一致比如头发和皮肤用了不同方向的渐变映射导致同一盏灯下亮暗区域对不上。我统一规定所有材质都采同一个横轴坐标差异只体现在ramp图内容上不体现在采样方式上。2.2 MatCap的引入与法线空间陷阱MatCap材质捕获贴图在风格化渲染里用的极多尤其是硬表面和皮肤质感。本质是把一张预烘焙的球形光照贴图按法线方向采样让物体获得类似参考球的光影感好处是美术可以直接在Photoshop里画“光”所见即所得。但MatCap最大的坑是它根本不响应场景里的光源变化。角色在一个方向转了一圈MatCap光还在原来的地方看起来特别假。我的做法是保留主光源方向对MatCap采样坐标做旋转修正至少让光跟着主光源方向大致转动。修正很简单把法线从世界空间变换到光源空间再进行贴图采样half3 viewNormal mul((float3x3)_LightRotationMatrix, normalWS); half2 matcapUV viewNormal.xy * 0.5 0.5; half3 matcap tex2D(_MatCapTex, matcapUV).rgb;加了旋转修正后MatCap就不再是死光而是有方向响应的质感光。配合原本的ramp漫反射一起使用可以做出“底色卡通、材质有立体感”的效果。不过要注意MatCap的强度别盖过主漫反射否则角色会像抹了一层油。2.3 高光与边缘光的克制用法风格化高光最怕的就是“油腻”。PBR那种GGX高光高光点在风格化场景里几乎等于灾难因为它的能量分布和物理反射太“真实”和周围的扁平卡通放一起会显得跳。我在风格化系统里用的高光是Blinn-Phong的阈值截断版本核心是让高光硬切边缘half nh saturate(dot(N, halfDir)); half specStepped step(_SpecCutoff, nh);配合GGX的粗糙度感受在高光边缘稍微做一点渐变过渡就可以得到类似漫画高光的“一笔刷过”效果。高光强度、切边阈值、过渡宽度三个参数做好已经能覆盖大部分风格。边缘光也是风格化的重灾区。很多人习惯了Fresnel边缘光狠狠来一笔结果角色在夜景里直接变电灯泡。我的经验是边缘光必须受遮罩贴图控制并且强度不能超过漫反射亮部的一半。让边缘光受法线噪声扰动可以打破那道“水蜜桃轮廓线”的假感这个做法在角色头发上尤其管用。3. 描边不是加个“Outline”开关那么简单风格化渲染系统里描边往往是最先被注意到、也最容易做破坏的环节。我见过太多项目用Inverted Hull拉条线结果角色转个头描边的粗细忽大忽小关节处直接断线。描边的工程化处理比想象中复杂得多。3.1 不同描边方案的取舍风格化描边的主流方案主要有三种各有各的适用场景方案原理优势劣势Inverted Hull背面放大沿法线挤出实现简单移动端友好需要平滑法线展UV不规则的软体物品容易断裂深度法线描边像素级检测深度和法线不连续轮廓精确硬表面效果好需要额外depth/normal pass性能开销大后处理Sobel或Lerp方案屏幕空间边缘检测可统一处理全场景风格化可控性弱大色块场景容易误判我实际的组合方案是主角和关键NPC用Inverted Hull大场景建筑和道具用深度法线描边云和粒子这类特效干脆不描边。一个项目里可以同时存在两套描边技术但它们的颜色和宽度基准必须统一保证视觉上是一套系统。3.2 Inverted Hull的工程细节平滑法线与遮罩Inverted Hull的原理是分两个pass渲染同一个模型正面正常绘制背面用Cull Front剔除正面、顶点沿法线向外挤出再把模型填充成描边色。听起来简单但顶点法线是决定描边质量的关键。如果一个模型的法线没有重新平滑过它的顶点法线会因平滑组而分开挤出后各个面往各自方向顶就会出现描边断线。所以模型导入后必须让美术或工具把法线按面法线重新平滑一遍这个环节我习惯用脚本在AssetPostprocessor阶段自动处理不需要美术手动操作。另一个问题是描边宽度需要配合视距衰减。摄像机拉远后屏幕空间里固定像素宽度的描边在世界空间里会变得非常厚看起来像剃了头还戴了一圈围巾。我用的做法是让挤出量在远距离时持续衰减保证描边在屏幕上的宽度尽量稳定float outlineWidth _OutlineWidth * (1.0 - saturate(distance / _OutlineFadeDist));最后是遮罩。头发边缘、裙边这些地方描边会明显破坏轮廓需要提供一张描边遮罩贴图或者直接在顶点色里让美术标记哪些区域不描边。我自己在系统里规定顶点色R通道控制描边宽度G通道控制描边禁用区域一次导入全搞定。3.3 描边颜色不能是纯黑这是风格化系统里最反直觉的一条经验。大部分人觉得描边不就是黑线吗但纯黑的描边在打光场景下会让整个画面显得特别脏因为黑色不吸收任何光和周围亮部形成生硬反差。我的做法是让描边颜色响应用两个维度物体暗部的颜色以及环境光的冷暖倾向。简单办法是描边色从物体的基础色里做暗部推导再加一个饱和度偏移。也就是说一个红色物体的描边是暗红色而不是黑色冷色场景里的描边会微微带蓝暖色场景里的描边微微带棕。这样描边融入了画面而不是钉在画面上。当然后处理阶段也可以统一调描边色的明度但我更偏爱在材质阶段就把描边色计算好因为后处理阶段已经分不清哪个像素是描边了灰度调整会连带影响整个画面。4. 后期与色彩真正决定画面质感的最后一步很多团队做到shader层面就觉得风格化完成了剩下的交给后处理。但实际上一张画面到底是“高级”还是“廉价”后期调色占了一半以上的权重。风格化渲染系统如果不管色彩映射等于只做了一半。4.1 色调映射是从“物理正确”走向“画面气质”的关键Unity URP默认的色调映射是ACES它的作用是让HDR图像的明暗过渡更符合人眼感知能压低高光、提升暗部。但问题是ACES这套曲线太“自然”会把风格化的硬边光照弄得发灰、发软。我在风格化系统里推荐用自定义色调映射或者至少在ACES和自定义之间做一次混合。有一个很经典的思路是Karis风格的“近似拟合”它比ACES更留得住暗部对比但又比完全无色调映射更少噪点half3 CustomTonemap(half3 color) { color max(0, color - 0.004); return (color * (6.2 * color 0.5)) / (color * (6.2 * color 1.7) 0.06); }这个曲线看起来平平无奇但对画面气质的改变非常明显。加上之后亮部保留住了卡通的“纸面感”暗部也不会堆成死黑。后期调色时我会把曝光基准设到一个让中间灰反射率为18的灰球保持正常亮度的位置上再通过自定义曲线做风格偏移。4.2 色彩分级是风格化的秘密武器真正拉开画风差距的不是贴图是色彩分级。我在系统里放了一个Color Grading模块里面核心就三个参数对比度、饱和度、色温。再配合一个LUT贴图做风格化映射画面气质立刻就不一样了。对比度对风格化的影响最明显。偏高对比度会让画面去掉中间灰过渡显得更“漫画”偏低对比度会让画面暗部泛白、高光压平产生一种类似水彩纸的柔和感。饱和度同理高饱和偏卡通和儿童读物低饱和偏厚涂插画和文艺滤镜。色温则决定了画面是偏“午后温暖”还是“月色清冷”。我习惯把后期参数也做成一个ScriptableObject叫StyleProfile美术在不同的StyleProfile之间切换就能对比不同画风。这个工作流比改一个参数截图一次要高效太多也让“画风”变成了可版本管理的资产。4.3 Bloom和噪点风格化里的两匹野马Bloom泛光在PBR管线里是模拟眼睛对强烈亮光的散射但在风格化里它经常会破坏画面。卡通的轮廓线边上一圈光晕画面立刻变“油腻”。我的用法是把Bloom的阈值拉到比较高只让真正的HDR高光区域出光同时把Bloom应用到半分辨率渲染目标强度不超过0.25——它的作用是给高光区域加一点体积感和柔和度而不是让整张图发光。噪点和纸纹反而是我强烈推荐的风格化后期。尤其是手绘感的角色加一层轻微粒度和方向连续的Paper Grain能极大削弱数字感。我用的做法是把躁点贴图动态偏移加在屏幕空间让颗粒有轻微流动感看起来像真实扫描进去的纸纹理。强度控制在0.05以下达不到肉眼明显看见噪点的程度但整体画面的“作品感”会提高很多。5. 移动端性能预算和参数化工作流风格化渲染系统做出来不是摆着好看的最终要跑在目标平台上限频达标才算完成。性能优化在移动端尤其吃紧而风格化系统因为常常叠加多pass、多贴图采样性能控制不好就会爆炸。5.1 移动端的开销大头overdraw、半精度和pass数量风格化系统在移动端最常见的就是过度绘制。描边pass、阴影pass、高光pass叠加加上半透明特效中低端手机GPU的fillrate很快就满了。我给出的预算表通常是这样的针对骁龙8系和天玑9000级别屏幕分辨率1280x720左右overdraw控制在1.5倍以内全屏后处理节点不超过6个其中Bloom和Color Grading是最大头描边优先用Inverted Hull不放到深度法线检测里去角色shader变体控制在8个以内避免UE一样变体爆炸这里特别提一下半精度浮点。移动端GPU对half2/half3的运算速度远快于float但需要确保shader里没有隐式的类型转换。很多风格化shader里采样ramp图时用了float2坐标G-Buffer输出用了float4结果性能白白损失了20%。我写风格化shader的时候所有漫反射计算、采样坐标统一用half只有世界空间坐标和摄像机朝向这类需要大范围操作的才用float。5.2 用StyleProfile把控全项目风格参数前面提过把后期参数做成StyleProfile实际上光照、描边、漫反射也可以全部归入同一个资产文件里。这样一个好处的直观场景是下午美术提了需求说“画面再暖一点描边再细一点阴影再硬一点”我只需要调StyleProfile的几个字段重新进游戏看效果不用在几十个材质球里一个个翻参数。StyleProfile的字段设计也遵循第一部分的归类原则。光照部分放漫反射wrap值、漫反射曲线贴图引用、高光阈值、边缘光强度描边部分放描边颜色基调、宽度系数、视距衰减、遮罩开关后期部分放对比度、饱和度、色温、LUT引用、Bloom阈值和强度。5.3 调试和预览环境不可省最后聊一个很多人忽略的事风格化渲染系统的调试环境。我经历过最痛苦的调试场景是美术改了一个ramp图说暗部变奇怪了但我不知道是哪个材质的哪个ramp图引用了它也不知道灯光方向对它影响多大。所以我在系统里加了一套Debug View功能按下快捷键就能把当前画面切换成几个可视化通道漫反射渐变值、描边宽度、基础色、光照方向示意。排查问题的时候先看这些通道比盲调参数快十倍。同时在项目里放一个专用的“光照参考球”这个球上刻了由深到浅的灰阶渐变条任何光照调整都以这个球上的表现为基准美术调光调色时先看球再看角色心里有谱得多。预览环境还包括把材质放到一个固定的场景里切换不同时间的光照角度。很多团队角色好看、场景一黑就翻车就是因为预览时没考虑多种光照角度的表现。我在角色材质上放了一套Second Light的调试组件模拟太阳从头顶、背后、侧面、底部四个方向照射时的画面状态确认每个角度都没问题才算调试完成。最后分享一个小经验风格化系统最忌讳一味堆参数。一开始我总想着多给美术一点自由度结果每个材质的裤子都有20多个参数美术调着调着就不知道自己在调什么了。后来我把参数砍到只剩6到8个核心项漫反射曲线、高光阈值、边缘光强度、描边宽度、描边颜色冷暖、噪点强度剩下的全部通过全局StyleProfile控制。反而没人抱怨“调不出想要的效果”了因为能调的都调得明白调不了的是全局风格决策是一整个团队一起定的方向。这套系统跑过的项目里我自己最满意的不是某个镜头有多惊艳而是换了一个新画风参考之后美术团队只花两天就全局调整出了完全不同的气质——这种“系统级”的反应速度才是我觉得风格化渲染系统真正值钱的地方。
返回列表