200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > OpenGL实现碰撞检测与模拟重力效果(简单的物理系统)

OpenGL实现碰撞检测与模拟重力效果(简单的物理系统)

时间:2022-06-10 21:28:27

相关推荐

OpenGL实现碰撞检测与模拟重力效果(简单的物理系统)

最近在做一个OpenGL的小游戏,想要实现碰撞检测与模拟重力效果,即类似Unity3d的物理引擎。碰撞检测参考了一篇博文:/zju_fish1996/article/details/51869828 建议大家可以先看看。不过博文写的仅仅是不考虑Y方向信息,即高度信息的碰撞检测,而我想要实现的是有重力+跳跃的,高度信息仍然需要考虑的,所以下面是我的改进,以及引入了重力计算。

另外,下文完整项目代码可以访问我的github查看:/MarkMoHR/OpenglGame

编程环境:

Windows10 + VS(x86) + freeglut + glm库

效果展示:

(玩家跳跃上木箱子,再跳下来)

(游戏场景图,改进后)

技术实现(以下步骤按照整个物理引擎类PhysicsEngine的实现步骤讲解):

1、布置场景的时候先初始化外部边缘位置坐标,与内部物体边缘位置坐标:

需要先把所有碰撞边缘的坐标传递给PhysicsEngine:

void PhysicsEngine::setSceneOuterBoundary(float x1, float z1, float x2, float z2) {outerBoundary = glm::vec4(x1, z1, x2, z2);}void PhysicsEngine::setSceneInnerBoundary(float x1, float y1, float z1, float x2, float y2, float z2) {glm::vec3 key(x1 - BoundaryGap, y1 - BoundaryGap, z1 - BoundaryGap);glm::vec3 value(x2 + BoundaryGap, y2 + BoundaryGap, z2 + BoundaryGap);innerBoundaryMin.push_back(key);innerBoundaryMax.push_back(value);}

2、main函数的glutDisplayFunc()方法由于是循环调用,所以需要在里面每次调用的时候更新摄像机的水平和竖直方向的移动。在每一帧水平方向移动结束后,然后先进行水平方向的碰撞检测(外部与内部检测)。

