输入是一个正方形vr四棱锥变换后的视频,目的是手机上反映射播放全景视频。

app的demo基于native-media进行改动,因为大部分都是针对opengl ES的改动,所以主要修改文件为MyGLSurfaceView.java。

其他改动都是些界面或者按钮的改动,这里不赘述了。这里主要讲解一下opengl这部分的流程。


对应opengl函数请参考网上的文档

https://www.khronos.org/opengles/sdk/docs/man3/


1. 建立两个shader,vertexShader和fragmentShader,并创建program。

    每个shader都需要设定shader,编译shader,最后绑定到新建的program上。
    两个shader的输入是类C语言。具体参见另一篇文章,《Shader语言的一些简单解释和备注》

2. Shader如何作图。主要依赖两个数组:mVerticesValue[],mPositionValue[]。前者为目的投影点坐标,为三维坐标x,y,z。范围没有限制,只要保证比例就可以了。后者为原始图像上的点UV二维坐标,范围均为0~1。按比例对其进行选择。画图一般按三角形进行画图。

3. 建立一个texture(在显存中)

int[] textures = new int[1];
        GLES30.glGenTextures(1, textures, 0);
        mTextureID = textures[0];

4. 绑定texture。因为是YUV数据所以绑定GL_TEXTURE_EXTERNAL_OES

  The GL_TEXTURE_EXTERNAL_OES texture target isusually in a YUV color space

GLES30.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);

5. 设置参数,用于texture缩放时如何处理

// Can't do mipmapping with camera source
        GLES30.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MIN_FILTER,
                //GLES30.GL_NEAREST);
				GLES30.GL_LINEAR);
        GLES30.glTexParameterf(GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MAG_FILTER,
                //GLES30.GL_NEAREST);
                GLES30.GL_LINEAR);
        // Clamp to edge is the only option
        GLES30.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_S,<pre name="code" class="java">        mVerticesV.position(0);
        GLES30.glVertexAttribPointer(maPositionHandle, 3, GLES30.GL_FLOAT, false, 3 * FLOAT_SIZE_BYTES, mVerticesV);
        GLES30.glEnableVertexAttribArray(maPositionHandle);
        mPositionV.position(0);
        GLES30.glVertexAttribPointer(maTextureHandle, 2, GLES30.GL_FLOAT, false, 2 * FLOAT_SIZE_BYTES, mPositionV);
        GLES30.glEnableVertexAttribArray(maTextureHandle);



GLES30.GL_CLAMP_TO_EDGE); GLES30.glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);



6. 创建一个surface(用于内存),用于给这个texture(显存中)送数据。绑定监听

mSurface = new SurfaceTexture(mTextureID);
        mSurface.setOnFrameAvailableListener(this);

7. 准备画一幅图时,会来到onDrawFrame()。首先activate texture0,绑定当前创建的texture到GL_TEXTURE_EXTERNAL_OES上。


<pre name="code" class="java"><pre name="code" class="java">        GLES30.glClear( GLES30.GL_DEPTH_BUFFER_BIT | GLES30.GL_COLOR_BUFFER_BIT);
        GLES30.glUseProgram(mProgram);
        checkGlError("glUseProgram");

        GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
        GLES30.glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTextureID);




8. 把需要画的顶点数组和纹理位置数组绑定,一个三维的用于重建,一个二维的是输入图像坐标


mVerticesV.position(0);
        GLES30.glVertexAttribPointer(maPositionHandle, 3, GLES30.GL_FLOAT, false, 3 * FLOAT_SIZE_BYTES, mVerticesV);
        GLES30.glEnableVertexAttribArray(maPositionHandle);
        mPositionV.position(0);
        GLES30.glVertexAttribPointer(maTextureHandle, 2, GLES30.GL_FLOAT, false, 2 * FLOAT_SIZE_BYTES, mPositionV);
        GLES30.glEnableVertexAttribArray(maTextureHandle);

9. 设置观察点位置和方向。第二行三个坐标表示观察点坐标,例子中是原点。第二组3个坐标表示看的方向向量,可以变化。第三组大概意思是摄像机怎么摆放,可以理解为摄像师人头的坐标,(0,1,0)表示站在xz平面上,头在y正方向。 可以参考文章:

