原文地址:http://blog.csdn.net/mao_xiao_feng/article/details/52599298
上篇為大家介紹了如何實(shí)現(xiàn)虛擬視覺,鏈接如下:
寫給VR手游開發(fā)小白的教程:(六)Cardboard如何實(shí)現(xiàn)沉浸式VR體驗(yàn)之構(gòu)造雙眼
本篇分為五部分,為大家講述虛擬視覺的引入帶來的兩個(gè)基本問題以及Cardboard的解決方案
一、VR設(shè)備透鏡的作用
上篇已經(jīng)說過,要實(shí)現(xiàn)FOV的外部匹配,需要使得我們雙眼的視野正好覆蓋手機(jī)屏幕,這意味著手機(jī)會(huì)離我們的眼睛很近,當(dāng)我們長時(shí)間注視一個(gè)近距離物體時(shí)無疑會(huì)產(chǎn)生視覺上的疲勞,久而久之,產(chǎn)生近視或者是對(duì)身體更大的危害。
這是虛擬視覺帶來的很顯而易見的問題,如何解決?目前所有的VR設(shè)備都采用了一個(gè)方法:增加鏡頭。
關(guān)于VR透鏡的設(shè)計(jì)涉及到了光學(xué)方面的原理,提供兩篇好的文章:
http://www.elecfans.com/vr/433366_3.html
http://ivr.baidu.com/js/573d9236814e2.html
綜上所述,透鏡的作用大約可以概括為2個(gè):1、防止視覺疲勞2、增大視場角(FOV)
現(xiàn)在多數(shù)的VR眼鏡,都采用了菲涅爾透鏡,它與普通的透鏡其實(shí)沒什么區(qū)別,只不過它將內(nèi)部的材質(zhì)移去,使得整個(gè)鏡片更加輕薄。
二、畸變問題的產(chǎn)生
畸變是因?yàn)橄鄼C(jī)鏡頭在成像時(shí),視場不同區(qū)域的放大率不同而產(chǎn)生的變形。
下圖所示,畸變分為桶形畸變和枕形畸變。
桶形畸變的產(chǎn)生原因?qū)嶋H上是因?yàn)橹虚g部分的放大率比邊緣部分大,導(dǎo)致圖像產(chǎn)生了向內(nèi)彎曲的現(xiàn)象(如下圖中黑色圈中的線明顯比綠色圈中的線要長)。
反之枕形畸變則是中間部分的放大率比邊緣部分小而導(dǎo)致的。
光學(xué)上可以通過改進(jìn)透鏡的工藝來改善畸變,比如采用非球面鏡片,但是我們的VR設(shè)備的價(jià)格注定了我們觀察的圖像必然會(huì)發(fā)生畸變(果然是便宜沒好貨-.-),所以處理圖像畸變也是我們面臨的一個(gè)難題。
三、Unity中的扭曲矯正(RadialUndistortionEffect.cs)
Cardboard使用了RadialUndistortionEffect.cs這個(gè)腳本來做扭曲矯正:
using UnityEngine;
using System.Collections;
// Applies the inverse of the lens distortion to the image. The image is "undistorted" so
// that when viewed through the lenses (which redistort), the image looks normal. In the case
// of Cardboard, the lenses apply a pincushion distortion, so this effect applies a barrel
// distortion to counteract that.
[RequireComponent(typeof(Camera))]
public class RadialUndistortionEffect : MonoBehaviour {
#if UNITY_EDITOR
private StereoController controller;
#endif
private Material material;
void Awake() {
if (!SystemInfo.supportsRenderTextures) {
Debug.Log("Radial Undistortion disabled: render textures not supported.");
return;
}
Shader shader = Shader.Find("Cardboard/Radial Undistortion");
if (shader == null) {
Debug.Log("Radial Undistortion disabled: shader not found.");
return;
}
material = new Material(shader);
}
#if UNITY_EDITOR
void Start() {
var eye = GetComponent<CardboardEye>();
if (eye != null) {
controller = eye.Controller;
}
}
#endif
void OnRenderImage(RenderTexture source, RenderTexture dest) {
// Check if we found our shader, and that native distortion correction is OFF (except maybe in
// the editor, since native is not available here).
bool disabled = material == null || !Cardboard.SDK.UseDistortionEffect;
#if UNITY_EDITOR
bool mainCamera = controller != null && controller.GetComponent<Camera>().tag == "MainCamera";
disabled |= !mainCamera || !Cardboard.SDK.simulateDistortionCorrection;
#endif
if (disabled) {
// Pass through, no effect.
Graphics.Blit(source, dest);
} else {
// Undistort the image.
Graphics.Blit(source, dest, material);
}
}
}
來自CODE的代碼片RadialUndistortionEffect.cs
Applies the inverse of the lens distortion to the image. The image is "undistorted" so that when viewed through the lenses (which redistort), the image looks normal. In the case of Cardboard, the lenses apply a pincushion distortion, so this effect applies a barrel distortion to counteract that.
應(yīng)用了透鏡扭曲的反效果。圖像是“反扭曲”的以至于當(dāng)我們透過透鏡觀察的時(shí)候(經(jīng)過了扭曲),圖像會(huì)看起來正常一些。在Cardboard里,透鏡產(chǎn)生了枕形畸變,因此可以使用一個(gè)桶形畸變的圖像效果來消除畸變的影響。
這句注釋很清楚的告訴了我們?nèi)绾稳ハ?,那就是產(chǎn)生一個(gè)已經(jīng)畸變了的圖像去抵消透鏡產(chǎn)生的畸變,如果透鏡產(chǎn)生了枕形畸變,那我們直接生成已經(jīng)桶形畸變了的圖像,反之也同理。
如何產(chǎn)生桶形畸變的圖像?這主要涉及到Unity中shader的概念,shader主要用于生成一系列圖像效果,稱作著色器程序,它主要運(yùn)行在GPU上,編寫這樣的代碼段,需要一定的功力,也需要對(duì)Unity和其渲染機(jī)制有很高的理解。(博主沒有編寫過shader,沒法去解釋其實(shí)現(xiàn)了。。)
最后,通過該函數(shù)Graphics.Blit(source, dest, material);將我們的源RenderTexture經(jīng)過material處理后轉(zhuǎn)換成目標(biāo)RenderTexture。
可以做一個(gè)實(shí)驗(yàn),在運(yùn)行的時(shí)候,如果將腳本RadialUndistortionEffect.cs關(guān)閉,則會(huì)出現(xiàn)下圖的現(xiàn)象。
注意!!!這種方式只適用于Unity Editor,如果要在手機(jī)當(dāng)中產(chǎn)生失真矯正效果,需要調(diào)用的是手機(jī)對(duì)應(yīng)的圖形處理程序。
可惜的是所有Android或者iOS的方法在Unity中只能以庫的形式調(diào)用,以android為例,我們通常會(huì)在eclipse或者Android Studio中打包成類庫,然后在Unity當(dāng)中去做引用,基于這一點(diǎn),我們并不能看到Android產(chǎn)生畸變的圖像的具體實(shí)現(xiàn)過程,因?yàn)樗鼈円呀?jīng)被封裝起來了。
四、FOV的內(nèi)部匹配問題
我們?cè)谏掀呀?jīng)說過了FOV的外部匹配,通常人的視場角為60度左右,在60度的范圍內(nèi)我們要做到正好覆蓋圖像輸出的viewport位置,任何圖像小于視場或者大于視場的現(xiàn)象均會(huì)導(dǎo)致沉浸感降低。這是FOV的外部匹配問題。
現(xiàn)在我們不僅有現(xiàn)實(shí)中的視覺,別忘了在虛擬世界中,我們也有視覺,對(duì)應(yīng)的當(dāng)然也有FOV。這里所說的FOV內(nèi)部匹配,就是現(xiàn)實(shí)視覺的FOV與虛擬視覺的FOV之間的匹配。
換言之,就是保證虛擬視覺的視場角保持在一個(gè)對(duì)于人來說舒適的范圍,60度左右。
那么為什么要做匹配呢?我們完全可以在虛擬世界中獲得更大或者是更窄的FOV,從實(shí)現(xiàn)的角度考慮,這沒有問題,但是人體習(xí)慣于以60度左右的FOV觀察世界,并且我們想要觀察超出人體視場的物體時(shí),會(huì)習(xí)慣性的轉(zhuǎn)頭,這就像是一種本能,大腦已經(jīng)習(xí)慣于這樣的FOV,現(xiàn)在當(dāng)我們被迫接受不匹配的FOV時(shí),大腦對(duì)于新的改變表現(xiàn)出不適,從而產(chǎn)生強(qiáng)烈的眩暈感。
從某些方面來看,F(xiàn)OV內(nèi)部匹配甚至比外部匹配更加重要。
五、FOV內(nèi)部匹配的解決方案(StereoController.cs)
StereoController這個(gè)類主要用來對(duì)雙眼(Cardboard Eye)進(jìn)行控制和調(diào)整,它綁定在mono Camera上,對(duì)兩個(gè)子物體stereo Camera進(jìn)行控制。
Controls a pair of CardboardEye objects that will render the stereo view of the camera this script is attached to.
這個(gè)腳本控制一對(duì)用來產(chǎn)生立體畫面的攝像機(jī)物體。
這個(gè)腳本下有幾個(gè)重要的屬性:
/***********************************************************************************************************************************/
stereoMultiplier:
Adjusts the level of stereopsis for this stereo rig. Note that this parameter is not the virtual size of the head -- use a scale on the head game object for that. Instead, it is a control on eye vergence, or rather, how cross-eyed or not the stereo rig is. Set to 0 to turn off stereo in this rig independently of any others.
通過本控制器調(diào)整立體度等級(jí)。注意這個(gè)參數(shù)不是虛擬頭部的大小--使用scale來調(diào)整頭部物體,而是對(duì)眼睛離散度的控制,這個(gè)離散度也可以理解為視線怎樣傾斜。設(shè)置為0的時(shí)候,關(guān)閉立體效果。
上兩張圖大家就能發(fā)現(xiàn)這個(gè)參數(shù)的功能了:
這是一張stereoMultiplier設(shè)置為1時(shí)候的圖
這是stereoMultiplier設(shè)置為0時(shí)候的圖
兩者的差別,大家可以仔細(xì)的觀察一下(必須要很仔細(xì)很仔細(xì)的觀察,才能發(fā)現(xiàn)不同 :-D)
如果還是找不到不同,好吧,我們用兩張圖來表達(dá)差異:
平常,我們的眼睛觀察物體,是這樣的。
現(xiàn)在,stereoMultiplier設(shè)為0,是這樣的。
第一種方式,同時(shí)考慮了視線的傾斜,而第二種方式只是將畫面平視輸出,很顯然,如果想要更高的立體感,這個(gè)參數(shù)設(shè)為1更好。
/***********************************************************************************************************************************/
matchMonoFOV:
這個(gè)參數(shù),就是上述的FOV內(nèi)部匹配
The stereo cameras by default use the actual optical FOV of the Cardboard device,because otherwise the match between head motion and scene motion is broken, which impacts the virtual reality effect. However, in some cases it is desirable to adjust the FOV anyway, for special effects or artistic reasons. But in no case should the FOV be allowed to remain very different from the true optical FOV for very long, or users will experience discomfort.This value determines how much to match the mono camera's field of view. This is a fraction: 0 means no matching, 1 means full matching, and values in between are compromises. Reasons for not matching 100% would include preserving some VR-ness,and that due to the lens distortion the edges of the view are not as easily seen as when the phone is not in VR-mode.Another use for this variable is to preserve scene composition against differences in the optical FOV of various Cardboard models. In all cases, this value simply lets the mono camera have some control over the scene in VR mode, like it does in non-VR mode.
立體攝像機(jī)默認(rèn)使用Cardboard設(shè)備實(shí)際的FOV,不然會(huì)導(dǎo)致頭部運(yùn)動(dòng)和場景運(yùn)動(dòng)的匹配關(guān)系破裂(這會(huì)影響沉浸感),然而,在一定情況下,也可以調(diào)整FOV(為了特效或者藝術(shù)感),但是長時(shí)間使用與實(shí)際視覺FOV不同的FOV是不允許的,這會(huì)讓用戶不舒服。這個(gè)值決定了mono Camera獲得的FOV的匹配度。0意味著不匹配,1意味著完全匹配,中間的值折中。不進(jìn)行100%匹配的原因包括,保留VR的特性,VR模式下圖像的邊緣不易被非VR模式看到,不同類型的Cardboard進(jìn)行兼容等。這個(gè)值通常讓mono Camera在VR模式下對(duì)于場景有一些控制。
/***********************************************************************************************************************************/
matchByZoom:
Determines the method by which the stereo cameras' FOVs are matched to the mono camera's FOV (assuming matchMonoFOV is not 0). The default is to move the stereo cameras (matchByZoom = 0), with the option to instead do a simple camera zoom (matchByZoom = 1). In-between values yield a mix of the two behaviors.It is not recommended to use simple zooming for typical scene composition, as it conflicts with the VR need to match the user's head motion with the corresponding scene motion. This should be reserved for special effects such as when the player views the scene through a telescope or other magnifier (and thus the player knows that VR is going to be affected), or similar situations.Note that matching by moving the eyes requires that the centerOfInterest object be non-null, or there will be no effect.
決定stereo cameras的FOV和mono camera的FOV進(jìn)行匹配的方式(前提是matchMonoFOV不是0)。默認(rèn)是移動(dòng)stereocameras(matchByZoom = 0),還有一種方式是簡單的對(duì)camera的視野進(jìn)行縮放(matchByZoom = 1),中間的值,表示兩種方式的混合,不建議使用簡單縮放,因?yàn)樗皖^部移動(dòng)與場景移動(dòng)的匹配相沖突。同樣縮放效果可以被保留用作特效,比如玩家通過望遠(yuǎn)鏡觀看場景。注意移動(dòng)匹配需要興趣中心點(diǎn)物體非空,否則會(huì)無效。
/***********************************************************************************************************************************/
centerOfInterest:
Matching the mono camera's field of view in stereo by moving the eyes requires a designated "center of interest". This is either a point in space (an empty gameobject) you place in the scene as a sort of "3D cursor", or an actual scene entity which the player is likely to be focussed on.The FOV adjustment is done by moving the eyes toward or away from the COI so that it appears to have the same size on screen as it would in the mono camera. This is disabled if the COI is null.
為了移動(dòng)stereo camera匹配FOV,我們需要一個(gè)被設(shè)計(jì)好的“興趣中心點(diǎn)”。這可以是一個(gè)空物體,也可以是非空物體,甚至是場景中玩家可能會(huì)關(guān)注的實(shí)際物體,F(xiàn)OV調(diào)整根據(jù)距離興趣中心點(diǎn)的距離以至于看起來與在mono camera中有同樣的尺寸。
radiusOfInterest:
在興趣中心為非空物體時(shí),可以定義半徑大小。
綜上,可以看出Cardboard使用基于mono camera來調(diào)整每個(gè)stereo camera的方法,并且設(shè)置了興趣中心點(diǎn),這個(gè)點(diǎn)相當(dāng)于我們雙眼視線的中心交點(diǎn),除了作為基準(zhǔn)外,一些游戲?yàn)榱宋婕易⒁庖矔?huì)在這個(gè)點(diǎn)上放置重要的物體。