网易首页 > 网易号 > 正文 申请入驻

揭秘叠纸游戏黑科技!《恋与深空》如何打造影视级渲染管线

恋与深空

0
分享至

在 2025 年 10 月 24 日的游戏案例专场中,《恋与深空》制作组的引擎负责人阮天龙和技术美术负责人秦平带来了演讲《打造影视级渲染管线》,详细阐述了如何在移动端实现影视级渲染效果。

阮天龙:大家好,我是叠纸游戏《恋与深空》的制作人阮天龙,很荣幸能够站在 Unite 的讲台上。今天我们要给大家介绍的是“我们是如何在恋与深空这款游戏中打造影视级渲染管线”的。我叫阮天龙、是 2010 年进入的游戏行业,之前先后在盛大、完美和网易,从事过引擎开发相关工作,2018 年入职的叠纸,目前担任《恋与深空》制作组的引擎负责人。


《恋与深空》作为一款超现实 3D 沉浸恋爱互动手游,自 2024 年 1 月 18 日上线以来,目前全球玩家的数量已经突破了 7000 万、并荣获了科隆游戏展 2025 最佳移动游戏。我们是基于 Unity 2019 开发的,并且对引擎源码进行了深度修改,开发了一套自定义的 SRP 管线。Android线 上版本是基于 GLES3.1,未来也即将上线 Vulkan 版本。持续提升性能,满足玩家对高品质游戏的需求。

接下来,看一段实机画面的混剪视频。

这次分享分为两个部分:第一部分由我为大家介绍《恋与深空》的渲染管线设计与优化。主要涵盖场景渲染的优化、光照的方案与管线设计、阴影优化这三个部分内容。第二部分将由我们的 TA 负责人秦平为大家介绍如何聚焦高质量角色表演核心渲染技术,来打造影视品质的效果。

渲染管线设计与优化

场景渲染优化

前面我们对《恋与深空》这个项目有了整体了解。现在,我们将聚焦到游戏开发中至关重要的一环——场景渲染优化。我们开发了一套场景渲染系统,称之为 RenderGroupRenderer。我们将每个渲染批次称之为一个 RenderGroup。RenderGroupRenderer 主要做了三件事情。首先是自定义静态场景描述,去除 GameObject,避免了更新大量的 GameObject 时带来的性能损耗。其次我们对 CPU 上 GPU 的 Upload 的频率做了优化,主要包括了 Instance Data 和 Constant Buffer 的 Upload。Constant Buffer 的 Upload 优化方面,我会在后面介绍单 draw call 性能优化时详细解释。关于 Instance Data 的 Upload 优化,我们在项目初期主要针对室内小规模的场景,采用的是静态生成 InstanceDataBuffer,配合 BVH 分割裁剪的形式。随着项目的推进,我们的场景精度要求不断地提高,后期便转向在 GPU 端完成裁剪与 Instance 的填充。最后我们对静态物件的 CPU 侧裁剪也做了对应的优化。通过 Burst + Job System 实现了一套高度并发的裁剪系统,同时只在 CPU 侧进行粗略的裁剪,将细粒度的裁剪任务交由 GPU 完成。


接下来想介绍一下我们的 InstanceData 的数据形式。大家常用的应该都是 Constant Buffer 形式的 InstanceData。但是它也有一些缺点。首先它有一个尺寸限制,通常是 64kb;其次常量缓存通常都比较小。当我们声明的 buffer 比较大,并且通过 Instant ID 对它做动态索引时,即使没有超过最大尺寸,也容易发生缓存击穿,导致性能显著下降。另一种常用的形式是用 SSBO 来传递 InstanceData,这种方法也存在一些缺点。首先它的读取性能通常是不如缓存未击穿情况下的 ConstantBuffer,甚至通常不如 Texture。很多移动设备芯片会为 Texture 设计更高效的缓存。其次,一些安卓设备在 GLES 下不支持在 Vertex Shader 中读取 SSBO,这也限制了它的兼容性。最后这两个方案都有一个共同的问题,就是都需要使用动态索引。在低端手机上,这是一种对缓存非常不友好的操作。针对以上问题我们提出了一种“新瓶装旧酒”的方案。使用 PerInstance Step 的 Vertex Stream 作为 Instance Buffer。这是一种在 GPU Instancing 诞生之初就被支持的 Instance 方法,既可避免动态索引带来的性能问题,也可避免 SSBO 的兼容性问题;还可以通过 Compute Shader 向 Instance Vertex Buffer 输出,来实现 GLES 下兼容性较高的 GPU 积累。最后因为 Unity 引擎的底层没有支持 PerInstance Step 的 Vertex Stream,所以我们对引擎做了相应的定制。最终暴露给上层的是 CommanBuffer 中添加的一个 DrawMeshInstancedTraditional 接口。它需要将另一个 mesh 作为 instance data 传进来。我们也加了相应的接口来配置 instance mesh 中各个数据段对应的顶点 semantic。


