{"id":22,"date":"2025-05-30T20:57:37","date_gmt":"2025-05-30T20:57:37","guid":{"rendered":"https:\/\/blog.metu.edu.tr\/e252135\/?p=22"},"modified":"2025-05-30T20:57:39","modified_gmt":"2025-05-30T20:57:39","slug":"particle-simulation","status":"publish","type":"post","link":"https:\/\/blog.metu.edu.tr\/e252135\/2025\/05\/30\/particle-simulation\/","title":{"rendered":"Particle Simulation"},"content":{"rendered":"<h2><strong>Introduction<\/strong><\/h2>\n<p>In this blog, I will share my experience completing the third assigment for course Ceng469 Computer Graphics 2.<\/p>\n<p>For this assigment, I implemented a particle system with attractors ,using OpenGL and compute shaders.<\/p>\n<h2><strong>Initializing Window and Buffers<\/strong><\/h2>\n<p>I first set up the OpenGL context and window\u00a0 using GLFW. I configured opengl to use version 4.3 for the modern features like compute shaders.<\/p>\n<h2><strong>Creating Buffers and TBOS<\/strong><\/h2>\n\n\n<p>After setting up the OpenGL context, I created GPU buffers for the particle system\u2014one for positions and one for velocities. Each particle is represented as a <code>vec4<\/code>, where the <code>xyz<\/code> components store the 3d position, and the <code>w<\/code> is used for age of the particle,which will be colored accordingly. I also created a vec4 buffer for attractors ,but for their <strong>w<\/strong> component is used as mass. <\/p>\n\n\n\n<p>In <code>initComputeBuffers()<\/code>, I allocated and filled the position buffer with random 3D vectors using <code>random_vector()<\/code>, and assigned a random mass (stored in the <code>w<\/code> component). Velocities were initialized with small random values in <code>xyz<\/code>, while the <code>w<\/code> component was set to zero.<\/p>\n\n\n\n<p>To make these buffers accessible to compute shaders, I created texture buffer objects (TBOs) in <code>initTBOs()<\/code>. Each TBO binds its corresponding buffer and exposes it to the shader as a <code>samplerBuffer<\/code> using <code>glTexBuffer()<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Rendering Points<\/strong><\/h2>\n\n\n\n<p>Before running any physics simulation, I started by rendering the particles as simple GL points to confirm everything was set up correctly. The vertex shader takes each particle\u2019s position (<code>vec4<\/code>), transforms it using the <code>mvp<\/code> matrix, and assigns a <code>gl_PointSize<\/code> for rendering.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#vertex shader\n#version 430\n\nlayout(location = 0) in vec4 in_position;\n\nuniform float pointSize;\nuniform mat4 mvp;\n\nvoid main()\n{\n    gl_Position = mvp * vec4(in_position.xyz, 1.0);\n    gl_PointSize = pointSize;\n}\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>#fragment shader\n#version 430\n\nlayout(location = 0) out vec4 color;\n\nvoid main() {\n    color = vec4(0.0, 0.8, 1.0, 1.0);\n}\n<\/code><\/pre>\n\n\n\n<p>At first, I couldn&#8217;t see anything on the screen. It looked like rendering was broken. After checking everything, I realized I was missing a memory barrier after the compute shader had written to the position buffer.<\/p>\n\n\n\n<p>Adding this line between the rendering and computing solved the problem:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);<\/code><\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"537\" src=\"https:\/\/blog.metu.edu.tr\/e252135\/files\/2025\/05\/Screenshot-from-2025-05-22-14-22-00-1024x537.png\" alt=\"\" class=\"wp-image-25\" srcset=\"https:\/\/blog.metu.edu.tr\/e252135\/files\/2025\/05\/Screenshot-from-2025-05-22-14-22-00-1024x537.png 1024w, https:\/\/blog.metu.edu.tr\/e252135\/files\/2025\/05\/Screenshot-from-2025-05-22-14-22-00-300x157.png 300w, https:\/\/blog.metu.edu.tr\/e252135\/files\/2025\/05\/Screenshot-from-2025-05-22-14-22-00-768x403.png 768w, https:\/\/blog.metu.edu.tr\/e252135\/files\/2025\/05\/Screenshot-from-2025-05-22-14-22-00-1536x805.png 1536w, https:\/\/blog.metu.edu.tr\/e252135\/files\/2025\/05\/Screenshot-from-2025-05-22-14-22-00.png 1919w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Simulating Particles<\/strong><\/h2>\n\n\n\n<p>For the particle simulation, I modified a sample compute shader from the course slides to make it fit my project\u2019s setup. The idea is simple: each particle moves under the influence of multiple attractors, and its velocity gets updated every frame. If a particle \u201cdies\u201d (based on age or going out of bounds), it respawns at the origin with a new random velocity.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#version 430\n\nlayout(std140, binding = 0) uniform AttractorBlock {\n    vec4 attractor&#091;12];\n};\n\nuniform int numAttractors;\nuniform vec3 origin;\n\nlayout(local_size_x = 100) in;\n\nlayout(rgba32f, binding = 0) uniform imageBuffer velocityBuffer;\nlayout(rgba32f, binding = 1) uniform imageBuffer positionBuffer;\n\nuniform float dt;\nfloat rand(vec2 co) {\n    return fract(sin(dot(co, vec2(14.9898, 78.23))) * 4378.5453);\n}\n\n\nvoid main() {\n    uint index = gl_GlobalInvocationID.x;\n\n    vec4 vel = imageLoad(velocityBuffer, int(index));\n    vec4 pos = imageLoad(positionBuffer, int(index));\n\n    pos.xyz += vel.xyz * dt;\n    pos.w -= 0.005 * dt;\n\n    for (int i = 0; i &lt; numAttractors; i++) {\n        vec3 dist = attractor&#091;i].xyz - pos.xyz;\n        vel.xyz += dt * dt * attractor&#091;i].w * normalize(dist) \/ (dot(dist, dist) + 10.0);\n    }\n\n    if (pos.w &lt;= 0.0 || pos.x &lt; -4.0 || pos.x &gt; 4.0 || pos.y &lt; -2.0 || pos.y &gt; 2.0 || pos.z &lt; -2.0 || pos.z &gt; 2.0) {\n        vec2 seed = vec2(float(gl_GlobalInvocationID.x), pos.w);\n        float r1 = rand(seed);\n        float r2 = rand(seed + 1.0);\n        float r3 = rand(seed + 2.0);\n\n        vel.xyz = vec3(r1, r2, r3) * 2.0 - 1.0;\n        vel.xyz *= 0.01;\n\n        pos.xyz = origin;\n        pos.w = 1.0;\n    }\n\n    imageStore(positionBuffer, int(index), pos);\n    imageStore(velocityBuffer, int(index), vel); \n}<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Age-Based Rendering<\/strong><\/h2>\n\n\n\n<p>To visualize the particle \u201clifespan,\u201d I used the <code>w<\/code> component of the position (which I treat as age) to interpolate color. As particles get older, they fade from green to red.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#version 430\n\nlayout(location = 0) out vec4 color;\n\nin float intensity;\nvoid main() {\n   color = mix(vec4(0.0f,0.8f,0.2f,1.0f),vec4(0.8f,0.f,0.2f,1.0f),\n    intensity);\n}<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-video\"><video height=\"720\" style=\"aspect-ratio: 1280 \/ 720;\" width=\"1280\" controls src=\"https:\/\/blog.metu.edu.tr\/e252135\/files\/2025\/05\/output_720p.mp4\"><\/video><\/figure>\n\n\n\n<p>                                                      with no attractors<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>Controls<\/strong><\/h2>\n\n\n\n<h4 class=\"wp-block-heading\">\ud83d\uddb1 Mouse Controls<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Left Click<\/strong>\n<ul class=\"wp-block-list\">\n<li>In <strong>Attractor Mode<\/strong>: Adds an attractor at the point you click in the 3D space. Attractors are limited by a predefined capacity.(12)<\/li>\n\n\n\n<li>In <strong>Origin Mode<\/strong>: Sets the simulation\u2019s origin point to the clicked location.<\/li>\n<\/ul>\n<\/li>\n\n\n\n<li><strong>Right Click<\/strong>\n<ul class=\"wp-block-list\">\n<li>Removes the most recently added attractor from the scene, if any exist.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">\ud83d\udd04 Mouse Scroll<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Scroll Up \/ Down<\/strong>\n<ul class=\"wp-block-list\">\n<li>Increases or decreases the mass of the current attractor to be added.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<h4 class=\"wp-block-heading\">\u2328\ufe0f Keyboard Controls<\/h4>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Q<\/strong> \u2013 Quits the simulation.<\/li>\n\n\n\n<li><strong>V<\/strong> \u2013 Toggles V-Sync on or off.<\/li>\n\n\n\n<li><strong>W \/ S<\/strong> \u2013 Speeds up or slows down the simulation time scale.<\/li>\n\n\n\n<li><strong>T<\/strong> \u2013 Toggles UI visibility.<\/li>\n\n\n\n<li><strong>R<\/strong> \u2013 Starts or pauses the animation.<\/li>\n\n\n\n<li><strong>F<\/strong> \u2013 Toggles fullscreen mode.<\/li>\n\n\n\n<li><strong>G<\/strong> \u2013 Switches between Origin Mode and Attractor Mode.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Final Result<\/h2>\n\n\n<figure class=\"wp-block-embed-youtube wp-block-embed is-type-video is-provider-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\"><div class=\"lyte-wrapper fourthree\" style=\"width:420px;max-width:100%;margin:5px;\"><div class=\"lyMe\" id=\"WYL_QDmLL5CLles\"><div id=\"lyte_QDmLL5CLles\" data-src=\"\/\/i.ytimg.com\/vi\/QDmLL5CLles\/hqdefault.jpg\" class=\"pL\"><div class=\"tC\"><div class=\"tT\"><\/div><\/div><div class=\"play\"><\/div><div class=\"ctrl\"><div class=\"Lctrl\"><\/div><div class=\"Rctrl\"><\/div><\/div><\/div><noscript><a href=\"https:\/\/youtu.be\/QDmLL5CLles\" rel=\"nofollow\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/i.ytimg.com\/vi\/QDmLL5CLles\/0.jpg\" alt=\"YouTube video thumbnail\" width=\"420\" height=\"295\" \/><br \/>Watch this video on YouTube<\/a><\/noscript><\/div><\/div><div class=\"lL\" style=\"max-width:100%;width:420px;margin:5px;\"><\/div><figcaption><\/figcaption><\/figure>","protected":false},"excerpt":{"rendered":"<p>Introduction In this blog, I will share my experience completing the third assigment for course Ceng469 Computer Graphics 2. For this assigment, I implemented a particle system with attractors ,using OpenGL and compute shaders. Initializing Window and Buffers I first set up the OpenGL context and window\u00a0 using GLFW. I configured opengl to use version [&hellip;]<\/p>\n","protected":false},"author":9367,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_links_to":"","_links_to_target":""},"categories":[1],"tags":[],"class_list":["post-22","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blog.metu.edu.tr\/e252135\/wp-json\/wp\/v2\/posts\/22","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.metu.edu.tr\/e252135\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.metu.edu.tr\/e252135\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.metu.edu.tr\/e252135\/wp-json\/wp\/v2\/users\/9367"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.metu.edu.tr\/e252135\/wp-json\/wp\/v2\/comments?post=22"}],"version-history":[{"count":0,"href":"https:\/\/blog.metu.edu.tr\/e252135\/wp-json\/wp\/v2\/posts\/22\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.metu.edu.tr\/e252135\/wp-json\/wp\/v2\/media?parent=22"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.metu.edu.tr\/e252135\/wp-json\/wp\/v2\/categories?post=22"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.metu.edu.tr\/e252135\/wp-json\/wp\/v2\/tags?post=22"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}