本教程介绍的是如何一个人制作出像模像样的VR全景动画。笔者没有去详细的研究过动画的制作方式,或者说根本就不了解啊,所以本文章只是门外汉的一次自嗨,请勿认真,作为一种参考即可。
我们都知道,任何项目的开始都需要有策划的参与,没有完整的项目书,在项目的制作过程中你是会很痛苦的,不管是针对团队或者是个人开发,计划书都是必须的存在。
笔者认为,一个简单的动画项目,至少需要有角色、场景和故事,才能够进行下去,所以这三者的获取对于个人开发者而言就十分重要的。
故事即剧本,对于想单人开发VR动画的人而言,脑海里肯定是已经有了一定的想法了,那么,把他写下来,你就成功的获取了三要素中的故事了。
场景即故事发生的地点,如果你的动画不是那么要求的话,一个Plane就可以作为场景了,稍微讲究一点就可以自己使用Terrain慢慢刷吧,那么场景也就有了。
角色是比较难搞到的一个要素,想要一个机子逾期的模型和动画可没那么的简单,外包是不可能外包的,一辈子都不可能外包,原因自然是没钱啊,那么怎么获取符合自己故事的人物模型呢,请看下文。
提到角色,制作VR全景动画,需要的是3D的模型,那么如何快速地获取自己想要的模型呢,答案当然是自己制作,这里笔者推荐个人开发这使用体素模型,何为体素模型,就是身体由一个个的方块、像素快组成的模型,好处自然是制作简单,快速成型,制作这种模型,几乎不要花费额外的学习成本,上手就来,下面说一下笔者自己的人物模型制作过程。
首先,你需要去下载一个软件MagicalVoxel,打开软件是这个样子的。
这款软件使用特别的交单,几乎就是秒上手,网上也有很多的使用教程,这里就不重复造轮子了,给你们看看笔者花了5分钟制作出来的一个模型,推荐使用T Pose,有没有感觉很震撼,哈哈,现在是丑了点,等下动起来会好看一点,制作完成后记得将模型导出成obj格式的。
有了角色模型,就需要让模型动起来,给模型添加动画,这里笔者使用的是一个在线的网站mixamo,可以在线为人形的模型添加骨骼和动画,使用前需要先注册帐号。
截至到2017年5月5日,使用这个网站需要注意两点,这两点开始没注意到,浪费了笔者很多的时间:(1)注册帐号,选择的地区不能选择中国,否则你将没有权限下载绑定好骨骼的模型和动画。(2)浏览器,使用的浏览器需要支持WEBGL功能,笔者之前使用的是360安全浏览器,不支持这个功能,导致无法上传模型,后面切换到360极速浏览器,通过简单的设置后就可以了,如果你使用的是其他的浏览器,需要支持这个功能,下面的图片是360极速浏览器的简单设置。
一切都搞定后,点击上传,具体流程直接看图
选中上传的文件
绑定骨骼
点击Next生成完之后就可以在自己的库中见到你的模型,而且是绑定骨骼的,模型已经动起来了。
下一步是选择动画,这个网站上有很多的动画供你选择,而且全是免费的应该
选中一个动画,点击添加到包里即可
进入到你的库中,就可以看到你的模型和选择的动画文件了
接下来就是选中模型和动画,点击QUEUE DOWNLOAD,如果你的帐号选得是中国,这边的按钮是不可用的,模型和动画是分开下载的,模型下载选中FBX格式,可以直接导入Unity使用。
嗯,素材什么的都有了,接下来就是在Unity中的开发了,笔者使用的是Unity3D 5.6版本,新建一个项目,将模型导入到项目中,再将模型拉入场景中,你会发现模型是没有贴图的,下载的时候也是没有的。
别着急,别忘了之前从MagicaVoxel导出的时候是有一张的贴图的,我们将这张贴图一起拖入项目中,再将贴图的类型改成Sprite类型,为模型的材质球附上这张贴图即可。(下面的效果是我之前制作的模型)
接下来新建一个动画控制器,为模型指定上,再将需要的动画拉入控制器,点击运行,就会发现模型已经动起来了。
以上是很简单的方式,实际上,在动画的流程中,角色的动作和语音是很复杂的一个过程,需要使用脚本去精准的控制整个流程的进行。
第二步也是最重要的一步,即渲染VR全景视频,即下面这样的效果。
如果是自己写算法去渲染估计可以独立成一本书,我们自然要偷懒咯,这里推荐一款商店里的插件,如果是商业开发最好去支持一下作者,最新已经更新到了2.2.1。
下面介绍一下这个插件的简单使用,这款插件有几种渲染模式,笔者这边只介绍如何渲染出正常VR全景视频的方法,在使用前需要注意一点,这个插件必须放在Assets的第一层目录,这涉及到一些插件自带工具的路径问题。
这个插件的核心就是一个预制体,VRPanoramaCamera
想要渲染出正常的VR全景视频,需要注意VRCapture这个脚本的一些属性
(1)Capture Type:渲染的类型,我们需要选择最后一个EquidistantMono
(2)视频的分辨率设置
(3)Fps设置
(4)设置渲染的时长,这边和你设置的fps有关,如果是25fps那,这边就是25帧标识渲染1S
(5)这个是选择渲染视频还是音频,需要分开渲染,当然你也可以为你的视频进行后期配音也是可以的。
设置好这个脚本的参数,点击运行就可以开始渲染你的VR视频了,最后会渲染出一大堆的图片,一帧一张,存放在项目的跟目录位置,最下面的就是你的VR视频了。
这个教程其实就是多个工具插件的使用合集,虽说能够快速制作出一个VR全景视频,当真正想制作出优秀的作品,还是需要做很多的工作,如模型的细节、动画的类型、脚本控制流程的运行以及配音什么的,都是很重要的。这个教程只是抛砖引玉,希望有更多有兴趣的人可以加入到VR开发的行业中,为虚拟现实提供更多内容,同时也促进这个行业的良性发展。
咳咳!以上都是废话,好了,教程到此为止。还有什么疑问的地方可以留言,我有看到就会回复,谢谢。
最后放上一个我花了一天时间(建模,录音,选材,脚本什么地)做的一个demo视频的地址,因为选择的是最低画质(我电脑差点炸了,渣电脑渲染不起啊),而且最后合并视频和音频的时候没处理好,导致不同步,就将就看着吧。
http://pan.baidu.com/s/1eR8up3c
还有我控制流程的一个脚本,写的有点渣,纯粹为了实现而实现的,当做参考吧,制作动画的话感觉应该以时间线为事件的基准,嗯,我是这么认为的。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class DanceManController : MonoBehaviour {
public AudioSource BgmSource;
public Transform DanceManAim;
public float MoveSpeed = 0.5f;
public float RotateSpeed = 0.5f;
public float WaitTime = 5.0f;
//Voice
public AudioClip Voice01;
public AudioClip Voice02;
public AudioClip Voice03;
public AudioClip Voice04;
public AudioClip Bgm01;
public AudioClip Bgm02;
public AudioClip Bgm03;
private Animator _animator;
private GameObject _player;
private Transform _danceManTransform;
private AudioSource _audioSource;
private bool _isWalk = false;
private bool _isRotate = false;
private float _bmgVolume = 0.15f;
void Awake() {
_animator = GetComponent();
_audioSource = GetComponent();
_player = GameObject.FindGameObjectWithTag("Player") as GameObject;
_danceManTransform = transform;
}
void Start () {
StartCoroutine("MoveMan");
PlayAudio(Voice01);
PlayBGM(Bgm01, _bmgVolume);
}
void Update () {
if (_isWalk) {
if (Vector3.Distance(_danceManTransform.localPosition, DanceManAim.localPosition) > 0.05f)
{
_animator.SetBool("Walk", true);
_danceManTransform.localPosition += (DanceManAim.localPosition - _danceManTransform.localPosition).normalized * MoveSpeed * Time.deltaTime;
}
else {
OnManIdle();
}
}
if (_isRotate) {
if (_danceManTransform.localEulerAngles.y < 180.0f)
{
_danceManTransform.Rotate(new Vector3(0, RotateSpeed, 0));
}
else {
_isRotate = false;
PlayAudio(Voice02);
StartCoroutine("Dance01");
}
}
}
IEnumerator MoveMan() {
yield return new WaitForSecondsRealtime(WaitTime);
_isWalk = true;
yield return 0;
}
IEnumerator Dance01() {
yield return new WaitForSecondsRealtime(16.0f);
PlayBGM(Bgm02, _bmgVolume);
_animator.SetBool("Dance01", true);
yield return 0;
}
IEnumerator Dance02()
{
yield return new WaitForSecondsRealtime(15.0f);
PlayBGM(Bgm03, _bmgVolume);
_animator.SetBool("Dance02", true);
yield return 0;
}
IEnumerator EndVoice()
{
Debug.Log("结束语");
yield return new WaitForSecondsRealtime(33.0f);
BgmSource.volume = _bmgVolume;
_audioSource.Stop();
yield return 0;
}
public void OnManIdle()
{
_isWalk = false;
_animator.SetBool("Walk", false);
_isRotate = true;
}
public void PlayAudio(AudioClip clip) {
_audioSource.volume = 1.0f;
_audioSource.clip = clip;
_audioSource.Play();
}
public void PlayBGM(AudioClip clip,float volume)
{
BgmSource.volume = volume;
BgmSource.clip = clip;
BgmSource.Play();
}
private int _dance01Size = 0;//Dance01的播放次数,三遍停止
private bool _endVoice = false;//是否播放结束音
public void OnDanceOver(int index) {
if (index == 1) {
if (_dance01Size == 2)
{
BgmSource.Stop();
_animator.SetBool("Dance01", false);
PlayAudio(Voice03);
StartCoroutine("Dance02");
}
else {
_dance01Size++;
}
}
if (index == 2 && !_endVoice)
{
//_endVoice = true;
//_bgmSource.volume = 0.05f;
//PlayAudio(Voice04);
//StartCoroutine("EndVoice");
}
}
}