1. 程式人生 > >Android使用OpenGL ES 3.0實現隨手指旋轉3D立方體

Android使用OpenGL ES 3.0實現隨手指旋轉3D立方體

OpenGL ES在做普通應用方面3D使用的不多,但有時候實現一些有趣的功能也是蠻不錯的。畫立方體的的demo網上已經很多了,這次我們就實現一個隨手指旋轉的立方體,這個demo基本可以瞭解各個座標系轉換矩陣的使用了。
先看一下最終效果:
這裡寫圖片描述
話不多說,直接上程式碼了。

EGL的配置

EGL的配置也就是常規配置了,但是需要注意的一點是:為了使立方體看起來更加真實,需要開啟深度測試,需要在egl的環境中加入深度測試的配置。不然就算啟用了深度測試也會沒用。

 //通過屬性去篩選合適的配置
    const EGLint attibutes[] = {
            EGL_BUFFER_SIZE, 32
, EGL_ALPHA_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_RED_SIZE, 8, EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, //指定渲染api版本 2 EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_DEPTH_SIZE, 24, //新增這段來請求深度緩衝區 EGL_NONE };

繪製一個立方體

首先使用OpenGL繪製一個立方體十分簡單,一個面兩個三角形,繪製十二個三角形就可以了。一個三角形3個點,也就是需要36個點。其實立方體很多點是重複的,我們完全可以用下標的形式去畫點,但是這篇文章我沒有用下標,使用了36個點來直接畫。

  float vertices[] = {
            -0.5f, -0.5f, -0.5f, 0.0f, 0.0f,
            0.5f, -0.5f, -0.5f, 1.0f, 0.0f,
            0.5f, 0.5f, -0.5f, 1.0f, 1.0f,
            0.5f, 0.5f, -0.5f, 1.0f
, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f }; glDrawArrays(GL_TRIANGLES, 0, 36);

這個立方體可以想象一下,就是邊長為1.0的立方體了,中心在原點。

矩陣變換

我們也還是按照幾種常用矩陣變換的套路來,我們還是使用模型,檢視,投影矩陣來變換座標。

#version 300 es
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;


void main(){
   gl_Position = projection*view*model*vec4(aPos, 1.0f);
   TexCoord=vec2(aTexCoord.x,1.0-aTexCoord.y);
}
1、不同向量的旋轉

首先我們在只考慮在model中加入旋轉。想象一下手指在螢幕上滑動是有一個方向向量的,加入我們在螢幕上這樣滑動
這裡寫圖片描述
那麼立方體的旋轉軸就是垂直於手指滑動方向的一個向量,而這個向量的求取方式,我們使用這個向量於Z軸的向量進行叉乘來取得。還要注意一點,因為我們取得螢幕座標系和OpenGL的座標系又差別,所以需要一定的變化來求得正確的旋轉軸。

  if (x > 0 && y > 0) {
        y = -y;
    } else if (x <= 0 && y > 0) {
        x = -x;
        degree = -degree;
    } else if (x > 0 && y < 0) {
        y = -y;
    } else if (x <= 0 && y < 0) {
        y = -y;
    }
   LOGE("tedu %f %f %f" ,x,y,degree);

    if (degree != 0) {
        glm::vec3 cross = glm::cross(glm::vec3(0.0f, 0.0f, 1.0), glm::vec3(x, y, 0.0f));
        glm::mat4 tmpMat = glm::mat4(1.0f);
        tmpMat = glm::rotate(tmpMat, degree, cross);
        tmpMat *= model;
        model = tmpMat;
    }

簡單分析一下,我們每次求得對應的向量後,生成一個新的選擇矩陣,然後右乘原來的矩陣,注意不能交換順序,然後求得最終的矩陣並賦值了model。通過狀態的疊加,最終將會獲得最終的效果。

2、雙指的縮放

這裡就不得不提一下對於雙指的判定了。我們的邏輯是:

  • 單指的時候不進行縮放,進行旋轉
  • 雙指的時候進行縮放,不進行旋轉
  • 雙指以上情況直接不處理,或者有雙指變為單指的情況也不處理

    邏輯理順之後,我們只需要加入判斷的bool變數即可,注意在擡起手指,以及單指切換雙指的時候需要清空旋轉的值,看一下邏輯。

 cube.setOnTouchListener { v, event ->


            when (event.actionMasked) {
                MotionEvent.ACTION_DOWN -> {
                 //單指觸控情況
                    lastX = event.getX()
                    lastY = event.getY()
                    canRotate = true
                    canScale = false

                }
                MotionEvent.ACTION_POINTER_DOWN -> {
                    //雙指觸控
                    canRotate = false
                    canScale = true
                    if (event.pointerCount > 2) {
                        canScale = false
                        return@setOnTouchListener false
                    }
                    val index0 = event.getPointerId(0)
                    val index1 = event.getPointerId(1)
                    lastX = event.getX(index0)
                    lastY = event.getY(index0)
                    last1X = event.getX(index1)
                    last1Y = event.getY(index1)
                    controler.rotate(0.0f, 0.0f, 0.0f)

                }
                MotionEvent.ACTION_POINTER_UP -> {
                    //還原
                    canScale=false
                    canRotate=false
                    controler.rotate(0.0f, 0.0f, 0.0f)

                }
                MotionEvent.ACTION_MOVE -> {
                    if (canRotate) {
                        if(event.pointerCount>=2){
                            controler.rotate(0.0f, 0.0f, 0.0f)
                            return@setOnTouchListener true
                        }
                        val curY = event.y
                        val curX = event.x
                        val vecX = curX - lastX
                        val vecY = curY - lastY
                        lastY = curY
                        lastX = curX
                        val distance = Math.sqrt((vecX * vecX).toDouble() + (vecY * vecY).toDouble())
                        controler.rotate(vecX, vecY, distance.toFloat())
                    } else if (canScale) {
                    //通過雙指按下的距離比例來進行縮放,但是這裡的比例並不實際縮放的比例,只是通過簡單調整視野的範圍來進行縮放
                        val index0 = event.getPointerId(0)
                        val index1 = event.getPointerId(1)
                        val curX = event.getX(index0)
                        val curY = event.getY(index0)
                        val cur1X = event.getX(index1)
                        val cur1Y = event.getY(index1)
                        val lastDis=Math.sqrt(Math.pow((lastX-last1X).toDouble(), 2.0)+Math.pow((lastY-last1Y).toDouble(), 2.0))
                        val curDis=Math.sqrt(Math.pow((curX-cur1X).toDouble(), 2.0)+Math.pow((curY-cur1Y).toDouble(), 2.0))
                        controler.scale((curDis/lastDis).toFloat())
                    }
                }
            }
            return@setOnTouchListener true;
        }

然後我們看native程式碼,首先是做簡單的視野角度加減

void CubeTextureRender::setScale(jfloat scale) {
    LOGE("scale %f",scale);
    if (scale > 1) {
        fov -= scale;
    } else if(scale<1) {
        fov +=  (1.0f/scale);
    }
    if (fov <= 20.0) {
        fov = 20.0;
    } else if (fov > 140.0) {
        fov = 140.0;
    }
}

然後將視野傳入投影即可

    glm::mat4 projection = glm::mat4(1.0f);

    float ratio = (float) _backingWidth / (float) _backingHeight;

    projection = glm::perspective(glm::radians(fov), ratio, 0.1f, 100.0f);

文章到這裡就結束了,除了一些必要的數學知識,其實難度不大。我們通過新增互動讓立方體更加有趣。如果喜歡這篇文章,麻煩點一下關注謝謝。有什麼問題也可以在評論區留言。

原始碼地址