在 Houdini 20 中,SideFX 引入了一套非常高效的工具,用来创建云(cloud)与云海(cloudscape)。在这篇教程里,我会使用这些工具来制作一组很有压迫感的云层形态,并通过加入一个 SOP Solver 来扩展工具链,实现自定义的 advection(给云体添加细微的运动),同时在云层内部生成闪电。
我会覆盖从建模、模拟、渲染到最终合成的完整流程。希望你喜欢,我们开始吧!
云体建模
云形生成(Cloud Shape Generate)
正如我在上一篇教程 Creating Procedural Drift Ice in Houdini using SOPs and Solaris 里提到的那样,想做出好看的程序化模型,一个关键点是先拥有一个好的“基础形体”(base)。制作云也是同理。
幸运的是,SideFX 在 Houdini 20 中提供了一个新节点:Cloud Shape Generate。它会生成带有 pscale(用于设置粒子尺度)的粒子云,并以“云的形态”来组织这些粒子。生成云的 base shape 的方法当然不止这一种,但我认为它的结果最自然。
这个节点的参数一开始可能有点吓人,因为它实在太多了;不过我会重点讲讲我在这个项目里最常用的部分,希望能帮助你快速得到一个靠谱的起点。
Shape
Shape 区域包含一些云形态的通用参数。下面是我建议你优先调整的几个关键项。

Cloud Shape Generate 的 Shape 参数。
Cloud Species - 截止写作时,这里有 3 种云的类型:Mediocris、Humilis、Congestus。Humilis 是非常低、几乎扁平的云;Congestus 则相反,是很“竖向”的云,类似雷暴云;Mediocris 介于两者之间,兼顾“高耸感”和底部的一些宽度。本教程的镜头里,我大约 80% 使用 Mediocris,剩下的用 Congestus。

Cloud Shape Generate 中不同 Cloud Species 的示例。
Shape Offset - 本质上就是随机化云的形态。你在同一套 setup 里想生成多朵云时,这个参数很有用。

使用 Shape Offset 随机化云形。
Initial Size / Length / Width - 基础的尺寸设置;我建议先把这些大尺度调到位。
Secondary Shapes
在我看来,Cloud Shape Generate 的 Secondary Shapes 区域包含了一些最重要的参数,用来得到更自然的云。它允许你在竖向“生长”云体,获得漂亮的轮廓(silhouette)。下面是几个用法示例:
Iterations - 次级粒子生成的次数。你可以把它理解成“生长的步数”。

Secondary Shapes 的 Iterations。
Displacement - 控制沿着 up 向量(通常是世界 Y 轴)位移的程度。可以理解为“竖向生长的量”。

Secondary Shapes 的 Displacement。
Random Scale per Shape(1 & 2)
如果你观察 Cloud Shape Generate 节点,会发现有两组 Random Scale per Shape。节点本身的说明写得不太清楚,但它允许你基于噪声添加两层独立的随机缩放。我建议两层都开启,它能带来很好的变化与层次。这里没有所谓“万能数值”,但我放一个我在其中一朵云上使用的设置示例供参考。

两层 Random Scale per Shape。

Random Scale per Shape 示例。
你可以尽情尝试这个节点的各种设置,看看能做出什么效果!你甚至可以把多个 Cloud Shape Generate 合并在一起,拼成更大的云体。
云形复制(Cloud Shape Replicate)
Cloud Shape Replicate 是另一个很实用的节点。它会以输入的点云(通常来自 Cloud Shape Generate)为基础,在已有云形之上再散布一些点。
在这里我主要做的改动是打开 Scale Along Direction 和 Align Along Direction(我在 Direction 参数里把方向设为 Y 轴)。这样额外散布的球体/点会更集中在云的顶部。我觉得这很好地模拟了真实云:小尺度细节往往聚集在顶部,同时也暗示着云体向上的膨胀趋势。
随后我把这个节点的结果与前面的 Cloud Shape Generate 合并,得到最终的 base shape。

