openGL ES進階教程(五)制作一個簡單的VR播放器臊泌,播放全景視頻

之前寫過全景(VR)圖片,和用openGL ES+MediaPlayer 渲染播放視頻+濾鏡效果

這一篇就在之前的基礎(chǔ)上實現(xiàn)一個最簡單的VR播放器蚯嫌,播放全景視頻。

概述:

全景視頻是一種用3D攝像機進行全方位360度進行拍攝的視頻丙躏,用戶在觀看視頻的時候择示,可以隨意調(diào)節(jié)視頻上下左右進行觀看。

展示:

“身臨其境的槍戰(zhàn)” 可見晃動手機晒旅,視圖也跟著轉(zhuǎn)栅盲!


這里寫圖片描述

原理:

1.用OpenGL繪制一個球。
2.MediaPlayer 播放的視頻紋理貼到球上(可以想象為一個地球儀)废恋。
3.把觀測點設(shè)置在求內(nèi)部谈秫。 (想象為從球內(nèi)部中心點觀看球的表面)。
4.用手機內(nèi)置的傳感器得到一個手機移動的矩陣(視頻畫面就可以隨著手機的移動而移動了)拴签。

實踐:

1.用OpenGL繪制一個球
效果如圖:

這里寫圖片描述

用OpenGL繪制任何圖形孝常,都需要先知道頂點坐標旗们。然后用GLES20.glDrawArrays繪制出來蚓哩。
那么繪制一個球體,可以想象為無數(shù)個三角形在三維空間構(gòu)成上渴。
下面這個算法就會放回一個球體的坐標數(shù)組

 private float[] createBallPos(){
        //球以(0,0,0)為中心岸梨,以R為半徑,則球上任意一點的坐標為
        // ( R * cos(a) * sin(b),y0 = R * sin(a),R * cos(a) * cos(b))
        // 其中稠氮,a為圓心到點的線段與xz平面的夾角曹阔,b為圓心到點的線段在xz平面的投影與z軸的夾角
        ArrayList<Float> data=new ArrayList<>();
        float r1,r2;
        float h1,h2;
        float sin,cos;
        for(float i=-90;i<90+step;i+=step){
            r1 = (float)Math.cos(i * Math.PI / 180.0);
            r2 = (float)Math.cos((i + step) * Math.PI / 180.0);
            h1 = (float)Math.sin(i * Math.PI / 180.0);
            h2 = (float)Math.sin((i + step) * Math.PI / 180.0);
            // 固定緯度, 360 度旋轉(zhuǎn)遍歷一條緯線
            float step2=step*2;
            for (float j = 0.0f; j <360.0f+step; j +=step2 ) {
                cos = (float) Math.cos(j * Math.PI / 180.0);
                sin = -(float) Math.sin(j * Math.PI / 180.0);

                data.add(r2 * cos);
                data.add(h2);
                data.add(r2 * sin);
                data.add(r1 * cos);
                data.add(h1);
                data.add(r1 * sin);
            }
        }
        float[] f=new float[data.size()];
        for(int i=0;i<f.length;i++){
            f[i]=data.get(i);
        }
        return f;
    }

然后把坐標傳給natave層

  float[] dataPos=createBallPos();

        vertexBuffer = ByteBuffer.allocateDirect(dataPos.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(dataPos);
        vertexBuffer.position(0);

        vSize=dataPos.length/3;

有了坐標就可以繪制球了。

2.MediaPlayer 播放的視頻紋理貼到球上(可以想象為一個地球儀)隔披。

先貼著色器代碼

attribute vec4 aPosition;//頂點位置
attribute vec4 aTexCoord;//S T 紋理坐標
varying vec2 vTexCoord;
uniform mat4 uMatrix;
uniform mat4 uSTMatrix;

uniform mat4 uViewMatrix;
uniform mat4 uModelMatrix;
uniform mat4 uRotateMatrix;

void main() {
    vTexCoord = (uSTMatrix * aTexCoord).xy;
    gl_Position = uMatrix*uRotateMatrix*uViewMatrix*uModelMatrix*aPosition;
}
#extension GL_OES_EGL_image_external : require
precision mediump float;
varying vec2 vTexCoord;
uniform samplerExternalOES sTexture;
void main() {
    gl_FragColor=texture2D(sTexture, vTexCoord);
}

然后我們根據(jù)球的點把視頻紋理貼到球上

        GLES20.glEnableVertexAttribArray(aPositionLocation);
        GLES20.glVertexAttribPointer(aPositionLocation, 3, GLES20.GL_FLOAT, false,
                0, posBuffer);
        GLES20.glEnableVertexAttribArray(aTextureCoordLocation);
        GLES20.glVertexAttribPointer(aTextureCoordLocation,2,GLES20.GL_FLOAT,false,0,cooBuffer);

繪制:

 GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount);

