使用Quartz程序化生成音乐

Adam Block、Paul Oakley和Marcel Swanepoel
通常,如果玩家在《堡垒之夜》的两个赛季之间登录游戏,就会看到一个静态屏幕,在新赛季开始之前充当占位符。为了庆贺第三章第三赛季的开始,《堡垒之夜》团队打算创造一些惊喜。

他们利用虚幻引擎的一些核心功能,打造了一个丰富多彩的沉浸式场景,其中充满了源自新赛季的视觉元素。

然后,他们将这个场景同步到了Quartz。Quartz是引擎中的原生子系统,可用于实现采样级精度的音频回放。以程序化方式生成的音乐部分连同视觉效果,都被“订阅”到了Quartz时钟,使森林中美轮美奂的生物荧光完美地伴着音乐闪烁。

这是他们首次在《堡垒之夜》的开赛活动中做出这样的尝试。在这篇文章中,参与该项目的部分Epic设计师和美术师将向我们介绍他们的实现方式。
 

休赛屏幕上的电子游戏音频

大家好!我是Adam Block,在Epic Games担任技术音效设计师。我撰写这篇技术博文是为了分享一些想法,并展示我和团队最近使用虚幻引擎的Quartz子系统为《堡垒之夜》实现的一些成就。希望各位在读完这篇文章后,能够对Quartz及其工作原理产生更深刻的理解,并取得灵感,将Quartz纳入自己的项目。感谢大话花时间了解这项功能——它超级酷。

Quartz是什么?

Quartz是虚幻引擎中的一个子系统,它能够安排事件,使其发生于确切(采样级精度)的时间点,甚至是音频缓冲之间。当我们将Quartz用在音乐环境中时,可以将它看作站在管弦乐队前的指挥家。指挥家的右手会按照一定的模式挥舞指挥棒,帮助所有演奏者遵守节奏,而左手偶尔会伸出来,在不同声部的音乐家应该开始演奏的一刹那精准地发出信号。

Quartz支持采样级精度的音频回放,并能够为音频引擎提供足够的时间,让它将事件传回游戏线程,供PFX或其他游戏事件使用。很酷,对吧?Quartz可以使用任何uSoundbase对象(包括波形文件、Sound Cue、MetaSound或音频组件)播放“Play Quantized”事件。

在发生量化回放事件的确切瞬间,我们可以触发粒子爆发效果,点亮光源,等等。在Quartz子系统中,我们可以根据歌曲的节奏和节拍,定义任意数量的特定“量化边界”(例如:每四小节这样做;每八小节的第二拍那样做)。

虽然Quartz是实现音乐想法的理想之选,但在非音乐用途中,它也能表现出色。事实上,在《堡垒之夜》中,有几件武器极快、极精确的射击音频就是通过Quartz触发的。想象一下,当你使用Quartz时钟规划冲锋枪的射击音频时,武器射击将以每分钟110个节拍的时钟速度演奏每个三十二分音符。也许还可以让粒子特效订阅Quartz时钟,在每次射击时精确产生粒子特效。如果没有Quartz,受视频帧率的限制,短促的枪声往往会不协调,显得像马蹄声。

程序化音乐

在《堡垒之夜》中,我们有时需要定制音乐,以便在新赛季启动前大约10小时的“停赛期”中播放。这对我们来说是一个机会,我们可以使用Quartz创造一种由程序生成的非循环聆听体验,玩家将不会在其中察觉到明显的音乐模式或重复的循环。通常,如果一遍遍地聆听相同的内容,将很容易产生腻烦或厌倦感。因此,我们通过这种方法,采用了较小的乐句“块”,打乱它们,并在运行期间随机选择要回放的变奏。

我们在蓝图中构建了音乐控制器,用来处理这个逻辑。简而言之,它会打乱歌曲列表,并随机选择一首歌曲。一旦选择了一首歌曲,所有的音乐分轨(低音部、鼓点、旋律、打击乐,还有和弦等等)都会被引用为“当前歌曲”,而使用了Quartz子系统的蓝图逻辑只需决定播放曲目以及播放时机。