将 Cloud Shape Replicate 与 Cloud Shape Generate 合并。
体积生成(Volume Generation)
base shape 准备好之后,我们就可以进入体积(volume)阶段,开始生成细节了。首先需要把 base shape 转成一个 VDB Density 场(也就是 Fog VDB)。
这一步非常直接,而且有点出乎意料的是:并不需要任何“专门的云建模节点”。我们只需要用一个 VDB From Particles。
这个节点会把输入点云转换成一个 VDB(SDF 或 Fog VDB),并用 pscale 作为尺度参考。这非常适合我们的 Cloud Shape 输出:它们本质上就是输出一份带 pscale 的点云。
一般来说,你在这个节点里主要需要调整的是 Voxel Size。在这个示例里我用的是 0.07,但它强烈依赖你的云体尺度。我通常会以大约 1000 万 voxels 为目标,确保有足够分辨率来承载后续的细节。

VDB From Particles 的输出。
💡
Tip: 到这一步我通常会把当前结果 filecache 一下,因为每次打开 Houdini 都重新 cook 会很耗时,尤其是体素数量很高的时候。
云体绒状噪声(Cloud Billowy Noise)
很好!现在我们有了一个体积。但它看起来还不像云,对吧?这时就要靠体积噪声生成器登场了。
在 Houdini 20 中,SideFX 引入了一个专门为云设计的噪声生成节点(当然它也完全可以用在别的场景)。

Cloud Billowy Noise 的设置。
和前面节点类似,这个节点的参数也非常依赖你的输入;不过我会分享几个我常用来“调到位”的关键项。上图是我为这朵云使用的设置(点击图片可放大)。
Iterations - 噪声位移迭代次数。SideFX 建议增大这个数值,我也同意:在我的测试里 4 在大多数情况下都很稳。
💡
迭代次数越多,计算会显著变慢;但我认为多出来的 cook 时间是值得的。
Noise - Amplitude and Element Size - 这两个参数很可能是你花时间最多的地方,它们对最终外观影响非常大。Element Size 决定噪声的尺度,Amplitude 决定强度/幅度。
Worley Details - 默认是关闭的,但我建议打开。它能给原本较均匀的 billowy noise 增加一些变化。这个噪声会与已有的 billowy noise 混合,混合量由 Blend 控制。我建议保持低一些;在大多数场景里,0.2-0.5 的范围效果不错。
Value Correction - Value Offset - 这里可以对噪声结果做一些很实用的后期修正。我经常把 Value Offset 调到 0.25-0.45 左右:它会把位移稍微“收回”到更接近原始形体。
Advection - 在节点的第二个标签页里有 Advection 区域。我喜欢把它打开,并保持默认值。它会对噪声做一点 advection,让噪声有更自然的“推进感”,我发现多数时候都很好用。
云体裁剪与丝状噪声(Cloud Clip and Wispy Noise)
为了完成云体建模,我希望把云的底部压平,并加一点 wispy(丝状/飘散感)来贴近真实云的形态。

压平底部 + 增加 wispy 结构后的云。
实现方式上,我借鉴了 SideFX 的 Attila Torok 在他们的 content library files 中展示的一部分技巧。
这里我们会使用两条分支:左边分支用于裁剪/压平云底;右边分支用于在底部生成 wispy 的云丝。最后用 VDB Combine 把两条分支合并。我会进一步解释它的工作原理,不过先给你看一下整体网络结构。

网络结构概览。
先看左边分支:这里我生成云的平底。基本都用默认值,只有几点例外:我打开了 Noise Displacement,避免底部完全平坦;我也打开了 Smooth Edge Density 来软化边缘(默认会稍微偏锐)。最后把这条分支接到 VDB Combine 的第一个输入。

Cloud Clip(左侧分支)。
右边分支会更复杂一些。首先我复制另一条分支的 cloud clip,然后调整 Direction,让它更往上裁切一些。
然后我开启 Invert Clipping。这样 Cloud Clip SOP 会输出“被裁掉的那部分”,而不是裁剪后的剩余结果。我们后面做 wispy 效果需要用到这个部分。
但这还没完:我们还会额外输出一个叫 mask 的 volume。方法是在 Output 里打开 Clip Mask(见下方第二张图)。这个 mask 会作为后面 wispy noise 节点的遮罩使用。我还会给它一点 padding 让可用区域更大,并使用 Fade Range 软化过渡,避免切边太硬。
如果你想可视化 mask volume,可以点击 Fade Range 旁边的小“定位标记”按钮。

Cloud Clip(右侧分支)- density volume。

Cloud Clip(右侧分支)- mask volume。
在这个节点之后,我在右侧分支接了一个 VDB Activate。它不会带来明显的视觉差异,但它会扩展 active voxels,确保我们有足够空间添加额外噪声。
💡
如果你好奇为什么需要这样做:VDB 有“active voxel”和“inactive voxel”的概念。这也是 VDB 相比 Houdini 原生 volume 更高效的原因之一——它允许你标记哪些体素是“关心的区域”,从而让计算只针对这些被标记的体素进行。点击这里了解更多。

