中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Android?OpenGL如何實現APP裸眼3D效果

發布時間:2022-01-24 09:43:25 來源:億速云 閱讀:214 作者:kk 欄目:開發技術

Android OpenGL如何實現APP裸眼3D效果,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。

    原理簡介 & OpenGL 的優勢

    裸眼 3D 效果的本質是——將整個圖片結構分為 3 層:上層、中層、以及底層。在手機左右上下旋轉時,上層和底層的圖片呈相反的方向進行移動,中層則不動,在視覺上給人一種 3D 的感覺:

    Android?OpenGL如何實現APP裸眼3D效果

    也就是說效果是由以下三張圖構成的:

    Android?OpenGL如何實現APP裸眼3D效果

    接下來,如何感應手機的旋轉狀態,并將三層圖片進行對應的移動呢?當然是使用設備自身提供各種各樣優秀的傳感器了,通過傳感器不斷回調獲取設備的旋轉狀態,對 UI 進行對應地渲染即可。

    筆者最終選擇了 Android 平臺上的 OpenGL API 進行渲染,直接的原因是,無需將社區內已有的實現方案重復照搬。

    另一個重要的原因是,GPU 更適合圖形、圖像的處理,裸眼3D效果中有大量的縮放和位移操作,都可在 java 層通過一個 矩陣 對幾何變換進行描述,通過 shader 小程序中交給 GPU 處理 ——因此,理論上 OpenGL 的渲染性能比其它幾個方案更好一些。

    本文重點是描述 OpenGL 繪制時的思路描述,因此下文僅展示部分核心代碼。

    具體實現

    1. 繪制靜態圖片

    首先需要將3張圖片依次進行靜態繪制,這里涉及大量 OpenGL API 的使用,不熟悉的讀可略讀本小節,以捋清思路為主。

    首先看一下頂點和片元著色器的 shader 代碼,其定義了圖像紋理是如何在GPU中處理渲染的:

    // 頂點著色器代碼
    // 頂點坐標
    attribute vec4 av_Position;
    // 紋理坐標
    attribute vec2 af_Position;
    uniform mat4 u_Matrix;
    varying vec2 v_texPo;
    
    void main() {
        v_texPo = af_Position;
        gl_Position =  u_Matrix * av_Position;
    }
    // 頂點著色器代碼
    // 頂點坐標
    attribute vec4 av_Position;
    // 紋理坐標
    attribute vec2 af_Position;
    uniform mat4 u_Matrix;
    varying vec2 v_texPo;
    
    void main() {
        v_texPo = af_Position;
        gl_Position =  u_Matrix * av_Position;
    }

    定義好了 Shader ,接下來在 GLSurfaceView (可以理解為 OpenGL 中的畫布) 創建時,初始化Shader小程序,并將圖像紋理依次加載到GPU中:

    public class My3DRenderer implements GLSurfaceView.Renderer {
      
      @Override
      public void onSurfaceCreated(GL10 gl, EGLConfig config) {
          // 1.加載shader小程序
          mProgram = loadShaderWithResource(
                  mContext,
                  R.raw.projection_vertex_shader,
                  R.raw.projection_fragment_shader
          );
          
          // ... 
          
          // 2. 依次將3張切圖紋理傳入GPU
          this.texImageInner(R.drawable.bg_3d_back, mBackTextureId);
          this.texImageInner(R.drawable.bg_3d_mid, mMidTextureId);
          this.texImageInner(R.drawable.bg_3d_fore, mFrontTextureId);
      }
    }

    接下來是定義視口的大小,因為是2D圖像變換,且切圖和手機屏幕的寬高比基本一致,因此簡單定義一個單位矩陣的正交投影即可:

    public class My3DRenderer implements GLSurfaceView.Renderer {
      
        // 投影矩陣
        private float[] mProjectionMatrix = new float[16];
    
        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {
            // 設置視口大小,這里設置全屏
            GLES20.glViewport(0, 0, width, height);
            // 圖像和屏幕寬高比基本一致,簡化處理,使用一個單位矩陣
            Matrix.setIdentityM(mProjectionMatrix, 0);
        }
    }

    最后就是繪制,讀者需要理解,對于前、中、后三層圖像的渲染,其邏輯是基本一致的,差異僅僅有2點:圖像本身不同 以及 圖像的幾何變換不同

    public class My3DRenderer implements GLSurfaceView.Renderer {
      
        private float[] mBackMatrix = new float[16];
        private float[] mMidMatrix = new float[16];
        private float[] mFrontMatrix = new float[16];
    
        @Override
        public void onDrawFrame(GL10 gl) {
            GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
            GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    
            GLES20.glUseProgram(mProgram);
            
            // 依次繪制背景、中景、前景
            this.drawLayerInner(mBackTextureId, mTextureBuffer, mBackMatrix);
            this.drawLayerInner(mMidTextureId, mTextureBuffer, mMidMatrix);
            this.drawLayerInner(mFrontTextureId, mTextureBuffer, mFrontMatrix);
        }
        
        private void drawLayerInner(int textureId, FloatBuffer textureBuffer, float[] matrix) {
            // 1.綁定圖像紋理
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
            // 2.矩陣變換
            GLES20.glUniformMatrix4fv(uMatrixLocation, 1, false, matrix, 0);
            // ...
            // 3.執行繪制
            GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        }
    }

    參考 drawLayerInner 的代碼,其用于繪制單層的圖像,其中 textureId 參數對應不同圖像,matrix 參數對應不同的幾何變換。

    現在我們完成了圖像靜態的繪制,效果如下:

    Android?OpenGL如何實現APP裸眼3D效果

    接下來我們需要接入傳感器,并定義不同層級圖片各自的幾何變換,讓圖片動起來。

    2. 讓圖片動起來

    首先我們需要對 Android 平臺上的傳感器進行注冊,監聽手機的旋轉狀態,并拿到手機 xy 軸的旋轉角度。

    // 2.1 注冊傳感器
    mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
    mAcceleSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    mMagneticSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
    mSensorManager.registerListener(mSensorEventListener, mAcceleSensor, SensorManager.SENSOR_DELAY_GAME);
    mSensorManager.registerListener(mSensorEventListener, mMagneticSensor, SensorManager.SENSOR_DELAY_GAME);
    
    // 2.2 不斷接受旋轉狀態
    private final SensorEventListener mSensorEventListener = new SensorEventListener() {
        @Override
        public void onSensorChanged(SensorEvent event) {
            // ... 省略具體代碼
            float[] values = new float[3];
            float[] R = new float[9];
            SensorManager.getRotationMatrix(R, null, mAcceleValues, mMageneticValues);
            SensorManager.getOrientation(R, values);
            // x軸的偏轉角度
            float degreeX = (float) Math.toDegrees(values[1]);
            // y軸的偏轉角度
            float degreeY = (float) Math.toDegrees(values[2]);
            // z軸的偏轉角度
            float degreeZ = (float) Math.toDegrees(values[0]);
            
            // 拿到 xy 軸的旋轉角度,進行矩陣變換
            updateMatrix(degreeX, degreeY);
        }
    };

    注意,因為我們只需控制圖像的左右和上下移動,因此,我們只需關注設備本身 x 軸和 y 軸的偏轉角度:

    Android?OpenGL如何實現APP裸眼3D效果

    拿到了 x 軸和 y 軸的偏轉角度后,接下來開始定義圖像的位移了。

    但如果將圖片直接進行位移操作,將會因為位移后圖像的另一側沒有紋理數據,導致渲染結果有黑邊現象,為了避免這個問題,我們需要將圖像默認從中心點進行放大,保證圖像移動的過程中,不會超出自身的邊界。

    也就是說,我們一開始進入時,看到的肯定只是圖片的部分區域。給每一個圖層設置 scale,將圖片進行放大。顯示窗口是固定的,那么一開始只能看到圖片的正中位置。(中層可以不用,因為中層本身是不移動的,所以也不必放大)

    Android?OpenGL如何實現APP裸眼3D效果

    明白了這一點,我們就能理解,裸眼3D的效果實際上就是對 不同層級的圖像 進行縮放和位移的變換,下面是分別獲取幾何變換的代碼:

    public class My3DRenderer implements GLSurfaceView.Renderer {
      
        private float[] mBackMatrix = new float[16];
        private float[] mMidMatrix = new float[16];
        private float[] mFrontMatrix = new float[16];
    
        /**
         * 陀螺儀數據回調,更新各個層級的變換矩陣.
         *
         * @param degreeX x軸旋轉角度,圖片應該上下移動
         * @param degreeY y軸旋轉角度,圖片應該左右移動
         */
        private void updateMatrix(@FloatRange(from = -180.0f, to = 180.0f) float degreeX,
                                  @FloatRange(from = -180.0f, to = 180.0f) float degreeY) {
            // ... 其它處理                                                
    
            // 背景變換
            // 1.最大位移量
            float maxTransXY = MAX_VISIBLE_SIDE_BACKGROUND - 1f;
            // 2.本次的位移量
            float transX = ((maxTransXY) / MAX_TRANS_DEGREE_Y) * -degreeY;
            float transY = ((maxTransXY) / MAX_TRANS_DEGREE_X) * -degreeX;
            float[] backMatrix = new float[16];
            Matrix.setIdentityM(backMatrix, 0);
            Matrix.translateM(backMatrix, 0, transX, transY, 0f);                    // 2.平移
            Matrix.scaleM(backMatrix, 0, SCALE_BACK_GROUND, SCALE_BACK_GROUND, 1f);  // 1.縮放
            Matrix.multiplyMM(mBackMatrix, 0, mProjectionMatrix, 0, backMatrix, 0);  // 3.正交投影
    
            // 中景變換
            Matrix.setIdentityM(mMidMatrix, 0);
    
            // 前景變換
            // 1.最大位移量
            maxTransXY = MAX_VISIBLE_SIDE_FOREGROUND - 1f;
            // 2.本次的位移量
            transX = ((maxTransXY) / MAX_TRANS_DEGREE_Y) * -degreeY;
            transY = ((maxTransXY) / MAX_TRANS_DEGREE_X) * -degreeX;
            float[] frontMatrix = new float[16];
            Matrix.setIdentityM(frontMatrix, 0);
            Matrix.translateM(frontMatrix, 0, -transX, -transY - 0.10f, 0f);            // 2.平移
            Matrix.scaleM(frontMatrix, 0, SCALE_FORE_GROUND, SCALE_FORE_GROUND, 1f);    // 1.縮放
            Matrix.multiplyMM(mFrontMatrix, 0, mProjectionMatrix, 0, frontMatrix, 0);  // 3.正交投影
        }
    }

    這段代碼中還有幾點細節需要處理。

    3. 幾個反直覺的細節

    3.1 旋轉方向 ≠ 位移方向

    首先,設備旋轉方向和圖片的位移方向是相反的,舉例來說,當設備沿 X 軸旋轉,對于用戶而言,對應前后景的圖片應該上下移動,反過來,設備沿 Y 軸旋轉,圖片應該左右移動(沒太明白的同學可參考上文中陀螺儀的圖片加深理解):

    // 設備旋轉方向和圖片的位移方向是相反的
    float transX = ((maxTransXY) / MAX_TRANS_DEGREE_Y) * -degreeY;
    float transY = ((maxTransXY) / MAX_TRANS_DEGREE_X) * -degreeX;
    // ...
    Matrix.translateM(backMatrix, 0, transX, transY, 0f);

    3.2 默認旋轉角度 ≠ 0°

    其次,在定義最大旋轉角度的時候,不能主觀認為旋轉角度 = 0°是默認值。什么意思呢?Y 軸旋轉角度為0°,即 degreeY = 0 時,默認設備左右的高度差是 0,這個符合用戶的使用習慣,相對易于理解,因此,我們可以定義左右的最大旋轉角度,比如 Y ∈ (-45°,45°),超過這兩個旋轉角度,圖片也就移動到邊緣了。

    但當 X 軸旋轉角度為0°,即 degreeX = 0 時,意味著設備上下的高度差是 0,你可以理解為設備是放在水平的桌面上的,這個絕不符合大多數用戶的使用習慣,相比之下,設備屏幕平行于人的面部 才更適用大多數場景(degreeX = -90):

    Android?OpenGL如何實現APP裸眼3D效果

    因此,代碼上需對 X、Y 軸的最大旋轉角度區間進行分開定義:

    private static final float USER_X_AXIS_STANDARD = -45f;
    private static final float MAX_TRANS_DEGREE_X = 25f;   // X軸最大旋轉角度 ∈ (-20°,-70°)
    
    private static final float USER_Y_AXIS_STANDARD = 0f;
    private static final float MAX_TRANS_DEGREE_Y = 45f;   // Y軸最大旋轉角度 ∈ (-45°,45°)

    解決了這些 反直覺 的細節問題,我們基本完成了裸眼3D的效果。

    4. 帕金森綜合征?

    還差一點就大功告成了,最后還需要處理下3D效果抖動的問題:

    Android?OpenGL如何實現APP裸眼3D效果

    如圖,由于傳感器過于靈敏,即使平穩的握住設備,XYZ 三個方向上微弱的變化都會影響到用戶的實際體驗,會給用戶帶來 帕金森綜合征 的自我懷疑。

    解決這個問題,傳統的 OpenGL 以及 Android API 似乎都無能為力,好在 GitHub 上有人提供了另外一個思路。

    熟悉信號處理的同學比較了解,為了通過剔除短期波動、保留長期發展趨勢提供了信號的平滑形式,可以使用 低通濾波器,保證低于截止頻率的信號可以通過,高于截止頻率的信號不能通過。

    因此有人建立了 這個倉庫 , 通過對 Android 傳感器追加低通濾波 ,過濾掉小的噪聲信號,達到較為平穩的效果:

    private final SensorEventListener mSensorEventListener = new SensorEventListener() {
        @Override
        public void onSensorChanged(SensorEvent event) {
            // 對傳感器的數據追加低通濾波
            if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
                mAcceleValues = lowPass(event.values.clone(), mAcceleValues);
            }
            if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
                mMageneticValues = lowPass(event.values.clone(), mMageneticValues);
            }
          
            // ... 省略具體代碼
            // x軸的偏轉角度
            float degreeX = (float) Math.toDegrees(values[1]);
            // y軸的偏轉角度
            float degreeY = (float) Math.toDegrees(values[2]);
            // z軸的偏轉角度
            float degreeZ = (float) Math.toDegrees(values[0]);
            
            // 拿到 xy 軸的旋轉角度,進行矩陣變換
            updateMatrix(degreeX, degreeY);
        }
    };

    大功告成,最終我們實現了預期的效果:

    Android?OpenGL如何實現APP裸眼3D效果

    Android是什么

    Android是一種基于Linux內核的自由及開放源代碼的操作系統,主要使用于移動設備,如智能手機和平板電腦,由美國Google公司和開放手機聯盟領導及開發。

    看完上述內容,你們掌握Android OpenGL如何實現APP裸眼3D效果的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    平邑县| 嘉鱼县| 谷城县| 大新县| 马边| 翁牛特旗| 蓝山县| 西平县| 新河县| 龙陵县| 磴口县| 澄江县| 九台市| 得荣县| 沭阳县| 咸丰县| 班戈县| 西丰县| 武冈市| 威信县| 文昌市| 西充县| 岚皋县| 永登县| 石城县| 慈利县| 涡阳县| 馆陶县| 屏东县| 田东县| 驻马店市| 平遥县| 大厂| 凯里市| 讷河市| 鲜城| 东兴市| 乾安县| 永寿县| 神池县| 攀枝花市|