iOS VR視訊開發

VincentJac發表於2018-06-20

Demo的Github倉庫地址:https://github.com/filelife/FLVRPlayer

Demo效果圖.gif

為了讓大家更好的閱讀Demo程式碼,以下簡單介紹相關知識及實現思路。

Step 0.

0.1 渲染VR場景視訊的大致思路:

image
逐幀讀取VR視訊的逐幀資料,之後通過著色器渲染到球體模型上,獲取球體中心的作為視覺取鏡點,通過重力感應接受頭部移動向量方向,更新手機的對映取鏡點。

image

Step 1.

1.1 獲取模型

首先,需要建立一個球體。在OpenGL中建立模型,需要頂點和紋理座標,通過3D MAX等工具製作的obj模型在iOS中識別不了,所以需要進行轉換為OpenGL使用的頂點陣列。

有位大神寫了一個模型轉換為頂點陣列的工具:https://github.com/HBehrens/obj2opengl

產物是一套球體模型的頂點資料+頂點索引;

1.2 模型資料描述

為了更好理解頂點資料和定點索引的旨意,我們來分析一個正方形模型的建模資料。

GLfloat squareVertexData[] =
{
    0.5, -0.5, 0.0f,    1.0f, 0.0f, //右下
    -0.5, 0.5, 0.0f,    0.0f, 1.0f, //左上
    -0.5, -0.5, 0.0f,   0.0f, 0.0f, //左下
    0.5, 0.5, -0.0f,    1.0f, 1.0f, //右上
};
複製程式碼
這些點我們本意想要描述的是一個正方形,但是由於手機螢幕座標系內XY軸比例並不一樣,所以最終會產出以下形狀:

image

對於初學者還有個容易忽視的技術點,那就是 ==在OpenGL ES只能繪製三角形==,不能繪製多邊形,但是在OpenGL中確實可以直接繪製多邊形.
//頂點索引
GLuint indices[] ={ 
  0, 1, 2,
  1, 3, 0
};
複製程式碼

image

我們還可以通過以下頂點+索引,繪製一個3D的正方體。
//頂點資料,前三個是頂點座標, 中間三個是頂點顏色,    最後兩個是紋理座標
GLfloat attrArr[] =
{
    -0.5f, 0.5f, 0.0f,       0.0f, 0.0f, 0.5f,       0.0f, 1.0f,//左上
    0.5f, 0.5f, 0.0f,        0.0f, 0.5f, 0.0f,       1.0f, 1.0f,//右上
    -0.5f, -0.5f, 0.0f,      0.5f, 0.0f, 1.0f,       0.0f, 0.0f,//左下
    0.5f, -0.5f, 0.0f,       0.0f, 0.0f, 0.5f,       1.0f, 0.0f,//右下
    -0.5f, 0.5f, 1.0f,       0.0f, 0.0f, 0.5f,       0.0f, 1.0f,//後方左上
    0.5f, 0.5f, 1.0f,        0.0f, 0.5f, 0.0f,       1.0f, 1.0f,//後方右上
    -0.5f, -0.5f, 1.0f,      0.5f, 0.0f, 1.0f,       0.0f, 0.0f,//後方左下
    0.5f, -0.5f, 1.0f,       0.0f, 0.0f, 0.5f,       1.0f, 0.0f,//後方右下
};

複製程式碼
//頂點索引
    GLuint indices[] =
    {
        0, 3, 2,
        0, 1, 3,
        0, 4, 1,
        5, 4, 1,
        1, 5, 3,
        7, 5, 3,
        2, 6, 3,
        6, 7, 3,
        0, 6, 2,
        0, 4, 6,
        4, 5, 7,
        4, 6, 7,
    };

複製程式碼

Step 2.

2.1渲染基礎 - 快取

CPU的運算能力(每秒億次級別)遠高於其對記憶體的讀寫能力(每秒百萬次級別),為了優化效率,避免產生資料飢餓,在OpenGL ES中,可以讓程式從CPU的記憶體中複製資料到GPU控制的連續RAM快取中,GPU在取得一個快取資料之後,便會獨佔該快取,從而儘可能有效的讀寫記憶體資料,快取資料包含:幾何資料、顏色、燈光效果等等。

快取的生命週期:

2.1.1. 生成(Generate):請求OpenGLES為GPU控制的快取生成一個獨一無二的標誌符。

對應函式:glGenBuffers()

2.1.2. 繫結(Bind)告訴OpenGLES為接下來的運算使用一個快取。

對應函式:glBindBuffer()