VDB Activate。
active voxels 扩展好之后,就可以让云底部变得更“飘散(wispy)”了。
这里我们会使用 Houdini 20 云工具集里的另一个节点:Cloud Wispy Noise。

将 Cloud Wispy Noise 应用到云底部。
这个节点需要调一些设置。首先我们要用到前面生成的 mask:在节点顶部打开 Mask,它就会读取 mask volume,并用它来遮罩噪声。我还大幅提高了 Amplitude,把 Roughness 调到 0.7(让位移更锐一些),并把 Substeps 增加到 10。
在 Advection 区域,我启用了 Advection,并把 Noise Type 设为 Zero Centered Perlin,因为我觉得它能给出更有对比度的结果。
最后在 Velocity 区域,我启用了 wind(风),这样噪声会有一点方向性——用来模拟云的低层部分更容易受到风的影响。

Cloud Wispy Noise 设置。
在这一串节点的末尾,我用一个 VDB Combine(模式设为 Maximum)把两部分合并成一个 density volume。它会在每个空间位置上取两份 VDB 的最大值,从而形成最终的云体外观。

使用 VDB Combine 形成最终的云。
调整密度剖面(Cloud Adjust Density Profile)
在链路末尾,我还尝试使用了一个 Cloud Adjust Density Profile。这里我用它让云的边缘更厚一些,同时降低内部密度。我觉得这能在“散射感”和“造型感”之间取得不错的平衡。这个节点我之后还需要继续深入探索,但当前这套设置对我的云来说效果很好。
💡
一个好用的小技巧是在节点顶部打开 Visualize Density。这样就会显示类似下图的 density 可视化。别担心:输出依然是 Density volume——这个可视化只在你查看节点时显示,不会影响输出结果。

在 Cloud Density Profile 节点里可视化密度。
最后的 VDB Activate(Final VDB Activate)
为了完成云体,我会做一点优化:确保未使用的体素被禁用(这又回到了 VDB 的 inactive/active voxel 特性)。把这个节点切到 Deactivate,它就会禁用所有值为 0 的体素——这正是我们想要的。
💡
如果你想可视化当前哪些体素是 active 的,可以使用 VDB Visualize Tree。

VDB Activate 设为 Deactivate 模式。
💡
到这里我也建议你放一个 Filecache 把当前云缓存出来。高体素数下这一段计算很重,缓存能让你每次打开场景时避免重复 cook。
模拟(Simulation)
自定义云体 Advection(Custom Cloud Advection)
在这个镜头里,我希望云有一种非常细微的“呼吸式扩张”,给它带来更多生命力。我试过好几种方法(包括常规的 Pyro sim),但由于我想要非常具体的外观,最后我自己做了一个简单的 SOP Solver。
下面是这个 setup 的概览。

网络概览 - SOP Solver 外部。

网络概览 - SOP Solver 内部。
💡
如果你不熟悉 SOP Solver:它是一种特殊网络,允许你在每一帧执行一串 SOP 节点,并把上一帧的输出作为下一帧的输入。
我们从 VDB Analysis 开始,逐个讲讲每个节点在做什么以及关键参数。我用 VDB Analysis 生成一个叫 Gradient 的特殊 Vector Field,并把这个自定义 field 命名为 grad。
它本质上相当于“体积的法线”:它会创建一个向量场,使体素的方向指向“表面”。你可以想象它非常适合用来做可控的膨胀——因为我们可以沿着这个向量场把体积“往外长”。

用 VDB Analysis 生成 Gradient。
在这个节点之后,我接了一个 VDB Smooth 来软化 Gradient 场,因为我觉得默认的 Gradient 稍微有点“碎”。示例里我把 iterations 设为 3,但你可能需要根据实际情况微调。和大多数仿真相关的东西一样,这里需要一些试验才能调到合适。平滑操作我使用 Gaussian。