接下来介绍一下我们的 GPU Driven 系统。首先我们会依据 Group 的数量和 Instance 的数量,提前分配 IndirectParameter Buffer 与 Instant Data Buffer,注意这里 Instance Data Buffer 只是提前分配了空间,实际的数据是在 GPU Cull 的时候填入的。同时我们会预计算每个 Group 的 instance offset,并将其存储到 Parameter 的 InstanceStart 项。这样我们就可以全程只绑定一份 instance buffer。此外我们还需要生成逐物件的信息 buffer,其中包含了 Group ID、LOD Distance Range、Bounds、Transform 等信息,用于在 GPU 裁剪时获取每个物件的属性。


在 GPU 裁剪之前,我们会先执行一次 CPU 粗裁剪,CPU 裁剪仅判断 Group 整体是否可见,从一个根包围盒开始,比较物件包围盒体积总和与合并后包围盒的体积比值,低于阈值就递归分裂包围盒。这个主要是为了避免两个物件距离很远,拉出一个超大的总包围盒这种情况。同时我们还会配合 PVS 进一步判断 Group 的可见性,因为我们没有类似 DX12 的 IndirectExecute,我们的 GPU 裁剪只能减少 instance 数,并不能消除 Group 整体的 drawcall,因此需要 CPU 裁剪尽可能准确地剔除掉完全不可见的 Group。GPU 裁剪则通过一次 dispatch 对所有 Group 进行逐物件裁剪,包含视锥裁剪、LOD 裁剪、Hiz 遮挡剔除这 3 段裁剪,通过裁剪就将 Parameter 的 Instance Count 加 1,并输出 InstanceData。对于阴影剔除,我们参考了龙之教条分享的方法,将画面深度重投影到阴影空间作为 Shadow Receiver Mask,若 shadow caster 投出的 volume 与 Mask 不相交,就可剔除,避免多余阴影渲染。另外想解释一下我们为什么没有实现 Cluster/Meshlet。首先它不是免费的,在移动端存在比较大的基础开销。其次在 GLES 下实现 cluster 也存在一些兼容性问题,同样是之前提过的 Vertex Shader 访问 SSBO 的问题。综合考虑之下,我们认为优先优化单 draw call 的性能更能为我们带来免费且直接的性能提升。


接下来介绍一下我们对 draw call 调用本身做的性能优化。为什么我们今天要着重介绍这块内容呢?因为我发现我们周围有很多同事或同行对于渲染的 CPU 耗时优化这块,往往过于关注 draw call 的数量,而忽视了每个 draw call 本身的耗时。降低 Draw Call 数量只是一种优化方法,最终的 CPU 耗时才是唯一的衡量指标。而且现代移动设备与图形标准,其实早就可以胜任大量的 drawcall,这块 HypeHype 引擎的团队在 Siggraph 2023 有过一个分享,他们在 iPhone 6s 上测试了一万个不同 mesh 与材质的 drawcall,结果耗时仅有 11.27ms。其他同等的安卓设备也都基本能维持在 60 帧以上。另外在 2014 年,苹果的 Metal 刚刚诞生时,也早就提出过比 GLES 多画 10 倍 draw call 的口号。如果当年大家已经入行了的话,一定听不少人说过,等新的图形标准普及了以后就不需要合批了之类的话。那么 11 年后的今天,我们为什么仍在为 draw call 过多而苦恼?原因在于多方面的开销。我们总结了一下,主要包括 PSO 切换过多、Buffer 提交与拷贝、引擎渲染逻辑以及过多 RHI 调用的开销,都会增加 CPU 负担。所以性能优化不能只盯着 draw call 数,而要综合考量这些因素。


首先关于 PSO 切换的开销,其实主要取决于每个项目对 shader 变体数量和 shader 复杂度的权衡。我们的 RenderGroupRenderer 也只是做了一些基本的渲染队列排序。不过我们对于阴影做了一个特殊处理,没有 AlphaTest 的材质统一用相同 shader 渲染 Shadow Depth,可以减少阴影渲染时的 PSO 切换频率。相对而言我们在 Buffer 提交方面做了更多优化。在 GLES 下,Map/Unmap buffer 会带来显著开销,现代 RHI 支持的 persistent map 虽能显著减少 upload 耗时,但仍无法避免数据从主线程到渲染线程,再到 buffer 内存的多次拷贝以及 memcmp。我们采用了三种针对性的策略:PerRendererBuffer 将逐 Renderer 的参数,比如物体所受的环境光 SH,存放在由 Renderer 对象维护的 Uniform Buffer 中,渲染时直接绑定;PerShaderBuffer 针对不需要逐材质变化的 Uniform Buffer,只在 shader 切换时提交一次,相比 PerRendererBuffer 来说,PerShaderBuffer 更加灵活,可以支持不同的 shader 变体;最后针对 PerMaterialBuffer,我们借用了 SRP Batcher 代码预生成逐材质 buffer 并直接绑定,通过这些方法显著减少了 buffer upload。


