李永亮 黃滔
(中山職業(yè)技術(shù)學(xué)院藝術(shù)設(shè)計(jì)學(xué)院 廣東省中山市 528400)
虛擬現(xiàn)實(shí)技術(shù)所具備的沉浸感、交互性、想象性和智能性4種特征[1],使之在教育、培訓(xùn)和娛樂(lè)等領(lǐng)域具有相當(dāng)高的應(yīng)用價(jià)值[2],特別是在消費(fèi)級(jí)頭戴式顯示設(shè)備(HMD,Head Mounted Displayer)以及全景攝像設(shè)備相繼問(wèn)世以來(lái),虛擬現(xiàn)實(shí)技術(shù)進(jìn)入大眾生活的趨勢(shì)愈加明顯,其中文化娛樂(lè)是虛擬現(xiàn)實(shí)技術(shù)在大眾生活中發(fā)揮作用的重要領(lǐng)域[3],而借助HMD 欣賞360 度全景視頻是較為常見(jiàn)的一種娛樂(lè)方式。從用戶體驗(yàn)的角度看,利用HMD 播放360 度全景視頻的效果要求有較高的流暢度和清晰度,如果播放直播視頻則對(duì)低延時(shí)性也有較高的要求,因此該項(xiàng)應(yīng)用對(duì)設(shè)備的計(jì)算能力、顯示能力和網(wǎng)絡(luò)帶寬都有較高的要求。
HMD 按照其系統(tǒng)構(gòu)成形式可分為外接式、外殼式和一體式三種[4]。外接式HMD 需要與外部主機(jī)(主要是PC 機(jī))相連接,本身不包含主要計(jì)算能力,僅包含顯示系統(tǒng)與傳感系統(tǒng)。而外殼式HMD 不具有顯示和計(jì)算系統(tǒng),僅具有光學(xué)系統(tǒng),有些也具有傳感系統(tǒng),需要通過(guò)與智能手機(jī)等智能終端結(jié)合實(shí)現(xiàn)完整的功能。一體式HMD 則是將顯示、傳感和計(jì)算等系統(tǒng)集成在一起的設(shè)備。從設(shè)備的系統(tǒng)構(gòu)成可以看出,由于外接式HMD 可以充分借助PC 機(jī)的計(jì)算能力(包括CPU 和顯卡)和網(wǎng)絡(luò)帶寬,因此較為適合用于播放360 度全景視頻,特別是流媒體形式的直播視頻。
本文以libVLC 為基礎(chǔ),以三維引擎Unity 為開(kāi)發(fā)工具,C#為編程語(yǔ)言,設(shè)計(jì)和實(shí)現(xiàn)了一款用于外接式HMD 的流媒體播放器軟件,利用本軟件用戶可以沉浸式的觀看以流媒體方式在網(wǎng)絡(luò)上推送的全景視頻和全景攝像機(jī)的直播畫面,用戶可以通過(guò)PC 端的窗口設(shè)置流媒體的地址、畫面尺寸相關(guān)參數(shù)以及校正默認(rèn)觀看角度,并在佩戴HMD 設(shè)備后獲得沉浸式的360 度全景觀看效果。
本軟件的系統(tǒng)構(gòu)成如圖 1所示,其中l(wèi)ibVLC 為VideoLAN 項(xiàng)目組開(kāi)源的VLC media player 播放器軟件的內(nèi)核引擎及其接口,開(kāi)發(fā)者利用libVLC 可以開(kāi)發(fā)任何基于VLC 架構(gòu)的多媒體應(yīng)用程序[5]。由于libVLC 是由C 語(yǔ)言開(kāi)發(fā)的,為了使之能夠與Unity 引擎兼容,本文設(shè)計(jì)了VLCNativeMethod 類,利用C#的DllImport 特性將libVLC 中常用的方法引入到VLCNativeMethod 類中,從而為在Unity C#腳本中使用libVLC 的功能創(chuàng)造了條件。而VLCUnity 模塊則為一個(gè)典型的Unity C#腳本組件類,它是對(duì)VLCNativeMethod類的進(jìn)一步封裝,提供了創(chuàng)建VLC 實(shí)例對(duì)象、創(chuàng)建播放器對(duì)象、指定提取視頻畫面回調(diào)函數(shù)、播放媒體資源、設(shè)置視頻畫面規(guī)格、暫停播放和停止播放等功能。VLCUnityPlayer 也是一個(gè)Unity C#腳本組件類,其本質(zhì)上是構(gòu)造了一個(gè)在Unity 引擎中使用的播放器組件,該組件有兩大主要功能,其一為實(shí)現(xiàn)用戶的操作意圖,包括設(shè)置媒體資源、播放、暫停和停止等,其二為在虛擬場(chǎng)景中渲染視頻畫面,在渲染之前還需要對(duì)視頻的畫面進(jìn)行幀同步以保證每一幀畫面的渲染順序是正確的。FrameBuffer 類即為用于實(shí)現(xiàn)幀同步的視頻畫面數(shù)據(jù)緩沖區(qū)管理類,該類提供了實(shí)現(xiàn)幀同步的相關(guān)方法,包括讀、寫緩沖區(qū)以及設(shè)置緩沖區(qū)狀態(tài)的方法。CameraRigRotate則用于在播放360 度全景視頻的過(guò)程中,調(diào)整用戶視角的方位角和翻滾角。而UIManager 則為人機(jī)交互接口管理組件,用于將UI 界面上的控件與VLCUnityPlayer 組件以及CameraRigRotate 組件的功能進(jìn)行關(guān)聯(lián)。
圖1:系統(tǒng)構(gòu)成
本系統(tǒng)在VLCUnityPlayer 組件的控制下實(shí)現(xiàn)視頻資源的播放,具體流程如圖 2所示。其中,視頻URL 和畫面規(guī)格通過(guò)UIManager 組件從用戶界面上獲取。VLC 實(shí)例、播放器實(shí)例以及播放控制通過(guò)VLCUnity 組件實(shí)現(xiàn)。而畫面的渲染則通過(guò)將畫面提取回調(diào)函數(shù)中獲得的視頻畫面數(shù)據(jù)復(fù)制到目標(biāo)材質(zhì)的貼圖中實(shí)現(xiàn),目標(biāo)材質(zhì)事先已經(jīng)設(shè)置為虛擬場(chǎng)景的天空盒材質(zhì)從而保證視頻畫面以360 度全景的形式展現(xiàn)。
圖2:視頻資源播放流程
由于在視頻播放過(guò)程中,libVLC 模塊逐幀提取視頻畫面的過(guò)程與Unity 引擎的畫面渲染過(guò)程是相互獨(dú)立的,刷新頻率各不相同,因此如何在Unity 引擎中進(jìn)行視頻畫面的幀同步從而保證視頻畫面在場(chǎng)景中有正確的渲染順序是需要解決的一個(gè)關(guān)鍵技術(shù)問(wèn)題。
通過(guò)libVLC 模塊逐幀提取視頻畫面是通過(guò)VideoLockCB、VideoUnlockCB 和VideoDisplayCB 三個(gè)回調(diào)函數(shù)實(shí)現(xiàn)的。 當(dāng)libVLC 模塊將要提取新的一幀視頻畫面數(shù)據(jù)時(shí)VideoLockCB 函數(shù)被調(diào)用,在該函數(shù)中要?jiǎng)?chuàng)建并指定視頻畫面數(shù)據(jù)的緩沖區(qū),從而保證新的視頻畫面幀數(shù)據(jù)被寫入緩沖區(qū)中。當(dāng)VideoLockCB 函數(shù)執(zhí)行完畢后,libVLC 模塊開(kāi)始向緩沖區(qū)寫入視頻畫面幀數(shù)據(jù),當(dāng)寫入操作完成后VideoUnlockCB 函數(shù)會(huì)被調(diào)用,在該函數(shù)中可以從緩沖區(qū)中獲取視頻畫面幀數(shù)據(jù)。隨后VideoDisplayCB 函數(shù)會(huì)被調(diào)用,在該函數(shù)中可以將視頻畫面幀數(shù)據(jù)渲染到指定位置。在普通的應(yīng)用程序中,只要設(shè)置一個(gè)視頻畫面幀數(shù)據(jù)緩沖區(qū),并在VideoDisplayCB 函數(shù)中更新畫面顯示內(nèi)容即可實(shí)現(xiàn)畫面的播放,此時(shí)畫面的更新頻率取決于libVLC 模塊提取視頻畫面的頻率。然而在本系統(tǒng)中,視頻畫面的渲染由Unity 引擎控制,Unity 的畫面幀率是不固定的并且其刷新過(guò)程與libVLC 模塊提取視頻畫面的過(guò)程完全獨(dú)立,因此造成了視頻畫面數(shù)據(jù)的獲取與渲染不同步的情況。如果不進(jìn)行幀同步處理,當(dāng)渲染速度比獲取速度快時(shí),會(huì)導(dǎo)致緩存區(qū)中未寫入完成的畫面數(shù)據(jù)被渲染到場(chǎng)景中從而造成大量非正常的殘缺畫面;而當(dāng)渲染速度比獲取速度慢時(shí),又會(huì)導(dǎo)致部分畫面無(wú)法被渲染出來(lái)從而造成丟幀。
本系統(tǒng)通過(guò)VLCUnityPlayer 組件協(xié)調(diào)libVLC 模塊和緩沖區(qū)管理類FrameBuffer 來(lái)解決幀同步問(wèn)題。其中FrameBuffer 類采用了讀、寫雙緩沖區(qū)隊(duì)列的方法來(lái)保證每個(gè)視頻畫面幀的渲染順序是正確的,原理如圖 3所示。在最開(kāi)始時(shí)緩沖區(qū)都在寫隊(duì)列中,借助隊(duì)列所具有的先入先出的特點(diǎn),在每次從libVLC 模塊獲取幀畫面數(shù)據(jù)時(shí),首先從寫隊(duì)列中取出一個(gè)緩沖區(qū)并由libVLC 模塊將幀畫面數(shù)據(jù)寫入該緩沖區(qū),然后將該緩沖區(qū)放入讀隊(duì)列中,而當(dāng)需要渲染視頻畫面時(shí)則從讀隊(duì)列中取出最早放入隊(duì)列的緩沖區(qū),將其中的數(shù)據(jù)渲染到目標(biāo)材質(zhì)后再將該緩沖區(qū)放入寫隊(duì)列中。重復(fù)上述過(guò)程,從而在緩沖區(qū)數(shù)量足夠多的情況下,能夠保證視頻畫面的每一幀都會(huì)按照視頻中原本的順序被渲染到目標(biāo)材質(zhì)中。
圖3:采用讀寫雙緩沖區(qū)隊(duì)列實(shí)現(xiàn)幀同步的原理
幀同步過(guò)程的泳道圖如圖 4所示,由圖可知當(dāng)渲染畫面的速度較獲取畫面數(shù)據(jù)的速度慢時(shí)會(huì)造成寫緩沖區(qū)耗盡,從節(jié)約內(nèi)存資源的角度考慮本系統(tǒng)選擇不擴(kuò)充緩沖區(qū)的數(shù)量,而是提供一個(gè)棄幀緩沖區(qū)給libVLC用于保障運(yùn)行流程順暢但放棄該幀數(shù)據(jù)不進(jìn)行渲染。通過(guò)實(shí)驗(yàn),在HMD 為HTC VIVE,PC 機(jī)條件如下:CPU 為Intel Core i7,GPU 為NVIDIA GeForce GTX 1080,內(nèi)存容量64 GB ,操作系統(tǒng)為Windows10 時(shí),緩沖區(qū)個(gè)數(shù)設(shè)置為4 就已經(jīng)能夠保證長(zhǎng)時(shí)間播放像素寬度為3008、像素高度為1504 的360 度全景視頻而不出現(xiàn)緩沖區(qū)耗盡的情況。
圖4:實(shí)現(xiàn)幀同步的泳道圖
從libVLC 獲取的視頻畫面雖然是全景畫面,但是在媒體資源中其存儲(chǔ)形式是二維平面,為了使之在用戶身處的虛擬場(chǎng)景中呈現(xiàn)360 度的沉浸式效果,需要將二維畫面映射成立體球面。在Unity引擎所構(gòu)造的虛擬場(chǎng)景中有天空盒的概念,其本質(zhì)是以360 度全景的形式呈現(xiàn)場(chǎng)景背景畫面的一種特殊材質(zhì),因此只要將全景畫面的紋理復(fù)制到天空盒材質(zhì)的渲染紋理上,即可實(shí)現(xiàn)二維平面到立體球面的映射。具體實(shí)現(xiàn)方法分為虛擬場(chǎng)景中天空盒材質(zhì)相關(guān)的設(shè)置和運(yùn)行時(shí)對(duì)天空盒材質(zhì)的主貼圖紋理進(jìn)行更新兩部分,流程示例如圖5所示。
圖5:視頻全景圖像渲染的示例流程
在虛擬場(chǎng)景的設(shè)置中,需要?jiǎng)?chuàng)建播放全景視頻專用的材質(zhì),該材質(zhì)采用的Shader 為Skybox 分類下的Panoramic。并且由于全景畫面映射到天空盒的立體球面上之后畫面是上下顛倒的,因此還需要將場(chǎng)景中的主攝像機(jī)進(jìn)行180 度的翻滾,即圍繞自身坐標(biāo)系Z 軸正向旋轉(zhuǎn)180 度。其次,在VLCUnityPlayer 組件中,根據(jù)視頻畫面的提取規(guī)格(包括像素寬度、像素高度、色彩編碼)創(chuàng)建一個(gè)渲染貼圖(RenderTexture)對(duì)象,并將其設(shè)置為全景視頻專用材質(zhì)的主貼圖,同時(shí)要?jiǎng)?chuàng)建一個(gè)二維貼圖(Texture2D)對(duì)象作為從緩沖區(qū)獲取畫面數(shù)據(jù)的中介。當(dāng)播放視頻時(shí),每從緩沖區(qū)讀隊(duì)列獲取一幀畫面數(shù)據(jù),便使用緩沖區(qū)中的數(shù)據(jù)更新二維貼圖對(duì)象,再使用Unity 引擎 Graphics 類的靜態(tài)方法 Blit(Texture,RenderTexture)將二維貼圖上的紋理數(shù)據(jù)復(fù)制到天空盒材質(zhì)的渲染貼圖上,從而實(shí)現(xiàn)全景二維畫面到立體球面畫面的映射。
在全景視頻播放過(guò)程中,可能會(huì)出現(xiàn)視頻拍攝的視角與觀眾視角不匹配的情況,特別是拍攝視角處于移動(dòng)狀態(tài)的時(shí)候,移動(dòng)的方向與用戶視角默認(rèn)的前方(即視頻開(kāi)始播放時(shí)用戶視角在虛擬場(chǎng)景中的正前方)不一致的情況下,會(huì)給用戶帶來(lái)不適感,甚至可能導(dǎo)致動(dòng)暈癥。此外當(dāng)視頻數(shù)據(jù)的來(lái)源是全景攝像機(jī)的直播推流時(shí),如果全景攝像機(jī)的擺放不平整并且沒(méi)有對(duì)畫面的傾斜進(jìn)行修正,則全景視頻播放器中呈現(xiàn)的畫面將會(huì)持續(xù)的傾斜,同樣會(huì)給觀看體驗(yàn)帶來(lái)負(fù)面影響。
為了解決上述問(wèn)題,本系統(tǒng)設(shè)計(jì)了CameraRigRotate 組件用于實(shí)現(xiàn)在播放過(guò)程中調(diào)整用戶視角方位角和翻滾角的功能。通過(guò)向用戶提供方位角調(diào)整的功能可以解決運(yùn)動(dòng)過(guò)程中拍攝視角和觀看視角不匹配的問(wèn)題,而通過(guò)提供翻滾角調(diào)整的功能則可以解決畫面傾斜的問(wèn)題。
在Unity 場(chǎng)景中以CameraRig 對(duì)象代表用戶視角,方位角表示CameraRig 對(duì)象相對(duì)其默認(rèn)姿態(tài)圍繞世界坐標(biāo)系Y 軸旋轉(zhuǎn)的角度,而翻滾角則表示CameraRig 對(duì)象相對(duì)其默認(rèn)姿態(tài)圍繞自身坐標(biāo)系Z軸旋轉(zhuǎn)的角度。CameraRigRotate 組件具有SetRotation(float,float)和ResetRotation()兩個(gè)函數(shù),分別用于更新用戶視角和恢復(fù)默認(rèn)視角。其中SetRotation(float,float)的第一個(gè)形參表示方位角的值,第二個(gè)形參表示翻滾角的值,該函數(shù)被調(diào)用時(shí)用戶視角的主攝像機(jī)對(duì)象將會(huì)按照參數(shù)值更新其在虛擬場(chǎng)景中的姿態(tài),從而實(shí)現(xiàn)用戶視角的調(diào)整。而當(dāng)ResetRotation()函數(shù)被調(diào)用時(shí)則用戶視角會(huì)恢復(fù)到默認(rèn)狀態(tài)。
利用Unity 事件機(jī)制,將CameraRigRotate 組件的SetRotation(float,float)函數(shù)設(shè)置為UI 界面上水平滑動(dòng)條和垂直滑動(dòng)條OnValueChanged 事件的回調(diào)函數(shù),同時(shí)也設(shè)置為HMD 設(shè)備手柄用戶輸入事件的回調(diào)函數(shù)。對(duì)ResetRotation()函數(shù)也做類似的設(shè)置,從而保證用戶可以同時(shí)使用UI 界面和手柄來(lái)調(diào)整用戶視角。
進(jìn)入系統(tǒng)后,PC 端窗口全屏顯示,并且其操作界面默認(rèn)為隱藏狀態(tài),按下鍵盤上的空格鍵可以使操作界面在隱藏和顯示狀態(tài)之間切換。當(dāng)操作界面隱藏時(shí),窗口顯示流媒體中用戶可見(jiàn)部分的視頻畫面,當(dāng)操作界面顯示后的狀態(tài)如圖 6所示,其中steam path 輸入框用于輸入視頻資源的URL,Width 和Height 輸入框用于輸入提取視頻畫面的像素寬度和高度,用戶單擊OK 按鈕可以開(kāi)始播放視頻并隱藏界面控件,水平和垂直滑動(dòng)條則用于在播放過(guò)程中調(diào)整用戶視角的方位角和翻滾角,如果單擊Reset 按鈕則用戶視角會(huì)恢復(fù)到默認(rèn)狀態(tài)。
圖6:PC 端界面展示
視頻完整的全景畫面與用戶在佩戴HMD 后的視角畫面之間的對(duì)比如圖 7所示,可以看出用戶的體驗(yàn)是完全沉浸式的,就如同身處視頻拍攝的場(chǎng)景當(dāng)中。
圖7:全景畫面與玩家視角效果對(duì)比
本文提出了一種用于外接式HMD 設(shè)備的360 度全景視頻播放器軟件的設(shè)計(jì)與實(shí)現(xiàn)方法,該軟件以URL 的方式定位視頻資源,既可以播放PC 機(jī)本地的視頻也可以播放通過(guò)網(wǎng)絡(luò)推流的流媒體視頻,為使用外接式HMD 的用戶提供了一種沉浸式觀看360 度全景視頻和直播的手段。本系統(tǒng)結(jié)合視頻的拍攝設(shè)備以及直播推流技術(shù),可以在文化娛樂(lè)和培訓(xùn)教育等領(lǐng)域提供一種基于沉浸式360 度全景視頻的解決方案。