对 Gradient 场应用 VDB Smooth。
接着我放了一个 VDB Advect。它是这套模拟的核心驱动节点,负责沿着 Gradient 场对体素进行 advection(更直白一点就是:推动/扩张/移动体积)。
第一个输入我接 Prev_Frame DOP Import,第二个输入接 VDB Analysis + VDB Smooth 这条分支的结果。
这里我没改太多参数,但有几个关键项值得讲:我把 Advection Scheme 改成 MacCormack,我发现它的结果最好,而且默认的 Pyro Solver 也用类似的 advection scheme。我还把 Substeps 增加到 4,Renormalization Steps 增加到 6。这些设置可能需要针对你的云做一些微调,但它们能作为一个不错的起点。
最重要的是:我把 timestep 除到了一个很小的值。在示例中我使用 -0.25/$FPS,速度比较合适。这个设置决定每帧体积 advection 的程度。注意这里有一个负号:如果不加负号,由于 gradient field 的方向特性,体积会向内收缩而不是向外扩张。
这个参数很可能是你会花时间最多的地方:数值越小,变化越慢;找到合适的值可能需要一点时间。另外它也强依赖 VDB 的分辨率:如果你提高了分辨率,你可能也需要进一步降低这个值。

VDB Advect。
为了完成 SOP Solver,我在末尾加了一个 VDB Activate。这一步非常重要:它会把 active voxel 向外扩展 1 个体素,确保下一帧 advection 时有足够的 active 体素空间。如果不做这一步,你的 density volume 会因为空间不足而产生一种奇怪的“方块体积感”。

使用 VDB Activate 来生长/扩展 active voxels。
最后一步,我在 SOP Solver 之后(外部网络里)又加了一个 Cloud Billowy Noise 来补一点细节。它是静态噪声,但我发现它能补回一些 advection 过程中被软化掉的质感。在这个案例里,我还给 Amplitude 做了一点动画,让它在模拟末尾更强一些。我觉得这样效果最好,不过不同场景可能会不一样。

把 Cloud Billowy Noise 应用到模拟结果上。
之后我建议你用一个 Filecache 把模拟缓存出来。
到这里,我们就得到最终的模拟云体,可以开始渲染了!你现在可以复制这套 setup,做出任意多朵云,并根据每个副本的需求去修改参数。

云模拟 flipbook——如你所见,这是非常细微的效果,但它在渲染里能贡献很多“活感”。
灯光与着色(Lighting & Shading)
现在我们进入 Solaris。途中我们会回到 SOPs 来生成一些东西,但本教程剩下的大部分 3D 部分都会在 Solaris 中完成。
布局(Layout)
首先我把体积导入 Solaris 并做一个好看的布局。由于这个镜头云的数量不多,我跳过了 proxy 的制作流程——如果你想了解 proxy,可以看我之前的教程 Creating Procedural Drift Ice in Houdini using SOPs and Solaris。
这里的 layout 很简单。我用 Volume LOPs 把每朵云加载到 /stage context。Primitive Path 我设成了我喜欢的路径结构(我倾向于按 /world/<department>/<assetName>/<primName> 来组织,但这完全取决于你)。下面是一个示例。
一个有趣的小点:你可以把 bgeo.sc 文件直接加载进 Volume LOP,它的工作方式和 .vdb 很像。我没有测试这是否更快/更慢,但能这样用确实挺酷的。
在每个 Volume LOP 下面,我都加了一个 transform 节点,用来调整每朵云的位置。

把云加载到 Solaris 并进行变换定位。
💡
默认情况下,volume 节点是 time-dependent 的,会导致场景 cook 时间更长。解决方法是:在 Volume LOP 的 Frame Range / Subframes 参数里选择 Sample Current Frame。这样会写入 USD Time Samples,避免当前节点及后续节点不必要的重复 cook。
如果节点旁边不再显示“时钟”图标,就说明生效了。

云布局网络。
最终,所有 transform 都汇入一个 merge。请先忽略网络边缘的两个节点(geometrysequence1 和 atmos)——我后面会讲到它们。
闪电(Lightning Strikes)
这个镜头里的闪电我希望做得相对简单:目标只是让它在几个随机帧上“亮一下/灭一下”(最后我在 comp 里稍微扩展了一点,但后面再讲)。
我知道我想让闪电基于云的模型来生成:一些在云内部,一些在云外部。
为此我把在 Solaris 里搭的 layout 导回 SOPs,并用 scatter 在云里散布一堆点。

在云布局中散布点。
接着我用 Connect Adjacent Pieces 把这些点连接起来,作为闪电几何的基础。

