Hello again, dear graphics enjoyers!
This post details my approach to the “Particle Magic” assignment for CENG469 Computer Graphics II. The objective was to develop an OpenGL application capable of simulating a large number of points—up to one million—whose movements are influenced by user-configurable gravitational attractors. A key requirement was the use of compute shaders for updating particle states efficiently.
Architectural Overview
In contrast to the previous homeworks, I adopted an OOP methodology this time. This involved refactoring the initial code into several distinct classes, each with specific responsibilities:
InputHandler
: Processes keyboard and mouse inputs, translating them into actions within theApplication
andSceneManager
.
Application
: Serves as the central orchestrator, managing the GLFW window, the main application loop, and the interaction between other components.
ShaderManager
: Handles the loading, compilation, and linking of all GLSL shader programs (vertex, fragment, and compute).
ParticleSystem
: Manages the core particle data, including positions, velocities, and ages. It interfaces directly with the compute shader for state updates and handles the rendering of particles.
SceneManager
: Oversees the simulation environment, including the collection of gravitational attractors, the particle spawn origin, simulation speed (delta time scaling), and user interaction modes.
TextRenderer
: Responsible for rendering on-screen text for the user interface, utilizing the FreeType library.
Core Simulation: Particle Behavior and Compute Shader Mechanics
Each particle maintains a state comprising its position (glm::vec3
), velocity (glm::vec3
), and current age (float
). The age attribute decrements over time, and upon reaching zero, the particle is “respawned.”
Gravitational attractors, defined by their position and mass (float
), exert forces on the particles. The magnitude of this force is proportional to the attractor’s mass. The simulation is initialized with a default set of attractors, and users can add or remove attractors dynamically.
The computational core of the simulation resides in the compute shader. For each frame, the compute shader performs the following operations for every active particle:
- State Retrieval: The particle’s current position, velocity, age, and its initial maximum age (used for normalization) are read from a Shader Storage Buffer Object (SSBO).
- Gravitational Force Accumulation:
- A net force vector for the particle is initialized to zero.
- The shader iterates through all active attractors. For each attractor:
- The vector from the particle to the attractor (
Direction = AttractorPosition - ParticlePosition
) is calculated. - The squared distance (
DistanceSquared
) is computed. - The magnitude of the gravitational force is determined using a formula:
ForceMagnitude = (GravitationalConstant * AttractorMass) / DistanceSquared
. The particle mass is implicitly assumed to be 1. - The force vector from the current attractor is
normalize(Direction) * ForceMagnitude
. - This vector is added to the particle’s net force accumulator.
- The vector from the particle to the attractor (
- Velocity Update:
- The accumulated net force is treated as acceleration (assuming unit mass for particles:
Acceleration = NetForce
). - The particle’s velocity is updated:
newVelocity = oldVelocity + Acceleration * deltaTime
. - A damping factor (e.g.,
newVelocity *= 0.995f
) can be applied to gradually reduce velocity, contributing to simulation stability and visual style. Commented out in the version I submitted. - The velocity is capped at a
MAX_SPEED
to prevent particles from becoming uncontrollably fast.
- The accumulated net force is treated as acceleration (assuming unit mass for particles:
- Position Update:
- The particle’s position is updated based on its new velocity:
newPosition = oldPosition + newVelocity * deltaTime
.
- The particle’s position is updated based on its new velocity:
- Age Progression and Respawn Logic:
- The particle’s age is decremented:
currentAge -= deltaTime
. - If
currentAge <= 0.0f
:- The particle’s position is reset to the current
particleOrigin
(a user-controllable point). - Its
currentAge
is reset, often to itsinitial_max_age
(which can be randomized slightly upon each respawn for variation). - A new initial velocity is assigned. This is a key area for artistic control, allowing for various emission patterns.
- The particle’s position is reset to the current
- The particle’s age is decremented:
- State Storage: The updated position, velocity, and age are written back to the particle’s entry in the SSBO.
Conclusion
It was a very fun experience and a great opportunity to enhance my portfolio with such a nice project. Many thanks to Oğuz Hocam and Kadir Hocam. I’m thrilled to apply this knowledge to create various visual effects in my future projects. Thanks for reading!
Leave a Reply