3.把觀測點設(shè)置在求內(nèi)部赃份。 (想象為從球內(nèi)部中心點觀看球的表面)。

 //設(shè)置相機位置
        Matrix.setLookAtM(mViewMatrix, 0, 0f, 0.0f,0.0f, 0.0f, 0.0f,-1.0f, 0f,-1.0f, 0.0f);

4.用手機內(nèi)置的傳感器得到一個手機移動的矩陣(視頻畫面就可以隨著手機的移動而移動了)
在activity中

 @Override
    public void onSensorChanged(SensorEvent sensorEvent) {
        SensorManager.getRotationMatrixFromVector(matrix,sensorEvent.values);
        glRenderer.setMatrix(matrix);
    }

VRVideoRenderer 完整代碼如下:



import android.content.Context;
import android.graphics.SurfaceTexture;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;
import android.util.Log;
import android.view.Surface;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.ArrayList;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/**
 * Created by Shuo.Wang on 2017/4/19.
 */

public class VRVideoRenderer implements GLSurfaceView.Renderer
        , SurfaceTexture.OnFrameAvailableListener, MediaPlayer.OnVideoSizeChangedListener  {


    private static final String TAG = "GLRenderer";
    
    private static final float UNIT_SIZE = 1f;// 單位尺寸
    private float radius=2f;

    final double angleSpan = Math.PI/90f;// 將球進行單位切分的角度
    int vCount = 0;// 頂點個數(shù),先初始化為0
    private FloatBuffer posBuffer;
    private FloatBuffer cooBuffer;

    private int mHViewMatrix;
    private int mHModelMatrix;
    private int mHRotateMatrix;
    private float[] mViewMatrix=new float[16];
    private float[] mModelMatrix=new float[16];
    private float[] mRotateMatrix=new float[16];
    

    private Context context;
    private int aPositionLocation;
    private int programId;
    private FloatBuffer vertexBuffer;

    private final float[] projectionMatrix=new float[16];
    private int uMatrixLocation;

 
    private FloatBuffer textureVertexBuffer;
    private int uTextureSamplerLocation;
    private int aTextureCoordLocation;
    private int textureId;

    private SurfaceTexture surfaceTexture;
    private MediaPlayer mediaPlayer;
    private float[] mSTMatrix = new float[16];
    private int uSTMMatrixHandle;

    private boolean updateSurface;
    private boolean playerPrepared;
    private int screenWidth,screenHeight;
    public VRVideoRenderer(Context context, String videoPath) {
        this.context = context;
        playerPrepared=false;
        synchronized(this) {
            updateSurface = false;
        }
        calculateAttribute();

        mediaPlayer=new MediaPlayer();
        try{
            mediaPlayer.setDataSource(context, Uri.parse(videoPath));
        }catch (IOException e){
            e.printStackTrace();
        }
        mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
        mediaPlayer.setLooping(true);

        mediaPlayer.setOnVideoSizeChangedListener(this);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        String vertexShader = ShaderUtils.readRawTextFile(context, R.raw.simple_vertex_shader);
        String fragmentShader= ShaderUtils.readRawTextFile(context, R.raw.simple_fragment_shader);
        programId=ShaderUtils.createProgram(vertexShader,fragmentShader);
        aPositionLocation= GLES20.glGetAttribLocation(programId,"aPosition");

        uMatrixLocation=GLES20.glGetUniformLocation(programId,"uMatrix");
        uSTMMatrixHandle = GLES20.glGetUniformLocation(programId, "uSTMatrix");
        mHViewMatrix=GLES20.glGetUniformLocation(programId,"uViewMatrix");
        mHModelMatrix=GLES20.glGetUniformLocation(programId,"uModelMatrix");
        mHRotateMatrix=GLES20.glGetUniformLocation(programId,"uRotateMatrix");
        uTextureSamplerLocation=GLES20.glGetUniformLocation(programId,"sTexture");
        aTextureCoordLocation=GLES20.glGetAttribLocation(programId,"aTexCoord");


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

        textureId = textures[0];
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
        ShaderUtils.checkGlError("glBindTexture mTextureID");
   /*GLES11Ext.GL_TEXTURE_EXTERNAL_OES的用處抓韩?
      之前提到視頻解碼的輸出格式是YUV的(YUV420p纠永,應(yīng)該是),那么這個擴展紋理的作用就是實現(xiàn)YUV格式到RGB的自動轉(zhuǎn)化谒拴,
      我們就不需要再為此寫YUV轉(zhuǎn)RGB的代碼了*/
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
                GLES20.GL_NEAREST);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
                GLES20.GL_LINEAR);

        surfaceTexture = new SurfaceTexture(textureId);
        surfaceTexture.setOnFrameAvailableListener(this);//監(jiān)聽是否有新的一幀數(shù)據(jù)到來

        Surface surface = new Surface(surfaceTexture);
        mediaPlayer.setSurface(surface);
        surface.release();

        if (!playerPrepared){
            try {
                mediaPlayer.prepare();
                playerPrepared=true;
            } catch (IOException t) {
                Log.e(TAG, "media player prepare failed");
            }
            mediaPlayer.start();
            playerPrepared=true;
        }
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        Log.d(TAG, "onSurfaceChanged: "+width+" "+height);
        screenWidth=width; screenHeight=height;

        //計算寬高比
        float ratio=(float)width/height;
        //透視投影矩陣/視錐
        MatrixHelper.perspectiveM(projectionMatrix,0,90,ratio,1f,500);
        //設(shè)置相機位置
        Matrix.setLookAtM(mViewMatrix, 0, 0f, 0.0f,0.0f, 0.0f, 0.0f,-1.0f, 0f,-1.0f, 0.0f);
        //模型矩陣
        Matrix.setIdentityM(mModelMatrix,0);
        Matrix.rotateM(mModelMatrix,0,180f,1f,0f,0f);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES20.glClear( GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
        synchronized (this){
            if (updateSurface){
                surfaceTexture.updateTexImage();//獲取新數(shù)據(jù)
                surfaceTexture.getTransformMatrix(mSTMatrix);//讓新的紋理和紋理坐標系能夠正確的對應(yīng),mSTMatrix的定義是和projectionMatrix完全一樣的尝江。
                updateSurface = false;
            }
        }
        GLES20.glUseProgram(programId);
        GLES20.glUniformMatrix4fv(uMatrixLocation,1,false,projectionMatrix,0);
        GLES20.glUniformMatrix4fv(uSTMMatrixHandle, 1, false, mSTMatrix, 0);
        GLES20.glUniformMatrix4fv(mHViewMatrix,1,false,mViewMatrix,0);
        GLES20.glUniformMatrix4fv(mHModelMatrix,1,false,mModelMatrix,0);
        GLES20.glUniformMatrix4fv(mHRotateMatrix,1,false,mRotateMatrix,0);

        GLES20.glEnableVertexAttribArray(aPositionLocation);
        GLES20.glVertexAttribPointer(aPositionLocation, 3, GLES20.GL_FLOAT, false,
                0, posBuffer);
        GLES20.glEnableVertexAttribArray(aTextureCoordLocation);
        GLES20.glVertexAttribPointer(aTextureCoordLocation,2,GLES20.GL_FLOAT,false,0,cooBuffer);
        
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,textureId);

        GLES20.glUniform1i(uTextureSamplerLocation,0);
        GLES20.glViewport(0,0,screenWidth,screenHeight);
        //GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount);//added by wangshuo
    }

    @Override
    synchronized public void onFrameAvailable(SurfaceTexture surface) {
        updateSurface = true;
    }

    @Override
    public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
        Log.d(TAG, "onVideoSizeChanged: "+width+" "+height);
        //updateProjection(width,height);
    }

    public MediaPlayer getMediaPlayer() {
        return mediaPlayer;
    }

    public void setMatrix(float[] matrix){
        System.arraycopy(matrix,0,mRotateMatrix,0,16);
    }

    private void calculateAttribute(){
        ArrayList<Float> alVertix = new ArrayList<>();
        ArrayList<Float> textureVertix = new ArrayList<>();
        for (double vAngle = 0; vAngle < Math.PI; vAngle = vAngle + angleSpan){

            for (double hAngle = 0; hAngle < 2*Math.PI; hAngle = hAngle + angleSpan){
                float x0 = (float) (radius* Math.sin(vAngle) * Math.cos(hAngle));
                float y0 = (float) (radius* Math.sin(vAngle) * Math.sin(hAngle));
                float z0 = (float) (radius * Math.cos((vAngle)));

                float x1 = (float) (radius* Math.sin(vAngle) * Math.cos(hAngle + angleSpan));
                float y1 = (float) (radius* Math.sin(vAngle) * Math.sin(hAngle + angleSpan));
                float z1 = (float) (radius * Math.cos(vAngle));

                float x2 = (float) (radius* Math.sin(vAngle + angleSpan) * Math.cos(hAngle + angleSpan));
                float y2 = (float) (radius* Math.sin(vAngle + angleSpan) * Math.sin(hAngle + angleSpan));
                float z2 = (float) (radius * Math.cos(vAngle + angleSpan));

                float x3 = (float) (radius* Math.sin(vAngle + angleSpan) * Math.cos(hAngle));
                float y3 = (float) (radius* Math.sin(vAngle + angleSpan) * Math.sin(hAngle));
                float z3 = (float) (radius * Math.cos(vAngle + angleSpan));

                alVertix.add(x1);
                alVertix.add(y1);
                alVertix.add(z1);
                alVertix.add(x0);
                alVertix.add(y0);
                alVertix.add(z0);
                alVertix.add(x3);
                alVertix.add(y3);
                alVertix.add(z3);

                float s0 = (float) (hAngle / Math.PI/2);
                float s1 = (float) ((hAngle + angleSpan)/Math.PI/2);
                float t0 = (float) (vAngle / Math.PI);
                float t1 = (float) ((vAngle + angleSpan) / Math.PI);

                textureVertix.add(s1);// x1 y1對應(yīng)紋理坐標
                textureVertix.add(t0);
                textureVertix.add(s0);// x0 y0對應(yīng)紋理坐標
                textureVertix.add(t0);
                textureVertix.add(s0);// x3 y3對應(yīng)紋理坐標
                textureVertix.add(t1);

                alVertix.add(x1);
                alVertix.add(y1);
                alVertix.add(z1);
                alVertix.add(x3);
                alVertix.add(y3);
                alVertix.add(z3);
                alVertix.add(x2);
                alVertix.add(y2);
                alVertix.add(z2);

                textureVertix.add(s1);// x1 y1對應(yīng)紋理坐標
                textureVertix.add(t0);
                textureVertix.add(s0);// x3 y3對應(yīng)紋理坐標
                textureVertix.add(t1);
                textureVertix.add(s1);// x2 y3對應(yīng)紋理坐標
                textureVertix.add(t1);
            }
        }
        vCount = alVertix.size() / 3;
        posBuffer = convertToFloatBuffer(alVertix);
        cooBuffer=convertToFloatBuffer(textureVertix);
    }

    private FloatBuffer convertToFloatBuffer(ArrayList<Float> data){
        float[] d=new float[data.size()];
        for (int i=0;i<d.length;i++){
            d[i]=data.get(i);
        }

        ByteBuffer buffer=ByteBuffer.allocateDirect(data.size()*4);
        buffer.order(ByteOrder.nativeOrder());
        FloatBuffer ret=buffer.asFloatBuffer();
        ret.put(d);
        ret.position(0);
        return ret;
    }
}


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市英上,隨后出現(xiàn)的幾起案子炭序,更是在濱河造成了極大的恐慌,老刑警劉巖苍日,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惭聂,死亡現(xiàn)場離奇詭異,居然都是意外死亡相恃,警方通過查閱死者的電腦和手機彼妻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來豆茫,“玉大人侨歉,你說我怎么就攤上這事】辏” “怎么了幽邓?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長火脉。 經(jīng)常有香客問我牵舵,道長,這世上最難降的妖魔是什么倦挂? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任畸颅,我火速辦了婚禮,結(jié)果婚禮上方援,老公的妹妹穿的比我還像新娘没炒。我一直安慰自己,他們只是感情好犯戏,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布送火。 她就那樣靜靜地躺著,像睡著了一般先匪。 火紅的嫁衣襯著肌膚如雪种吸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天呀非,我揣著相機與錄音坚俗,去河邊找鬼。 笑死,一個胖子當著我的面吹牛猖败,可吹牛的內(nèi)容都是我干的形耗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼辙浑,長吁一口氣:“原來是場噩夢啊……” “哼激涤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起判呕,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤倦踢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后侠草,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辱挥,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年边涕,在試婚紗的時候發(fā)現(xiàn)自己被綠了晤碘。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡功蜓,死狀恐怖园爷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情式撼,我是刑警寧澤童社,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站著隆,受9級特大地震影響扰楼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜美浦,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一弦赖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浦辨,春花似錦蹬竖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽庆冕。三九已至康吵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間访递,已是汗流浹背晦嵌。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人惭载。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓旱函,卻偏偏與公主長得像,于是被迫代替她去往敵國和親描滔。 傳聞我的和親對象是個殘疾皇子棒妨,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容