接下来是渲染逻辑的优化。商业游戏引擎为保证灵活性与稳定性,渲染时会进行复杂的逻辑判断。在 Unity 引擎内部,每次调用 draw 的时候会先调用一个 ApplyMaterial 函数,它负责在渲染之前更新所有的渲染状态与参数。我们发现当 draw call 数量较多时,它存在一些较为可观的耗时。因此我们尝试对 ApplyMaterial 接口进行了单独拆分拆,仅在材质或参数需要切换时才由上层主动调用,另外如果我们只需改变 PerMaterialBuffer,就改用简化后的专用接口。做完上述优化之后,相同 draw call 数下,CPU 耗时可以减少 1/3。RHI 调用优化主要的目标是减少除了 draw primitive 以外的其他图形 API 调用。我们将相同 stride 的 Vertex&Index Buffer 合并到一个 buffer 里,通过 offset 渲染,可以避免逐 draw call bind VB/IB,耗时可以减少 15%。另一项优化是当 Resource 未发生变化时,我们跳过了 DescriptorSet 设置。SetDescriptors 本身是个耗时很高的接口,而且切换 Descriptor 还会增加下一次 draw 的耗时,这个在 Arm 的 Best Practice Guide 里有过介绍。做完这个可以使耗时进一步减少 30%。


我们在低端安卓设备上测试了 5000 个 drawcall 的耗时。使用引擎原生的渲染时,渲染线程的耗时是 34.79ms。当我们对 Buffer 提交与渲染逻辑进行优化后,耗时降低到 22.97ms。在进一步优化 RHI 调用次数后,耗时进一步大幅降至了 11.8ms。最终我们在 draw call 数量不变的前提下,让 CPU 耗时减少到了原来的 1/3 以下。


我们还尝试了一些新的 RHI 特性。首先是 MDI,它在支持的设备上能带来明显的优化效果,还能在一定程度上改善 GPU 遮挡剔除可能提交空 draw call 的问题,因为不同的 mesh 被合成了一个 draw call,总的 CPU 端提交变少了。然而, Bindless 的表现却不尽如人意,即便在最新的安卓设备上也出现了神秘的负优化。结合 MDI 与 Bindless,我们可以实现几乎用一个 draw call 渲染所有物件,但是 CPU 耗时却比不合批时还更高。这也是一个过度关注 draw call 数量的反面案例。当然,我们期待以后的移动芯片对 bindless 能有更好的支持。现阶段的话,我们尝试基于 Unity Texture Streaming 扩展出了一套无 Feedback SVT 系统作为替代方案,不过这个方案也还在验证阶段。从 Benchmark 场景测试结果来看,RenderGroupRenderer 对比原始无 instancing 渲染,draw call 数减少了 1/3,渲染线程耗时大幅减少 3/4,主线程耗时也减少了 2/3。虽然C,因为渲染与裁剪逻辑都移到 SRP 了,但是引擎原生裁剪与 GameObject 更新耗时减少,整体仍然带来了大幅的优化。

光照方案

前面我们探讨了渲染性能的多方面优化。现在让我们将目光转向光照的实现方案。我们在项目中选择使用前向渲染管线,这有多方面的理由。首先,前向管线在应对美术复杂且多变的需求方面有其优势,我们不需要担心一些材质属性的添加是否会导致 GBuffer 膨胀。其次,传统的延迟管线对于移动平台而言带宽不太友好。 OnePassDeferred 则在灵活性方面存在一些局限,比如无法在 RenderPass 中间改变 RT 的尺寸,也不能 fetch 当前位置以外的像素内容。另外在 GLES 下, FrameBufferFetch 的兼容性也存在问题,不同芯片支持的 fetch RT 数量不同,有的只支持 1 张 RT,需要改成通过 PLS 实现,但是我们测试 PLS 的性能并不是很理想。引擎自带的逐物件 4 盏光源对于较大的物件来说不太够用,因此我们尝试了 Forward+。但是 Forward+ 在早期设备上耗时太高,若限制逐 tile 最大光源数,镜头变化时,tile 内光源数量不可控,超上限会带来表现 bug。为解决这些问题,我们采用了水平世界空间 Tile 划分,默认 2 米一格,分布于相机前方,逐 Tile 最多 4 盏光源,用一张 128*128 Index Map。这种划分方式使 Tile 光源重叠状态稳定,便于在制作时及时发现超限问题。


