diff --git a/.vscode/settings.json b/.vscode/settings.json index 00276e1..096abd1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -92,11 +92,11 @@ "ranges": "cpp", "cfenv": "cpp", "regex": "cpp", - "valarray": "cpp" + "valarray": "cpp", + "*.inc": "cpp" }, "files.exclude": { "build": true, - "OpenGL/vendor": true, }, "C_Cpp.default.cppStandard": "c++20" } diff --git a/OpenGL/CMakeLists.txt b/OpenGL/CMakeLists.txt index 308aeb1..a281f9b 100644 --- a/OpenGL/CMakeLists.txt +++ b/OpenGL/CMakeLists.txt @@ -32,14 +32,15 @@ endif() file(GLOB_RECURSE res_files "res/*") # Add WebGPU -# Also adds Add GLFW +# Also adds GLFW FetchContent_Declare( webgpu GIT_REPOSITORY https://github.com/anchpop/WebGPU-distribution.git - GIT_TAG 257f230bdad880339eef961b7ff5974a04e30dc5 + GIT_TAG 2f6b3ec866a0c579782b759ef79649dae57ffc9b ) FetchContent_MakeAvailable(webgpu) + add_subdirectory(${VENDOR_DIR}/glfw3webgpu) # Add GLM diff --git a/OpenGL/res/shaders/particles.wgsl b/OpenGL/res/shaders/particles.wgsl index 65890a6..303ce96 100644 --- a/OpenGL/res/shaders/particles.wgsl +++ b/OpenGL/res/shaders/particles.wgsl @@ -2,6 +2,8 @@ struct Particle { @location(1) position: vec2, // World space position @location(2) velocity: vec2, @location(3) color: vec4, + @location(4) age: f32, + @location(5) lifetime: f32, }; struct VertexInput { @@ -34,7 +36,10 @@ fn vertex_main(vertex: VertexInput, particle: Particle) -> VertexOutput { // Transform from world space to clip space using world_to_clip matrix output.Position = vertexUniforms.world_to_clip * vec4(world_pos, 0.0, 1.0); - output.color = particle.color; + // Calculate opacity based on remaining lifetime + var finalColor = particle.color; + finalColor.a *= 1.0 - clamp(particle.age / particle.lifetime, 0.0, 1.0); + output.color = finalColor; return output; } diff --git a/OpenGL/res/shaders/particlesCompute.wgsl b/OpenGL/res/shaders/particlesCompute.wgsl index e44742d..52c1e74 100644 --- a/OpenGL/res/shaders/particlesCompute.wgsl +++ b/OpenGL/res/shaders/particlesCompute.wgsl @@ -1,12 +1,13 @@ struct WorldInfo { deltaTime : f32, - mousePos : vec2, }; struct Particle { position : vec2, // World space position velocity : vec2, color : vec4, + age : f32, + lifetime : f32, }; struct Segment { @@ -120,18 +121,7 @@ fn compute_main(@builtin(global_invocation_id) id : vec3) { let particle = &particleBuffer[index]; - // Calculate direction to mouse - let toMouse = world.mousePos - particle.position; - let distanceSquared = max(dot2D(toMouse, toMouse), MIN_DISTANCE_SQUARED); - - // Calculate gravitational force (F = G * m1 * m2 / r^2) - // Since mass is uniform we can simplify - let force = normalize(toMouse) * G / distanceSquared; - - // Update velocity (a = F/m, simplified since mass = 1) - particleBuffer[index].velocity += force * world.deltaTime; - - // New position based on velocity + // New position based on current velocity let newPosition = particle.position + particleBuffer[index].velocity * world.deltaTime; // Check for collision with walls @@ -139,7 +129,7 @@ fn compute_main(@builtin(global_invocation_id) id : vec3) { if (intersection.hit) { // Bounce coefficient (1.0 = perfect bounce, 0.0 = full stop) - let bounce = 0.8; + let bounce = 0.2; // Calculate reflection vector let v = particleBuffer[index].velocity; @@ -156,6 +146,9 @@ fn compute_main(@builtin(global_invocation_id) id : vec3) { // No collision, update particle position normally particleBuffer[index].position = newPosition; } + + // Update age + particleBuffer[index].age += world.deltaTime; } fn findWallCollision(particlePosition : vec2, newPosition : vec2) -> SegmentIntersection { diff --git a/OpenGL/src/World.cpp b/OpenGL/src/World.cpp index 436b68b..2dcdd32 100644 --- a/OpenGL/src/World.cpp +++ b/OpenGL/src/World.cpp @@ -54,7 +54,8 @@ void World::LoadMap(const std::filesystem::path& map_path) { if (c == 'p') { // player gameobjects.push_back(std::make_shared("Coolbox", (float)x, (float)y)); gameobjects.push_back(std::make_shared("Floor", (float)x, (float)y)); - gameobjects.push_back(std::make_shared("Floor", DrawPriority::Character, glm::vec2(x, y))); + gameobjects.push_back( + std::make_shared("Floor", DrawPriority::Character, glm::vec2(x, y), 1000, 8.0f, 8.0f)); } if (c == 'f') { // floor gameobjects.push_back(std::make_shared("Floor", (float)x, (float)y)); diff --git a/OpenGL/src/game_objects/Particles.cpp b/OpenGL/src/game_objects/Particles.cpp index 419ccbd..bd8bbba 100644 --- a/OpenGL/src/game_objects/Particles.cpp +++ b/OpenGL/src/game_objects/Particles.cpp @@ -4,39 +4,40 @@ #include -Particles::Particles(const std::string& name, DrawPriority drawPriority, glm::vec2 position) +Particles::Particles(const std::string& name, DrawPriority drawPriority, glm::vec2 position, size_t particleCount, + float initialSpeed, float lifetime) : GameObject(name, drawPriority, position) - , particles(std::vector{ - Particle{position + glm::vec2(0.0f, 0.0f), glm::vec2(1.0f, 0.0f), glm::vec4(1.0f, 1.0f, 0.0f, 1.0f)}, - Particle{position + glm::vec2(0.0f, 0.0f), glm::vec2(0.0f, 1.0f), glm::vec4(0.0f, 1.0f, 0.0f, 1.0f)} -}), - particleBuffer( - std::make_shared>(particles, - wgpu::bothBufferUsages(wgpu::BufferUsage::Vertex, wgpu::BufferUsage::CopyDst, - wgpu::BufferUsage::CopySrc, wgpu::BufferUsage::Storage), - "Particles")), - pointBuffer(Buffer::create( - { - ParticleVertex{glm::vec2(-0.5f, -0.5f) * (1.0f / 16.0f)}, // 0 - ParticleVertex{glm::vec2(0.5f, -0.5f) * (1.0f / 16.0f)}, // 1 - ParticleVertex{glm::vec2(0.5f, 0.5f) * (1.0f / 16.0f)}, // 2 - ParticleVertex{glm::vec2(-0.5f, 0.5f) * (1.0f / 16.0f)}, // 3 - }, - wgpu::bothBufferUsages(wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Vertex))), - indexBuffer(IndexBuffer::create( - { - 0, 1, 2, // Triangle #0 connects points #0, #1 and #2 - 0, 2, 3 // Triangle #1 connects points #0, #2 and #3 - }, - wgpu::bothBufferUsages(wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Index))), - segmentBuffer(Buffer( - {}, wgpu::bothBufferUsages(wgpu::BufferUsage::CopySrc, wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Storage), - "segments")), - bvhBuffer(Buffer( - {}, wgpu::bothBufferUsages(wgpu::BufferUsage::CopySrc, wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Storage), - "bvh")), - vertexUniform(UniformBufferView::create(ParticleVertexUniform{VP()})), - worldInfo(UniformBufferView::create(ParticleWorldInfo(0.0f, glm::vec2(0.0f)))) {} + , particles(std::vector()) + , particleBuffer(std::make_shared>( + particles, + wgpu::bothBufferUsages(wgpu::BufferUsage::Vertex, wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::CopySrc, + wgpu::BufferUsage::Storage), + "Particles")) + , pointBuffer(Buffer::create( + { + ParticleVertex{glm::vec2(-0.5f, -0.5f) * (1.0f / 16.0f)}, // 0 + ParticleVertex{glm::vec2(0.5f, -0.5f) * (1.0f / 16.0f)}, // 1 + ParticleVertex{glm::vec2(0.5f, 0.5f) * (1.0f / 16.0f)}, // 2 + ParticleVertex{glm::vec2(-0.5f, 0.5f) * (1.0f / 16.0f)}, // 3 + }, + wgpu::bothBufferUsages(wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Vertex))) + , indexBuffer(IndexBuffer::create( + { + 0, 1, 2, // Triangle #0 connects points #0, #1 and #2 + 0, 2, 3 // Triangle #1 connects points #0, #2 and #3 + }, + wgpu::bothBufferUsages(wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Index))) + , segmentBuffer(Buffer( + {}, wgpu::bothBufferUsages(wgpu::BufferUsage::CopySrc, wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Storage), + "segments")) + , bvhBuffer(Buffer( + {}, wgpu::bothBufferUsages(wgpu::BufferUsage::CopySrc, wgpu::BufferUsage::CopyDst, wgpu::BufferUsage::Storage), + "bvh")) + , vertexUniform(UniformBufferView::create(ParticleVertexUniform{VP()})) + , worldInfo(UniformBufferView::create(ParticleWorldInfo(0.01f))) + , particleCount(particleCount) + , initialSpeed(initialSpeed) + , lifetime(lifetime) {} void Particles::render(Renderer& renderer, RenderPass& renderPass) { if (particles.empty()) @@ -58,7 +59,7 @@ void Particles::pre_compute() { } void Particles::compute(Renderer& renderer, ComputePass& computePass) { - worldInfo.Update(ParticleWorldInfo(Input::deltaTime, Renderer::MousePos())); + worldInfo.Update(ParticleWorldInfo(Input::deltaTime)); BindGroup bindGroup = ParticleComputeLayout::ToBindGroup(renderer.device, std::forward_as_tuple(*particleBuffer, 0), worldInfo, std::forward_as_tuple(segmentBuffer, 0), std::forward_as_tuple(bvhBuffer, 0)); @@ -66,21 +67,38 @@ void Particles::compute(Renderer& renderer, ComputePass& computePass) { } void Particles::update() { - // add a particle if the p key is pressed - if (Input::keys_pressed[GLFW_KEY_P]) { + // Initialize particles if we haven't yet + if (particles.empty()) { std::random_device rd; std::mt19937 gen(rd()); // Mersenne Twister generator - // Define distribution from 0 to 1 + // Define distributions + std::uniform_real_distribution<> pos_dist(-1.0, 1.0); std::uniform_real_distribution<> vel_dist(-0.5, 0.5); std::uniform_real_distribution<> color_dist(0.0, 1.0); + std::uniform_real_distribution<> lifetime_dist(lifetime * 0.7f, lifetime * 1.3f); // 30% variation - auto random_vel = glm::vec2(vel_dist(gen), vel_dist(gen)) * 2.0f; - addParticle(position, random_vel, glm::vec4(color_dist(gen), color_dist(gen), color_dist(gen), 1.0f)); + // Reserve space for better performance + particles.reserve(particleCount); + particleViews.reserve(particleCount); + + // Create all particles + for (size_t i = 0; i < particleCount; ++i) { + // Random position offset from center + glm::vec2 pos_offset(pos_dist(gen), pos_dist(gen)); + glm::vec2 random_vel(vel_dist(gen), vel_dist(gen)); + random_vel *= initialSpeed; // Scale velocity by parameter + + float random_lifetime = lifetime_dist(gen); + addParticle(position + pos_offset, random_vel, + glm::vec4(color_dist(gen), color_dist(gen), color_dist(gen), 1.0f), 0.0f, random_lifetime); + } } } -void Particles::addParticle(const glm::vec2& pos, const glm::vec2& vel, const glm::vec4& color) { - particles.push_back({pos, vel, color}); +void Particles::addParticle(const glm::vec2& pos, const glm::vec2& vel, const glm::vec4& color, float age, + float lifetime) { + + particles.emplace_back(pos, vel, color, age, lifetime); particleViews.push_back(particleBuffer->Add(particles.back())); } diff --git a/OpenGL/src/game_objects/Particles.h b/OpenGL/src/game_objects/Particles.h index 9a8e708..a719efe 100644 --- a/OpenGL/src/game_objects/Particles.h +++ b/OpenGL/src/game_objects/Particles.h @@ -7,12 +7,14 @@ class Particles : public GameObject { public: - Particles(const std::string& name, DrawPriority drawPriority, glm::vec2 position); + Particles(const std::string& name, DrawPriority drawPriority, glm::vec2 position, size_t particleCount, + float initialSpeed = 2.0f, float lifetime = 2.0f); virtual void render(Renderer& renderer, RenderPass& renderPass) override; virtual void update() override; virtual void pre_compute() override; virtual void compute(Renderer& renderer, ComputePass& computePass) override; - void addParticle(const glm::vec2& pos, const glm::vec2& vel, const glm::vec4& color); + void addParticle(const glm::vec2& pos, const glm::vec2& vel, const glm::vec4& color, + float age, float lifetime); private: std::vector particles; @@ -24,6 +26,9 @@ class Particles : public GameObject { Buffer bvhBuffer; UniformBufferView vertexUniform; UniformBufferView worldInfo; + size_t particleCount; + float initialSpeed; + float lifetime; private: protected: diff --git a/OpenGL/src/rendering/DataFormats.h b/OpenGL/src/rendering/DataFormats.h index 5a98218..bdf5e3b 100644 --- a/OpenGL/src/rendering/DataFormats.h +++ b/OpenGL/src/rendering/DataFormats.h @@ -152,6 +152,16 @@ struct Particle { glm::vec2 position; glm::vec2 velocity; glm::vec4 color; + float age; + float lifetime; + float _pad0[2]; + + Particle(glm::vec2 position, glm::vec2 velocity, glm::vec4 color, float age, float lifetime) + : position(position) + , velocity(velocity) + , color(color) + , age(age) + , lifetime(lifetime) {} typedef InstanceBufferLayout Layout; }; @@ -179,13 +189,10 @@ struct hash { } // namespace std struct ParticleWorldInfo { - float deltaTime; // at byte offset 0 - float _pad0; - glm::vec2 mousePos; // at byte offset 8 + float deltaTime; // at byte offset 0 - ParticleWorldInfo(float deltaTime, glm::vec2 mousePos) - : deltaTime(deltaTime) - , mousePos(mousePos) {} + ParticleWorldInfo(float deltaTime) + : deltaTime(deltaTime) {} }; using ParticleLayout = BindGroupLayout< diff --git a/OpenGL/src/rendering/Renderer.cpp b/OpenGL/src/rendering/Renderer.cpp index 465eb41..2a18866 100644 --- a/OpenGL/src/rendering/Renderer.cpp +++ b/OpenGL/src/rendering/Renderer.cpp @@ -12,11 +12,10 @@ Renderer::Renderer() , line( RenderPipeline, VertexBufferLayouts>>("line.wgsl")) , fog(RenderPipeline, VertexBufferLayouts>>("fog.wgsl")) - , particles( - RenderPipeline< - BindGroupLayouts, - VertexBufferLayouts, InstanceBufferLayout>>( - "particles.wgsl")) + , particles(RenderPipeline, + VertexBufferLayouts, + InstanceBufferLayout>>( + "particles.wgsl")) , particlesCompute(ComputePipeline>("particlesCompute.wgsl")) , device(Application::get().getDevice()) , linePoints( diff --git a/OpenGL/src/rendering/Renderer.h b/OpenGL/src/rendering/Renderer.h index 970b808..11f1fe6 100644 --- a/OpenGL/src/rendering/Renderer.h +++ b/OpenGL/src/rendering/Renderer.h @@ -31,9 +31,9 @@ class Renderer { squareObject; RenderPipeline, VertexBufferLayouts>> line; RenderPipeline, VertexBufferLayouts>> fog; - RenderPipeline< - BindGroupLayouts, - VertexBufferLayouts, InstanceBufferLayout>> + RenderPipeline, + VertexBufferLayouts, + InstanceBufferLayout>> particles; ComputePipeline> particlesCompute;