Quartz有一个巨大的优势:由于它是虚幻引擎的子系统,开发团队中的任何人都可以订阅我的Quartz时钟(例如,“Song 1”和“Song 2”等等),并获得当前曲子的所有小节和节拍。当其他人获得了小节、节拍等信息之后,他们就有了创作的自由,可以完美地伴随着音乐,做他们想做的任何事情。

不过,在我们的案例中,为了最大程度地简化其他团队的工作,我们只是简单地通过Quartz时钟在小节、节拍和分拍上调用委托。这些委托绑定了视效团队用来执行视效变更的事件。以下是我们整理的工作总结:

主数据资产:特定于歌曲的信息

每首歌曲都是其自身主要数据资产的集合。这些数据资产包含了与曲子相关的所有信息。曲子的数据资产囊括了:曲子名称(我们将这个变量称为“时钟名称”)、节奏、以小节为单位的歌曲时长(即我们希望这首曲子以程序化方式播放多长时间。在播放结束后,播放列表将被打乱)、每条音轨所使用的小节和节拍数(例如,低音线的每个乐句包含八个小节,最后一拍是第四拍;旋律的每个乐句包含四个小节,最后一拍是第四拍),以及每条音乐分轨(音轨)的所有uSoundBase资产。

我们参照了DJ混音台上的“A盘”和“B盘”的概念。所有声部都在“A盘”上开始播放(即旋律A),播放完毕后,将随机选择另一声部在“B盘”上播放。虽然我们在使用音频组件时并没有考虑到这种行为,但交换乐句和参考对唱系统的想法在当时似乎是有意义的。

有一个由“A盘”旋律、和弦声部、鼓点乐句、低音线和打击乐声部组成的数组,其中的内容都交换自或挑选自“B盘”的对应资产数组。这个系统基本上以程序化方式生成了一套对唱方法。数据资产只需保留这些资产以及定义了各个音轨时长的参数和设置,因此,持有Quartz时钟的音乐控制器知道何时寻找、打乱和排列新的音轨,用于播放。

开始播放

我将向大家介绍这个流程的具体工作原理。首先,我会推送一段混音,确保如果还有未播完的菜单音乐或存在某种极端情况,其他任何音乐都会让位和/或消音。我还会慢慢增强沼泽的环境循环音效,因为从视觉上看,这是一块很酷的低保真沼泽区域。回放15秒后,环境音效将在30秒内渐渐消失。此时,我会播放唱片机落针音效,就好像正在播放的是一张唱片(呼应“低保真”的主题),然后我启动了设置,回放音乐。

打乱数据资产

在这个函数中,我要打乱DataAsset数组中的元素,并将其中一个元素设置为CurrentDataAsset。我将从DataAsset中取得BPM,将其设置为“Next BPM”变量,并将布尔值“IntroComplete”设置为“false”,因为这是我们第一次播放新歌曲。

设置歌曲时长

从这里开始,所有逻辑都是从“Current Data Asset”变量中提取的。例如,在这个新函数中,我将取得歌曲时长(小节数),并将其设置为叫作“Current Song Duration”的变量。音乐控制器就是通过这种方式知道何时再次打乱歌曲列表的顺序,并选择另一首歌曲(Quartz会为我清点小节数)。

时钟是否存在

当工具第一次执行逻辑时,我要根据曲子名称(例如“Track02”)检查时钟是否已经存在。在这个例子中,它并不存在,所以我要为这个时钟设置Quartz量化边界,同时创建一个新的时钟,以当前歌曲的名称命名。

创建和缓存量化边界

在这个函数中,我要创建和缓存我所需要的各种Quartz量化边界。例如,“每四小节”、“下一小节”和“立即”,这些情境对安排回放很有帮助。通过创建这些量化边界,并将其缓存为变量,蓝图图表将显得不那么杂乱无章,并且当我播放量化音乐时,可以轻松访问各个边界。

重置布尔值

在这个函数中,我将重置前一个循环中可能设置的所有布尔值。

检查歌曲形式的循环