我们在未来的 Vulkan 版本的管线中增加了基于 Subpass 的 Light Pre-pass。在 Pre-Z Pass 中,我们会输出一张简易的 GBuffer RT 并且 store 下来。由于我们的 local light 光照使用了没有 fresnel 的简化 PBR 模型,所以我们不需要在 GBuffer 中输出 specular 或者 Albedo。我们只将 normal,roughness 和一些特殊的材质 id 或属性信息 pack 到了一张 RGBA8 的 Gbuffer 上。然后我们就可以跑一遍类似 deferred shading 的光源 volume 渲染流程,将几何光照结果保存到 Tile Memory 上。之后在 shading pass 中,我们会把物件再画一遍,然后 fetch 这些光照信息,再结合渲染时获得的 albedo 等材质属性,就可以得到最终的光照结果。我们将 TAA 所需的 MotionVector Encode 为 RGBA8,R + G == 0 代表无有效速度。这样某些不输出速度的材质可在 BA 通道存其他信息,比如我们针对一些简易且大量的植被,会在 MotionVector 的 BA 通道上保存他们的 UV 信息,这样在 shading pass 时我们就不需要再画一遍植被,只需要一个后处理获取 gbuffer 中的几何信息与 MotionVector 中的 UV 信息即可还原出植被的材质表现。


Vulkan 版本的管线大致是这样的流程,首先由 PreZ Pass 输出 Depth,GBuffe与 MotionVector。然后计算阴影的遮挡剔除,接着执行阴影的深度渲染,再然后是一些 AO 和屏幕空间 SSS 之类的计算。然后我们就进入了 NativeRenderPass,在 SubPass 中计算 ShadowMask,Light Pre-Pass,以及执行正常的 Shading Pass。最后退出 RenderPass,再执行其他后处理 Pass。Vulkan 版本管线改进存在一定局限。Light Pre-Pass 只能替换默认 Lighting Model,对于需要更多 Gbuffer 通道的 Lighting Model,还是需要采用 Forward+。不过我们提供了一个逐光源可选参数,可以针对某个光源强行使用 Standard Lit Model,对所有材质统一处理,这样可以在牺牲 lighting model 准确性的条件下实现让同 Tile 内的像素受 4 盏以上灯的影响。


然后是 GI 部分。我们线上的 Diffuse GI 用的还是比较传统的 Lightmap+light probe 的方式,我们的 Lightmap 只保存了间接光信息。另外我们正在开发一套实时 GI 系统,相信不久就能和大家见面。我们的 Light Probe 除了正常的逐物件单个采样点的模式以外,还提供了一种多采样点模式。能为每个物体设置多个采样点,依据线段、三角形或四面体的重心坐标进行插值。下面是两张对比图,左边这张图是单采样点的效果,可以看到这个 box 的底部受到的是统一的环境光照。右边的是用了两个采样点的结果,可以发现左右两边受到了不同的间接光照。Specular GI 方面,我们主要是基于使用了 AABB 校正的 Reflection Probe。另外对于一些特定的地板或水面,我们还会使用平面反射代理。大致可以看成一种专门用来画反射的 HLOD。此外我们还参考了战神的做法,对 Reflection Probe 的CubeMap 做了归一化。具体来说就是我们会根据 CubeMap 的像素生成一份环境光照的 SH 系数,然后将 Cubemap 中的像素颜色与该方向的环境光照相除,就得到了归一化的 Cubemap。实际渲染时,我们再用每个像素在反射方向上所受的实际环境光照与 Cubemap 像素相乘,还原出反射颜色。这么做的好处是,即使大量的物件采样的都是同一个 Reflection Probe,不同区域的反射也能产生不同的明暗差别。


阴影优化

前面我们探讨了光照和渲染管线相关的内容。接下来,我们来进入“阴影优化”这一环节。

我们阴影系统的基本设计是,最多 3 级 cascade 的 CSM 加上一级角色专属的特写阴影,或者在某些多角色场景时会使用 POSM,也就是 Per-Object Shadow Map。我们目前支持两盏锥灯投影,上面所有阴影的结果都输出到了一张 RGBA8的 ScreenSpaceShadowMask上,R 通道保存主光阴影,G 和 B保存了锥灯阴影,A 通道保存了 AO 信息。我们首先做了一个简单的距离剔除,根据阴影距离修改 ScreenSpaceShadow 后处理三角形顶点的深度值。再用 ZTest Greater 渲染,剔除阴影距离外的 Shadow 计算。因为我们在计算阴影时需要采样 depth,所以我们需要两份 depth 分别用于 Test 与 Sample。我们在NativeRenderPass 中拷贝了一份 memoryless 的 depth buffer 用于 Test,尽量避免了额外的读写带宽。


