教程
OpenGL ES實(shí)踐教程1-Demo01-AVPlayer
OpenGL ES實(shí)踐教程2-Demo02-攝像頭采集數(shù)據(jù)和渲染
OpenGL ES實(shí)踐教程3-Demo03-Mirror
其他教程請(qǐng)移步OpenGL ES文集,這一篇介紹以下知識(shí)點(diǎn):
- AVFoundation——加載視頻;
- CoreVideo——配置紋理;
- OpenGL ES——渲染視頻;
- 3D數(shù)學(xué)——球體以及3維變換;
核心思路
通過AVFoundation加載視頻源,讀取到每一幀的CMSampleBuffer之后,用CoreVideo創(chuàng)建OpenGL ES紋理緩存并上傳GPU;OpenGL ES按照球體的模型來渲染視頻;用移動(dòng)攝像機(jī)朝向或者旋轉(zhuǎn)球體的方式來響應(yīng)手指的移動(dòng)達(dá)到移動(dòng)鏡頭的效果。
效果展示
具體細(xì)節(jié)
1、配置OpenGL ES;
-
loadShaders
加載著色器和compileShader
編譯著色器的內(nèi)容前面的教程已經(jīng)介紹過都次,不再贅述; -
setupBuffers
配置緩存信息,并且創(chuàng)建頂點(diǎn)數(shù)據(jù)緩存,把球體的頂點(diǎn)和紋理數(shù)據(jù)先上傳GPU;
因?yàn)槟P偷捻旤c(diǎn)數(shù)據(jù)不會(huì)變化,故而可以預(yù)先上傳,使用時(shí)只需通過
glBindBuffer
即可使用頂點(diǎn)數(shù)據(jù);
如果想每幀都上傳頂點(diǎn)數(shù)據(jù)亦可以。(不推薦)
-
glUniform
常量賦值在編譯鏈接完成頂點(diǎn)著色器后,可以設(shè)置著色器里面用到常量;
2、加載視頻;
-
loadAsset
創(chuàng)建視頻源,并用loadValuesAsynchronouslyForKeys
加載軌道信息; -
createAssetReader
創(chuàng)建Reader,并設(shè)置讀取的格式與軌道目標(biāo); -
processAsset
開始Reader,并啟動(dòng)CADisplayLink開始讀取視頻幀;
通過mReaderVideoTrackOutput的copyNextSampleBuffer
可以獲取到下一幀的視頻信息;
通過CMSampleBufferGetImageBuffer
可以獲取到CMSampleBuffer里面的像素緩存pixelBuffer,將其傳給GLView用于配置紋理;
CMSampleBufferRef sampleBuffer = [self.mReaderVideoTrackOutput copyNextSampleBuffer];
CVPixelBufferRef pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
3、配置紋理;
- 通過
CVBufferGetAttachment
獲取pixelBuffer的顏色空間格式,決定使用的顏色轉(zhuǎn)換矩陣,用于下一步的YUV到RGB顏色空間的轉(zhuǎn)換; - 通過
glActiveTexture
啟用紋理,用
CVOpenGLESTextureCacheCreateTextureFromImage
創(chuàng)建紋理,再glBindTexture
綁定紋理,設(shè)置好紋理格式;
CVOpenGLESTextureCacheCreateTextureFromImage
創(chuàng)建的亮度紋理為frameHeight * frameWidth,格式為GL_LUMINANCE
;
CVOpenGLESTextureCacheCreateTextureFromImage
創(chuàng)建的亮度紋理為frameHeight/2 * frameWidth/2,格式為GL_LUMINANCE_ALPHA
;
思考0:為何要使用CV?是否可以不使用CV直接讀取紋理信息?
4、YUV到RGB顏色空間的轉(zhuǎn)換;
YUV顏色空間由亮度+色度組成,GPU支持的RGB的顏色空間,故而需要進(jìn)行一次轉(zhuǎn)換。
下面是demo用到的顏色轉(zhuǎn)換矩陣:
const GLfloat kColorConversion601FullRange[] = {
1.0, 1.0, 1.0,
0.0, -0.343, 1.765,
1.4, -0.711, 0.0,
};
注意:顏色轉(zhuǎn)換和頂點(diǎn)變換都是通過矩陣來計(jì)算。
5、球體渲染
簡單介紹下全景視頻的原理:
通過多個(gè)攝像機(jī)錄制多方向的視頻,通過投影計(jì)算,存儲(chǔ)到一個(gè)視頻中;
將視頻渲染到球面上,通過攝像機(jī)的位置與朝向,計(jì)算每次能顯示的內(nèi)容并繪制到屏幕。如下圖:
這就涉及到兩個(gè)問題:
- 將全景的視頻信息存儲(chǔ)在二維的視頻里面;
- 將二維的視頻還原成全景的視頻信息。
(攝像機(jī)的位置和朝向計(jì)算看下面)
思考1:全景視頻顯示效果與普通視頻有何區(qū)別?為什么?
球面到2D視頻的展開
假設(shè)地球被圍在一中空的圓柱里,其基準(zhǔn)緯線與圓柱相切(赤道)接觸,然后再假想地球中心有一盞燈,把球面上的圖形投影到圓柱體上,再把圓柱體展開,得到投影。
越靠近畫面的TOP和BOTTOM,圖像的扭曲效果就越嚴(yán)重。上圖還看不太出來,看看下圖。
思考2:是否存在沒有扭曲效果的全景顯示?
2D視頻到球面的顯示
之前的教程有介紹過,點(diǎn)這里
下圖是一張展開了的地球圖像
下圖是按照球體的頂點(diǎn)數(shù)據(jù)進(jìn)行渲染
6、視角變化
球的圓心在原點(diǎn),攝像機(jī)的所在也是原點(diǎn),如下圖。
球坐標(biāo)系(r,θ,φ)與直角坐標(biāo)系(x,y,z)的轉(zhuǎn)換關(guān)系:
x=rsinθcosφ
y=rsinθsinφ
z=rcosθ
思考
思考0:視頻的紋理創(chuàng)建、銷毀非常頻繁,并且紋理普遍較大,CV對(duì)紋理的創(chuàng)建和緩存有針對(duì)的優(yōu)化,故而在處理視頻幀的時(shí)候推薦通過CV來處理紋理(圖像不行)。
如果不是用CV創(chuàng)建紋理,可以通過CVPixelBufferGetBaseAddress,拿到pixelBuffer的像素指針p;p的起始是亮度紋理,p加上亮度紋理的偏移量(frameWidth * frameHeight)之后是色度紋理。
思考1:全景視頻帶有明顯的扭曲效果。因?yàn)槭前?D平面的紋理渲染到球面上,故而帶有扭曲效果。
思考2:存在。天空盒可以做到。天空盒
擴(kuò)展
1、投影方式
Equisolid投影
Mercator投影
2、錄制難點(diǎn)
同步、角度、分屏(雙倍設(shè)備)
和VR的區(qū)別。全景+雙屏。
總結(jié)
demo的起因是群里和徐杰聊天的時(shí)候說到最近看到一個(gè)全景視頻直播,想起以前自己曾想過做一個(gè)全景圖像,結(jié)果因?yàn)椴欢瓹V和AVFoundation、沒有球體的頂點(diǎn)數(shù)據(jù)而放棄。
剛好他有一個(gè)視頻源,就要過來再試試。
結(jié)果這次的demo只花一天的時(shí)間就做完了,第二天的時(shí)間都是微調(diào)手指觸摸的體驗(yàn)。
實(shí)現(xiàn)過程中遇到一些坑,但是在分析完數(shù)據(jù)之后也馬上解決,一次很好的實(shí)踐體驗(yàn)。
篇幅有限,代碼在這里,歡迎star、fork。疑問請(qǐng)留言。