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);
文章到这里就结束了,除了一些必要的数学知识,其实难度不大。我们通过添加互动让立方体更加有趣。如果喜欢这篇文章,麻烦点一下关注谢谢。有什么问题也可以在评论区留言。