如何在Unity中制作VR全景动画

来源:互联网 发布:unity3d 导入 box 编辑:程序博客网 时间:2024/05/22 15:41

超简单的引言

本教程介绍的是如何一个人制作出像模像样的VR全景动画。笔者没有去详细的研究过动画的制作方式,或者说根本就不了解啊,所以本文章只是门外汉的一次自嗨,请勿认真,作为一种参考即可。

一 总览

我们都知道,任何项目的开始都需要有策划的参与,没有完整的项目书,在项目的制作过程中你是会很痛苦的,不管是针对团队或者是个人开发,计划书都是必须的存在。
笔者认为,一个简单的动画项目,至少需要有角色、场景和故事,才能够进行下去,所以这三者的获取对于个人开发者而言就十分重要的。
故事即剧本,对于想单人开发VR动画的人而言,脑海里肯定是已经有了一定的想法了,那么,把他写下来,你就成功的获取了三要素中的故事了。
场景即故事发生的地点,如果你的动画不是那么要求的话,一个Plane就可以作为场景了,稍微讲究一点就可以自己使用Terrain慢慢刷吧,那么场景也就有了。
角色是比较难搞到的一个要素,想要一个机子逾期的模型和动画可没那么的简单,外包是不可能外包的,一辈子都不可能外包,原因自然是没钱啊,那么怎么获取符合自己故事的人物模型呢,请看下文。

二 角色模型

提到角色,制作VR全景动画,需要的是3D的模型,那么如何快速地获取自己想要的模型呢,答案当然是自己制作,这里笔者推荐个人开发这使用体素模型,何为体素模型,就是身体由一个个的方块、像素快组成的模型,好处自然是制作简单,快速成型,制作这种模型,几乎不要花费额外的学习成本,上手就来,下面说一下笔者自己的人物模型制作过程。
首先,你需要去下载一个软件MagicalVoxel,打开软件是这个样子的。
MagicalVoxel
这款软件使用特别的交单,几乎就是秒上手,网上也有很多的使用教程,这里就不重复造轮子了,给你们看看笔者花了5分钟制作出来的一个模型,推荐使用T Pose,有没有感觉很震撼,哈哈,现在是丑了点,等下动起来会好看一点,制作完成后记得将模型导出成obj格式的。
man

三 角色动画

有了角色模型,就需要让模型动起来,给模型添加动画,这里笔者使用的是一个在线的网站mixamo,可以在线为人形的模型添加骨骼和动画,使用前需要先注册帐号。
截至到2017年5月5日,使用这个网站需要注意两点,这两点开始没注意到,浪费了笔者很多的时间:(1)注册帐号,选择的地区不能选择中国,否则你将没有权限下载绑定好骨骼的模型和动画。(2)浏览器,使用的浏览器需要支持WEBGL功能,笔者之前使用的是360安全浏览器,不支持这个功能,导致无法上传模型,后面切换到360极速浏览器,通过简单的设置后就可以了,如果你使用的是其他的浏览器,需要支持这个功能,下面的图片是360极速浏览器的简单设置。
开启webgl
一切都搞定后,点击上传,具体流程直接看图
上传
选中上传的文件
选中上传的文件
绑定骨骼
绑定骨骼
点击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<Animator>();        _audioSource = GetComponent<AudioSource>();        _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");        }    }}
0 0