spec定位是能够完成实时渲染、可由用户自定义且极具艺术感的音频可视化图形。
这个项目目前已维护一年半左右,除去第三方库外,仍具有10w+代码量,4w+的下载量,其中架构设计,UI设计,代码编写均由我一人实现。
起因只是想做一个音频可视化播放器,为了美化图形效果,提升绘图性能,才逐渐接触到图形学、OpenGL、UE4等一些东西,本来的计划是学完基础,直接在UE4上一直发展,结果折腾了几个月,越学越迷茫,感觉什么都能试一下,但完整的东西却什么也做不出来,可图形渲染管线明明就那么点东西。(很明显,不懂是因为我只关注顶层的接口,却不了解底层的实现)
而我在学习过程中,无意间看到了这样一句话:“道生一,一生二,二生三,三生万物” ——《道德经》
犹豫了很久,最终也还是下定了决心:我要追求自己的道!
而Spec,则是我用来验证,阐述道的工具。
笔者很喜欢听音乐,无意间在朋友的电脑上看到一款频谱壁纸,当时就萌生了,自己用代码写一个的想法,但想归想,却找不到地方下手,只能作罢。直到遇上了我的软件工程老师,他给我一个好的开始,为了完成他的课程设计,我开始学习Qt,无意间在Qt的官方Demo中找到了一个频谱仪项目,当时不知道有多激动,可是七八千行的代码对当时只会做编程题的我来说,简直就是泰山压顶,我不了解音视频编码,不了解多媒体驱动,甚至Qt,也才学了没多久,可是难道有项目给我抄我都做不了吗?
于是我根据代码的逻辑,不断地剔除无用代码,就跟拆炸弹一样的,项目崩了就使用之前的备份还原,崩了就还原,崩了就还原,花了两个多星期的时间,终于是让我改了只有一千多行的核心代码,这个过程也让我对音频处理的流程也有了个大概的认知,于是我用这部分代码,结合Qt的绘图机制,完成了Specinker的第一个版本
现在回头看看,是不是很烂,但这么点东西,我做了两个多月,不过当时收到了同学的鼓励,也是挺开心的,然后我把这个项目发到了网上,结果,还真有人用!然后我就跟磕了药一样,开始进行疯狂的扩展,我开始把原来的频谱播放器改成了频谱设计器。
结果写到一半,我突然发现我是不是脑袋有毛病,既然这个是制作好显示在桌面上,那我为什么不直接在桌面上进行设计,还弄个预览框,脱裤子放屁。
然后只能把一部分代码抛弃了重新写,然后有了下面这个版本(也就是动音,网上使用的多的可能就是这一个版本,多谢大家的宣传哈)
这一版在使用体验有了很大的变化,简单了很多,比较容易上手,代码量已经很多了,破万了已经,当时还很激动的跟老师说:“我嫌弃Qt的调色器太丑,自己做了一个,写了两千多行代码呢”,我本打算在这个版本上持续扩展下去,结果问题来了:
博主,桌面感觉频谱有点延迟啊,图形还有点卡顿。
我苦口婆心的说到:“这个东西很耗资源的,有点卡顿应该是正常的。”,我也一直以为确实是这样的,直到有人拿spec的效果跟wallpaper engine做了对比,我才知道是我自欺欺人,那咋办?只能改了呗,难道我能认怂?
然后我把疯狂找资料查阅怎么高效录制电脑声音
,最终让我发现了windows底层的声卡驱动API(IMMDevice),我利用这套API写了音频采集的模块,替换了原本的Qt音频API,基本上可以说是重写了我从Qt官方例程中拿到的核心代码,只是使用到了其中的傅里叶运算库FFTReal。之后我使用了一些信号处理的技术,对音频数据进行平滑,音频的节奏感可以说是有了质的飞跃!延迟没有了,节奏感还不错,那个时候我感觉我tm是真的nb,结果转折又来了:
up:【视频】
up: 厉害吧
学弟:这是什么?
up:我自己做的软件,用来做频谱的。
学弟:哦哦,我知道是频谱。
up:我做了好长时间,写了**代码呢
学弟:兄弟,说实话,你这个真的太烂了,我看过很多频谱视频,比你这个炫酷多了,你等一下我给你翻翻。
学弟:【视频】
看完视频我就没说话了,因为我觉得自己就是个笑话,那个视频是AE做的,Spec的频谱效果跟AE相比简直是天壤之别。
那天晚上一夜没睡,想啥呢?“AE是怎么做出这样的效果的”,然后搜一下,“哦,原来它有个辉光效果”,再一找:“原来辉光就是把图像变模糊,然后再调亮一点就好了”,然后我立马动手,去找模糊原理,写模糊算法,折腾了几个小时,结果发现我模糊一张1000*1000得跑好几秒,“哦,对了,Qt不是有模糊效果吗?我把那个代码拿过来用用看”,又折腾几个小时,还是得一两秒,对了,他们不是总说图像处理用opencv吗,我再试试这个,然后时间确实是快多了,但是接近100ms,实时绘制得卡成ppt,最后我终于了解到高性能绘图需要使用GPU,然后又再知乎上搜,GPU绘图用什么?就这样折腾到天亮,室友都去上课了,我上床睡觉了
之后就闲了一个多星期,回到家,开始了我的OpenGL学习之路,那段时间很枯燥,写着写着就趴桌子上睡着了,不过好在我拉我弟弟垫背,我学OpenGL的时候他也得看书,QQ微信也没怎么用,跟人间蒸发了一样,不过还是坚持了下来,学完OpenGL我没有直接动手,而是去简单学了3D max,UE4,没有学的很深入,但是特别留意他们的架构和功能,最后才开始写代码的,刚好,也到大四了,凭借这个项目的雏形,拿到了网易的offer,可惜没去,不过还没完,这个项目还成为了我的毕业设计,我又做了很多牛逼的扩展,比如粒子系统,骨骼动画,Lua脚本引擎等,也就是现在的Spec。
音频处理**:利用底层WAS API 实时采集系统声音设备的数据,通过傅里叶运算库FFTW得到频谱数据,通过加窗滤波对数据进行平滑及节奏过滤,得到实时的频谱数据及时域节奏信息供Lua脚本使用。
基础类型封装:由于图形引擎总是需要加载与存储和基础类型数据,和一些特定功能 ,这涉及到两个机制——序列化和反射。C++本身并不提供这样的机制,而Qt通过MOC机制完成了反射机制并提供了序列化方法,在我深入研究了Qt的Moc机制,并找到了Qt的moc工程文件后,通过修改 moc的词法分析机制,实现了一套自己的标记语法,并通过moc为这些标记生成相应的附加代码。(只此一家)
脚本引擎:动画是图形的精髓,制作动画就需要能够动态变化的属性,内部通过代码很容易实现,要在外部实现就很麻烦,我一开始的做法是构建一棵表达式树,根据树来创建控件,树的节点能进行转化成一些运算操作或者基础值,这是参考了UE4 niagara的处理方式,后面重构发现这种方法的扩展性太差,因此想通过脚本进行实现,一开始选择的是Python,花了半月左右实现之后发现60帧运行有明显卡顿,然后又重新实现改用Lua,使用Lua第三方库 LuaPlus搭建Lua环境,并使用第三方开源库QsciScintilla实现了Lua的IDE(支持语法高亮,智能填充),对lua注册C++函数及类型以供Lua脚本调用。
**Shader接口:**我使用的图形API是OpenGL,所以着色器使用GLSL进行编写,为了能够让使用者能够自己编写着色器,因此我也用QsciScintilla做了GLSL的IDE。结合lua脚本提供Uniform变量可以实现很多炫酷的shader。
**模型系统:**借助开源模型加载库Assimp实现模型导入功能,Assimp是将大部分主流的3D模型格式转化为一种统一的数据结构,我做的部分是,解析这个数据结构,并将之用作绘图,另外我还写了骨骼动画的处理部分,通过骨骼节点以及动画数据,生成骨骼动画的变换矩阵,内部以定时器的方式播放动画。
**粒子系统:**借鉴UE4 Niagara系统架构,在脚本引擎的基础上搭建了一个可编程的GPU粒子系统。原理是将粒子状态进行归纳,形状则由通过实例化完成,通过两个tranformFeedBack流水线处理分别完成粒子的生成及运行,这两个tranformFeedBack主要处理粒子的【位置,旋转,速度,颜色,尺寸】,处理完毕之后使用另一条tranformFeedBack流水线生成每个粒子的变换矩阵,最后绘制实例的时候利用得到的矩阵绘制出粒子。开发人员只需提供粒子实例顶点数据,粒子生成shader代码,粒子运动shader代码,可完成任意的粒子效果。
详细功能描述请查看我的毕业论文(虽然写的也比较简略,但对大多功能的核心步骤都做了简述)