2.1.3. 啟用(Enable)或者禁止(Disable):告訴OpenGLES再接下來的渲染中,是否使用快取資料。

對應函式:glEnableVertexAttribArray()

2.1.4. 設定指標(Set Points):告訴OpenGLES在快取中的資料儲存的型別和所需要訪問的資料的記憶體指標偏移。

對應函式:geVertexAttribPointer()

2.1.5. 繪圖(Draw):告訴OpenGLES使用當前繫結並啟用的快取中的資料渲染整個場景或者某個場景的一部分。

對應函式:glDrawArrays()

2.1.6. 刪除(Delete):告訴OpenGLES刪除以前生成的快取並釋放相關的資源。

對應函式:glDeleteBuffers()

2.2 一個3D場景的幾何資料

2.2.1. 座標系(笛卡爾座標系):OpeGLES座標系沒有單位,點{1,0,0}到點{2,0,0}的距離就是沿著X軸的的1單位。 2.2.2. 向量:向量是理解現代GPU的關鍵,因為圖形處理器就是大規模向量處理器。 2.2.3. 點、線、三角形:OpenGLES只渲染頂點、線段和三角形。下圖就是一種渲染案例,通過點、線、三角形渲染環形體。

2.3 Core Animation層:

由於iOS作業系統不會讓應用直接向前幀快取或者後幀快取繪圖,也不會讓應用直接控制前幀快取和後幀快取之間的切換。所以iOS使用Core Animation合成器去控制所有繪製層的合成。(例如:StatusBar層+開發者提供的渲染層 = 螢幕最終顯示畫素)

Core Animation合成器使用OpenGLES來儘可能高效地控制GPU、混合層和切換幀快取,因此Core Animation合成器合成影象形成一個合成結果的過程,都和OpenGLES有關。

2.4 GLKView/GLKViewController:

GLKView/GLKViewController都是GLKit框架的一部分。GLKView是UIView的子類,GLKView簡化了 通過CoreAnimation對於:建立幀快取、管理幀快取、繪製幀快取的所需要作的工作。於GLKView相關的GLKViewController是檢視的委託,並接收當檢視需要重繪時的訊息。

Step 3.

理解球體內的取景視角

image

球座標系(r,θ,φ)與直角座標系(x,y,z)的轉換關係:
x=rsinθcosφ
y=rsinθsinφ
z=rcosθ

image

Step 4.

程式碼淺析。

1. GLKBaseEffecty: 隱藏了iOS裝置支援的多格OpenGLES版本之間的差異。在應用中使用BaseEffect能夠減少程式碼量,例如簡化避免我們去使用OpenGLES2中的Shading Lauguage。
EAGL:Embedded Apple GL
2. EAGLContext: Context上下文是為了多工在互不干擾情況下,共享影象的硬體裝置而存在的, EAGLContext則提供並行工作的函式方法,並控制GPU去執行渲染運算。
3. EAGLSharegroup: 管理Context中的OpenGL ES物件,Context需要通過EAGLSharegroup物件建立和管理的。

[圖片上傳失敗...(image-9f3553-1529478576825)]

4. glTexParameter :函式用於制定紋理的應用方式。[濾波模式(第14頁)]
紋理放大與縮小如下圖
/* TextureParameterName */
//提供紋理放大縮小濾波
#define GL_TEXTURE_MAG_FILTER                            0x2800
#define GL_TEXTURE_MIN_FILTER                            0x2801
#define GL_TEXTURE_WRAP_S                                0x2802
#define GL_TEXTURE_WRAP_T                                0x2803
@ glTexParameteri (GLenum target, GLenum pname, GLint param);
//glTexParameterf vs glTexParameteri:不同點在於 integerfloat型別入參。
//In the case where the pname (second) parameter is GL_TEXTURE_WRAP_S where you are passing an enum you should use glTexParameteri but for other possible values such as GL_TEXTURE_MIN_LOD and GL_TEXTURE_MAX_LOD it makes sense to pass a float parameter using glTexParameterf. 
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_REPEAT);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
複製程式碼
5. 紋理空間對映到物件空間上產生了Wrap,關於S方向和T方向上的延伸方案:
#define GL_TEXTURE_WRAP_S                                0x2802
#define GL_TEXTURE_WRAP_T                                0x2803
複製程式碼
6. 紋理在做Mipmap(紋理對映)變化中產生的縮小方案:

取樣方案:

點取樣方法:最近紋素的紋理值。
線性濾波:最近領域(2x2)紋素加權平均值。
複製程式碼

補充兩點:

1. Shader語法介紹
2. 齊次記法

相關文章