Skip to content

Commit

Permalink
Merge pull request #150 from edunad/features/particle-engine
Browse files Browse the repository at this point in the history
[FEATURE] GPU Particle System
  • Loading branch information
edunad authored Apr 8, 2024
2 parents 21851da + 685f199 commit 7c88e93
Show file tree
Hide file tree
Showing 86 changed files with 1,387 additions and 457 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@
- Packet networking support
- I18N (internationalization) support
- GPU picking
- GPU particle engine

# TODO LIST

- Particle engine
- Add animation blending
- Add lights shadow maps

Expand Down Expand Up @@ -190,9 +190,9 @@

# SAMPLES

| 001-stencil<br/><a href='/samples/001-stencil'><img src="https://i.rawr.dev/sample1-min-2.gif" width="240" /></a> | 002-generated-models<br/><a href='/samples/002-generated-models'><img src="https://i.rawr.dev/sample2-min-3.gif" width="240" /></a> | 003-light<br/><a href='/samples/003-light'><img src="https://i.rawr.dev/sample3-min-3.gif" width="240" /></a> |
| :-------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------: |
| 004-instancing<br/><a href='/samples/004-instancing'><img src="https://i.rawr.dev/sample4-min.gif" width="240" /></a> | 005-post-process<br/><a href='/samples/005-post-process'><img src="https://i.rawr.dev/sample5-min.gif" width="240" /></a> | 006-decals<br/><a href='/samples/006-decals'><img src="https://i.rawr.dev/sample6-min-2.gif" width="240" /></a> |
| ~~007-particle-system~~ (TODO) | 008-ui<br/><a href='/samples/008-ui'><img src="https://i.rawr.dev/sample8-min.gif" width="240" /></a> | 009-assimp<br/><a href='/samples/009-assimp'><img src="https://i.rawr.dev/sample9-min.gif" width="240" /></a> |
| 010-bass-audio<br/><a href='/samples/010-bass-audio'><img src="https://i.rawr.dev/bylavGsjpB.png" width="240" /></a> | 011-physics-3D<br/><a href='/samples/011-physics-3D'><img src="https://i.rawr.dev/sample11-min.gif" width="240" /></a> | 012-physics-2D<br/><a href='/samples/012-physics-2D'><img src="https://i.rawr.dev/sample12-min.gif" width="240" /></a> |
| 013-webm<br/><a href='/samples/013-webm'><img src="https://i.rawr.dev/sample13-min.gif" width="240" /></a> | 014-scripting<br/><a href='/samples/014-scripting'><img src="https://i.rawr.dev/sample14-min.gif" width="240" /></a> | 015-gpu-picking<br/><a href='/samples/015-gpu-picking'><img src="https://i.rawr.dev/sample15-min.gif" width="240" /></a> |
| 001-stencil<br/><a href='/samples/001-stencil'><img src="https://i.rawr.dev/sample1-min-2.gif" width="240" /></a> | 002-generated-models<br/><a href='/samples/002-generated-models'><img src="https://i.rawr.dev/sample2-min-3.gif" width="240" /></a> | 003-light<br/><a href='/samples/003-light'><img src="https://i.rawr.dev/sample3-min-3.gif" width="240" /></a> |
| :-----------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------: |
| 004-instancing<br/><a href='/samples/004-instancing'><img src="https://i.rawr.dev/sample4-min.gif" width="240" /></a> | 005-post-process<br/><a href='/samples/005-post-process'><img src="https://i.rawr.dev/sample5-min.gif" width="240" /></a> | 006-decals<br/><a href='/samples/006-decals'><img src="https://i.rawr.dev/sample6-min-2.gif" width="240" /></a> |
| 007-particle-system<br/><a href='/samples/007-particle-system'><img src="https://i.rawr.dev/sample7-min.gif" width="240" /></a> | 008-ui<br/><a href='/samples/008-ui'><img src="https://i.rawr.dev/sample8-min.gif" width="240" /></a> | 009-assimp<br/><a href='/samples/009-assimp'><img src="https://i.rawr.dev/sample9-min.gif" width="240" /></a> |
| 010-bass-audio<br/><a href='/samples/010-bass-audio'><img src="https://i.rawr.dev/bylavGsjpB.png" width="240" /></a> | 011-physics-3D<br/><a href='/samples/011-physics-3D'><img src="https://i.rawr.dev/sample11-min.gif" width="240" /></a> | 012-physics-2D<br/><a href='/samples/012-physics-2D'><img src="https://i.rawr.dev/sample12-min.gif" width="240" /></a> |
| 013-webm<br/><a href='/samples/013-webm'><img src="https://i.rawr.dev/sample13-min.gif" width="240" /></a> | 014-scripting<br/><a href='/samples/014-scripting'><img src="https://i.rawr.dev/sample14-min.gif" width="240" /></a> | 015-gpu-picking<br/><a href='/samples/015-gpu-picking'><img src="https://i.rawr.dev/sample15-min.gif" width="240" /></a> |
Binary file added assets/raw/particles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion rawrbox.assimp/include/rawrbox/assimp/utils/model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace rawrbox {

class AssimpUtils {
public:
template <typename M = MaterialUnlit>
template <typename M = rawrbox::MaterialUnlit>
requires(std::derived_from<M, rawrbox::MaterialBase>)
static rawrbox::Mesh<typename M::vertexBufferType> extractMesh(const rawrbox::AssimpImporter& model, size_t indx) {
const auto& meshes = model.meshes;
Expand Down
2 changes: 1 addition & 1 deletion rawrbox.bass/include/rawrbox/bass/manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ namespace rawrbox {
static void setMasterVolume(float volume, bool set = true);
static void setMuteOnUnfocus(bool set);
static void setHasFocus(bool hasFocus);
static void setListenerLocation(const rawrbox::Vector3f& location, const rawrbox::Vector3f& front = {0, 0, 1}, const rawrbox::Vector3f& top = {0, 1, 0});
static void setListenerLocation(const rawrbox::Vector3f& location, const rawrbox::Vector3f& front = {0, 0, 1}, const rawrbox::Vector3f& up = {0, 1, 0});
// -----
};
} // namespace rawrbox
1 change: 1 addition & 0 deletions rawrbox.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"workbench.editor.wrapTabs": true,
"files.insertFinalNewline": true,
"files.associations": {
"*.gsh": "hlsl",
"*.csh": "hlsl",
"xstring": "cpp",
"functional": "cpp",
Expand Down
4 changes: 3 additions & 1 deletion rawrbox.render/assets/shaders/include/camera.fxh
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@

struct CameraStruct {
float4x4 view;
float4x4 viewInv;

float4x4 world;
float4x4 worldViewProj;

float4 pos;
float3 pos;
float deltaTime;
};

// Data that never / very rarelly changes
Expand Down
18 changes: 18 additions & 0 deletions rawrbox.render/assets/shaders/include/hash.fxh
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,22 @@

return v;
}

// Generate random float2 in range [0, 1)
float2 generateRandom(uint2 v) {
uint2 p = pcg2d(v);
return float2(p) / float(0xffffffffu);
}

// Generate random float3 in range [0, 1)
float3 generateRandom(uint3 v) {
uint3 p = pcg3d(v);
return float3(p) / float(0xffffffffu);
}

// Generate random float4 in range [0, 1)
float4 generateRandom(uint4 v) {
uint4 p = pcg4d(v);
return float4(p) / float(0xffffffffu);
}
#endif
25 changes: 25 additions & 0 deletions rawrbox.render/assets/shaders/include/math.fxh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
static const float FLT_MIN = 1.175494351e-38F;
static const float FLT_MAX = 3.402823466e+38F;

static const float GRAVITY = -9.81F;

static const float PI = 3.14159265358979323846;
static const float INV_PI = 0.31830988618379067154;
static const float INV_2PI = 0.15915494309189533577;
Expand All @@ -30,5 +32,28 @@
float xx = x * x;
return xx * xx * x;
}

// UTILS -----------------
uint Flatten2D(uint2 index, uint dimensionsX) {
return index.x + index.y * dimensionsX;
}

uint Flatten3D(uint3 index, uint2 dimensionsXY) {
return index.x + index.y * dimensionsXY.x + index.z * dimensionsXY.x * dimensionsXY.y;
}

uint2 UnFlatten2D(uint index, uint dimensionsX) {
return uint2(index % dimensionsX, index / dimensionsX);
}

uint3 UnFlatten3D(uint index, uint2 dimensionsXY) {
uint3 outIndex;
outIndex.z = index / (dimensionsXY.x * dimensionsXY.y);
index -= (outIndex.z * dimensionsXY.x * dimensionsXY.y);
outIndex.y = index / dimensionsXY.x;
outIndex.x = index % dimensionsXY.x;
return outIndex;
}
// -----------------------
#endif

Original file line number Diff line number Diff line change
Expand Up @@ -8,34 +8,30 @@
};