另外我们还做了一个半影区域检测功能。我们先在 1/4 分辨率下计算一次 PCF,随后在全分辨率 shadow pass 里采样 1/4 mask,仅对 shadow 值处于中间区域的像素执行全分辨率 PCF,这样能在保证效果的同时,降低计算量。不过,这么做之后某些细节像素会存在检测不准确的问题。为此,我们分别依据 1/4 buffer 中 position 的偏导与全分辨率 gather 的 4 个深度值计算两组法线。如果法线夹角大于阈值,就判定低分辨率像素不可靠,并强行执行全分辨率 PCF。下面是一个祈煜画室场景的 debug 视图,红色区域是被我们判定为半影区间的区域,只有这些像素才会执行全分辨率的 PCF。


我们利用 Receiver Plane Depth Bias 算法实现了逐像素的 Shadow Bias。它的原理也比较简单,首先我们求出屏幕空间 Shadow Coordinates 的偏导,然后我们可以发现,我们如果使用屏幕空间的 UV 偏导对阴影空间的深度偏导做个二维链式转换,就能得到屏幕空间的深度偏导。将链式转换描述为雅可比矩阵之后,轻易就能得出阴影空间的深度偏导等于雅可比矩阵的逆乘以屏幕空间深度偏导。这个偏导值可以近似描述像素在阴影空间深度的坡度,进而就可以用它与 PCF 的采样偏移量相乘,得到近似的 bias 值。对于中心点来说,我们增加了 1 个像素偏移的 bias 结果作为起始 bias。


下面是逐像素 bias 与固定 bias 的对比结果。左边用的是固定的 bias 值,可以看到 box 的底部有一段漏光区域,并且与光照方向接近垂直的表面存在一些自阴影的走样。用了逐像素 bias 之后,我们就只会在偏导较大的区域增加 bias,可以在保持细节投影的同时解决自阴影的走样问题。不过,当屏幕深度不连续时,逐像素 bias 可能算出错误结果,导致一些漏光现象。为了解决这一问题,需要美术手动指定 bias 的最大最小范围。


另外我们针对 draw call 较多的场景,还尝试了 Scrolling Cached Shadow Map。我们通过缓存 CSM 的深度,对于前后两帧都被阴影视锥完全包含的对象,将上一帧的 CSM 滚动到当前帧投影位置直接得到阴影深度,避免直接渲染对象。我们只对最后一级 cascade 应用了 scrolling,当 cascade 范围比较小时,大量物体与会与视锥相交,优化效果就会受限。另外针对移动平台的带宽压力,我们选择间隔多帧来更新 CSM 缓存。最后在未来,我们还准备支持 Local ShadowMap Atlas 以及缓存机制。我们将会支持两盏以上的局部灯投影,并且根据光源的屏占比动态调整 ShadowDepth 精度。对于远距离的局部光源,也会引入静态缓存支持。


以上是我本次分享引用的一些参考资料。下面将由我们的 TA 负责人秦平为大家介绍如何打造影视品质的效果,谢谢大家!

打造影视品质效果


秦平:各位朋友,大家好!前面我的搭档分享了《恋与深空》项目渲染底层框架的一些内容,接下来我来分享一下我们在这样的框架下面是如何打造一个高品质效果的。主要是一些方案相关的东西。我叫秦平,十七年游戏行业从业经验,曾经有幸参与制作《神秘海域4》、《怪物猎人世界》等游戏项目的制作;2017 年加入叠纸成为叠纸的第一个 TA,参与开发了《闪耀暖暖》项目,现任《恋与深空》项目 TA 负责人;因为之前做过一些主机游戏,所以我对高品质的游戏效果一直都有一些追求,今天借这个机会跟大家聊一聊《恋与深空》项目追求角色效果表现的路上遇到的一些问题和解决思路。

这里我大致总结了一下对我们角色表现影响比较大的几个方面:首先就是分镜,我们每一个剧情表演和每一个约会,都是采用了影视级的导演分镜思维来指导创作的。一个好的分镜思维对一个好的作品是必不可少的内容。然后我们大量采用了真人动捕的技术来提升动作表现。有了这个增进和动作之后,我们还需要一个专用的剧情编辑工具。这里我们工具组的同学花了大概大半年的时间,精心打造了一套专用的剧情表演工具,配合上 AI 组同学支持的一个口型和表情系统。最后是定制化的光照和渲染方案,这个也是我这次主要要跟大家分享的东西。

角色光照方案

