简介
各大手机厂商在自己的OS中都逐步合入可交互的桌面游戏或动效。比如小米miui14中的桌面花宠摆件、动态桌面图标;vivo的OriginOS3中的动态壁纸和基于物理引擎算法的情景天气;OPPO的ColorOS13中的游戏追光效果、动态壁纸和应用使用主题动效等。因此在平板端,借助Bullet引擎和OpenGL ES,可以尝试实现类似的动态壁纸、动效交互等等。
Bullet引擎
Bullet是开源的物理模拟计算引擎,能够检测、计算和模拟物理世界物体碰撞,常用于游戏和影视制作。我们所熟知的电影《大侦探福尔摩斯》和《侠盗猎车手4、5》、《荒野大嫖客》等游戏都是基于Bullet物理游戏引擎。
OpenGL ES
OpenGL ES(OpenGL for Embedded Systems)是OpenGL三维图形API的子集。相较于OpenGL,OpenGL ES删除了一些较少使用、不够高性能的接口,所以存在OpenGL可以使用但OpenGL ES不可使用的接口。
OpenGL ES与硬件平台无关,因此在移动端NDK开发能够做到多平台的兼容。
Bullet3基础使用
创建世界
首先需要初始化相关配置,并获取到指向btDiscreteDynamicsWorld的指针dynamicsWorld,它是我们创建用来表示三维碰撞的世界。
///-----initialization_start-----
//collision configuration contains default setup for memory, collision setup. Advanced users can create their own configuration.
btDefaultCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration();
///use the default collision dispatcher. For parallel processing you can use a diffent dispatcher (see Extras/BulletMultiThreaded)
btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration);
///btDbvtBroadphase is a good general purpose broadphase. You can also try out btAxis3Sweep.
btBroadphaseInterface* overlappingPairCache = new btDbvtBroadphase();
///the default constraint solver. For parallel processing you can use a different solver (see Extras/BulletMultiThreaded)
btSequentialImpulseConstraintSolver* solver = new btSequentialImpulseConstraintSolver;
dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher, overlappingPairCache, solver, collisionConfiguration);
dynamicsWorld->setGravity(btVector3(0, -10, 0));
///-----initialization_end-----
世界的组成一定不是一个简单的三维空间,还需要各种各样的物体构成。在上述Bullet初始化即一个三维世界创建后,可以通过主动的添加刚体来构成这个世界中的各个物体。刚体是指在运动中和受到力的作用后,形状和大小不变,内部各点的相对位置不变的物体。现实中不存在运动或受到力后还保持形体大小等不变的物体,所以刚体可以理解为一种理想化的物体。
创建刚体
在物理世界中,首先创建一个静态刚体来表示“地面”。刚体的创建和合入世界需要完成四个步骤:
- 创建物体形状(btCollisionShape)
- 初始化物体位置和旋转状态(btTransform)
- 将物体封装成Body(btRigidBody/btSoftBody)
- 将对象加入到世界中
btCollisionShape* groundShape = new btBoxShape(btVector3(btScalar(200.), btScalar(200.), btScalar(50.)));
collisionShapes.push_back(groundShape);
btTransform groundTransform;
groundTransform.setIdentity();
groundTransform.setOrigin(btVector3(0, 200, 0));
btScalar mass(0.);
//rigidbody is dynamic if and only if mass is non zero, otherwise static
bool isDynamic = (mass != 0.f);
btVector3 localInertia(0, 0, 0);
if (isDynamic)
groundShape->calculateLocalInertia(mass, localInertia);
//using motionstate is optional, it provides interpolation capabilities, and only synchronizes 'active' objects
btDefaultMotionState* myMotionState = new btDefaultMotionState(groundTransform);
btRigidBody::btRigidBodyConstructionInfo rbInfo(mass, myMotionState, groundShape, localInertia);
btRigidBody* body = new btRigidBody(rbInfo);
body->setRestitution(btScalar(1.2));
//add the body to the dynamics world
dynamicsWorld->addRigidBody(body);
Bullet中有许多形状,包括btBoxShape、btSphereShape、btCapsuleShape、btStaticPlaneShape、TaperedCapsule、btConvexHullShape等,他们分别代表四面体、球体、胶囊体等。通过对Body对象的操作可以但不局限于摩檫力设置void btCollisionObject::setFriction(btScalar frict)、碰撞反弹系数设置void btCollisionObject::setRestitution(btScalar rest)。
上述过程创建的是静态刚体,同样的步骤通过加入动态刚体对象来模拟相关物理碰撞现象。静态刚体的质量为0,且不受重力影响。
获取刚体实时位置
通过遍历世界dynamicsWorl中的collisionObjects可以获取相应的刚体的实时位置。
5dynamicsWorld->stepSimulation(1.f / 30.f, 10);
//print positions of all objects
for (int j = dynamicsWorld->getNumCollisionObjects() - 1; j >= 0; j--) {
btCollisionObject *obj = dynamicsWorld->getCollisionObjectArray()[j];
btRigidBody *body = btRigidBody::upcast(obj);
btTransform trans;
if (body && body->getMotionState()) {
body->getMotionState()->getWorldTransform(trans);
btCollisionShape *shape = body->getCollisionShape();
glm::mat4 model = glm::mat4(1.0f);
trans.getOpenGLMatrix(glm::value_ptr(model));
btBoxShape *tempShape = static_cast<btBoxShape * >(shape);
btVector3 tempVector = tempShape->getImplicitShapeDimensions();
void *attachedNode = body->getUserPointer();
if (attachedNode) {//update cubes only
int index = body->getUserIndex();
//z轴坐标取负数可以和屏幕对应
//x1,y1,z1,x2,y2,z2
float collsionPos[6] = {trans.getOrigin().getX() - tempVector.getX(),
trans.getOrigin().getY() - tempVector.getY(),
-trans.getOrigin().getZ() + tempVector.getZ(),
trans.getOrigin().getX() + tempVector.getX(),
trans.getOrigin().getY() + tempVector.getY(),
-trans.getOrigin().getZ() - tempVector.getZ()};
}
} else {
trans = obj->getWorldTransform();
}
}
OpenGL ES渲染结合
将刚体坐标根据对应的物体转换为顶点坐标进行图形渲染,这样就可以看到具有碰撞效果的图形在界面上移动。
if (m_ProgramObj == 0)
return;
int size = map.size();
GLfloat vVertices[6*6*3*(map.size())];
drawBox(vVertices,map);
// Set the viewport
glViewport(0,0,m_ScreenW,m_ScreenH);
UpdateMVPMatrix(m_MVPMatrix, 0, 0, m_ScreenW / m_ScreenH);
glUniformMatrix4fv(m_MVPMatrixLoc, 1, GL_FALSE, &m_MVPMatrix[0][0]);
//Clear the color buffer
glClear(GL_COLOR_BUFFER_BIT);
//Use the program object
glUseProgram(m_ProgramObj);
//Load the vertex data
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,vVertices);
glEnableVertexAttribArray(0);
glDrawArrays(GL_TRIANGLES,0, 6*6*map.size());
在此提供正方体顶点计算相关算法
float leftX = collsionPos[0] /(float)m_ScreenW;
float leftY = collsionPos[1] / (float)m_ScreenH;
float leftZ = collsionPos[2] /(float)m_ScreenH;
float rightX = collsionPos[3] /(float)m_ScreenW;
float rightY = collsionPos[4] /(float)m_ScreenH;
float rightZ = collsionPos[5] /(float)m_ScreenH;
float vVerticesTemp[108] = {
//正面
leftX,leftY,leftZ,
rightX,leftY,leftZ,
rightX,rightY,leftZ,
rightX,rightY,leftZ,
leftX,rightY,leftZ,
leftX,leftY,leftZ,
//背面
leftX,leftY,rightZ,
rightX,leftY,rightZ,
rightX,rightY,rightZ,
rightX,rightY,rightZ,
leftX,rightY,rightZ,
leftX,leftY,rightZ,
//左面
leftX,rightY,rightZ,
leftX,rightY,leftZ,
leftX,leftY,leftZ,
leftX,leftY,leftZ,
leftX,leftY,rightZ,
leftX,rightY,rightZ,
//右面
rightX,rightY,rightZ,
rightX,rightY,leftZ,
rightX,leftY,leftZ,
rightX,leftY,leftZ,
rightX,leftY,rightZ,
rightX,rightY,rightZ,
//下面
leftX,leftY,leftZ,
rightX,leftY,leftZ,
rightX,leftY,rightZ,
rightX,leftY,rightZ,
leftX,leftY,rightZ,
leftX,leftY,leftZ,
//上面
leftX,rightY,leftZ,
rightX,rightY,leftZ,
rightX,rightY,rightZ,
rightX,rightY,rightZ,
leftX,rightY,rightZ,
leftX,rightY,leftZ
};
for(float i : vVerticesTemp){
vVertices[pos++] = i;
}
总结
在后续的实现过程中,不仅仅局限于桌面交互、动效图标,还能通过构建整机系统的物理服务来管理移动端整机的注册窗口动效、应用内的窗口动效等。