void FPSCamera::updateCameraHoriMovement() {float dx = 0;float dz = 0;if (isWPressing)dz += 2;if (isSPressing)dz -= 2;if (isAPressing)dx -= 2;if (isDPressing)dx += 2;if (dz != 0 || dx != 0) {//行走不改变y轴坐标glm::vec3 forward = glm::vec3(viewMatrix[0][2], 0.f, viewMatrix[2][2]);glm::vec3 strafe = glm::vec3(viewMatrix[0][0], 0.f, viewMatrix[2][0]);cameraPos += (-dz * forward + dx * strafe) * MoveSpeed;targetPos = cameraPos + (-dz * forward + dx * strafe) * 1.5f;//每次做完坐标变换后,先进行碰撞检测来调整坐标physicsEngine->outCollisionTest(cameraPos, targetPos);physicsEngine->inCollisionTest(cameraPos, targetPos);}}

3、水平方向的外部/内部边缘碰撞检测:

上面代码可以看到调用了PhysicsEngine的两个碰撞检测方法。具体的实现原理与实现代码大多是参考上面发的第一个链接,大家可以参考

(1) 外部边缘碰撞检测:这个相对简单,就是让出了边界的视点放回来,再做调整:

void PhysicsEngine::outCollisionTest(glm::vec3 & cameraPos, glm::vec3 & targetPos) {outCollisionTestXZ(outerBoundary[0], outerBoundary[1], outerBoundary[2], outerBoundary[3], cameraPos, targetPos);}void PhysicsEngine::outCollisionTestXZ(float x1, float z1, float x2, float z2, glm::vec3 & cameraPos, glm::vec3 & targetPos) {//先设置包围盒:比空间外部边缘小一点if (x1 < 0)x1 += 2;else x1 -= 2;if (x2 < 0)x2 += 2;else x2 -= 2;if (z1 < 0)z1 += 2;else z1 -= 2;if (z2 < 0)z2 += 2;else z2 -= 2;//如果目标位置出了包围盒,先放回来if (targetPos[0] < x1) {targetPos[0] = x1;}if (targetPos[0] > x2) {targetPos[0] = x2;}if (targetPos[2] < z1) {targetPos[2] = z1;}if (targetPos[2] > z2) {targetPos[2] = z2;}float distance = sqrt((cameraPos[0] - targetPos[0])*(cameraPos[0] - targetPos[0]) +(cameraPos[2] - targetPos[2])*(cameraPos[2] - targetPos[2]));//若视点与目标距离太小,则固定目标位置,视点沿正对目标的逆方向移动if (distance <= 2.0f) {cameraPos[0] = 2.0f*(cameraPos[0] - targetPos[0]) / distance + targetPos[0];cameraPos[2] = 2.0f*(cameraPos[2] - targetPos[2]) / distance + targetPos[2];}bool flag = false;//再检测视点是否出了包围盒,若是则放回if (cameraPos[0] < x1) {flag = true;cameraPos[0] = x1;}if (cameraPos[0] > x2) {flag = true;cameraPos[0] = x2;}if (cameraPos[2] < z1) {flag = true;cameraPos[2] = z1;}if (cameraPos[2] > z2) {flag = true;cameraPos[2] = z2;}//重复上述远离两点距离的操作if (flag) {distance = sqrt((cameraPos[0] - targetPos[0])*(cameraPos[0] - targetPos[0]) +(cameraPos[2] - targetPos[2])*(cameraPos[2] - targetPos[2]));if (distance <= 2.0f) {targetPos[0] = 2.0f*(targetPos[0] - cameraPos[0]) / distance + cameraPos[0];targetPos[2] = 2.0f*(targetPos[2] - cameraPos[2]) / distance + cameraPos[2];}}}

(2) 内部边缘碰撞检测:这个相对复杂。上面提到的博文只是实现了不考虑y方向,即高度信息的xz平面的碰撞检测。但是如果我们加入了重力,高度信息明显也需要考虑进去,所以我做了如下改进:只有当玩家身体处于碰撞体垂直区域范围内,才进行XZ平面的碰撞检测。而内部边缘的碰撞检测需要用到线段相交快速算法,以及利用相似三角形进行camera视点、目标点的调整(具体的上述博文有详细说明)。

void PhysicsEngine::inCollisionTest(glm::vec3 & cameraPos, glm::vec3 & targetPos) {//后面可以在这里添加:预处理,排除当前肯定不会产生碰撞的物体for (int i = 0; i < innerBoundaryMin.size(); i++) {inCollisionTestWithHeight(innerBoundaryMin[i][0], innerBoundaryMin[i][1], innerBoundaryMin[i][2],innerBoundaryMax[i][0], innerBoundaryMax[i][1], innerBoundaryMax[i][2], cameraPos, targetPos);}}void PhysicsEngine::inCollisionTestWithHeight(float x1, float y1, float z1, float x2, float y2, float z2, glm::vec3 & cameraPos, glm::vec3 & targetPos) {//当身体处于碰撞体垂直区域范围内,才进行XZ平面的碰撞检测if (!(cameraPos[1] <= y1 || cameraPos[1] - HeroHeight >= y2)) {inCollisionTestXZ(x1, z1, x2, z2, cameraPos, targetPos);}}double Direction(dot pi, dot pj, dot pk) {return (pk.x - pi.x)*(pj.y - pi.y) - (pj.x - pi.x)*(pk.y - pi.y);}bool OnSegment(dot pi, dot pj, dot pk) {if ((min(pi.x, pj.x) <= pk.x) && (pk.x <= max(pi.x, pj.x))&& (min(pi.y, pj.y) <= pk.y) && (pk.y <= max(pi.y, pj.y)))return true;else return false;}//检测线段相交快速算法bool SegmentIntersect(dot p1, dot p2, dot p3, dot p4) {int d1, d2, d3, d4;d1 = Direction(p3, p4, p1);d2 = Direction(p3, p4, p2);d3 = Direction(p1, p2, p3);d4 = Direction(p1, p2, p4);if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2>0)) && ((d3>0 && d4 < 0) || (d3 < 0 && d4>0)))return true;else if (d1 == 0 && OnSegment(p3, p4, p1))return true;else if (d2 == 0 && OnSegment(p3, p4, p2))return true;else if (d3 == 0 && OnSegment(p1, p2, p3))return true;else if (d4 == 0 && OnSegment(p1, p2, p4))return true;elsereturn false;}void PhysicsEngine::inCollisionTestXZ(float x1, float z1, float x2, float z2, glm::vec3 & cameraPos, glm::vec3 & targetPos) {const float d = 2.0f;float tarX = targetPos[0], camX = cameraPos[0], tarZ = targetPos[2], camZ = cameraPos[2];float len = sqrt((camX - tarX)*(camX - tarX) + (camZ - tarZ)*(camZ - tarZ));dot d1(cameraPos[0], cameraPos[2]), d2(targetPos[0], targetPos[2]);dot d3(x1, z1), d4(x1, z2), d5(x2, z1), d6(x2, z2);if (SegmentIntersect(d1, d2, d4, d6)) {if (targetPos[2] < cameraPos[2]) {printf("1\n");//利用相似三角形原理计算,//仅改变z坐标targetPos[2] = z2;cameraPos[2] += (targetPos[2] - tarZ);}else if (targetPos[2] > cameraPos[2]) {printf("2\n");cameraPos[2] = z2;targetPos[2] += (cameraPos[2] - camZ);}}else if (SegmentIntersect(d1, d2, d5, d6)) {if (targetPos[0]<cameraPos[0]) {printf("3\n");targetPos[0] = x2;cameraPos[0] += (targetPos[0] - tarX);}else if (targetPos[0]>cameraPos[0]) {printf("4\n");cameraPos[0] = x2;targetPos[0] += (cameraPos[0] - camX);}}else if (SegmentIntersect(d1, d2, d3, d5)) {if (targetPos[2] > cameraPos[2]) {printf("5\n");targetPos[2] = z1;cameraPos[2] += (targetPos[2] - tarZ);}else if (targetPos[2] < cameraPos[2]) {printf("6\n");cameraPos[2] = z1;targetPos[2] += (cameraPos[2] - camZ);}}else if (SegmentIntersect(d1, d2, d3, d4)) {if (targetPos[0] > cameraPos[0]) {printf("7\n");targetPos[0] = x1;cameraPos[0] += (targetPos[0] - tarX);}else if (targetPos[0] < cameraPos[0]) {printf("8\n");cameraPos[0] = x1;targetPos[0] += (cameraPos[0] - camX);}}}

4、接着是更新竖直方向的移动:

(1) 重力计算:利用公式v = v0 + g * ∆t 、h = h0 + k * v * ∆t 。把公式转化为代码即可。

(2) y方向的碰撞检测:当加入重力模拟之后,我们就需要考虑以下两种情况了:玩家跳到箱子上,或者在箱子下起跳顶到箱子(上面已有的碰撞检测无法处理这两种情况)。而这两种情况也需要结合物理的知识:

前面的情况,玩家跳到箱子上时(通过当摄像机在XZ平面处于碰撞体XZ平面区域内部时,判断玩家的脚是否落到箱子顶部),提供一个方向向上的加速度(与重力加速度大小相同方向相反),速度设为0,此时Y方向没有加速度,不会下落;

后面的情况,玩家头部顶到箱子底部时(与前面情况类似判断),设置速度为0,玩家之后做自由落体运动。

//判断在xz平面,相机位置是否位于碰撞体内部bool insideTheCollider(glm::vec3 _cameraPos, glm::vec3 _innerMin, glm::vec3 _innerMax) {float camX = _cameraPos.x;float camZ = _cameraPos.z;float minX = _innerMin.x;float minZ = _innerMin.z;float maxX = _innerMax.x;float maxZ = _innerMax.z;if (minX <= camX && camX <= maxX && minZ <= camZ && camZ <= maxZ)return true;elsereturn false;}void PhysicsEngine::updateCameraVertMovement(glm::vec3 & cameraPos, glm::vec3 & targetPos) {glm::vec3 acceleration = gravity + accelerUp;velocity += acceleration * GravityFactor;cameraPos += velocity * JumpFactor;targetPos += velocity * JumpFactor;//if (abs(velocity.y) < 0.1f)//cout << "#### cameraPos.y " << cameraPos.y << endl;//检测所有碰撞体for (int i = 0; i < innerBoundaryMin.size(); i++) {//如果在XZ平面进入碰撞体所在区域if (insideTheCollider(cameraPos, innerBoundaryMin[i], innerBoundaryMax[i])) {if (cameraPos.y - HeroHeight <= innerBoundaryMax[i][1]&& cameraPos.y >= innerBoundaryMax[i][1]) { //脚接触到碰撞体顶部//cout << "touch the top of collider" << endl;isJumping = false;accelerUp.y = -GravityAcceler;velocity.y = 0.f;cameraPos.y = innerBoundaryMax[i][1] + HeroHeight;break;}if (cameraPos.y >= innerBoundaryMin[i][1] &&cameraPos.y - HeroHeight <= innerBoundaryMin[i][1]) { //头接触到碰撞体底部//cout << "touch the bottom of collider" << endl;velocity.y = 0.f;cameraPos.y = innerBoundaryMin[i][1];break;}}else {accelerUp.y = 0.f;}}}

5、按空格键跳跃:

此时只需改变速度和加速度即可。即加一个向上的速度,向上的加速度设为0.

void PhysicsEngine::jumpAndUpdateVelocity() {velocity += glm::vec3(0.f, JumpInitialSpeed, 0.f);accelerUp.y = 0.f;}

6、以上便是整个由重力+碰撞检测构成的简单物理引擎类PhysicsEngine的大致实现过程。下面是该类的定义(PhysicsEngine.h):

#ifndef PHYSICSENGINE_H#define PHYSICSENGINE_H#include <glm/glm.hpp>#include <iostream>#include <vector>using namespace std;#define min(x,y) ((x) < (y) ? (x) : (y))#define max(x,y) ((x) < (y) ? (y) : (x))#define HeroHeight 7.5f //玩家视点到脚的高度#define GravityAcceler -9.8f#define MoveSpeed 0.15f //玩家移动速度#define BoundaryGap 1.0f//碰撞间距#define JumpInitialSpeed 12.0f //起跳初速度#define JumpFactor 0.04f//跳起速度系数#define GravityFactor 0.04f //下落速度系数struct dot {float x;float y;dot(float _x, float _y) :x(_x), y(_y) { }};class PhysicsEngine {public:PhysicsEngine();~PhysicsEngine();//设置空间外部边缘void setSceneOuterBoundary(float x1, float z1, float x2, float z2);//外部碰撞检测void outCollisionTest(glm::vec3 & cameraPos, glm::vec3 & targetPos);//设置空间内部边缘void setSceneInnerBoundary(float x1, float y1, float z1, float x2, float y2, float z2);//内部碰撞检测void inCollisionTest(glm::vec3 & cameraPos, glm::vec3 & targetPos);bool isJumping;void jumpAndUpdateVelocity(); //按下space跳跃时调用//每帧绘制的时候更新摄像机垂直方向移动void updateCameraVertMovement(glm::vec3 & cameraPos, glm::vec3 & targetPos);private://空间内部边缘碰撞检测(考虑高度)void inCollisionTestWithHeight(float x1, float y1, float z1, float x2, float y2, float z2, glm::vec3 & cameraPos, glm::vec3 & targetPos);//空间内部边缘碰撞检测(不考虑高度,即XZ平面)void inCollisionTestXZ(float x1, float z1, float x2, float z2, glm::vec3 & cameraPos, glm::vec3 & targetPos);//空间外部边缘碰撞检测void outCollisionTestXZ(float x1, float z1, float x2, float z2, glm::vec3 & cameraPos, glm::vec3 & targetPos);glm::vec3 velocity; //垂直方向速度glm::vec3 gravity; //重力加速度glm::vec3 accelerUp; //方向向上的加速度glm::vec4 outerBoundary;vector<glm::vec3> innerBoundaryMin; //碰撞器小的x/y/z坐标vector<glm::vec3> innerBoundaryMax; //碰撞器大的x/y/z坐标};#endif // !PHYSICSENGINE_H

引擎类工作流程图:

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。