“V”來已來——淺談光學VR實現原理
本文是在CSDN一個公開課的演講,現整理釋出於知乎,為筆者原創。歡迎VR愛好者與從業者相互交流學習。
VR(Virtual Reality),似乎從2016年起,成為了一個人人都喜歡掛在嘴邊的新鮮名詞,好像不談論VR就像你不認識特斯拉不聽薛之謙不看愛樂之城一樣,暗示著與時代的“脫節”。如果你偶然得知身邊有人在做“VR專案”,那就好像是發現了下一個
馬克·扎克伯格
一樣,相信他一定會成為時代的“弄潮兒”。不過,大多數人對於VR的理解,也僅僅停留在“
虛擬現實
”這四個字上面。那麼,VR究竟是怎樣的,它的實踐原理又是什麼,本文將嘗試用較為簡單的語言,來闡述這一“
現在與未來的風口
”。
本文將主要從紅極一時Google CardBoard到號稱很可能改變未來人們遊戲的方式的Oculus Rift來與大家探討VR的實現原理與前景。首先,我們一起來看兩個簡單卻基本的問題:
1. 為什麼目前的VR被稱為光學VR?
答:因為人們對世界的認識,是透過
人眼觀察世界
。目前一切的VR,都是透過扭曲光線,讓光線進入視網膜,欺騙眼睛來實現的。
2. 什麼情況下會有脫離光學的VR?
答:人類可以脫離眼睛溝通這個世界的時候(笑)。
這兩個問題看似簡單,實則涉及到本文的一個核心要點:
光學
。不管是VR(Virtual Reality 虛擬現實),還是AR(Augmented Reality 增強現實),還是號稱象徵著未來的MR(Mix reality 混合現實),都脫離不了光學。本文會將
VR的基礎實現原理
儘量講一遍,由於讀者對行業的瞭解程度關係,可能不會講得太深入。但是,如果有朋友希望瞭解一些更深入的東東,歡迎大家在文章下留言評論,我會盡力與大家交流解答。
最廉價的VR體驗裝置——Google CardBoard一代
曾擊敗Xbox One與PS4獲得“最佳硬體獎”的遊戲裝置——Oculus Rift CV1
以上兩個,就是比較有代表性的
HMD
(做筆記以後出去談VR的時候又有了一個專(zhuang)業(bi)名詞了呢),
HMD是Head Mount Display的縮寫,也就是俗稱的頭顯
,這是VR最核心的裝置,只有有了這個東西,關於VR的一切才有可能。
上面的兩個裝置,一個是最原始的Google Cardboard,一個是大名鼎鼎的“三大頭顯”之一:Oculus rift CV1,CV1看起來相較於Google Cardboard顯得十分高大上,充滿了科技感與時尚感。但究其核心,還是那些東西:
一個塑膠外殼,兩個鏡片,一個顯示器。
看到這裡有些小夥伴可能不免失望,原來所謂的科技最前沿,就是這3樣東西呀。不急,下面我們來繼續探究其中的原理。
首先我們來看一個大致的結構圖:
這是一個典型的VR眼鏡功能模擬,主要包括
三部分
:
人眼
凸透鏡
OLED成像螢幕
看起來大家會覺得更簡單了,好像我隨便
找個螢幕,找個透鏡,再找個盒子,就組成了一個VR裝置
(瞬間化身科技公司大佬可以指點江山了)。那麼究竟實時是否如此呢?答案是:
是的!
大家沒看錯,國內一大把各種魔鏡,都是這麼做的。那麼究竟怎麼做呢? 下面為大家介紹一條
創(fa)新(jia)創(zhi)業(fu)之路:
首先,先去市場(淘寶),購買一大把透鏡,從幾毛到幾塊一塊都有,然後找一撥人(例如公司員工),大家一塊一塊看,最終大家公認哪一款效果最好,然後定了,就這款了!再用手工做一個手版的盒子,裝上鏡片,再拉上這幫人一起看效果,慢慢調,當效果大家覺得湊合的時候,那麼,引數定下來了,一款VR眼鏡誕生了!
這種VR眼鏡,被稱作
人海VR
,常見於市面上幾十、一百塊的各種盒子,在華強北據稱一年出貨量幾千萬個。這類盒子的體驗效果可以預見是非常差的,容易使人暈眩,基本上可以看做是一個玩具。
那麼,一款優秀的HMD應該是怎麼樣的呢?筆者個人認為
以下兩點至關重要
:
完全符合人體結構
儘量輕便,降低存在感,也就是說,你戴上去跟沒戴一樣
第二點是小型化問題,目前不考慮,我們來詳細講講第一點。
要做好完全符合人體結構,或者說盡量的去符合人體結構,至少有
以下因素需要考慮
:
人眼觀察角度(也可以叫視場角,簡稱FOV——Field of View)
人雙眼之間的距離(俗稱瞳距,簡稱IPD——Interpupillary distance)
人眼到鏡片的距離
鏡片到螢幕的距離
螢幕成像的大小計算
螢幕成像的反畸變
螢幕成像的渲染幀率
螢幕的重新整理延遲
……
一款優秀的HMD,至少要考慮到這8點。其實除了這8點,還有其他更復雜的東西,例如
自動對焦,運動模糊
等。下面,我們會側重談談這8點應該如何來考慮,做到最好。
要考慮設計一款優秀的HMD,首先要想明白一個問題,
VR的沉浸感
從何而來?要知道這一點就需要先了解視網膜成像原理:
透過視網膜成像,人眼觀察著這個世界。VR如何欺騙了眼睛呢?答案是:
凸透鏡
。
再看下圖:
上圖很好的表述了一個觀點:
你看到的世界不一定是真實的
。你以為看到的是紅色的虛像世界,實際你所看到的,只是藍色螢幕中的一方天地。
為什麼我們需要一塊透鏡?嘗試一下,伸出一根手指,放在你的眼前1釐米,你會發現,你看不清你的手指。很詭異是不是?人眼的成像是有距離的,透鏡是為了把近距離的影象放大成一個虛像,增加成像距離。
透過該圖,很容易得出一些結論:
HMD不能漏光。一旦漏光,談不上什麼沉浸感了
人眼到鏡片最合適的距離,就是鏡片的焦距稍稍往前。
由此我們可以知道,鏡片儘量設計到焦距夠小(便於鏡片覆蓋眼睛)
螢幕到透鏡的距離,跟鏡片的散射角度有關
(參考一下藍線傾斜即可)
最理想的狀態是,人眼觀察角度與紅線部分重合
(基本上不可能達到理想狀態,因為HMD是固定的,而人與人眼睛是不同的)。
好了,根據這四點結論,我們可以比較輕易判斷,對於一款HMD而言,合適的鏡片至關重要,直接決定了HMD的最終質量。因此,在
VR鏡片
上,各大公司可謂不遺餘力。除了所謂的
光學鏡片
,最近還在熱炒各種概念,例如
菲涅爾鏡片
(Fresnel lens)
,光場鏡片
(這塊獨立出來都是一個很複雜的概念,由於篇幅有限,這裡不打算細講)。
既然一款鏡片如此重要,那麼,如何才能設計出一款足夠牛逼的鏡片?需要重點關注哪些引數?下面幾個引數非常重要:
視場角(FOV)
符合人眼構造的成像系統
清晰度
視場角多少合適?有一種主流的看法是:
合理範圍內,視場角越大越好
。那麼,什麼叫做
“合理範圍內”
?,可以理解為:
沒有導致明顯的透視變形之前
(關於透視變形,這是3d的基礎概念,不打算科普,大家度娘一下就能找到)
儘量達到人眼最大視場角。
由於人的眼珠是可以轉動的,單眼最大理論視場角大概在150度左右。那麼,FOV要達到150度嗎?其實不是,FOV大小還跟螢幕解析度有關,當解析度不足夠的時候,FOV越大,會導致紗窗效果越明顯。關於這個,計算也很簡單,只需要計算觀察範圍面積(透過FOV和觀察距離),再用螢幕畫素 / 觀察面積,就能夠得到每平方cm有多少個畫素。單位範圍內畫素越小,效果越差。如果是做開發的應該能輕易算出來。
因此,在目前大部分裝置上,視場角並沒有越大越好,
普遍在90 - 100之間,最高的貌似也就105左右
符合人眼的成像系統,這個又是什麼意思呢?
熟悉3d圖形學的朋友會知道:
3d中,透視投影變換主要是三個矩陣:world, view, projection
。前兩個是座標變換,不在今天的討論範圍,第三個,卻是實打實的投影模擬。但是,在普通3d遊戲中,這個投影模擬的不是人眼視網膜,而是一個計算機顯示器視窗,所以,這個矩陣的計算引數一般有:
Y方向FOV,視窗寬高比,最近可視距離,最遠可視距離
。大概演算法如下:
float thetaY(mFOVy / 2。0f);
float tanThetaY = tan(thetaY);
// Calc matrix elements
float w = (1。0f / tanThetaY) / mAspect;
float h = 1。0f / tanThetaY;
float q, qn;
q = -(mFarDist + mNearDist) / (mFarDist - mNearDist);
qn = -2 * (mFarDist * mNearDist) / (mFarDist - mNearDist);
// [ w 0 0 0 ]
// [ 0 h 0 0 ]
// [ 0 0 q qn ]
// [ 0 0 -1 0 ]
Matrix4 dest = Matrix4::ZERO;
dest[0][0] = w;
dest[1][1] = h;
dest[2][2] = q;
dest[2][3] = qn;
dest[3][2] = -1;
mProjectionMatrix = dest;
問題來了,我們做VR,適合採用這種方式嗎?答案是:這樣可以做,但是效果不夠理想,因為並沒有符合人體結構。
在瞭解“
符合人體結構
”這個概念前,大家可以做一個小實驗。嘗試閉上一隻眼睛,然後只用一隻眼睛,盡力前後左右看,你覺得這是一個正投影嗎?也就是說,上下,左右看到的角度大小,是一樣的嗎?很顯然,我們基本能夠確定,上下,左右能觀察到的範圍,並不對稱,這才是真正的人體結構。
因此,我們計算的投影矩陣,理想的狀態,不能是正矩陣。理想的資料是多少?這裡我給出Oculus的一個眼睛資料:上下左右角度分別為:41。65, 48, 43。98, 35。57。
那麼,這個角度範圍就是最理想的範圍了嗎?答案是:不是。既然不是(正常人平均值大概是56、74、91、65),作為VR界的領軍人物,難道Oculus不知道這個事情嗎,為什麼不直接用最合適的範圍角度?很簡單,問題在於光學鏡片的設計,光學鏡片並不能為所欲為的設計這個視場角(還有一個原因還是螢幕解析度),所以現在的VR大熱菲尼爾鏡片應運而生。
菲尼爾鏡片
好處多多,可以有其他很多好處例如清晰度,畸變……
據說希拉里的眼鏡就是菲涅爾鏡片哦
。
綜上所述,我們大概理清了鏡片的一些重要的引數和合適設計,下面,我們來講講重要的
成像
以及
反畸變
。
原圖(左)經過VR觀看的效果(右)
關於成像與反畸變的一些問題解答:
VR成像跟普通的3d遊戲成像有什麼不同?
Projection Matrix不同
。參見上一條,Projection的計算不能採用傳統的正矩陣計算。
View Matrix不同。
考慮到IPD(瞳距),View Matrix的計算應該分左右眼,position的計算應該是:
LeftPosition = position + rotation * (-IPD / 2);RightPosition = position + rotation * (IPD / 2)
;其實就是左右眼分別沿x軸的
正負方向
偏移IPD的一半。
渲染方式不同
。正常的3d渲染是單屏,而VR是需要先渲染兩隻眼睛,得到
render texture
,然後再把左右眼貼圖渲染一遍到螢幕。
由於第三個特性,VR渲染跟傳統渲染相比,有很多細節需要注意
。例如到底使用延遲渲染還是使用向前渲染,抗鋸齒應該怎麼處理,模糊怎麼處理……一句話概括:VR有可能使得傳統的渲染模式發生改變。典型的例如Google Day Dream本身推薦使用放大RTT的做法來實現抗鋸齒(跟FSAA類似)。
VR成像的內容不算太多,更重要的是反畸變。
為什麼要反畸變?
答:
因為正常的東西,透過凸透鏡去看,是變形的
(隨便拿塊透鏡試試便知)。所以我們不能夠直接把render texture直接貼到螢幕上,需要把圖片做一個反畸變,然後透過透鏡的畸變,觀看的畫面變成了正常。
那麼問題來了,這個反畸變應該如何做?
我們先來看看大名鼎鼎的Google CardBoard的實現方式。
這是一張放爛了的圖,Google文件專用。在他們的開發文件裡,他們號稱使用了布朗畸變模型(細節可以在這裡看到:https://en。wikipedia。org/wiki/Distortion_(optics),又或者google一下vr
lens distortion
,資料很多)。這裡,我就不做純粹的搬運工,直接複製貼上,來幫大家分析一下要點:
首先,得到鏡頭的中心點
(中心點由於是垂直透過,沒有角度,無畸變)
任意點的畸變,先計算該點到圓心的半徑
根據鏡頭K1、K2的引數,計算出畸變後的座標
紋理取樣時,根據這個偏移做UV偏移,取樣
這個是大概過程。目測很多好奇的群眾會跟我一樣,好奇這個K1、K2從何而來?我其實也很好奇,但是由於Google的文件語焉不詳,很詭異的是,Google文件裡面還提及到手工調節這兩個引數,言下之意是:如果你根據鏡片廠商提供的這兩個引數,沒有達到最好的效果,請手動調節這兩個引數,以達到最好的效果。
再來看看同樣大名鼎鼎的Oculus Rift的實現方式。其實Oculus早期採用了跟Google一樣的方式(誰模仿誰不知道),
只不過兩個引數K1、K2變成了四個:K1,K2,K3,K4
,其實這樣做意義不大,最早期的資料,K3很接近0,K4直接是0,幾乎不起作用,後期陸陸續續有各種改動。但是,Oculus越做越好,後面徹底摒棄了之前的方式,
採用了全新的Distortion Mesh的渲染方式
。我們的鏡片,同樣採用了這種方式,核心做法是:
設計好鏡片
根據鏡片的引數(大概20多個),生成一個畸變模型
畸變模型的UV要分為RGB三塊,三組UV
貼圖渲染的時候,分別做RGB取樣,得到新的顏色的RGB,組合成新顏色
大概shader代如下:
static char* defaultDistortionVertexShaderSrc =
“float4x4 ProjView;float4 MasterCol;”
“void main(in float4 Position : POSITION, in float2 TexCoordR : TEXCOORD0, in float2 TexCoordG : TEXCOORD1,in float2 TexCoordB : TEXCOORD2,”
“ out float4 oPosition : SV_Position, out float4 oColor: COLOR0, out float2 oTexCoordR : TEXCOORD0, out float2 oTexCoordG : TEXCOORD1, out float2 oTexCoordB : TEXCOORD2)”
“{ oPosition = Position;”
“ oColor = MasterCol;”
“ oTexCoordR = TexCoordR;”
“ oTexCoordG = TexCoordG;”
“ oTexCoordB = TexCoordB;}”;
static char* defaultDistortionPixelShaderSrc =
“Texture2D Texture : register(t0); SamplerState Linear : register(s0);”
“float4 main(in float4 Position : SV_Position, in float4 Color: COLOR0, in float2 TexCoordR : TEXCOORD0, in float2 TexCoordG : TEXCOORD1, in float2 TexCoordB : TEXCOORD2) : SV_Target”
“{ float4 TexColR = Texture。Sample(Linear, TexCoordR);”
“ float4 TexColG = Texture。Sample(Linear, TexCoordG);”
“ float4 TexColB = Texture。Sample(Linear, TexCoordB);”
“ return float4(TexColR。r * Color。r, TexColG。g * Color。g, TexColB。b * Color。b, 1); }”;
那麼,
兩種方式的差別是什麼?是什麼原因導致了兩大巨頭分別採用了不同的實現方式?
我個人的判斷是:Oculus的方式,效果明顯要優於Google的方式,
最明顯的問題,在光線經過透鏡的時候,RGB由於波長不同,折射率是不一樣的
,這部分在高中物理裡有講過。
所以紋理反畸變的時候,必須要做rgb偏移,不然,一定會有色差問題,並且邊緣地方越加明顯。
既然如此,難道Google不知道嗎,為什麼不也採用Oculus的方式?我的看法:
Google希望做的是一個開放的系統,希望適配所有的鏡片與手機
。這可以理解為是
從商業角度去考慮
。
而使用Distortion Mesh的方式,不可能做到這一點或者說很不容易做到這一點(我其實覺得可以開放驅動介面的方式也是可以實現的,詳情請看Windows適配各種硬體),但是,Google採用了最簡單暴力的方式,這樣的話,讓移動平臺的VR蒙上了陰影(Google最新推出的DayDream系統、盒子、樣機。黃牛價大概7000多,幾乎沒有色差矯正)。
配合手機使用的Daydream View頭盔和控制器
2016,號稱VR元年,但是,風風火火看似聲勢浩大的開局,交出的成績單卻不盡如人意,很大程度上在於體驗效果並沒有達到預期。
為了相容大量不適合vr的手機而選擇了一種效果更差的方式。簡而言之,這是一種體驗效果與商業模式的動態博弈。
這種方式的是否可取,以目前看來, 我個人認為,還是要優先深耕技術與體驗效果,再進一步考慮
商業成本
與盈利的問題,好的體驗感一定會有消費者來買單。
為什麼Distortion Mesh方式效果要優於Google的實現方式?很簡單,這就像3d渲染的兩種方式,光線跟蹤與光柵化。
眾所周知,目前的實時渲染都是光柵化,再配合少量光線跟蹤,純光線跟蹤的Real Time,三五年內看不到希望。而Distortion Mesh的方式,就是光線跟蹤,根據設計好的鏡片,做一個光線模擬,計算,生成各個頂點的RGB偏移。而Google的計算方式,希望透過一條公式計算匹配所有的鏡片,在未來菲涅爾鏡片爛大街的時候,會更顯無力。以上是我個人判斷。
上面簡述了
光學VR
實現的一些基本原理,希望能夠對對VR行業有興趣的小夥伴有所幫助,更多的細節問題,如果大家有興趣可以一起探討。行業的發展需要技術的沉澱與市場的逐步認可與接收,
大多數人長期徘徊在“等風來”的時期。現在,風口已至,是否能夠迎風直上,讓我們拭目以待。
本文作者:
深圳市奇境資訊科技有限公司
劉粵桂