在这部分,我将检查这是否是我们第一次循环播放一首歌曲。如果是,我首先会检查目前是否有时钟处于活动状态。如果没有在运行的时钟,我就会抓取歌曲的立体声四小节前奏,将它设置为音频组件,并使用“立即”量化边界执行“Play Quantized”。

这个“立即”量化边界还会为我启动时钟,并重置传输(这些是Quartz量化边界中的选项)。基本上,此时我可以确定这是我们第一次播放一首歌曲,我要将歌曲前奏排列到最前面,将回放传输设置为00:00:00。
接下来,我要执行“Play Quantized”,并订阅不同的量化节拍。此时,我们将演奏各个分拍,并使用此事件清点和发送委托,作为底层时钟脉冲事件。

主要回放逻辑

当小节和节拍开始跳动时,我要分别为小节和节拍设置变量,以便清点小节的总时长,并了解我们所在的节拍。我稍后会通过这些变量了解打乱歌曲播放列表和选择新歌曲的正确时间。
在每个节拍上,我都会检查是否应该更换歌曲:
在这个“Calculate Song Duration”函数中,我引用了当前数据资产的小节总时长,并在每个节拍上计算“现在是否应该更换歌曲?”它引用了“Bar Counter”变量,并使用取模运算在正确的时间告知我播放时长达到了“Current Song Duration”的值,并且我们处于该小节的第四拍。当得到这一结果时,我们就知道此时应该打乱歌曲列表,然后继续执行我们的逻辑。
当需要更换曲子时,我会调出DJ转动唱片的音效,生成过渡音,这有助于我们结束第一首歌,并开始下一首歌的前奏。
最后,我们到达“InitiateNewTrackPlayback”事件,它让我们跳转到蓝图逻辑的顶端,重新开始执行同样的逻辑。这一次,我们将绕过最初的落针音效和环境循环音频。

程序化音乐的生成

当开始播放一首曲子时,它将随机选择和排列要播放的音乐元素,此时会有一些事情发生。我们来一窥究竟。在每个节拍上,我都会通过这些“Calculate Part Timing”函数检查是否到了播放新歌曲的合适时间。
举例而言,低音声部会引用“Current Data Asset”,并检查我们在选择变奏之前,是否处在正确的小节和节拍。如果满足了正确的条件,我们将继续沿着执行路径,进入下一段逻辑。
现在,我们要选择接下来适合演奏哪一组低音声部。之前提到过,每条音轨都有两组:“A组”和“B组”。我之前称它们为“盘”。这有点像DJ混音台上的A盘和B盘——如果目前正在播放“A盘”,那么就从“B盘”中随机选择一首歌曲。如果目前正在播放“B盘”,则从“A盘”中随机选择一首歌曲。
做出选择之后,我会为变量“Deck A Is Playing”执行取反操作,这样一来,下次执行逻辑时,就会选择另一组。从本质上说,这个布尔变量每次都会被翻转:如果正在播放A,就选择B,并将B设置为活动状态;反之亦然。

将声音变量传递给Play函数

由于每个声部都是被随机选择的,蓝图会将该声部的uSoundBase引用(音乐片段)作为输出传递给“Queue Next Deck”函数。
“Queue Next Deck”函数执行下列检查:这是四小节的乐句吗?这是过渡性元素(两个小节)吗?这是八小节的乐句吗?根据结果,蓝图会使用使用正确的量化边界,将声部分配给“Play Quantized”。
差不多就是这样了!如大家所见,Quartz是一个非常强大的子系统,允许你做出安排,在精确的时间点以采样级精度回放音乐。在这套设置中,我所做的就是将音乐的音轨拆分开来,将它们分成四小节和八小节的乐句,使用不同的量化边界做出安排,在恰当的时间播放它们。

当Quartz完成清点,同时我们也达到了每首歌曲的最大小节数后,我们只需打乱“Data Asset Pool”中的数据,选取另一首歌曲,设置BPM,清除所有特定的布尔变量、门节点和DoOnce节点等对象中的值,然后再次执行相同的逻辑。

