Interactive Physics-Based
Simulation with Real-Time GUI
Real-Time Graphics Programming (RTGP)
Final Project – A.A. 2024/2025
Student: Yusuf Kemahlı
Instructor: Prof. Davide Gadia
Date: July 11, 2025
Contents
1 Introduction
2
2 Starting Point: Lecture06b
3
3 Design Choices and Techniques
3.1 Impulse-Based Jumping Mechanism . . . . . . . . . . . . . . . . . . . . .
3.2 Collision Detection with Bullet Physics . . . . . . . . . . . . . . . . . . .
3.3 Visual Feedback via Texture Switching . . . . . . . . . . . . . . . . . . .
3.4 Interactive GUI with Dear ImGui . . . . . . . . . . . . . . . . . . . . . .
3.5 Per-Object Shader Switching Using Uniforms . . . . . . . . . . . . . . . .
4
4
4
4
5
5
4 Implementation Details
4.1 Impulse Logic for Jumping . . . . . . . . . . . . . . . . . . . . . . . . . .
4.2 Collision Detection and State Tracking . . . . . . . . . . . . . . . . . . .
4.3 Texture Logic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4.4 Shader Modifications: Vertex and Fragment Shaders . . . . . . . . . . . .
4.5 ImGui GUI and Runtime Updates . . . . . . . . . . . . . . . . . . . . . .
6
6
7
8
9
10
5 Performance and Testing
12
6 Screenshots and Visual Results
14
7 Conclusion
16
1
Chapter 1
Introduction
This project presents a real-time physics simulation built using modern graphics programming techniques. Developed as the final project for the Real-Time Graphics Programming
(RTGP) course. It extends the foundational lecture06b.cpp lab code with interactive
dynamics, runtime parameter control, and visual feedback mechanisms.
Our main objective was to create a responsive 3D environment where the user can launch
projectiles and trigger vertical impulses on rigid bodies (simulating a jump). These
interactions result in realistic physics behavior, objects move under gravity, collisions
occur, and visual effects reflect those events through dynamic texture changes driven by
shaders.
To accomplish this, the project integrates three key systems:
• OpenGL for real-time 3D rendering
• Bullet Physics for rigid body dynamics, impulse forces, and collision detection
• ImGui for an interactive graphical interface that allows live tuning of simulation
parameters
By combining these components through modular development and careful coordination,
our simulation achieves both technical accuracy and interactive usability. That is fully
aligned with the goals of the RTGP course.
2
Chapter 2
Starting Point: Lecture06b
The initial codebase for this project was provided through the lecture06b.cpp lab exercise, which served as the basis for the development of more advanced behavior. The
original implementation featured:
• A 3D scene containing 25 cube-shaped rigid bodies placed on a flat-colored ground
plane
• Gravity-based motion using Bullet Physics, with basic restitution and friction
• A projectile firing system where spheres are launched forward using keyboard input
• A programmable OpenGL rendering pipeline using a vertex and fragment shader
pair
The shaders implemented a GGX illumination model, delivering physically based lighting
for all scene objects. However, all geometries, including the cubes, plane, and projectiles,
were rendered with solid colors only. No textures or UV mapping were used.
Although technically correct and visually consistent, the simulation lacked deeper interactivity. There was no per-object reaction to collisions, no visual variation based on state,
and no interface for adjusting simulation parameters. The extended project work mainly
focused on these gaps.
3
Chapter 3
Design Choices and Techniques
3.1
Impulse-Based Jumping Mechanism
To simulate vertical motion and introduce dynamic interaction, vertical impulses were
applied to all cube-shaped rigid bodies simultaneously when the user pressed a key. This
created a jumping effect managed by the built-in physics and gravity system of Bullet.
Using impulses allowed for immediate and controllable motion while preserving realistic
physical behavior.
3.2
Collision Detection with Bullet Physics
Collisions between projectiles and cubes were handled using Bullet’s persistent manifold
system, which provides narrow-phase, per-frame contact data. Each object’s role (cube
or projectile) was determined by inspecting its index within the collision object array.
This consistent ordering allowed for efficient runtime checks and accurate classification
during the collision loop.
3.3
Visual Feedback via Texture Switching
To visually indicate that a cube had been hit, its appearance changed from a solid color to
a cracked glass texture. This was implemented using per-object logic in the rendering loop
and controlled via a simple uniform. The method was GPU-efficient and didn’t require
any changes to mesh data or lighting. Provided clear visual feedback that improved the
interactivity of the scene.
4
3.4
Interactive GUI with Dear ImGui
For runtime parameter control, Dear ImGui was integrated into the system. The interface
allowed users to adjust the simulation parameters, such as jump force, gravity, projectile speed, texture repeat, and friction, without restarting or recompiling the program.
ImGui’s immediate-mode API made it easy to update variables live, and its OpenGL
integration fit smoothly into the rendering pipeline.
3.5
Per-Object Shader Switching Using Uniforms
Rather than managing separate shaders for different object types, a single shader program
was used for all rendering. The fragment shader accepted a useTexture uniform that
determined whether to sample a texture or apply a flat color. This design kept the
rendering pipeline clean and flexible while enabling object-level variation based on state
(e.g., hit or not hit).
5
Chapter 4
Implementation Details
4.1
Impulse Logic for Jumping
To simulate jumping behavior, a vertical impulse is applied to all cube-shaped rigid bodies
when the user presses the J key. This logic is handled inside the key callback() function
and triggers only on the initial key press.
During development, an issue emerged where certain cubes would not jump after being
idle. This was caused by Bullet’s internal optimization, which puts inactive objects into a
“sleep” state to conserve resources. To fix this, each body is explicitly reactivated before
applying the impulse, ensuring reliable behavior across all frames.
The jumpForce variable—adjustable through the GUI—controls the strength of the upward impulse.
Cubes are retrieved from Bullet’s getCollisionObjectArray(), which stores all simulation objects in order of insertion. Index 0 is reserved for the ground plane, so the
cubes occupy indices 1 through 25. After retrieving each cube’s rigid body, an impulse
is applied along the positive Y axis to simulate a vertical jump.
if ( key == GLFW_KEY_J && action == GLFW_PRESS )
{
int numCubes = 25; // Total cubes , excluding ground plane
for ( int i = 1; i <= numCubes ; ++ i )
{
btC ollisi onObje ct * obj = bulletSimulation . dynamicsWorld - >
g e t C o l l i s i o n O b j e c t A r r a y () [ i ];
btRigidBody * body = btRigidBody :: upcast ( obj ) ;
if ( body )
{
body - > activate () ; // Wake up sleeping bodies
body - > a pp l y Ce n t ra l I mp u l se ( btVector3 (0 , jumpForce , 0) ) ;
}
}
}
6
4.2
Collision Detection and State Tracking
To detect collisions between projectiles and cube rigid bodies, we use Bullet’s persistent
manifold system. A btPersistentManifold stores contact information for a pair of
colliding objects.
The detection process follows these steps:
1. We retrieve the number of active manifolds and loop through them.
2. For each manifold, we get the two objects involved in the collision.
3. We use findLinearSearch() to retrieve their indices from Bullet’s collision object
array.
4. Indices 1–25 represent cubes, and 26 and above are projectiles (index 0 is the static
ground plane and is ignored).
5. If a cube and projectile are found to be in contact, we mark the cube as hit.
We maintain three boolean arrays to track collision outcomes:
• cubeHitForTexture[] — used by the rendering loop to decide whether to apply
the cracked texture
• cubeHit[] — temporarily stores hit status during the current frame
• alreadyHit[] — ensures that each cube only prints a “hit” message once
int numManifolds = bulletSimulation . dynamicsWorld - > getDispatcher () ->
getNumManifolds () ;
for ( int i = 0; i < numManifolds ; ++ i )
{
b t P e r s i s t e n t M a n i f o l d * contactManifold = bulletSimulation .
dynamicsWorld - > getDispatcher () -> g e t M a n i f o l d B y I n d e x I n t e r n a l ( i ) ;
btC ollisi onObje ct * objA = const_cast < btCol lision Object * >(
contactManifold - > getBody0 () ) ;
btC ollisi onObje ct * objB = const_cast < btCol lision Object * >(
contactManifold - > getBody1 () ) ;
int indexA = bulletSimulation . dynamicsWorld - >
g e t C o l l i s i o n O b j e c t A r r a y () . findLinearSearch ( objA ) ;
int indexB = bulletSimulation . dynamicsWorld - >
g e t C o l l i s i o n O b j e c t A r r a y () . findLinearSearch ( objB ) ;
bool aIsCube = ( indexA >= 1 && indexA <= 25) ;
bool bIsCube = ( indexB >= 1 && indexB <= 25) ;
bool aIsProjectile = ( indexA >= 26) ;
bool bIsProjectile = ( indexB >= 26) ;
if ( aIsCube && bIsProjectile ) {
7
cubeHit [ indexA - 1] = true ;
cub eHitFo rTextu re [ indexA - 1] = true ;
}
if ( bIsCube && aIsProjectile ) {
cubeHit [ indexB - 1] = true ;
cub eHitFo rTextu re [ indexB - 1] = true ;
}
}
for ( int i = 0; i < 25; i ++)
{
if ( cubeHit [ i ] && ! alreadyHit [ i ]) {
alreadyHit [ i ] = true ;
cubeHit [ i ] = false ;
std :: cout << " Cube ␣ " << i << " ␣ was ␣ hit !\ n " ;
}
}
4.3
Texture Logic
To provide visual feedback when a cube is hit by a projectile, a cracked glass texture is
applied. This logic is implemented inside the per-object rendering loop and evaluated
once per frame for every object.
The texture is loaded at initialization using:
GLuint crackedTextureID = LoadTexture ( " ../../ textures / broken_glass6 . png
");
Before drawing each object, we bind the texture unit and determine whether the object
should use the texture or a solid material color.
The texture is activated as follows:
glActiveTexture ( GL_TEXTURE0 ) ;
glBindTexture ( GL_TEXTURE_2D , crackedTextureID ) ;
We use a uniform named useTexture, which is passed to the fragment shader. This
variable is used to switch between two rendering paths: one that samples the texture,
and one that uses only lighting and color.
For objects like the ground plane and projectiles, we always pass 0 to this uniform so they
render without any texturing. For each cube, we check the cubeHitForTexture[] flag.
If the cube was hit, we enable texture mode and bind the cracked texture. Otherwise,
texturing remains disabled:
if ( cubeH itForT exture [ i - 1]) {
glUniform1i ( useTextureLocation , 1) ; // enable texture mode in
shader
glBindTexture ( GL_TEXTURE_2D , crackedTextureID ) ;
} else {
8
glUniform1i ( useTextureLocation , 0) ; // render with basic material
color
}
This logic runs inside the main rendering loop and is evaluated for each object. It enables
precise control over how each cube is rendered, allowing only hit cubes to visually change
while the rest of the scene remains unaffected.
4.4
Shader Modifications: Vertex and Fragment Shaders
We extended both shaders to support per-object texture control, UV interpolation, and
runtime tiling. The updated files used in the project are:
• vertexShaderWithTexture.vert
• fragmentShaderWithTexture.frag
Vertex Shader
In the vertex shader, we added support for UV coordinates by:
• Declaring a new input: layout(location = 2) in vec2 UV;
• Creating an output variable: out vec2 interp UV;
• Passing the per-vertex UV data to the fragment shader:
// inside main ()
interp_UV = UV ;
This allowed the fragment shader to receive properly interpolated UV coordinates for
every fragment.
Fragment Shader
In the fragment shader, we made the following changes:
• Added a boolean uniform: uniform bool useTexture; — to toggle texture usage
per object
• Added the interpolated UV input: in vec2 interp UV;
• Added a float uniform: uniform float repeat; — to control tiling from the GUI
9
• Replaced the hardcoded diffuse color with a dynamic surfaceColor, selected via
conditional logic
Texture sampling and switching is handled at the beginning of GGX function, which
computes lighting using the GGX model:
vec3 surfaceColor ;
vec2 repeated_Uv = mod ( interp_UV * repeat , 1.0) ;
surfaceColor = useTexture ? texture ( tex , repeated_Uv ) . rgb :
diffuseColor ;
The final lighting calculation uses surfaceColor instead of the previously fixed diffuseColor.
This setup allows hit cubes to appear with a tiled cracked texture, while all other objects
remain solid-colored, with no additional shaders or branches needed.
4.5
ImGui GUI and Runtime Updates
To enable real-time control of simulation parameters, the Dear ImGui library was integrated into the OpenGL application. This immediate-mode GUI library provides
lightweight and flexible widgets, such as sliders and panels, that render directly on top of
the 3D scene. It allowed for interactive tuning of both physics and visual settings during
runtime.
Integration Steps
ImGui was added to the project by downloading its source files and backend implementations into the local directory. The integration process included:
• Include headers in the source file:
# include " imgui . h "
# include " backends / imgui_impl_glfw . h "
# include " backends / i mgu i_ im pl _o pe ng l3 . h "
• Initialize the ImGui context and backend in the main() function:
IM GU I_ CH EC KV ER SI ON () ;
ImGui :: CreateContext () ;
ImGuiIO & io = ImGui :: GetIO () ; ( void ) io ;
ImGui :: StyleColorsDark () ; // Set dark theme
I m G u i _ I m p l G l f w _ I n i t F o r O p e n G L ( window , true ) ;
I m G u i _ I m p l O p e n G L 3 _ I n i t ( " # version ␣ 410 " ) ;
• Inside the rendering loop, create a new frame and draw GUI elements:
I m G u i _ I m p l O p e n G L 3 _ N e w F r a m e () ;
I m G u i _ I m p l G l f w _ N e w F r a m e () ;
ImGui :: NewFrame () ;
10
ImGui :: Begin ( " Controls " ) ;
ImGui :: SliderFloat ( " Jump ␣ Force " , & jumpForce , 0.0 f , 50.0 f ) ;
ImGui :: SliderFloat ( " Gravity " , & gravityY , -50.0 f , 0.0 f ) ;
ImGui :: SliderFloat ( " Projectile ␣ Speed " , & shootInitialSpeed , 0.0 f ,
200.0 f ) ;
ImGui :: SliderFloat ( " Friction " , & friction , 0.0 f , 1.0 f ) ;
ImGui :: SliderFloat ( " Texture ␣ Repeat " , & repeat , 0.1 f , 5.0 f ) ;
ImGui :: End () ;
• At the end of the frame, render and display the GUI:
ImGui :: Render () ;
I m G u i _ I m p l O p e n G L 3 _ R e n d e r D r a w D a t a ( ImGui :: GetDrawData () ) ;
• At shutdown, properly clean up ImGui resources:
I m G u i _ I m p l O p e n G L 3 _ S h u t d o w n () ;
I m G u i _ I m p l G l f w _ S h u t d o w n () ;
ImGui :: DestroyContext () ;
Runtime Controls
The GUI allows live tuning of five core simulation parameters:
• Jump Force: vertical impulse strength when applying jumps
• Gravity Y: gravity vector applied to the Bullet physics world
• Projectile Speed: initial velocity for fired spheres
• Friction: friction coefficient passed to each rigid body
• Texture Repeat: UV tiling factor for the cracked texture
Each value is updated in every frame and applied immediately within the simulation logic.
For instance, changes to gravity are reflected by resetting the gravity vector directly:
bulletSimulation . dynamicsWorld - > setGravity ( btVector3 (0.0 f , gravityY ,
0.0 f ) ) ;
Overall, ImGui provided a minimal but powerful way to interactively control both visual
and physical aspects of the simulation.
11
Chapter 5
Performance and Testing
The simulation was interactively tested throughout the development process to ensure
stable behavior and responsiveness under normal usage conditions. No formal benchmarks were collected, but performance remained smooth and consistent across all tested
scenarios.
Real-Time Responsiveness
User input (keyboard and GUI sliders) was applied and immediately reflected in the
simulation. Projectile spawning, cube jumping, and texture switching occurred without
noticeable delay. The use of ImGui did not introduce an observable overhead during
rendering.
Efficient Physics Handling
Bullet’s built-in optimizations, including automatic sleep mode for inactive bodies, contributed to efficient physics updates. The project’s use of impulse based interactions,
rather than continuous force application, also minimized computational load.
GPU Load and Shaders
The rendering performance remained stable due to the unified shader approach. Texture
switching was handled via uniforms instead of swapping shaders, keeping GPU operations
minimal. Texture tiling and sampling did not show measurable slowdown, even with
multiple textured cubes on the screen.
12
Stress Testing
The simulation was tested with repeated projectile launches, high jump forces, and frequent GUI interaction. No crashes, memory leaks, or instability were observed. Cube
collisions and texture changes remained reliable and visually consistent even during extended use.
Overall, the system performed as expected for real-time simulation use, with no perceptible latency or degradation across core features.
Testing Environment
The simulation was developed and tested on a MacBook Air (Apple Silicon M1) using
Visual Studio Code as the development environment. All results and behavior described
in this report were observed on this configuration.
13
Chapter 6
Screenshots and Visual Results
Figure 6.1: Cubes launched with vertical impulses. Several have been hit and now display
the cracked texture. The ImGui panel is open, showing real-time control sliders.
14
Figure 6.2: A closer view of the rigid bodies, including projectiles and hit cubes. Only
cubes that were struck show the cracked glass texture, demonstrating the selective visual
effect.
15
Chapter 7
Conclusion
This project implemented a fully interactive, physics-driven simulation using OpenGL,
Bullet Physics, and custom shader logic. Built on top of the lecture06b.cpp base,
the system was significantly extended with new capabilities, including vertical impulses,
collision detection, texture-based feedback, and an interactive GUI.
Rigid body cubes respond to keyboard-triggered impulses, simulating jumps under gravity. When struck by projectiles, they react visually by displaying a cracked texture,
providing immediate, intuitive feedback powered by shader logic. This added both functionality and clarity to the simulation.
The integration of Dear ImGui enabled real-time control over key parameters such as
gravity, jump force, projectile speed, friction, and texture tiling. This allowed for rapid
experimentation and fine-tuning without recompilation, significantly improving the development experience and the interactivity of the simulation.
Rendering performance remained stable throughout, supported by a unified shader and
efficient use of GPU resources. Shader changes were minimal but effective, enabling
dynamic texturing while preserving lighting calculations.
Overall, the project successfully brought together key elements from the Real-Time
Graphics Programming course, shading, simulation, interaction, and rendering, into a
cohesive, extensible system.
16
0
You can add this document to your study collection(s)
Sign in Available only to authorized usersYou can add this document to your saved list
Sign in Available only to authorized users(For complaints, use another form )