Matrix.setLookAtM(mVMatrix, 0, 
                                    0f, 0f, 0f, 
                                    lookX, lookY, lookZ, 
                                    0f, 1f, 0.0f);

10. 不太好解释frustum这个方法,怕误导,照搬一个解释放这:View frustum is justa visual representation of perspective projection that is used to convert 3Dpoint in the world coordinate space to the 2D point on the screen.

void glFrustum(GLdouble left, GLdouble Right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far);
创建一个透视型的视景体。其操作是创建一个透视投影的矩阵,并且用这个矩阵乘以当前矩阵。这个函数的参数只定义近裁剪平面的左下角点和右上角点的三维空间坐标,即(left,bottom,-near)和(right,top,-near);最后一个参数far是远裁剪平面的离视点的距离值,其左下角点和右上角点空间坐标由函数根据透视投影原理自动生成。near和far表示离视点的远近,它们总为正值(near/far 必须>0)。


final float ratio = 0.7f;
Matrix.frustumM(mProjMatrix, 0, -ratio, ratio, -ratio, ratio, 1f, 10f);

11.  一些对数组的操作以及使能(具体没太深究,也没有修改这部分),最后调用GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 18) 画图。第一个参数表示按三角形画图,第二个参数表示输入一共有18个顶点,也就是6个三角形。

Matrix.multiplyMM(mMVPMatrix, 0, mVMatrix, 0, mMMatrix, 0);
        Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mMVPMatrix, 0);

        GLES30.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0);
        GLES30.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0);
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 18);



下面是我的顶点和纹理坐标数组,涉及到转四棱锥时的扩充参数以及反变换去缝的操作,也和四个三角形中心点取值位置有关,仅供参考。

// size 18
private final float[] mVerticesValue = {
    // X, Y, Z,
    0.f,  0.f, -3.16987f,	// O
    -5.f,  0.f, 1.830127f,	// A
    0.f, -5.f, 1.830127f,	// D
    
    0.f,  0.f, -3.16987f,	// O
    0.f, -5.f, 1.830127f,	// D
    5.f,  0.f, 1.830127f,	// C
    
    0.f,  0.f, -3.16987f,	// O
    5.f,  0.f, 1.830127f,	// C
    0.f,  5.f, 1.830127f,	// B
    
    0.f,  0.f, -3.16987f,	// O
    0.f,  5.f, 1.830127f,	// B
    -5.f,  0.f, 1.830127f,	// A
    
    // square
    0.f,  5.f, 1.830127f,	// B
    -5.f,  0.f, 1.830127f,	// A
    0.f, -5.f, 1.830127f,	// D
    
    0.f,  5.f, 1.830127f,	// B
    0.f, -5.f, 1.830127f,	// D
    5.f,  0.f, 1.830127f,	// C
};

private final float[] mPositionValue = {
    // U, V   1.1					
    0.005769f,	0.005769f,	// O(OAD
    0.005769f,	0.486538f,	// A	
    0.486538f,	0.005769f,	// D	
    
    0.994231f,	0.005769f, // O(ODC 
    0.513462f,	0.005769f, // D 	
    0.994231f,	0.486538f, // C 	
    
    0.994231f,	0.994231f,	// O(OCB
    0.994231f,	0.513462f,	// C	
    0.513462f,	0.994231f,	// B	
    
    0.005769f,	0.994231f,	// O(OBA
    0.486538f,	0.994231f,	// B	
    0.005769f,	0.513462f,	// A	
    
    // square						
    0.5f,		0.980769f,	// B	
    0.019231f,	0.5f,  // A 		
    0.5f,		0.019231f,	// D	
    
    0.5f,		0.980769f,	// B	
    0.5f,		0.019231f,	// D	
    0.980769f,	0.5f,  // C 		
};



最后输出一下结果图。原始vr视频的截图如下:

android opengl 渲染 yuv数据_数组

四棱锥变换后的视频如下:

android opengl 渲染 yuv数据_全景视频_02

手机上通过opengl ES渲染为全景视频效果,角度正面偏左,可随意变换角度(无缝):

android opengl 渲染 yuv数据_全景视频_03