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