I've been working on a game using Bullet Physics for C++ as well as SDL for the windowing (with OpenGL)
I've recently threaded the game, so that a separate while loop runs the Physics Simulation, whilst the rendering runs independently (Right now I've got a Car driving around a Heightfield, which worked perfectly without jitter, prior to thread it)
The reason was, because I wanted to de-coupled physics simulation with rendering (So low FPS could still have the same Physics simulation speed, which would inevitably cause choppiness)
Problem: at FPS greater, or lower than 60 FPS (pretty much if it isn't synced with the 60 Hz Physics Timestep) there will be jittering of the vehicle, and physics objects in general...
The btTransform = vehiclePhysics.GetTransform() is the Vehicle's motion state. Right now it's a btDefaultMotionState, so this would deal with some bit of interpolation, but below 50 fps, it's still very jittery.
When I activate the lerpVec3 transformation on my Camera's lookAt, the vehicle at low FPS jitters less, but the entire scene starts to jitter. And when I don't the scene is LERP'd and camera movement is smooth, but the vehicle jitters, and the Debug Drawing of the vehicle also jitters, so it's clearly the transform of the vehicle or in general, physics objects that are wonky due to the threading...
Render Thread Code (essentials)
This is the code within my rendering loop (main thread) that will get the transforms from the shared Resource, and get it ready for rendering (As well as Debug Drawing code for BulletDebugDrawer)
vehiclePhysicsInfo vI = sharedPhysicsRessource.GetVehiclePhyInfo(); btTransform vehicleTransform = vI.transform; if (isFirstUpdate) { lastVehicleTransform = vehicleTransform; isFirstUpdate = false; } btVector3 vehiclePosition = vehicleTransform.getOrigin(); btVector3 lastVehiclePosition = lastVehicleTransform.getOrigin(); //... Redacted trials at interpolation (On or off, I still get the problem, so it's useless anyways) //Tried manual LERP & SLERP interpolations for positional and rotational data, and it's togglable with the If stmt below, still doesn't change much at all.. if(interpolated_transforms){ vehiclePosition = interpolatedPosition; vehicleRotation = interpolatedRotation; } glm::quat glmVehicleRotation = glm::quat(vehicleRotation.w(), vehicleRotation.x(), vehicleRotation.y(), vehicleRotation.z()); if(interpolated_transforms){ glmVehicleRotation = glm::quat(interpolatedRotation.w(), interpolatedRotation.x(), interpolatedRotation.y(), interpolatedRotation.z()); } //Position Translation glm::mat4 translation = glm::translate(glm::mat4(1.0f), glm::vec3(vehiclePosition.x(), vehiclePosition.y(), vehiclePosition.z())); glm::mat4 rotation = glm::mat4_cast(glmVehicleRotation); glm::mat4 rotate90DEG_Adjustment = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), glm::vec3(0.0f, 1.0f, 0.0f)); vehicleModelMatrix = translation * rotation * rotate90DEG_Adjustment * glm::scale(glm::vec3(0.75f)); // WheelMatrix = translation * glm::scale(glm::vec3(0.25f)); //! PROTOTYPING: VEHICLE RENDERING CODE // Specify the color of the background glClearColor(0.07f, 0.13f, 0.17f, 1.0f); // Clean the back buffer and depth buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Tell OpenGL which Shader Program we want to use shaderProgram.Activate(); // Assigns a value to the uniform; NOTE: Must always be done after activating the Shader Program // brickTex.Bind(); glUniform1i(useTextureLocation, GL_FALSE); //debugDrawer->setDebugMode(btIDebugDraw::DBG_DrawWireframe); if(show_debug_draw) { physicsWorldPTR->debugDrawWorld(); debugDrawer->flushLines(); } glUniform1i(useTextureLocation, GL_TRUE); //* ###### CAMERA ####### camera.DEBUG = false; if(!camera.DEBUG) { auto targetVec = glm::vec3(vehiclePosition.x() + 1.0f, vehiclePosition.y() + 3.0f, vehiclePosition.z() - 5.0f); //* Camera Offset auto dirVec = targetVec - camera.Position; if (glm::distance2(targetVec, camera.Position) > 0.02f) camera.Position += dirVec * 0.03f; glm::vec3 lookAtPosition = glm::vec3(vehiclePosition.x(), vehiclePosition.y(), vehiclePosition.z()); //camera.LookAt = lookAtPosition; //Naive Approach camera.LookAt = lerpVec3(camera.LookAt, lookAtPosition, 0.35f); } else { camera.Inputs(Window); } camera.Matrix(45.0f, 0.1f, 1000.0f, shaderProgram, "camMatrix"); //! IMPORTANT //*############## OpenGL - Draw Calls ################ This is the Physics Thread Code (essentials)
auto lastTime = std::chrono::high_resolution_clock::now(); double accumulator = 0.0; const double physicsTimestep = 1.00 / 60.0; if (debugDrawer) { physicsWorld->dynamicsWorld->setDebugDrawer(debugDrawer); sharedRSRC->UpdatePhysicsWorld(physicsWorld->dynamicsWorld); } while (running) { auto currentTime = std::chrono::high_resolution_clock::now(); double frameTime = std::chrono::duration_cast<std::chrono::duration<double>>(currentTime - lastTime).count(); lastTime = currentTime; accumulator += frameTime; uint8_t input; while (playerInputQueue.TryPop(input)) { //... input handing, works fine (Thread Safe Queue) // Process and print the input command } while (accumulator >= physicsTimestep) { // Step the physics simulation physicsWorld->dynamicsWorld->stepSimulation(physicsTimestep, 2, physicsTimestep); // Get the latest vehicle transform and update shared resource btTransform vehicleTransform = vehiclePhysics.GetTransform(); if (sharedRSRC) { vehiclePhysicsInfo vInfo; vInfo.transform = vehicleTransform; for (int i = 0; i < vehiclePhysics.vehicle->getNumWheels(); i++) { btWheelInfo wheelinfo = vehiclePhysics.vehicle->getWheelInfo(i); vInfo.wheelVec.push_back(wheelinfo); } sharedRSRC->UpdateVehiclePhyInfo(vInfo); sharedRSRC->SwapBuffers(); } // Decrease the accumulated time accumulator -= physicsTimestep; } btVector3 vehiclePosition = vehiclePhysics.GetTransform().getOrigin(); //player positions (vehicle) btScalar pXpos = vehiclePosition.getX(); btScalar pYpos = vehiclePosition.getY(); btScalar pZpos = vehiclePosition.getZ(); terrainChunkManager.update(pXpos, pZpos); // std::cout << "Thread is running..." << std::endl; // Sleep or wait logic can be added here to control update rate } And here is the shared resource, used to communicate between threads, basic header file, and these functions have Thread_guards that lock and update mutex
class SharedPhysicsResource { public: void UpdateVehiclePhyInfo(const vehiclePhysicsInfo& data); vehiclePhysicsInfo GetVehiclePhyInfo(); void SwapBuffers(); void UpdatePhysicsWorld(btDiscreteDynamicsWorld* physicsWorld); btDiscreteDynamicsWorld* getPhysicsWorld(); private: vehiclePhysicsInfo playerVehicleBuffer[2]; int rI = 0; //Read int wI = 1; //Write std::mutex mutex_; btDiscreteDynamicsWorld* physicsWorld_; }; I've tried:
- Manual linear interpolation (and spherical as well for rotations)
- Using
btMotionStateinstead of getting the RigidBody transforms (This helps a lot for when the FPS is a constant 60) - Double buffering the shared resource that contains the
btTransformfor the rendering of Vehicle - Unsuccessfully tried to build my own
btMotionState, but I don't think this is the problem, as at 60 FPS, the default motion state works great
I've briefly looked at some other Bullet setups and they use different different constraint solvers & generally other components that seem inherently multi-threaded.
For instance, here's my dynamics world setup (that I'm running in an independent thread)
PhysicsWorldSingleton::PhysicsWorldSingleton() { broadphase = new btDbvtBroadphase(); //uses QuadTree Data Structure to minimize expensive collision checking collisionConfiguration = new btDefaultCollisionConfiguration(); dispatcher = new btCollisionDispatcher(collisionConfiguration); solver = new btSequentialImpulseConstraintSolver(); dynamicsWorld = new btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration); dynamicsWorld->setGravity(btVector3(0.0f, -13.81f , 0.0f)); }; I'd be very grateful to hear any advise, wisdom, or things I might potentially be doing wrong with my setup, as I know games that fluctuate between 30-144 FPS, with smooth physics simulation...? Also please let me know if you need clarification on anything, or additional code snippets