From 867e174a4724d135b5820b5bc362b914fd115f28 Mon Sep 17 00:00:00 2001 From: BoyBaykiller Date: Mon, 25 Mar 2024 20:48:15 +0100 Subject: [PATCH] reduce particle size to 24bytes, small changes --- .../Newtonian-Particle-Simulator.csproj | 2 +- .../res/shaders/particles/fragment.glsl | 4 +- .../res/shaders/particles/vertex.glsl | 51 ++++++++++----- Newtonian-Particle-Simulator/src/Helper.cs | 13 +--- .../src/KeyboardManager.cs | 3 +- .../src/MainWindow.cs | 2 + Newtonian-Particle-Simulator/src/Particle.cs | 2 - .../src/Render/Objects/BufferObject.cs | 62 ++----------------- .../src/Render/ParticleSimulator.cs | 12 ++-- README.md | 2 +- 10 files changed, 59 insertions(+), 94 deletions(-) diff --git a/Newtonian-Particle-Simulator/Newtonian-Particle-Simulator.csproj b/Newtonian-Particle-Simulator/Newtonian-Particle-Simulator.csproj index e858937..3521143 100644 --- a/Newtonian-Particle-Simulator/Newtonian-Particle-Simulator.csproj +++ b/Newtonian-Particle-Simulator/Newtonian-Particle-Simulator.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/Newtonian-Particle-Simulator/res/shaders/particles/fragment.glsl b/Newtonian-Particle-Simulator/res/shaders/particles/fragment.glsl index a854e93..c529146 100644 --- a/Newtonian-Particle-Simulator/res/shaders/particles/fragment.glsl +++ b/Newtonian-Particle-Simulator/res/shaders/particles/fragment.glsl @@ -3,10 +3,10 @@ layout(location = 0) out vec4 FragColor; in InOutVars { - vec4 Color; + vec3 Color; } inData; void main() { - FragColor = vec4(inData.Color); + FragColor = vec4(inData.Color, 0.25); } \ No newline at end of file diff --git a/Newtonian-Particle-Simulator/res/shaders/particles/vertex.glsl b/Newtonian-Particle-Simulator/res/shaders/particles/vertex.glsl index 49837c4..3bfaa26 100644 --- a/Newtonian-Particle-Simulator/res/shaders/particles/vertex.glsl +++ b/Newtonian-Particle-Simulator/res/shaders/particles/vertex.glsl @@ -2,12 +2,15 @@ #define EPSILON 0.001 const float DRAG_COEF = log(0.998) * 176.0; // log(0.70303228048) +struct PackedVector3 +{ + float X, Y, Z; +}; + struct Particle { - vec3 Position; - float _pad0; - vec3 Velocity; - float _pad1; + PackedVector3 Position; + PackedVector3 Velocity; }; layout(std430, binding = 0) restrict buffer ParticlesSSBO @@ -21,16 +24,22 @@ layout(location = 2) uniform float isActive; layout(location = 3) uniform float isRunning; layout(location = 4) uniform mat4 projViewMatrix; +vec3 PackedVec3ToVec3(PackedVector3 vec); +PackedVector3 Vec3ToPackedVec3(vec3 vec); + out InOutVars { - vec4 Color; + vec3 Color; } outData; void main() { - Particle particle = particleSSBO.Particles[gl_VertexID]; + PackedVector3 packedPosition = particleSSBO.Particles[gl_VertexID].Position; + PackedVector3 packedVelocity = particleSSBO.Particles[gl_VertexID].Velocity; + vec3 position = PackedVec3ToVec3(packedPosition); + vec3 velocity = PackedVec3ToVec3(packedVelocity); - const vec3 toMass = pointOfMass - particle.Position; + const vec3 toMass = pointOfMass - position; /// Implementation of Newton's law of gravity const float m1 = 1.0; // constant particle mass @@ -44,15 +53,27 @@ void main() // acceleration = force / m. const vec3 acceleration = (force * isRunning * isActive) / m1; - particle.Velocity *= mix(1.0, exp(DRAG_COEF * dT), isRunning); // https://stackoverflow.com/questions/61812575/which-formula-to-use-for-drag-simulation-each-frame - particle.Position += (dT * particle.Velocity + 0.5 * acceleration * dT * dT) * isRunning; // Euler integration - particle.Velocity += acceleration * dT; - particleSSBO.Particles[gl_VertexID] = particle; + velocity *= mix(1.0, exp(DRAG_COEF * dT), isRunning); // https://stackoverflow.com/questions/61812575/which-formula-to-use-for-drag-simulation-each-frame + position += (dT * velocity + 0.5 * acceleration * dT * dT) * isRunning; // Euler integration + velocity += acceleration * dT; + + particleSSBO.Particles[gl_VertexID].Position = Vec3ToPackedVec3(position); + particleSSBO.Particles[gl_VertexID].Velocity = Vec3ToPackedVec3(velocity); - const float red = 0.0045 * dot(particle.Velocity, particle.Velocity); - const float green = clamp(0.08 * max(particle.Velocity.x, max(particle.Velocity.y, particle.Velocity.z)), 0.2, 0.5); + const float red = 0.0045 * dot(velocity, velocity); + const float green = clamp(0.08 * max(velocity.x, max(velocity.y, velocity.z)), 0.2, 0.5); const float blue = 0.7 - red; - outData.Color = vec4(red, green, blue, 0.25); - gl_Position = projViewMatrix * vec4(particle.Position, 1.0); + outData.Color = vec3(red, green, blue); + gl_Position = projViewMatrix * vec4(position, 1.0); +} + +vec3 PackedVec3ToVec3(PackedVector3 vec) +{ + return vec3(vec.X, vec.Y, vec.Z); +} + +PackedVector3 Vec3ToPackedVec3(vec3 vec) +{ + return PackedVector3(vec.x, vec.y, vec.z); } \ No newline at end of file diff --git a/Newtonian-Particle-Simulator/src/Helper.cs b/Newtonian-Particle-Simulator/src/Helper.cs index 89926a4..6a1b4bd 100644 --- a/Newtonian-Particle-Simulator/src/Helper.cs +++ b/Newtonian-Particle-Simulator/src/Helper.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Collections.Generic; using OpenTK; using OpenTK.Graphics.OpenGL4; @@ -10,13 +9,6 @@ static class Helper { // API version as a decimal. For example major 4 and minor 6 => 4.6 public static readonly double APIVersion = Convert.ToDouble($"{GL.GetInteger(GetPName.MajorVersion)}{GL.GetInteger(GetPName.MinorVersion)}") / 10.0; - public static string GetPathContent(this string path) - { - if (!File.Exists(path)) - throw new FileNotFoundException($"{path} does not exist"); - - return File.ReadAllText(path); - } public static Vector3 RandomUnitVector(Random rng) { @@ -29,17 +21,18 @@ public static Vector3 RandomUnitVector(Random rng) return new Vector3(x, y, z); } + private static readonly HashSet glExtensions = new HashSet(GetExtensions()); private static HashSet GetExtensions() { HashSet extensions = new HashSet(GL.GetInteger(GetPName.NumExtensions)); for (int i = 0; i < GL.GetInteger(GetPName.NumExtensions); i++) + { extensions.Add(GL.GetString(StringNameIndexed.Extensions, i)); + } return extensions; } - private static readonly HashSet glExtensions = new HashSet(GetExtensions()); - public static bool IsExtensionsAvailable(string extension) { return glExtensions.Contains(extension); diff --git a/Newtonian-Particle-Simulator/src/KeyboardManager.cs b/Newtonian-Particle-Simulator/src/KeyboardManager.cs index e946230..a90e75c 100644 --- a/Newtonian-Particle-Simulator/src/KeyboardManager.cs +++ b/Newtonian-Particle-Simulator/src/KeyboardManager.cs @@ -1,5 +1,4 @@ -using OpenTK; -using OpenTK.Input; +using OpenTK.Input; namespace Newtonian_Particle_Simulator { diff --git a/Newtonian-Particle-Simulator/src/MainWindow.cs b/Newtonian-Particle-Simulator/src/MainWindow.cs index 0117cef..c4cd4cf 100644 --- a/Newtonian-Particle-Simulator/src/MainWindow.cs +++ b/Newtonian-Particle-Simulator/src/MainWindow.cs @@ -106,6 +106,8 @@ protected override void OnLoad(EventArgs e) } particleSimulator = new ParticleSimulator(particles); + GC.Collect(); + base.OnLoad(e); } diff --git a/Newtonian-Particle-Simulator/src/Particle.cs b/Newtonian-Particle-Simulator/src/Particle.cs index c9693fd..a836d97 100644 --- a/Newtonian-Particle-Simulator/src/Particle.cs +++ b/Newtonian-Particle-Simulator/src/Particle.cs @@ -5,8 +5,6 @@ namespace Newtonian_Particle_Simulator struct Particle { public Vector3 Position; - private readonly int _pad0; public Vector3 Velocity; - private readonly int _pad1; } } diff --git a/Newtonian-Particle-Simulator/src/Render/Objects/BufferObject.cs b/Newtonian-Particle-Simulator/src/Render/Objects/BufferObject.cs index a0feffa..264bcb2 100644 --- a/Newtonian-Particle-Simulator/src/Render/Objects/BufferObject.cs +++ b/Newtonian-Particle-Simulator/src/Render/Objects/BufferObject.cs @@ -1,5 +1,4 @@ using System; -using System.Runtime.InteropServices; using OpenTK.Graphics.OpenGL4; namespace Newtonian_Particle_Simulator.Render.Objects @@ -25,71 +24,20 @@ public void BindBase(BufferRangeTarget bufferRangeTarget, int index) GL.BindBufferBase(bufferRangeTarget, index, ID); } - public void Bind(BufferTarget bufferTarget) + public unsafe void ImmutableAllocate(nint size, in T data, BufferStorageFlags bufferStorageFlags) where T : unmanaged { - GL.BindBuffer(bufferTarget, ID); + fixed (void* ptr = &data) + { + ImmutableAllocate(size, (IntPtr)ptr, bufferStorageFlags); + } } - public void SubData(nint offset, nint size, T data) where T : struct - { - GL.NamedBufferSubData(ID, offset, size, ref data); - } - public void SubData(nint offset, nint size, T[] data) where T : struct - { - GL.NamedBufferSubData(ID, offset, size, data); - } - public void SubData(nint offset, nint size, IntPtr data) - { - GL.NamedBufferSubData(ID, offset, size, data); - } - - public void MutableAllocate(nint size, T data, BufferUsageHint bufferUsageHint) where T : struct - { - GL.NamedBufferData(ID, size, ref data, bufferUsageHint); - Size = size; - } - public void MutableAllocate(nint size, T[] data, BufferUsageHint bufferUsageHint) where T : struct - { - GL.NamedBufferData(ID, size, data, bufferUsageHint); - Size = size; - } - public void MutableAllocate(nint size, IntPtr data, BufferUsageHint bufferUsageHint) - { - GL.NamedBufferData(ID, size, data, bufferUsageHint); - Size = size; - } - - public void ImmutableAllocate(nint size, T data, BufferStorageFlags bufferStorageFlags) where T : struct - { - GL.NamedBufferStorage(ID, size, ref data, bufferStorageFlags); - Size = size; - } - public void ImmutableAllocate(nint size, T[] data, BufferStorageFlags bufferStorageFlags) where T : struct - { - GL.NamedBufferStorage(ID, size, data, bufferStorageFlags); - Size = size; - } public void ImmutableAllocate(nint size, IntPtr data, BufferStorageFlags bufferStorageFlags) { GL.NamedBufferStorage(ID, size, data, bufferStorageFlags); Size = size; } - public void GetSubData(nint offset, nint size, out T data) where T : struct - { - data = new T(); - GL.GetNamedBufferSubData(ID, offset, size, ref data); - } - public void GetSubData(nint offset, nint size, T[] data) where T : struct - { - GL.GetNamedBufferSubData(ID, offset, size, data); - } - public void GetSubData(nint offset, nint size, out IntPtr data) - { - data = Marshal.AllocHGlobal(size); - GL.GetNamedBufferSubData(ID, offset, size, data); - } - public void Dispose() { GL.DeleteBuffer(ID); diff --git a/Newtonian-Particle-Simulator/src/Render/ParticleSimulator.cs b/Newtonian-Particle-Simulator/src/Render/ParticleSimulator.cs index 82f4349..3f34ec2 100644 --- a/Newtonian-Particle-Simulator/src/Render/ParticleSimulator.cs +++ b/Newtonian-Particle-Simulator/src/Render/ParticleSimulator.cs @@ -1,7 +1,9 @@ -using Newtonian_Particle_Simulator.Render.Objects; +using System; +using System.IO; using OpenTK; using OpenTK.Input; using OpenTK.Graphics.OpenGL4; +using Newtonian_Particle_Simulator.Render.Objects; namespace Newtonian_Particle_Simulator.Render { @@ -10,14 +12,16 @@ class ParticleSimulator public readonly int NumParticles; private readonly BufferObject particleBuffer; private readonly ShaderProgram shaderProgram; - public unsafe ParticleSimulator(Particle[] particles) + public unsafe ParticleSimulator(ReadOnlySpan particles) { NumParticles = particles.Length; - shaderProgram = new ShaderProgram(new Shader(ShaderType.VertexShader, "res/shaders/particles/vertex.glsl".GetPathContent()), new Shader(ShaderType.FragmentShader, "res/shaders/particles/fragment.glsl".GetPathContent())); + shaderProgram = new ShaderProgram( + new Shader(ShaderType.VertexShader, File.ReadAllText("res/shaders/particles/vertex.glsl")), + new Shader(ShaderType.FragmentShader, File.ReadAllText("res/shaders/particles/fragment.glsl"))); particleBuffer = new BufferObject(BufferRangeTarget.ShaderStorageBuffer, 0); - particleBuffer.ImmutableAllocate(sizeof(Particle) * (nint)NumParticles, particles, BufferStorageFlags.DynamicStorageBit); + particleBuffer.ImmutableAllocate(sizeof(Particle) * (nint)NumParticles, particles[0], BufferStorageFlags.None); IsRunning = true; } diff --git a/README.md b/README.md index 878a021..b05124d 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ![gif](Video.gif?raw=true) ![img1](sample.png?raw=true) -This is a very simplistic particle engine. +This is a very simple interactive particle simulator. The whole pipeline consists out of a [single shader program](Newtonian-Particle-Simulator/res/shaders/particles). The vertex shader computes and renders particles at the same time. The fragment shader just outputs the computed colors with blending enabled. The actual particles are stored in a Shader Storage Buffer which is a really useful arbitrary read/write interface of global GPU memory.