我这边先分享的是“角色光照方案”,这里讲的“光照方案”不是大家熟知的前向渲染、延迟渲染这些管线相关的内容。我这个方案指的是一个解决思路,比如我角色应该受哪些光,怎么管理这些光。经常有美术的同学报怨,我的场景和角色割裂感很重;我一个近帧打光打好了之后效果还可以,但是镜头和角色一动起来效果就崩了,这些问题经常会困扰我们。其中最突出的几个点:

第一是场景氛围和角色灯光要求冲突。比如夜晚场景需要冷色调营造氛围,但是角色面部一般都需要暖色调保持自然肤色,如果想要用一个冷色调去着重表现当前是一个夜晚的氛围,就很容易让角色显得不自然或者失去立体感;第二个跟前面这个是息息相关的,就是场景和角色的美术同学需要反复沟通。比如一个室内场景做好了,但是角色入场之后发现这个表演的位置透不进光,想在这个角色附近开个窗,透一个光进来;或者补一个光源,比如台灯。场景补好之后剧情编辑的时候可能又发现这新增的物体会在表演过程当中遮挡镜头,或者是角色动作会穿到这个物体里面,反反复复增加了很多沟通成本;三就是如果每一张卡,每一个剧情、每一个表演都要从零开始的话,会带来海量的工作内容;最后就是我们希望我们提供的作品每一帧都经得起反复观看,这就需要灯光和效果逐帧精修。


有了这些问题,我们就可以把具体需求拆解出来了,我们需要角色和场景可以分开调整,尽量互不影响;要支持逐帧调整灯光参数;还需要支持把调整好的效果保存成模板,支持编辑和切换功能。


有了明确的需求就可以开始干活了:我们知道光照是由直接光和间接光组成的,直接光正常就是平行光、射灯和点光。一般情况下我们只会有一个平行光,我们习惯称之为主光。对这个主光,我们让他正常照亮场景,但在照亮角色的时候我们保留了它的方向,然后用角色 PPV(Post Process Volume)去复写它的颜色和强度。具体实现方式就是给 shader 多传了一份角色主光颜色,角色的 shader 在获取主光的时候获取到的颜色就是这个角色主光颜色;另外我们给角色提供了一盏额外的不投影的平行光用来做轮廓光;此外我们预留了两个额外光给角色,他可以是任意的点光和射灯组合,这两个额外光就是正常的光源,他们可以正常照亮范围内的角色和场景物件。因为我们一个 2 米的格子最多可以有四盏额外光,所以在这里划分了一下,角色两盏,场景两盏。间接光我们使用 Unity 的 LightProbe 系统来创建探针,自己实现了保存间接光信息到探针里的部分。我们把场景的环境光和角色的环境光分开存储,分成了两套;至于环境光高光还是使用的同一个反射球,但是我们在特殊的材质上支持了一些 cubemap 的覆盖收入。


然后我们把这些影响角色的光照信息存到一个 scriptableobject 里,由灯光师调整好之后保存成一个模板;大家可以看最右边这张图就是我们保存下来的 scriptableobject。它包含了我上面提到的两盏平行光,两个额外光,还有探针保存下来的 sh 信息,以及一些后处理盒子上可以额外调整的信息。比如主光颜色、额外光颜色;最后用一个 manager 去用一种类似栈的方式去管理起来,这里选用栈的管理方式跟它的使用关系很大,通常情况下除了加载新的灯光方案之外,“栈”就很好的满足了这个特性、我们就用这个方式把它管理了起来。


到这里角色灯光方案基本上就完成了,它实现了我们拆分出来的需求。比如:角色和场景可以分开调整,还可以实时的切换。这个动图就是切换不同的光照方案时这个角色的表现。

我们把切换灯光方案这个事情定义为一个剧情编辑器上的一个事件,这样它就天然支持后续可以衔接光照动画。因为只有在切换这个事件发生的那一帧,然后修改了当前的这些参数,后续就可以对这些参数额外进行 K 帧。下面是一段剧情编辑器上动态 K 帧的一个视频,给大家展示一下我们可以逐帧调整光照动画的内容。可以看到,这个视频上面很多的参数都是被 K 了侦的、包括一些后处理、灯光、阴影,那些蓝掉的地方全部都是 K 了帧的。但是不仅是光会用,剧情编译器是我们所有表演的入口,比如:物理之类的东西,也是在这个地方 K 进去的。

特写阴影

