ARTICLE DETAIL

资讯详情

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

RE Engine发丝渲染实战:光照、覆盖率与LOD优化

RE Engine发丝渲染实战:光照、覆盖率与LOD优化 提到RE Engine的发丝渲染我得先说句实在话头发是角色渲染里少有的“看着简单、做好极难”的东西。系列上篇聊了曲线怎么从DCC里导出来、物理模拟怎么让头发动起来这篇把后半段补完——光照着色、覆盖率优化、透明排序、性能预算、LOD策略还有你只有真在项目里趟过一遍才会明白的坑。如果你正在做PC或主机平台的写实角色头发或者手里有毛皮、布料这类半透明薄片需求这篇应该能帮你少走小半年的弯路。1. 为什么发丝渲染这么难搞1.1 发丝不是“网格”是半透明的一维结构传统角色身上的衣服、皮肤、盔甲本质都是不透明表面你在引擎里摆一个PBR材质球、给一套贴图、调个粗糙度深度缓冲就能把遮挡关系处理得明明白白。头发不一样。单根头发的直径大概是60到90微米在近景镜头下一个像素里可能同时存在十几根甚至几十根发丝。这意味着两种传统做法全部失效要么用不透明网格渲染头发会变成一片片硬纸板要么用alpha blend排序问题马上让你疯掉——头发丝之间互相穿插帧与帧之间闪烁不停。我在第一次接头发渲染需求时一开始天真地想“把每根头发当细圆柱体处理就行了”结果把模型倒进引擎一看一个角色光头发就消耗了全帧三分之一的绘制时间而且近看圆珠笔笔迹一样、远看闪成雪花。后来才明白发丝渲染的核心矛盾是你要在“单根细节”和“整体体积感”之间找一个工程上的平衡点而RE Engine的做法本质上是用覆盖率把“半透明难题”转成了“不透明表面加密度蒙版”的问题。1.2 RE Engine的思路把“半透明”变成“可控覆盖率”RE Engine在处理头发时并没有一头扎进逐像素光线追踪或者全屏OIT无序透明而是先为头发生成一张覆盖率贴图——你可以理解成一张“头发密度图”每个像素记录有多少根头发覆盖了这个位置。下一步光照、阴影、深度测试都基于这张密度图来做。这套路的好处非常明显头发不再是需要和场景里其他半透明物体排序的特殊存在它在遮挡关系上变成了一张“不透明但有透明度的表面”只要覆盖率的阈值控制得好剪影、体积感、边缘光都能呈现出来。而且这个思路在性能上非常友善覆盖率贴图可以用低分辨率渲染再用TAA或者双线性升采样远景表现依旧稳定。注意覆盖率贴图不等于alpha贴图。alpha贴图只给美术一个“透亮度”参数覆盖率贴图需要参与光照积分一个像素里的头发根数会直接影响高光能量和阴影暗部。实际工程里两张图经常同时存在但语义上别混。2. 光照与着色让头发亮起来还要像头发2.1 Marschner模型与R/TT/TRT三光路头发不是普通漫反射表面它的光学行为比皮肤还复杂。一束光打到头发丝上大致分三条路径表面直接反射R、穿透内层再透射出来TT、射入纤维内部经过内壁反射后再射出TRT。这三条路径分别决定了头发的环境高光、透光感和次级高光。直接用完整的Marschner模型在GPU里逐像素跑是不现实的因为它牵扯了好几层高斯分布拟合。RE Engine这类主机引擎的做法是把模型拆分R路径用一层窄高光近似负责发丝滑动时的高光点TT路径用一层宽高光近似负责透射光带来的“发丝内部亮起来”的感觉TRT路径则通常叠加在R路径上做一点偏移和色散。我实测下来有一个关键参数比颜色还重要高光的纵向偏移量。头发的高光并不垂直落在光源方向上而是会沿发丝方向轻微移动偏移量调得小了头发会油亮亮像上了发油调得大了又会变成塑料丝。稳妥的起点是让偏移角度在2到4度之间再根据贴图微调。2.2 覆盖率贴图与延迟光照的结合RE Engine是延迟渲染为主。延迟渲染天生适合处理不透明表面而头发这种“半透明”属性必须特殊处理。在实现上头发会单独过一个Pass把覆盖率写入一张RT然后等不透明场景的光照跑完后再对头发RT执行一次全屏光照。这个过程我踩过的坑是覆盖率阈值的设置不能太死。如果阈值太高稀疏的碎发会被直接裁掉头顶边缘会出现难看的锯齿如果太低又会有大量半透明像素进入光照计算浪费性能。经验值是阈值的起始范围设在0.3到0.5之间然后根据项目的MSAA等级和TAA强度再微调。特别好用的工具是给覆盖率RT开一个Debug视图把值直接灰度化显示在屏幕上一眼就能看出哪里密度不足。2.3 高光偏移与二重高光避免“塑料感”只给头发上一套PBR高光绝大多数情况下会得到“塑料”或“果冻”一样的质感。原因在于真实头发有主高光和次高光两条高光带主高光偏尖锐、副高光偏宽且副高光带有轻微色偏通常偏暖。RE Engine的实际渲染里副高光一般用第二种高光瓣叠加颜色可以往橙黄色调偏移一点点这能很大程度模拟光在毛鳞片内部的多次反射。我在做女性长发角色时把副高光的强度压到主高光的四分之一到三分之一效果显著更自然。另外要注意高光的坐标要沿着发丝切线方向计算很多闪烁来自切线方向在卡片插片之间突变光线一旋转就“跳”了。3. 透明排序与抗锯齿头发不闪的底层功夫3.1 为什么头发会疯狂闪烁头发闪烁的主要来源是透明排序不稳定。头发丝是由成千上万个细长三角形组成的而三角形之间有大量1到2像素宽的重叠区域。普通alpha blend要求物体从后往前排序但头发的层次几乎无法精确排序——远看后脑勺一片头发谁在前谁在后根本分不清。另一个闪烁来源是MIPMAP。头发贴图经常有极细的线条和高频细节一旦MIP层生成太激进较远距离的头发会变得斑驳。我见过一个项目场景不动没事镜头一拉远头发像下雨一样闪。排查到最后发现是贴图的MIPMAP开了线性过滤但没做各向异性发丝纹理在斜视角下混出了马赛克。3.2 覆盖率排序与深度偏移解决排序问题RE Engine的路线其实是“不追求完美排序”。头发卡片会按中心深度从后往前画但在像素层面你并不做逐像素透明混合而是通过覆盖率阈值来决定丢弃还是显示。这样排序错误最多造成一两帧细微的遮挡偏差不会出现半透明像素叠加的“鬼影”。这里有一个容易被忽视的配套操作深度偏移要分远近两档。贴近头皮的碎发容易和头皮上的Mesh穿帮偏移值过小会露出后面的头皮偏移值过大又会让发丝看起来“飘”在头皮上方。我常用的做法是以头皮顶点法线为参考把发根点沿法线方向扎入皮肤约0.5到1毫米而不是单纯改深度偏移。3.3 TAA、抖动与边缘收敛现代引擎里不用TAA做抗锯齿几乎不可能因为头发边缘的锯齿和像素闪烁光靠MSAA根本压不住。但TAA也有副作用——头发是细薄结构时间维度上的抖动会造成“发丝在抖动”的错觉严重时像水波纹。我踩过的一个坑是TAA的History颜色夹取范围不够导致头发边缘被“钳”成一圈光晕。解决办法是在TAA的History验证里专门针对头发材质增加一个略微放宽的夹取范围同时对头发RT的覆盖率值做1到2帧的时间滤波。这个操作听着小但在4K分辨率下能明显减少过场动画中的杂草感。注意不要让头发的运动向量直接沿用角色的运动向量。头发自身有物理模拟位移如果你只给一个身体骨骼的Motion VectorTAA会认为头发向后拉伸从而产生残影。最稳妥的办法是为头发模拟结果单独算Motion Vector哪怕每帧多花一点点代价。4. 性能预算与LOD一个角色头发能吃掉多少毫秒4.1 发丝数量的背后数据量与渲染量的平衡头发的数据量远比想象中大。一根发丝如果按曲线存储一个控制点至少是12到16字节几千根头发就是几万控制点而渲染时每根头发要拆成条带或卡片顶点数再乘以2到4倍。我不建议一上来就追求“每根头发都渲染”。以我接触过的中高端写实项目为例用于近景过场的主角色头发总数可以做到8到12万根中景配角砍到3到5万远景NPC直接换成低模头发片。这里说的“根”在渲染时会合并成很多“束”实际绘制三角形数量要控制在2万以内。4.2 卡片插片与根节点LODRE Engine的EC实现里发丝渲染强调“根节点LOD”。具体做法是每一束头发在靠近头皮的地方保留原始曲线从根部往上每隔一段距离合并相邻发丝越远越合并最后生成宽条带。这相当于把头发从“一根根”变成“一撮撮”但通过覆盖率和法线方向保持体积感。插片方向很容易调错。我的习惯是让插片的方向和发丝的主弯曲方向一致换句话说插片不应该都朝向摄像机而应该跟随发丝的自然弯曲侧面朝外。否则头发在转头时会突然“消失”或者“变成纸片”因为从视线方向看整个插片变成了一条线。4.3 阴影与遮挡剔除的小技巧头发对阴影的需求和角色身体不同通常要软阴影但不能太糊。RE Engine里给头发阴影开一个单独的Shadow Pass分辨率可以比主阴影低一档近处PCF在2x2就够了。头发很忌讳高锐度阴影因为头发丝间的阴影缝隙会变成闪烁的噪点。还有一点物理模拟线程和渲染线程的解耦。头发模拟更新频率一般不需要每帧都跑全量计算可以在30Hz以下插值到渲染帧。经过这样调整一个8万根头发的角色完整模拟加绘制能压进1.5毫秒左右给其他系统留出空间。如果发现卡顿先检查是不是每根发丝都在跑碰撞检测而不是先去砍数量。5. 工具链与美术工作流从DCC到引擎的最后一公里5.1 卡片生成与参数校验DCC导出的头发生成“卡片”阶段是我见过最多“看起来一样、进引擎全崩”的环节。生成卡片的参数里最关键的不是卡片宽度而是沿曲线分布的控制点数量和插片旋转角度。插片旋转角度决定头发的体积。四根插片通常设为垂直相交角度均匀分布会让头发看起来太规整、太假稍微加一点随机扰动比如每根卡片的张角在60度到90度之间随机会让发丝之间有自然的缝隙和穿插。如果你看到头上稳稳当当几片“西瓜皮”大概率就是角度没加随机。5.2 骨骼绑定与物理参数的粗调细调头发模拟的参数我一般分为粗调和细调两轮。粗调只调刚度、阻尼、重力这三项目标是让头发在静止姿态下自然下垂、不穿耳朵细调则处理局部联动比如肩胛骨、颈部转动时头发应该延迟多少帧才跟随。这里有一个建议物理调参前先关掉所有动画查看器里的自动化手动旋转角色的头观察头发的响应。如果头发在头部快速转动时“甩”出明显穿模优先检查发根约束的刚度而不是增加碰撞体层数。多数穿模是发根约束被物理解算“拖”离了头皮而不是没有碰撞。5.3 验证与迭代看剪影、看运动、看高光每改一版头发我都会做三件事关掉贴图看剪影、开一格光源旋转看高光、跑一段人物侧面行走看运动。剪影只暴露两个问题体积是否够以及发际线边缘是否自然。高光检查最容易被遗漏许多头发材质在静态帧看着还行光源一转动高光带像“扫描线”一样扫过头顶那就是高光的切线方向出了问题。运动测试则要看头发的滞后是否过度——头发不是弹簧床垫如果模拟阻尼过小跑起来后面会拖一段“尾巴”在过场里非常出戏。6. 常见问题与排查实录6.1 头发发黑或发紫头发发黑最直接的原因是法线方向错误。头发是双面渲染的细薄结构但光照需要统一从“外侧”方向计算。如果发丝卡片的背面朝向了光源就会出现一块一块的灰黑斑块。排查方法给材质加一个Debug模式显示像素的法线方向视角转动时观察亮面和暗面是否连续。RE Engine里可以把材质翻转成“双面统一法线”而不是用Double Sided把两个面都正常打光。发紫的情况通常来自覆盖率贴图采样越界或贴图通道错位常见于MIPMAP的某层上覆盖率值出现了负值或超大值光照的中间量被钳制后产生了异常色偏。打开RT通道逐一查看基本能找到问题源头。6.2 边缘毛刺与闪烁边缘毛刺多数是覆盖率阈值和抗锯齿等级不匹配。4K下你可能看不到但在1080p或动态分辨率下毛刺会很明显。我的排查顺序是先关闭TAA看是否还有毛刺有的话基本是阈值或MIP问题没有的话就是TAA Motion Vector没处理对。闪烁的进阶问题是过渡帧闪烁这种最烦人——单独看每一帧都正常连起来播放就像头发在“呼吸”。这种现象往往是覆盖率贴图的时域滤波和主场景的TAA产生了相位差解决方案是让覆盖率贴图的滤波权重与TAA当前强度挂钩。6.3 光追下头发效果不一致打开光追后头发会变得有时候太亮、有时候太黑因为光追反射对低覆盖率像素的响应和光栅化高光不同。如果项目同时支持光栅和光追混合模式建议为头发单独开放一个“覆盖率采样数量”的参数光追下用8到16次采样光栅模式保持4次左右这样切换时不至于产生跳变。6.4 常见问题速查表现象可能原因排查方法头发局部发黑卡片法线翻转打开法线Debug视图检查朝向边缘毛刺严重覆盖率阈值过高或MIP采样不均匀降低阈值、开启各向异性过滤镜头拉远后闪烁TAA运动向量未包含头发模拟为头发单独计算Motion Vector头发“飘”在头皮上深度偏移过强改为发根沿法线内嵌1毫米阴影噪点阴影Pass分辨率不足降低PCF尺寸、提高阴影贴图精度光追下过亮/过暗覆盖率采样数量不足提高光追模式下的覆盖率采样次数转动头部时发丝穿模发根约束刚度不足优先调发根约束不盲加碰撞体高光像扫描线扫过高光切线方向突跳检查卡片插片的法线连续性7. 实测心得与避坑总结最后写几条我个人的体会都是真金白银换来的。第一头发的渲染质量70%在覆盖率贴图和卡片布局阶段就决定了。材质Shader写得再花如果卡片角度、覆盖率和物理模拟参数不对出来的效果还是“假发套”。所以每次调参前我都会先在覆盖率Debug视图下确认密度分布再回头看颜色。第二并发帧调试特别重要。头发渲染很吃GPU但也有很多随机闪烁问题只有在并行渲染时才会暴露。开启并发帧、来回切换近远景连续跑几分钟才能看到真正稳定的效果。别拿单帧截图当判断依据。第三RE Engine里做头发最大的感悟是它逼你用“工程思维”去理解头发。头发不是一个材质难题而是一个系统难题DCC怎么导、卡片怎么分、覆盖率怎么算、物理怎么解、排序怎么稳、LOD怎么切。任何一环掉链子都会在最终画面里被放大。后面如果还想继续深挖可以从这两个方向拓展一是把覆盖率贴图升级成可交互的“密度体积”用于电影级实时渲染二是尝试用神经网络预测高光分布把离线渲染里更复杂的散射模型实时化。不过这些都是后话了先把基础的链路跑通再说锦上添花的事。
返回列表