写在前面
之前的一个博文里分享了日本Unity酱的项目,如果大家有去仔细搜Unity酱的话,就会发现日本Unity官方还放出了一个更完整的Unity酱的项目,感觉被萌化了!(事实上,Unity日本经常会分享一些开源项目,要常关注github~)
一些效果
项目里还有有一些特效实现可以借鉴下的~
MusicPlayer
这个项目里的特效有个最大的特点就是,特效会根据音效做出反馈。这个功能主要是通过作者的另一个开源插件Reaktion来实现的(Keijiro Takahashi是个多产哥,这哥们感觉一年时间都用在写开源项目了,一年有几十的repositories。。)只可惜文档都不是很全,大多需要自己去稍微读一下源码。Keijiro曾经为Reaktion写过一版文档,不过看来没有维护下去……
想要让Reaktion工作就需要有三个关键部分,音频注入器Injector、音频核心处理器Reaktor以及接收信号的装置Gear,Injector把音频信号传递给某个Reaktor,然后场景里所有的Gear会监听某个Reaktor的输出,它们之间的链接都是通过GenericLink类实现的。也就是我们需要两个links,一个link(InjectorLink类型)负责让每个Reaktor找到一个Injector,一个link(ReaktorLink类型)负责让每个Gear找到一个Reaktor。它们都有四种链接模式(在GenericLink.cs中被定义):Not Bound,即不绑定输入;Auto Bind,找到离该物体最近的那个输入器(先在自身身上找,不然再找父亲,不然再找儿子,不然再找全场景);Bind By Reference则可以让我们直接拖一个输入的引用;Bind By Name则会在场景里找名字为该字符串的特定输入。
因此,整个过程大概就是:
- 在场景里添加一个AudioSource,然后在同一个物体上添加AudioInjector.cs,以及添加Reaktor.cs,保证该Reaktor可以找到这个Injector(可以直接把模式设成Auto Bind就会直接找到自身绑定的这个Injector了)。我们可以调整Reaktor上的属性来对音频信号进行调整。
- 想要接收声音信号的话可以在自定义脚本中声明Reaktion.ReaktorLink类型的变量。例如:
public Reaktion.ReaktorLink spectrum1; public Reaktion.ReaktorLink spectrum2; public Reaktion.ReaktorLink spectrum3; public Reaktion.ReaktorLink spectrum4;
声明了四个音频来源,那么在面板上就是这样的结果:
我们可以通过不同的链接模式来为它们绑定不同的Reaktor,如上所示。然后在代码里通过Reaktion.ReaktorLink.Output就可以直接得到输出信号了。
- 作者为我们内置了一些常见的信号功能,例如把信号传递给材质来设置属性值(MaterialGear)、设置Transform属性(TransformGear)、设置粒子系统属性(ParticleSystemGear)等。我们可以直接使用这些内置的脚本(都在Reaktion/Gear文件夹下)。
到这个具体项目里,所有的Injectors和Reaktors都在名为MusicPlayer的prefab中。它的结构如下:
MusicPlayer包含了五个子物体,其中四个子物体(Spectrum 1,Spectrum 2等)各包含了一对Injector和Reaktor,之所以有四个是因为作者想要使用不同的过滤器(BandPassFilter.cs)来过滤同一个音频,得到四种音频信号。注意到每个Spectrum上的AudioInjector上的Mute属性被勾选了,这意味着虽然它们身上都有一个AudioSource但并不会发出任何声音。真正的音乐是由第五个子物体Main负责发出的。
舞台光柱、台标、光圈和环状大轨道
这个项目里很多效果都会随着音效变化,变化比较相似的是舞台后面光柱(Pillars)、Unity Chan的台标(Ribbon)、和环状大轨道(Orbit Inner和Orbit Outer)。
这些都是使用MaterialGear来配合材质的。下面是光柱(Pillars)的面板:
它使用了3个材质,其中第二个材质YellowSurface和第三个材质RedSurface使用了自定义的自发光Custom/Untextured Emmisive Surface,这个shader其实就是一个有最简单自发光效果的surface shader,它的自发光大小是靠属性_Amplitude控制的:
o.Emission = _Emission * _Amplitude;
为了让自发光大小随着音乐变化,这里使用了两个MaterialGear,分别对应一个材质。为了让两个材质的变化不要完全一致,这里把它们的Reaktor来源设成了不同的模式。MaterialIndex用于指定是作用于当前的第几个材质,后面的TargetType和TargetName是设置的属性,FloatCurve是可以让我们进一步对输入信号进行加工。
舞台音响
舞台音响也是个很有特色的效果。它的音波(其实就是很多圆圈)会随着音乐忽大忽小。
这个就是通过AnimatorGear来触发Animator里面的状态来实现的。
酷炫的地板
Unity Chan脚下的地板(Visualizer)特效很是酷炫,一会画圈圈一会画蜂巢的,还能随着音乐变化,很有意思。
Visualizer大概是这里面最复杂的一个。作者不仅为它自定义了音频信号处理脚本,它使用的shader也比较复杂,还用到了额外的渲染纹理来实现反射效果(可以从下图的材质上看到有些许舞台反射效果)。
Visualizer.cs脚本还是比较简单的,它声明了四个用于连接Reaktor的ReaktorLink,分别对应了MusicPlayer中的四个Reaktor。每次Update时,它从ReaktorLink.Output得到四个音频信号,再在OnWillRenderObject中、在渲染该物体之前,把信号传递给自己的材质,并找到渲染物体的摄像机,把该摄像机的视角-投影矩阵的逆矩阵传给材质,这主要是为了根据反射的深度图重建每个点的世界坐标,再根据坐标的高度值(y)来计算一个反射程度的衰减值。
另一个脚本MirrorReflection比较复杂,负责渲染反射的彩色纹理和深度纹理。有兴趣的可以仔细看下,大概就是在地板附近放一个摄像机,然后设置它的各个矩阵和状态,得到两张渲染纹理,再传递给材质。
Visualizer.shader也比较复杂,感觉性能堪忧,竟然还是用surface shader写的。Anyway,它的那些光圈和格格的效果全是代码实现的,然后添加到自发光变量上。除了上面的自发光颜色,还添加了舞台反射的颜色,反射点距离地板越远,反射强度越弱。此外,还使用了模糊技术,取了9个采样点平均后作为最终的反射颜色。计算量还是很大的,优化做的也不是很好,采样坐标的计算等都放到了surf里计算,而且还有一次矩阵乘法。感兴趣的需要自己优化下了。
舞台前灯
Unity Chan的舞台前灯(场景里的Front Lights和Front Lights Beam物体)虽然不会随音乐变化,但它用的shader也挺有意思的。Front Lights的颜色(其实就是一个使用了自发光的surface shader)会随着时间变化,这是靠Animator实现的。而它发出的光(Front Lights Beam)是靠一个椎体实现的,而且看起来好像有飘渺的效果(也有Bloom特效的功劳),这主要是靠Custom/Light Beam来实现的。这个shader是一个双面半透明的vf shader,包含一个主颜色(控制光的整体颜色)、渐变纹理(控制光由强到弱)、两张噪声纹理(控制光的移动效果)、噪声程度和噪声速度(控制噪声的大小和平移速度)。有一些有趣的法线:
- 计算噪声纹理的采样坐标时,作者使用了世界空间位置的xy来让各个前灯的光都不一样:
o.uv1 = wp.xy * _NoiseScale.xy + _NoiseSpeed.xy * _Time.y;
- 计算光的透明度时,作者还使用了一个衰减因子就是观察方向和法线的角度,当角度在某个区间范围内该衰减因子为0。这样做的目的是为了让光锥的边界更加平滑,这一点很重要。
float falloff = max(abs(dot(camDir, normal))-0.4, 0.0); falloff = falloff * falloff * 5.0;
舞台大屏幕
另一个挺好的就是后面的大屏幕(Back Screen)。
这个肯定是通过另一个摄像机渲染一张渲染纹理得到的,但上面的屏幕黑条效果使用了Custom/Back Screen的shader。
shader还是很简单的,就是在主纹理上添加了一个条状纹理,并使用sin配合lerp函数添加一个不断闪烁的效果:
float4 frag(v2f i) : COLOR
{
float4 color = tex2D(_MainTex, i.uv0);
float amp = tex2D(_StripeTex, i.uv1).r;
amp = _BaseLevel + _StripeLevel * amp;
float time = _Time.y * 3.14f * _FlickerFreq;
float flicker = lerp(1.0f, sin(time) * 0.5f, _FlickerLevel);
return color * (amp * flicker);
}
其他
还有一些就不一一写了。比如摄像机的运动很飘逸啊,这是靠CameraSwitcher.cs在很多个位置之间不断lerp得到的;激枪(Laser)发射的效果,是靠Animator实现的;各种撒花(Confetti),是粒子系统。
写在最后
这个项目还有一个特点就是基本所有的物体都是后来加载到场景的,运行前整个场景基本是空的,作者说这样有助于多人协作。
关于作者之一的Keijiro Takahashi再多说几句,Keijiro之前在索尼工作了十年,然后跳到了Unity日本,期间做了大量关于Unity的开源项目,有一些还是很不错的~