光和影一直都是密不可分的,前面介绍了光,接下来介绍一下阴影。前面我的搭档也有提到,我们的阴影方案是三级 CSM 加特写阴影,这里我着重介绍一下特写阴影。我们特写阴影就是在角色身上选一根骨骼,然后作为一个球心,然后用这个球心来指定一个指定半径的球。可以看到这里有一个子系统,子系统上面有这个包围球半径,还有这个负节点,这个节点就是指定的这根骨骼,然后用它作为球心,然后用这个 0.85 作为半径来构建一个球。如果有两个角色的话,我们会对这两个球进行一个包裹,用算法求一个包裹球,然后用这个球来计算最终的 ShadowMap。

这个视频里面我们可以看到,就是原本因为在室内整个都在阴影里面的这个角色,因为我打开了特写阴影,然后他的面部被照亮了。我这里展示的主要就是想要说明我们这个特写阴影的很多参数都可以单独的调整。比如说这个近裁切平面,近裁切平面往前推室内的墙就被不投影了,然后这个角色就被照亮了。这个特写阴影上面这些参数都是可以改动的,我希望调整到一个角色最好的效果,然后呈现给玩家。


皮肤细节

说完了光影,接下来聊一聊角色的皮肤、脸上细节相关的内容。皮肤分了两档:高配就是屏幕空间的 3S,SSS 图降了分辨率,就相当于做了一次模糊。低配的其实是用了一个工具,去实时地拟合和保存这预积分图,这里不做过多展开。接下来,我们聚焦一些细节的表现。例如:脸红、流汗的效果,当然我们也做了一些张嘴、闭嘴的 AO,以及可以 k 帧的眼睛高光,睁眼闭眼的双眼皮的一些效果。由于时间有限,这里先聊一下脸红和流汗的效果。这里我录了一张卡,可以展示角色的脸红的表现。当女主和男主进行一些互动的时候,如果我改变脸上皮肤的颜色、可以很明显地让画面看起来更加的生动和更真实。

通常来讲,“脸红”的过程是一个逐渐变化、并且不同区域变化程度不一样的过程。大部分的人在脸红的时候会从耳朵先开始红,然后脸颊,偶尔会有整张脸变得通红的情况。为了模拟这个过程,我们把“脸红”做成了一个可以区分通道的遮罩图。每一个通道对应一个区域。这里也分别提供了对应的色块,去改变脸红的颜色。如果逐帧去 K 的话,这个工作量也会非常大。这里我们也是采用了模板的思路,美术同学可以根据不同的男主,定制不同的脸红的曲线做成模板。我们只要在 K 脸红效果的时候,调用对应的模板、就可以做到对应的脸红效果。


脸红到一定程度,比如:我现在是在运动,运动的时候脸非常的红,可能我接下来就会流汗。如果只有脸红没有流汗的效果还是不够真实,接下来我们做了流汗的效果。我们的流汗有两种:一种是粒子,主要用的是 vfx,用 skinmeshrender 发射粒子出来,用来描述一些“甩汗”的效果。材质上的实现,也分为两部分:一是修改粗糙度,让皮肤看起来比较湿润。第二个就是凝聚成的汗珠,这个凝聚汗珠的实现思路与 Unite2019 闪耀暖暖里分享的实现思路是一样的,它详细介绍了一个闪点,有兴趣的同学可以翻一翻当年的这篇分享。这就是大概的一个实现思路。


下图最上面这个部分就是计算汗滴生成位置,并根据汗滴的位置修改粗糙度。我们先把 uv 划分成格子,然后把格子的 ID 做成材质的输入。这样每个格子里所有的 uv 就会返回同一个随机数,我们把这个随机数处理当成汗滴生成的位置,详细代码如下图。下方的视频就是最终实现出来的流汗效果。流汗时汗滴流下来就是 uv 动画。但为了让汗滴流下来的过程更真实,我们做了一个模拟它轨迹的很小的算法实现(下图右下角)。


谢谢大家,我的分享结束了。

Unity 官方微信

第一时间了解Unity引擎动向,学习进阶开发技能

每一个“点赞”、“在看”,都是我们前进的动力


特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
33岁刘灿,已破格升任人大教授

33岁刘灿,已破格升任人大教授

观察者网
2025-11-12 15:41:05
独行侠解雇哈里森!Shams:是他坚定要交易走东契奇

独行侠解雇哈里森!Shams:是他坚定要交易走东契奇

搜狐体育
2025-11-12 07:15:10
围绕弗拉格重建!独行侠要送走的不仅是浓眉 还有欧文和所有老将

围绕弗拉格重建!独行侠要送走的不仅是浓眉 还有欧文和所有老将

云隐南山
2025-11-12 16:11:04
高层聚餐唯独不叫我,我默默定了他们隔壁包间,请来几个最大客户

高层聚餐唯独不叫我,我默默定了他们隔壁包间,请来几个最大客户

牛魔王与芭蕉扇
2025-11-08 10:00:07
湖南人民医院祖曾艳照门时间中的三十六计