重要的是,大家必须知道,我解决这个问题的方法并不是“专门的”方法,也不是“Epic官方”的方法,它只是“某一种”方法。今后,我们将不断完善和优化这个控制器,在这个过程中,我们将有大量机会进行巩固,减少冗余,并将MetaSound整合到设置中。我鼓励所有对游戏音乐(尤其是对程序化音乐生成)感兴趣的人深入研究Quartz,使用对你而言最有意义的方式构建自己的系统。请记住,Quartz子系统并不是一款只能用于音乐的工具,除了音乐应用之外,还有大量Quartz用例。当用到武器、事件时,或在整个项目范围内同步任何东西时,都是你创建Quartz时钟完成任务的绝佳机会。

希望各位对虚幻引擎Quartz子系统有了更深入的了解,并对如何在自己的项目中使用它产生了初步想法。再次感谢,保重!

创建视觉效果

大家好!我是Paul Oakley,在Epic Games担任营销美术总监。我将谈谈我们如何创建出《堡垒之夜》第三章第三赛季开始前的屏幕视觉效果。
总体任务是创建能让玩家陶醉其中的低保真环境屏幕。这完美地吻合了新赛季的基调以及自然的概念。我们认为“泛着生物荧光的森林”这个想法非常合适。

我们在开发环境时使用了透视和景深,确保它极具抽象美感。你能看清前景中的细节,但随着你看向远方,景物将变得越来越抽象。
当我们讨论这个部分的低保真配乐时,我问,不如把生物荧光森林的想法与配乐的想法联系起来,让荧光闪烁的各个阶段与节拍起伏相匹配,这样怎么样?这是让音频团队大显身手的地方。

我们让CG团队创建了静态的美景渲染。然后,我们将图像的各个通道(所有荧光树木等)载入到了不同的贴图中。UI/UX团队创建了UV卡片,将图像映射到卡片上。

他们使用这些通道驱动强度和颜色的变化,并将其连接到Quartz时钟。然后,这将驱动由各个节拍振幅触发的强度乘法的值。

从根本上说,这是被映射到UV卡片上的CG图像,它用通道表示图像中的各种遮罩元素。
之后,我们定义了如何改变颜色,如何改变强度,以及如何使用柏林噪声或程序性噪声作为数学函数在整张卡片上改变起伏或运动。

对开发者来说,它有什么令人兴奋的地方?

这个项目有许多很酷的东西能够引起开发者的兴趣。首先,这是将传统电影制作融入实时工作流程的实例。

它用到了老派电影制作原则,例如,无论是自发光遮罩、纹理遮罩还是雾体积通道,都采用了AOV渲染输出或者说次级输出。
如果你有美景板和这些额外的通道。通常,你会合成它们,并完成离线渲染,这样又得到了静态图像——所以,你的做法基本上就是把它夹在中间,重新压缩,然后就能得到这张图像。

但这种方式不同。你要拿到所有输出,在实时环境中将它们馈入卡片,从根本上说,这张卡片稍后将由摄像机实时捕捉。而且在卡片中,你还需要重新合成所有次级输出,然后通过数学方法,使用这些遮罩进行修改。

Quartz也会与这些遮罩相连接,修改它们,并在摄像机的实时捕捉下使它们起伏。然后,图像将回到屏幕空间,呈现给作为观众的你。这是一种打破常规的思维。它采用了一堆预先存在的东西,然后以非常前卫的方式进行处理。

UI和着色器

大家好,我是Marcel Swanepoel,在Epic担任《堡垒之夜》高级UI美术师。我将介绍我们为《堡垒之夜》第三章第三赛季的开赛屏幕所做的一些UI和UI材质设置。

在这个功能的开发周期之初,我们对所需的UI技术要求做了一些早期探索。这里的目标是生成一个能够接收动态音频输入的独立系统,然后用它控制最终视效输出。同时,我们必须确保系统足够灵活,以便作为加载屏幕使用。

对于最终系统,我们选择将音频范围从蓝图输出到材质参数集合(MPC)。这可以作为所需的动态输入,将数据传入UI材质。在UI材质中,我们将用它对各种渲染通道以及用于构建最终视觉画面的2D视效施加影响。然后,在加载屏幕的小部件蓝图中,我们会引用该UI材质。我们还会使用虚幻示意图形(UMG)为加载屏幕添加文本和计时器等其他UI元素。