对散点使用 Connect Adjacent Pieces。
然后我创建了两个 group:start 和 end。我用一个 group 节点(类型设为 Points),并打开 Keep By Random Chance 来实现。因为我只需要很少的点,所以百分比设得很低。它们会作为程序化闪电的起点与终点。

Start group 设置。

End group 设置。
接下来是闪电生成器的“核心”。我在网络里接了一个 Find Shortest Path 节点。它基本就是字面意思:我把 start 和 end group 输入进去,它会尝试为对应的终点寻找最短路径。

Find Shortest Path。
生成出来的闪电有点太平滑了,所以我加了一个 Resample 来获得更多点,再用一个 Mountain SOP 加点噪声,让它更“闪电”一点。

使用 Mountain SOP 与 Resample SOP 给闪电增加细节。
现在让闪电动起来!我在一个 Blast SOP(设为 Primitives:)里使用了下面的表达式:
\`fit(rand($F4), 0, 1, 0, nprims(0))*8\`
它会为每个 primitive(闪电段)随机分配一个 0-1 的数,然后把它映射到 0 到 primitive 数量的范围,并乘以 8。
这会让闪电在每一帧随机出现/消失。表达式里最后的整数(这里是 8)控制闪电出现的频率:数值越大,出现越频繁;数值越小,出现越少。

用于闪电动画的 Blast 节点表达式。
在最后,我加了一个 Polywire SOP,设置合适的 Wire Radius;然后用 File Cache 把整个帧范围缓存出来,供渲染使用。

Polywire 与导出。
在 Solaris 里,我用 Geometry Sequence LOP 加载这个 .bgeo.sc 文件。Import Path Prefix 设成与我的 scenegraph 组织一致的路径;由于拓扑是动画的,我把 Topology Attributes 设为 Animated。
然后我接了一个 Cache LOP,设为 Always Cache All Frames,强制 Solaris 写入 USD Time Samples 并移除 time dependency,从而提升 cook 时间。

将闪电导入 Solaris。
你会注意到我还加了一个 Prune LOP。我用它默认禁用闪电 primitive,等到后面 lightning render layer 时再启用。
大气(Atmosphere)
最后,我还为整个镜头加了一点 atmosphere。我希望从画面底部向上有一个渐变的大气层,并在画面下方三分之一附近逐渐淡出。
我先创建了一个大致符合需求尺寸的 box(后续会在 Solaris 里再做变换),然后用 VDB From Polygons 把它转换成 Fog VDB。

对 atmosphere box 使用 VDB From Polygons。
接着我加了一个 Volume Wrangle,代码如下。记得点击 “Generate Spare Parameters”,因为这段代码会创建你在下图中看到的 spline ramp。
这段代码本质上是创建一个 ramp,并用它去乘 density。ramp 基于 bounding box 的 Y 最小/最大位置(通过 getbbox_max 和 getbbox_min 函数计算)。
f@density *= chramp("gradient", fit(@P.y, getbbox_max(0).y, getbbox_min(0).y, 0, 1))*.01;

用于控制 atmosphere density 的 Volume Wrangle。
照例我用 File Cache 导出这条链路的输出。

在 Solaris 中加载 atmosphere VDB。
导入 Solaris 的方式和云一样:用 Volume LOP 读取,再接一个 transform 来按需求缩放。
着色(Shading)
云着色器(Cloud Shader)
现在进入着色!先说云的材质。所有 shader 都通过 Material Library LOP 构建与分配。
云材质方面,我使用了 Houdini 20 自带的 cloud shader 预设,因为我很喜欢它的效果。你可以在 Tab 菜单里搜索 “Karma Cloud Material” 来创建。
我只调整了 Density Scale,并打开了 Use Secondary Anisotropy。我发现 Secondary Anisoptropy 能给云增加一些更清晰的“造型感”;否则很多细节很容易被 multi-scatter 效果吞掉。

云材质。
大气材质(Atmosphere Shader)
大气方面,我创建了一个 Karma Material Builder,进入内部后用一个 Karma Pyro Shader 替换所有内容,并把它接到 material output。这是一个非常简单的材质:我把 smoke color 调成深蓝色(希望它作为从底部到下三分之一的暗色渐变),并按需求提高 density。

Atmosphere shader 设置。
灯光(Lighting)
灯光部分我会分成两节:beauty render layer 和 lightning render layer。因为我把云的 beauty 与闪电分开渲染,方便后期合成时有更多控制。我们先从 beauty 开始!
Beauty Render Layer

Beauty render。
Beauty 包含云、大气以及两盏灯。

Beauty 网络概览。
主光(key light)我使用了一盏简单的 Distant Light,颜色偏淡粉,然后旋转它直到得到类似黄昏时分那种漂亮的背光感觉。
环境光/补光(environment light / fill)我使用了 Dome Light,HDRI 来自 Poly Haven 的夜空。随后我用 Color 把它染得更蓝一些:当 Dome Light 接了 HDRI 后,修改颜色会把 HDRI 乘以这个颜色值。

Key light。

Env light(dome light)。
渲染设置方面,我把 Render Engine 设为 XPU,因为我发现它快很多——即使没有 RTX 显卡也是如此。我是在一台带 M2 Ultra 的 Mac Studio 上渲染这个镜头的。
这里最重要的是把 Volume Limit 调到 22。为了让云材质有正确的散射效果,这个值需要设得相对高;如果你的云看起来很怪,优先检查这个设置。
最后在 Image Output 的 AOV 设置里,我输出了 Direct Volume 和 Indirect Volume,分别用于得到云的直射光与间接散射。它们在后期会非常有用。

Karma 渲染设置(Limit 标签页)。

Karma 渲染设置(Image Output 标签页)。
之后我接了一个 USD Render ROP 并输出序列。Beauty 完成!
Lightning Render Layer

fxLightning render layer。
由于我希望闪电作为单独的 layer,方便在 comp 里控制,所以我为它单独做了一条渲染分支。

fxLightning render layer 网络概览。
核心设置在 fxLighting_overrides 子网络里。我喜欢把网络拆成这种小模块,便于整体把握。我们进入子网络内部看看。
首先,在 Wrangle 节点里,我用一段 VEX 把灯的强度设为 0。我原本想直接 prune 掉灯,但我没法禁用 Karma 的 headlights(当场景里没有灯时的默认灯),不知道为什么关闭相关开关完全不起作用,所以最后只能用这种方式处理。

通过把 intensity 设为 0 来禁用灯光。
接着我把闪电 primitive “解 prune”。还记得我们之前在网络里把它关掉了吗?现在我们通过把 method 设为 Deactivate 并把 Prune 参数设为 0 来重新启用它,于是闪电又回到场景里了。

用 prune 重新启用闪电。
然后在 Material Library 里,我创建一个 Karma Material Builder,并把生成的 Mtlx Standard Surface 里除了 emission 以外的所有项都关掉,把 emission 设为 10,000。要在 Karma 里创建几何灯(geometry light),这一步是必要的。

闪电材质设置。
然后我在 Material Library 里把 shader 赋给闪电,如下:

闪电 Material Library 配置。
最后,为了让闪电几何真正作为几何灯工作,我加了一个 Render Geometry Settings,target 到闪电 primitive,并把 Treat As Light Source 设为 Yes。
注意:你需要先像上面那样给它一个 emission 材质,这一步才会正常工作。

将闪电 primitive 配置为光源/几何灯。
渲染设置上,我基本沿用 beauty 的设置,但把 Volume Limit 提高到 32,Path Traced Samples 提高到 512,因为我发现这样效果最好。

fxLightning render layer 渲染设置。
之后我同样接一个 USD Render ROP,只渲染闪电激活的那些帧。
现在我们进入收尾阶段了!如果你一路跟到了这里,我向你致敬——这是我目前写过最长的一篇文章。感谢你坚持读到现在。
我在之前的教程里收到一些请求,希望我也讲讲合成,所以这次我会比上次更细一些。这支片子在 comp 上花的功夫也比我平时个人作品更多,希望你能从中捡到一些实用的小技巧。
Beauty 处理(Beauty treatment)

Beauty 处理的网络概览。
对于 beauty render layer,我做了比较重的调色,把画面再往上推了 10% - 20%。你可以在下方看到最终结果;如果需要整体网络的概览,可以参考上面的截图。

Beauty 调色前/后对比。
我做的第一件事是:用 Karma 输出的 AOV 把 beauty 拆成 Direct 与 Indirect lighting。这很重要,尤其是我希望对 Indirect lighting AOV 做降噪。
如下图所示,Indirect Lighting AOV 决定了大部分观感;而 Direct Lighting 则很有用,因为它可以给云提供更清晰的造型(避免模型被散射淹没)。
关于调色,我最推荐的建议永远是:找一些优秀的电影/摄影参考,把它们导入,然后用 Nuke 的 viewer info/吸管工具去采样他们的数值。

Direct Lighting AOV。

Indirect Lighting AOV。
Direct Lighting

Direct Lighting 处理前/后对比。
Direct Lighting 的调色很简单:我通过降低 white point、略微提高 black point 来提升对比度。然后用一个 sharpen 节点(数值非常小)略微增强细节,让云不要那么软。
Indirect Lighting
Indirect Lighting 的处理会更复杂一点,因为我还决定对它做降噪。

Indirect Lighting 处理前/后对比。
降噪我使用了 Neat Video Reduce Noise plugin。我和这家公司没有任何合作关系,但他们的插件在很多 VFX 工作室中用来给素材/实拍 plate 做降噪。它还有一个副作用:对渲染序列的降噪也相当有效,所以我这次就用它来处理渲染。

降噪前/后。
然后我做了一个比较细微的调色:通过 grade 节点里的 colored gain,把橙色再往外推一点;随后用 lumakey 作为遮罩做 color correct,大幅提升最亮区域的数值,并做 hueshift,让色相更贴近参考。

用于 color correct 的 Luma key(用 Keyer 节点创建)。

使用 Luma Key 做遮罩后的 color correct 与 hue shift 前/后对比。
最后,我用 plus 模式的 merge 把它与 Direct Lighting 合在一起,把原 beauty 的 alpha 拷贝回来,并做了一个很轻微的 edge blur 来软化边缘。最终得到如下结果。具体连接关系可以参考前面那张网络截图。

最终的 beauty 合成效果。
背景(Background)

背景网络概览。
这个镜头的背景我直接在 Nuke 里做了一个非常简单的版本,因为我觉得这一版已经足够。我用一个 constant,设成从参考里采样到的颜色,然后把它与一个 ramp 相乘,让画面顶部更暗。
之后我用 RotoPaint 画了几颗 1 像素的小星星,再加一个 soften 轻微模糊;最后加一个 grade 把整体再压暗一些,让它更贴合 beauty。

背景(这张 jpg 里细节可能不太容易看清)。
然后我用常规的 A over B merge 把背景与 beauty 合成在一起。记得确保背景也有 alpha。
闪电(Lightning)

调色后的闪电层。
闪电层最终是以 plus 模式(merge node 的 plus operation)叠加到 beauty 上的。这样我就能单独调闪电的色与强度,来微调外观。
首先我对闪电层也做了一次降噪(和前面类似,使用 Reduce Noise 节点)。随后我用一系列 color correct、grade 与 hue shift 对闪电做全局调色。

调色后的闪电层。
这里有个点需要注意:我对其中一个 grade 节点的 gain 做了动画。这样可以对某些闪电单独加强,因为其中一些太暗了。
我还在某一帧上用 RotoPaint 手绘了一条闪电,然后接一个 erode(让它细一点)。这是用来修复某条几何闪电不够亮的问题。通常我会为这条闪电做一个带 alpha 的 utility layer,但因为只是一帧,我就直接手工处理了。它会叠加在闪电渲染层之上。

手绘闪电。
在把它加入总合成流之前,我加了一个 frameblend,用来为每次闪电产生一个额外的“残影帧”。我把 Number of Frames 设为 2,并把 mix 做了动画:只在闪电击中的下一帧让 mix 为 1。

加入闪电后的合成。
最后我用 plus 模式的 merge 把它合入主合成流。

加入闪电后的合成。
最终细节(Final details)

Final details 网络概览。
最后我加了一些收尾细节。首先我用一个 grade 节点略微降低 gamma(让对比稍微更强一些)。
然后我使用了多个 Nuke Gizmo,包括 Chromatik、Exponential Glow、Lens Engine、Grain Advanced,用来添加色差(chromatic aberration)、一点点 glow、非常轻微的暗角(vignette)以及胶片颗粒。这些节点都可以在优秀的免费工具包 Nuke Survival Toolkit 中找到。
最后我加了一个 reformat,确保没有任何 stray bounding box。
结语(Conclusion)
到这里这篇教程就结束了!再次感谢阅读,这篇文章写起来相当长,所以非常感谢你愿意看到这里。如果你有任何问题或建议,欢迎在下方评论区留言。
如果你对类似内容感兴趣,也可以考虑订阅我下方的 newsletter。这样我发布新文章时,你会直接在收件箱里收到。