湖南人民医院祖曾艳照门时间中的三十六计

留美教师的教育及健康译介
2025-11-10 10:20:56
陈梦4:0战胜何卓佳,孙颖莎边擦桌子边看热闹,赢球后举动好暖

陈梦4:0战胜何卓佳,孙颖莎边擦桌子边看热闹,赢球后举动好暖

妙知
2025-11-12 16:10:08
绝不当冤大头!巴向中国要巨款,中方:支持,但得去找发达国家要

绝不当冤大头!巴向中国要巨款,中方:支持,但得去找发达国家要

知鉴明史
2025-11-12 16:44:44
母亲被儿子暴打后续:男子身份被扒、高清正面照流出,悬针纹明显

母亲被儿子暴打后续:男子身份被扒、高清正面照流出,悬针纹明显

林子说事
2025-11-12 11:07:32
河南省纪委监委:文海周涉嫌严重违纪违法

河南省纪委监委:文海周涉嫌严重违纪违法

鲁中晨报
2025-11-11 18:31:02
土耳其确认:坠毁军机上20人全部遇难

土耳其确认:坠毁军机上20人全部遇难

极目新闻
2025-11-12 15:24:31
山东要下雪了!这里将迎中到大雪!

山东要下雪了!这里将迎中到大雪!

观威海
2025-11-12 10:46:02
骷髅变巨像! 《暗黑破坏神4》关键boss遭和谐

骷髅变巨像! 《暗黑破坏神4》关键boss遭和谐

3DM游戏
2025-11-11 15:41:03
王清海教授:陈皮和它是绝配!常喝血脂降了,斑块没了,血管通了

王清海教授:陈皮和它是绝配!常喝血脂降了,斑块没了,血管通了

蜡笔小小子
2025-11-08 14:18:03
联合国会场响起琉球古名!日本代表沉默摸领带,日媒集体炸锅

联合国会场响起琉球古名!日本代表沉默摸领带,日媒集体炸锅

李博世财经
2025-11-12 10:17:51
虞莉清在江西被带走接受调查

虞莉清在江西被带走接受调查

微月都
2025-11-12 11:57:56
陈国豪首秀4分3板,广东三人男篮险胜;苏蓥滢9分,四川开门红

陈国豪首秀4分3板,广东三人男篮险胜;苏蓥滢9分,四川开门红

萌兰聊个球
2025-11-12 11:40:56
突发!美国开始介入了,郑丽文参加统派活动,赴大陆前先被美约谈

突发!美国开始介入了,郑丽文参加统派活动,赴大陆前先被美约谈

潮鹿逐梦
2025-11-12 15:41:39
被年轻人的油汀用法“折服了”,换个思路后,变成家居“神器”了

被年轻人的油汀用法“折服了”,换个思路后,变成家居“神器”了

装修秀
2025-11-11 11:00:03
华裔总统汤安诺:上任2个月宣布和中国断交,亡国危机来袭又求援

华裔总统汤安诺:上任2个月宣布和中国断交,亡国危机来袭又求援

历史龙元阁
2025-11-12 15:50:06
梅西:明年是一届特殊的世界杯;我不想成为球队的负担

梅西:明年是一届特殊的世界杯;我不想成为球队的负担

懂球帝
2025-11-11 20:30:09
2025-11-12 17:39:00
Unity incentive-icons
Unity
Unity中国官方帐户
2392文章数 6728关注度
往期回顾 全部

游戏要闻

玩家强烈反对!任天堂首款“钥匙卡”游戏遭重,费钱又耗时间!

头条要闻

"大客户"租两豪车后人间蒸发 车行找到车后一看天塌了

头条要闻

"大客户"租两豪车后人间蒸发 车行找到车后一看天塌了

体育要闻

太阳三连胜&活塞东部第一 哪个更想不到

娱乐要闻

再王珞丹和白百何 明白两人"差别"在哪

财经要闻

专家建议设立5万亿房地产稳定基金

科技要闻

Meta"宫斗"持续,AI教父杨立昆被"气"走了

汽车要闻

7座皆独立座椅/新增5座版 体验第三代吉利豪越L

态度原创

健康
艺术
教育
手机
军事航空

血液科专家揭秘白血病七大误区

艺术要闻

毛主席珍贵签名照曝光,鲜为人知的历史瞬间!

教育要闻

教育部重磅部署为教师减负,这次能带来彻底好转吗? “除了畜牧局,都在使唤我们”,教育部重磅部署为教师...

手机要闻

OPPO Reno15星光蝴蝶结图赏:甜酷辣妹必备的小直屏

军事要闻

美媒爆出猛料 "北溪"破坏行动由扎卢日内指挥

无障碍浏览 进入关怀版