UI材质的设置属于比较复杂的一方面,因为需要重新合成渲染团队提供的各种渲染通道。我们从颜色通道开始,对各种通道进行分解。颜色通道是经过完整渲染的图像,其中关闭了被照亮的植被、河流和散景。此外,任何反射自或反弹自这些元素的光线也被关闭了。最终,这将是加载屏幕启动后、音乐开始前,第一个出现的静态镜头。

我们在各种纹理通道中引入了植被自发光,因为这些通道需要承载完整范围的RGB值。这也使我们能够生成可被不同时间间隔(取决于音频)触发的植被组。这允许我们赋予所有自发光植被更大的变化。
Z深度是整个图像中另一个被广泛使用的重要通道。它可用于控制和降低植被在场景深处的自发光强度。植被在场景中越靠后,自发光强度就越低。现在有一个很好的问题:为什么不在植被通道本身渲染场景深度和强度?答案是,我们倾向于让所有的植被都能在0-1的强度范围内自发光。这使我们能够在需要时真正增强效果,并在重新合成所有通道后更有效地平衡整个场景。总之,这允许我们更有力地控制最终输出和平衡性。

有了Z深度,我们还可以将渲染通道分为前景组和背景组,从而进一步扩展植被自发光的范围。大体上,我们在一个自发光通道中使用单个纹理,在屏幕上感觉自然的地方限制住Z深度,然后将限制的Z深度作为遮罩使用,这样我们就有两个自发光输出。

然后,我们可以将前景和背景映射到不同的音频输出,从而允许背景的循环周期快于或慢于前景。另一种做法是将整个自发光通道映射到单个音频输出,这样的话,整个屏幕上的循环将保持一致。这并不理想,因为我们很想增加变化。通过使用Z深度分割自发光,我们可以从单个植被自发光通道中获得双倍的变化量,获得想要的效果。
当谈到如何创建覆盖整条弯曲河流的条纹效果时,大家可以再次看到我们对Z深度通道的依赖程度。这些条纹是由通道组成的距离场,它们在自定义UV的V通道上平移。

当条纹在场景中移动时,我们使用Z深度限制距离场,这会让人产生一种错觉:它们正从背景中的焦点外移动到前景中的焦点内。此外,我们还使用Z深度影响场景中条纹的强度。自定义UV是按照河流轮廓生成的,所以我们可以从“0-1”空间中映射它,并让条纹适当地与轮廓平行。我们使用音频输入对条纹的显示强度和数量施加影响。
为了提高场景的完成度,我们使用散景和光线增添了一些气氛。它们同样都依靠Z深度融入场景,并适当地遮蔽了一些区域。散景效果是使用随机噪声函数生成的,用于驱动圆形有向距离场(SDF)的运动、尺寸和不透明度。光线遮罩是渲染团队提供的另一种通道,我们将它与Z深度、自定义UV遮罩放在一起,做了通道堆叠。通道打包是将多达三个的灰阶纹理合并到一个RGB纹理资产中的理想方式。每个颜色通道都持有单独的灰阶纹理值,这样就可以在材质中提取单独的通道。这对于优化增益非常有用。

我们与渲染团队密切合作,对材质和纹理做了最后的平衡和调整,确保将数值保持在一定的范围内,并忠于他们最初设定的场景。

总之,如果不使用UI材质,我们几乎无法构建出能够满足我们成功标准的独立系统。这一点从最开始就是显而易见的,我们唯一的真正问题是如何将音频输入转换成有意义的动态视觉体验。

对我来说,UI材质管线为UMG带来的灵活性是不容忽视的。它十分强大,用途广泛,过去五年,我们在UI开发中已经广泛地使用了它。

    立即获取虚幻引擎!

    获取全球最开放、最先进的创作工具。
    虚幻引擎包罗万象,并提供完整的源代码访问权限,开箱即用,诚意十足。