// Snap vertex to achieve PSX look
#ifdef TRANSFORM_PSX
float4 PSXTransform(float4 vertex, float2 resolution) {
float4 snappedPos = vertex;
snappedPos.xyz = vertex.xyz / vertex.w; // convert to normalised device coordinates (NDC)
snappedPos.xy = floor(resolution * snappedPos.xy) / resolution; // snap the vertex to the lower-resolution grid
snappedPos.xyz *= vertex.w; // convert back to projection-space

return snappedPos;
}
#endif
// ----------------------
float4 PSXTransform(float4 vertex, float2 resolution) {
float4 snappedPos = vertex;
snappedPos.xyz = vertex.xyz / vertex.w; // convert to normalised device coordinates (NDC)
snappedPos.xy = floor(resolution * snappedPos.xy) / resolution; // snap the vertex to the lower-resolution grid
snappedPos.xyz *= vertex.w; // convert back to projection-space

#ifdef TRANSFORM_BILLBOARD
float4 billboardTransform(float4 vertex, int billboard) {
float3 right = float3(1, 0, 0);
float3 up = float3(0, 1, 0);
return snappedPos;
}
// ----------------------

if ((billboard & 2) != 0) { // X
right = float3(Camera.view[0][0], Camera.view[1][0], Camera.view[2][0]);
}
float4 billboardTransform(float4 vertex, uint billboard) {
float3 right = float3(1, 0, 0);
float3 up = float3(0, 1, 0);

if ((billboard & 4) != 0) {// Y
up = float3(Camera.view[0][1], Camera.view[1][1], Camera.view[2][1]);
}
if ((billboard & 2) != 0) { // X
right = float3(Camera.view[0][0], Camera.view[1][0], Camera.view[2][0]);
}

return float4((right * vertex.x) + (up * vertex.y), 1.);
if ((billboard & 4) != 0) {// Y
up = float3(Camera.view[0][1], Camera.view[1][1], Camera.view[2][1]);
}
#endif

return float4((right * vertex.x) + (up * vertex.y), 1.);
}

#ifdef SKINNED
#ifdef TRANSFORM_BONES
Expand Down
2 changes: 1 addition & 1 deletion rawrbox.render/assets/shaders/materials/model/lit/lit.psh
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ void main(in PSInput PSIn, out PSOutput PSOut) {
float4 emissionTS = g_Textures[Constants.textureIDs.w].Sample(g_Sampler, float3(PSIn.UV, PSIn.TexIndex)) * PSIn.Color;

// LIGHT ------
float3 V = normalize(Camera.pos.xyz - PSIn.WorldPos.xyz);
float3 V = normalize(Camera.pos - PSIn.WorldPos.xyz);
float dither = InterleavedGradientNoise(PSIn.Pos.xy);

// Apply compute data ----
Expand Down
2 changes: 2 additions & 0 deletions rawrbox.render/assets/shaders/materials/model/unlit/unlit.psh
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#include "defines.fxh"
#include "math.fxh"

#include "pixel_bindless_uniforms.fxh"

#ifdef CLUSTER_PLUGIN
Expand Down
34 changes: 34 additions & 0 deletions rawrbox.render/assets/shaders/particles/include/particles.fxh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#ifndef INCLUDED_PARTICLES
#define INCLUDED_PARTICLES
#include "math.fxh"

struct Particle {
float3 position;
float lifeTime;

float3 velocity;
float atlasIndex;

float2 size;
float2 _padding;

float3 rotation;
float _padding_2;

float4 color;
};

#if defined(WRITE_PARTICLES)
RWStructuredBuffer<Particle> Particles; // Read-Write
#define PARTICLES
#elif defined(READ_PARTICLES)
StructuredBuffer<Particle> Particles; // Read-only
#define PARTICLES
#endif

#ifdef PARTICLES
Particle GetParticle(uint index) {
return Particles[NonUniformResourceIndex(index)];
}
#endif
#endif
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#ifndef INCLUDED_PARTICLES_UNIFORMS
#define INCLUDED_PARTICLES_UNIFORMS

struct EmitterConstantsStruct {
float3 position;
float time;

float3 velocityMin;
float lifeMin;

float3 velocityMax;
float lifeMax;

float3 rotationMin;
float spawnRate;

float3 rotationMax;
float gravity;

float4 color[4];
float4 size;

// ------------
uint billboard;
uint atlasMin;
uint atlasMax;
uint textureID;
// ------------
};

ConstantBuffer<EmitterConstantsStruct> EmitterConstants;
#endif
108 changes: 108 additions & 0 deletions rawrbox.render/assets/shaders/particles/particles.csh
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#include "camera.fxh"
#include "particles_uniforms.fxh"
#include "hash.fxh"
#include "math.fxh"

#define WRITE_PARTICLES
#include "particles.fxh"

float3 CalculateVelocity(uint hash) {
float3 randomVelocity;
randomVelocity.x = lerp(EmitterConstants.velocityMin.x, EmitterConstants.velocityMax.x, (float)(hash & 0xFF) * (1.0f / 255.0f));
randomVelocity.y = lerp(EmitterConstants.velocityMin.y, EmitterConstants.velocityMax.y, (float)((hash >> 8) & 0xFF) * (1.0f / 255.0f));
randomVelocity.z = lerp(EmitterConstants.velocityMin.z, EmitterConstants.velocityMax.z, (float)((hash >> 16) & 0xFF) * (1.0f / 255.0f));
return randomVelocity;
}

float3 CalculateRotation(uint hash) {
float3 randomRotation;
randomRotation.x = lerp(EmitterConstants.rotationMin.x, EmitterConstants.rotationMax.x, (float)(hash & 0xFF) * (1.0f / 255.0f));
randomRotation.y = lerp(EmitterConstants.rotationMin.y, EmitterConstants.rotationMax.y, (float)((hash >> 8) & 0xFF) * (1.0f / 255.0f));
randomRotation.z = lerp(EmitterConstants.rotationMin.z, EmitterConstants.rotationMax.z, (float)((hash >> 8) & 0xFF) * (1.0f / 255.0f));

return randomRotation;
}

float2 CalculateSize(uint hash) {
float2 randomSize;
randomSize.x = lerp(EmitterConstants.size.x, EmitterConstants.size.z, (float)(hash & 0xFFFF) * (1.0f / 65535.0f));
randomSize.y = lerp(EmitterConstants.size.y, EmitterConstants.size.w, (float)((hash >> 16) & 0xFFFF) * (1.0f / 65535.0f));
return randomSize;
}

uint CalculateAtlas(uint hash) {
return (uint)lerp(EmitterConstants.atlasMin, EmitterConstants.atlasMax, (float)(hash & 0xFFFF) * (1.0f / 65535.0f));
}

float CalculateLife(uint hash) {
return lerp(EmitterConstants.lifeMin, EmitterConstants.lifeMax, (float)(hash & 0xFFFF) * (1.0f / 65535.0f));
}

float4 ParticleColor(float lifetimeRatio) {
const float transitionPoints[3] = {0.33, 0.66, 0.99};
lifetimeRatio = saturate(1.0 - lifetimeRatio);

// Determine color based on lifetimeRatio
if (lifetimeRatio < transitionPoints[0]) {
float factor = lifetimeRatio / transitionPoints[0];
return lerp(EmitterConstants.color[0], EmitterConstants.color[1], factor);
} else if (lifetimeRatio < transitionPoints[1]) {
float factor = (lifetimeRatio - transitionPoints[0]) / (transitionPoints[1] - transitionPoints[0]);
return lerp(EmitterConstants.color[1], EmitterConstants.color[2], factor);
} else if (lifetimeRatio < transitionPoints[2]) {
float factor = (lifetimeRatio - transitionPoints[1]) / (transitionPoints[2] - transitionPoints[1]);
return lerp(EmitterConstants.color[2], EmitterConstants.color[3], factor);
} else {
return EmitterConstants.color[3];
}
}
// Use groupshared memory to reduce bandwidth
groupshared Particle localParticles[256];

[numthreads(256, 1, 1)]
void main(uint3 dispatchThreadID : SV_DispatchThreadID, uint groupIndex : SV_GroupIndex) {
uint particleIndex = dispatchThreadID.x;

uint maxParticles, stride;
Particles.GetDimensions(maxParticles, stride);

if (particleIndex >= maxParticles) return;

// Load particle data into groupshared memory
localParticles[groupIndex] = GetParticle(particleIndex);
GroupMemoryBarrierWithGroupSync();

Particle particle = localParticles[groupIndex];
uint hash = pcg(EmitterConstants.time + particleIndex * 1009);

// Calculate the spawn interval based on the spawn rate
float spawnInterval = 1.0f / EmitterConstants.spawnRate;
float particleSpawnTime = particleIndex * spawnInterval;

if (particle.lifeTime <= 0.0F && EmitterConstants.time >= particleSpawnTime) {
// Spawn / reset a particle
particle.position = EmitterConstants.position;
particle.color = EmitterConstants.color[0];

particle.velocity = CalculateVelocity(hash);
particle.size = CalculateSize(hash);
particle.lifeTime = CalculateLife(hash);
particle.atlasIndex = CalculateAtlas(hash);
particle.rotation = CalculateRotation(hash);
} else {
// Apply gravity to the particle velocity
float3 gravity = float3(0, GRAVITY, 0) * EmitterConstants.gravity;

particle.rotation += 0.5F * Camera.deltaTime;
particle.velocity += gravity * Camera.deltaTime;
particle.position += particle.velocity * Camera.deltaTime;
particle.lifeTime -= Camera.deltaTime;
particle.color = ParticleColor(particle.lifeTime);
}

localParticles[groupIndex] = particle;

// Ensure all threads have completed their work before writing back
GroupMemoryBarrierWithGroupSync();
Particles[particleIndex] = localParticles[groupIndex];
}
Loading

0 comments on commit 7c88e93

Please sign in to comment.