Browse Source

Gpu particles: Adding the skeleton for sparks

Panagiotis Christopoulos Charitos 1 month ago
parent
commit
01aab01555

+ 127 - 0
AnKi/Shaders/GpuParticlesCommon.hlsl

@@ -170,3 +170,130 @@ void simulatePhysics(Vec3 F, F32 m, F32 dt, inout Vec3 v, inout Vec3 x, Bool che
 	// Add some small damping to avoid jitter
 	// v *= 0.95;
 }
+
+void appendAlive(GpuSceneParticleEmitter2 emitter, Vec3 particlePos, F32 particleScale)
+{
+	// Add the alive particle index to the array
+	U32 count;
+	InterlockedAdd(g_scratch[0].m_aliveParticleCount, 1, count);
+	BAB_STORE(g_gpuScene, U32, emitter.m_aliveParticleIndicesOffset + count * sizeof(U32), g_particleIdx);
+
+	// Update the AABB
+	const F32 toCentimeters = 100.0;
+	const IVec3 quatizedPosMin = floor((particlePos + emitter.m_particleAabbMin * particleScale) * toCentimeters);
+	const IVec3 quatizedPosMax = ceil((particlePos + emitter.m_particleAabbMax * particleScale) * toCentimeters);
+	[unroll] for(U32 i = 0; i < 3; ++i)
+	{
+		InterlockedMin(g_scratch[0].m_aabbMin[i], quatizedPosMin[i]);
+		InterlockedMax(g_scratch[0].m_aabbMax[i], quatizedPosMax[i]);
+	}
+}
+
+template<typename TInterface>
+void particleMain(U32 svDispatchThreadId, U32 svGroupIndex, TInterface iface)
+{
+	particlesInitGlobals(svDispatchThreadId.x);
+
+	GpuSceneParticleEmitter2 emitter = SBUFF(g_gpuSceneParticleEmitters, g_consts.m_gpuSceneParticleEmitterIndex);
+	iface.initAnKiParticleEmitterProperties(emitter);
+	const Mat3x4 emitterTrf = SBUFF(g_gpuSceneTransforms, emitter.m_worldTransformsIndex);
+
+	const Bool reinit = emitter.m_reinitializeOnNextUpdate;
+	const Bool canEmitThisFrame = emitter.m_timeLeftForNextEmission - g_consts.m_dt <= 0.0;
+
+	if(g_particleIdx < emitter.m_particleCount)
+	{
+		// Decide what to do
+		Bool init = false;
+		Bool makeAlive = false;
+		Bool simulate = false;
+		F32 lifeFactor = 1.0;
+
+		if(reinit)
+		{
+			U32 emittedParticleCount;
+			InterlockedAdd(g_scratch[0].m_emittedParticleCount, 1, emittedParticleCount);
+
+			init = true;
+			makeAlive = emittedParticleCount < emitter.m_particlesPerEmission;
+		}
+		else
+		{
+			lifeFactor = readProp<F32>(emitter, ParticleProperty::kLifeFactor);
+			const Bool alive = lifeFactor < 1.0;
+
+			if(alive)
+			{
+				simulate = true;
+			}
+			else if(canEmitThisFrame)
+			{
+				U32 emittedParticleCount;
+				InterlockedAdd(g_scratch[0].m_emittedParticleCount, 1, emittedParticleCount);
+
+				init = emittedParticleCount < emitter.m_particlesPerEmission;
+				makeAlive = true;
+			}
+		}
+
+		// Do the actual work
+		if(simulate)
+		{
+			Vec3 particlePosition;
+			F32 particleScale;
+			iface.simulateParticle(emitter, lifeFactor, particlePosition, particleScale);
+			appendAlive(emitter, particlePosition, particleScale);
+		}
+		else if(init)
+		{
+			Vec3 particlePosition;
+			F32 particleScale;
+			iface.initializeParticle(emitter, emitterTrf, makeAlive, particlePosition, particleScale);
+			appendAlive(emitter, particlePosition, particleScale);
+		}
+	}
+
+	// Check if it's the last threadgroup running
+	if(svGroupIndex == 0)
+	{
+		U32 threadgroupIdx;
+		InterlockedAdd(g_scratch[0].m_threadgroupCount, 1, threadgroupIdx);
+		const U32 threadgroupCount = (emitter.m_particleCount + ANKI_WAVE_SIZE - 1) / ANKI_WAVE_SIZE;
+		const Bool lastThreadExecuting = (threadgroupIdx + 1 == threadgroupCount);
+
+		if(lastThreadExecuting)
+		{
+			// Inform about the bounding volume
+			const F32 toMeters = 1.0 / 100.0;
+			ParticleSimulationCpuFeedback feedback = (ParticleSimulationCpuFeedback)0;
+			feedback.m_aabbMin = g_scratch[0].m_aabbMin * toMeters;
+			feedback.m_aabbMax = g_scratch[0].m_aabbMax * toMeters;
+			feedback.m_uuid = emitter.m_uuid;
+			g_cpuFeedback[0] = feedback;
+
+			// Update the GPU scene emitter
+			if(canEmitThisFrame)
+			{
+				SBUFF(g_gpuSceneParticleEmitters, g_consts.m_gpuSceneParticleEmitterIndex).m_timeLeftForNextEmission = emitter.m_emissionPeriod;
+			}
+			else
+			{
+				SBUFF(g_gpuSceneParticleEmitters, g_consts.m_gpuSceneParticleEmitterIndex).m_timeLeftForNextEmission -= g_consts.m_dt;
+			}
+
+			if(reinit)
+			{
+				SBUFF(g_gpuSceneParticleEmitters, g_consts.m_gpuSceneParticleEmitterIndex).m_reinitializeOnNextUpdate = 0;
+			}
+
+			SBUFF(g_gpuSceneParticleEmitters, g_consts.m_gpuSceneParticleEmitterIndex).m_aliveParticleCount = g_scratch[0].m_aliveParticleCount;
+
+			// Reset the scratch struct for next frame
+			g_scratch[0].m_aabbMin = kMaxI32;
+			g_scratch[0].m_aabbMax = kMinI32;
+			g_scratch[0].m_threadgroupCount = 0;
+			g_scratch[0].m_emittedParticleCount = 0;
+			g_scratch[0].m_aliveParticleCount = 0;
+		}
+	}
+}

+ 72 - 178
AnKi/Shaders/GpuParticlesGass.ankiprog

@@ -44,191 +44,85 @@
 #define ALPHA_PROP ParticleProperty::kUserDefined1 // Initial alpha, final alpha, current alpha
 #define SIZE_PROP ParticleProperty::kUserDefined2 // Initial size, final size
 
-void appendAlive(GpuSceneParticleEmitter2 emitter, Vec3 particlePos, F32 particleScale)
+struct ParticleInterface
 {
-	// Add the alive particle index to the array
-	U32 count;
-	InterlockedAdd(g_scratch[0].m_aliveParticleCount, 1, count);
-	BAB_STORE(g_gpuScene, U32, emitter.m_aliveParticleIndicesOffset + count * sizeof(U32), g_particleIdx);
-
-	// Update the AABB
-	const F32 toCentimeters = 100.0;
-	const IVec3 quatizedPosMin = floor((particlePos + emitter.m_particleAabbMin * particleScale) * toCentimeters);
-	const IVec3 quatizedPosMax = ceil((particlePos + emitter.m_particleAabbMax * particleScale) * toCentimeters);
-	[unroll] for(U32 i = 0; i < 3; ++i)
+	AnKiParticleEmitterProperties m_props;
+
+	void initAnKiParticleEmitterProperties(GpuSceneParticleEmitter2 emitter)
 	{
-		InterlockedMin(g_scratch[0].m_aabbMin[i], quatizedPosMin[i]);
-		InterlockedMax(g_scratch[0].m_aabbMax[i], quatizedPosMax[i]);
+		const AnKiParticleEmitterProperties props = loadAnKiParticleEmitterProperties(g_gpuScene, emitter.m_particleEmitterPropertiesOffset);
+		m_props = props;
 	}
-}
-
-void simulateParticle(AnKiParticleEmitterProperties props, GpuSceneParticleEmitter2 emitter, F32 lifeFactor)
-{
-	const F32 m = readProp<F32>(emitter, ParticleProperty::kMass);
-	const Vec3 g = readProp<Vec4>(emitter, GRAVITY_PROP).xyz;
-	const Vec3 F = m * g;
-	Vec3 v = readProp<Vec3>(emitter, ParticleProperty::kVelocity);
-	Vec3 x = readProp<Vec3>(emitter, ParticleProperty::kPosition);
-	const Vec3 prevX = x;
-
-	simulatePhysics(F, m, g_consts.m_dt, v, x, false);
-
-	// Write
-	writeProp(emitter, ParticleProperty::kVelocity, v);
-	const F32 maxLife = readProp<F32>(emitter, ParticleProperty::kMaxLife);
-	writeProp(emitter, ParticleProperty::kLifeFactor, lifeFactor + g_consts.m_dt / maxLife);
-	writeProp(emitter, ParticleProperty::kPosition, x);
-	writeProp(emitter, ParticleProperty::kPreviousPosition, prevX);
-
-	const Vec4 sizeProp = readProp<Vec4>(emitter, SIZE_PROP);
-	const F32 scale = lerp(sizeProp.x, sizeProp.y, lifeFactor);
-	writeProp(emitter, ParticleProperty::kScale, Vec3(scale, scale, scale));
-
-	const Vec4 alphaProp = readProp<Vec4>(emitter, ALPHA_PROP);
-	const F32 alpha = lerp(alphaProp.x, alphaProp.y, lifeFactor);
-	writeProp(emitter, ALPHA_PROP, Vec4(alphaProp.x, alphaProp.y, alpha, 0.0));
-
-	// Append alive particle
-	appendAlive(emitter, x, scale);
-}
 
-void initializeParticle(AnKiParticleEmitterProperties props, GpuSceneParticleEmitter2 emitter, Mat3x4 emitterTrf, Bool makeAlive)
-{
-	const F32 m = getRandomRange(props.m_minMass, props.m_maxMass);
-
-	Vec3 g = normalize(getRandomRange(props.m_minGravityDirection, props.m_maxGravityDirection));
-	g = mul(emitterTrf, Vec4(g, 0.0));
-	g = normalize(g); // Because the emitter's trf might have scale
-	g *= getRandomRange(props.m_minGravityMagnitude, props.m_maxGravityMagnitude);
-
-	const F32 dt = 1.0 / 60;
-	const Vec3 v = g * dt;
-	const Vec3 x = getRandomRange(props.m_minStartingPosition, props.m_maxStartingPosition) + emitterTrf.getTranslationPart().xyz;
-
-	// Store
-	writeProp(emitter, ParticleProperty::kVelocity, v);
-	writeProp(emitter, ParticleProperty::kLifeFactor, makeAlive ? 0.0 : 1.0);
-	writeProp(emitter, ParticleProperty::kMaxLife, getRandomRange(props.m_minParticleLife, props.m_maxParticleLife));
-	writeProp(emitter, ParticleProperty::kPosition, x);
-	writeProp(emitter, ParticleProperty::kPreviousPosition, x);
-	writeProp(emitter, ParticleProperty::kMass, m);
-	writeProp(emitter, GRAVITY_PROP, Vec4(g, 0.0));
-
-	const F32 initialAlpha = getRandomRange(props.m_minInitialAlpha, props.m_maxInitialAlpha);
-	const F32 finalAlpha = getRandomRange(props.m_minFinalAlpha, props.m_maxFinalAlpha);
-	writeProp(emitter, ALPHA_PROP, Vec4(initialAlpha, finalAlpha, initialAlpha, 0.0));
-
-	const F32 initialSize = getRandomRange(props.m_minInitialSize, props.m_maxInitialSize);
-	const F32 finalSize = getRandomRange(props.m_minFinalSize, props.m_maxFinalSize);
-	writeProp(emitter, SIZE_PROP, Vec4(initialSize, finalSize, 0.0, 0.0));
-
-	writeProp(emitter, ParticleProperty::kScale, Vec3(initialSize, initialSize, initialSize));
-
-	// Append into the alive particles
-	appendAlive(emitter, x, initialSize);
-}
-
-[numthreads(ANKI_WAVE_SIZE, 1, 1)] void main(COMPUTE_ARGS)
-{
-	particlesInitGlobals(svDispatchThreadId.x);
-
-	GpuSceneParticleEmitter2 emitter = SBUFF(g_gpuSceneParticleEmitters, g_consts.m_gpuSceneParticleEmitterIndex);
-	const AnKiParticleEmitterProperties props = loadAnKiParticleEmitterProperties(g_gpuScene, emitter.m_particleEmitterPropertiesOffset);
-	const Mat3x4 emitterTrf = SBUFF(g_gpuSceneTransforms, emitter.m_worldTransformsIndex);
-
-	const Bool reinit = emitter.m_reinitializeOnNextUpdate;
-	const Bool canEmitThisFrame = emitter.m_timeLeftForNextEmission - g_consts.m_dt <= 0.0;
-
-	if(g_particleIdx < emitter.m_particleCount)
+	void simulateParticle(GpuSceneParticleEmitter2 emitter, F32 lifeFactor, out Vec3 particlePosition, out F32 particleScale)
 	{
-		// Decide what to do
-		Bool init = false;
-		Bool makeAlive = false;
-		Bool simulate = false;
-		F32 lifeFactor = 1.0;
-
-		if(reinit)
-		{
-			U32 emittedParticleCount;
-			InterlockedAdd(g_scratch[0].m_emittedParticleCount, 1, emittedParticleCount);
-
-			init = true;
-			makeAlive = emittedParticleCount < emitter.m_particlesPerEmission;
-		}
-		else
-		{
-			lifeFactor = readProp<F32>(emitter, ParticleProperty::kLifeFactor);
-			const Bool alive = lifeFactor < 1.0;
-
-			if(alive)
-			{
-				simulate = true;
-			}
-			else if(canEmitThisFrame)
-			{
-				U32 emittedParticleCount;
-				InterlockedAdd(g_scratch[0].m_emittedParticleCount, 1, emittedParticleCount);
-
-				init = emittedParticleCount < emitter.m_particlesPerEmission;
-				makeAlive = true;
-			}
-		}
-
-		// Do the actual work
-		if(simulate)
-		{
-			simulateParticle(props, emitter, lifeFactor);
-		}
-		else if(init)
-		{
-			initializeParticle(props, emitter, emitterTrf, makeAlive);
-		}
+		const F32 m = readProp<F32>(emitter, ParticleProperty::kMass);
+		const Vec3 g = readProp<Vec4>(emitter, GRAVITY_PROP).xyz;
+		const Vec3 F = m * g;
+		Vec3 v = readProp<Vec3>(emitter, ParticleProperty::kVelocity);
+		Vec3 x = readProp<Vec3>(emitter, ParticleProperty::kPosition);
+		const Vec3 prevX = x;
+
+		simulatePhysics(F, m, g_consts.m_dt, v, x, false);
+
+		// Write
+		writeProp(emitter, ParticleProperty::kVelocity, v);
+		const F32 maxLife = readProp<F32>(emitter, ParticleProperty::kMaxLife);
+		writeProp(emitter, ParticleProperty::kLifeFactor, lifeFactor + g_consts.m_dt / maxLife);
+		writeProp(emitter, ParticleProperty::kPosition, x);
+		writeProp(emitter, ParticleProperty::kPreviousPosition, prevX);
+
+		const Vec4 sizeProp = readProp<Vec4>(emitter, SIZE_PROP);
+		const F32 scale = lerp(sizeProp.x, sizeProp.y, lifeFactor);
+		writeProp(emitter, ParticleProperty::kScale, Vec3(scale, scale, scale));
+
+		const Vec4 alphaProp = readProp<Vec4>(emitter, ALPHA_PROP);
+		const F32 alpha = lerp(alphaProp.x, alphaProp.y, lifeFactor);
+		writeProp(emitter, ALPHA_PROP, Vec4(alphaProp.x, alphaProp.y, alpha, 0.0));
+
+		particlePosition = x;
+		particleScale = scale;
 	}
 
-	// Sync to make sure all the atomic ops have finished before the following code reads them
-	AllMemoryBarrierWithGroupSync();
-
-	// Check if it's the last threadgroup running
-	if(svGroupIndex == 0)
+	void initializeParticle(GpuSceneParticleEmitter2 emitter, Mat3x4 emitterTrf, Bool makeAlive, out Vec3 particlePosition, out F32 particleScale)
 	{
-		U32 threadgroupIdx;
-		InterlockedAdd(g_scratch[0].m_threadgroupCount, 1, threadgroupIdx);
-		const U32 threadgroupCount = (emitter.m_particleCount + ANKI_WAVE_SIZE - 1) / ANKI_WAVE_SIZE;
-		const Bool lastThreadExecuting = (threadgroupIdx + 1 == threadgroupCount);
-
-		if(lastThreadExecuting)
-		{
-			// Inform about the bounding volume
-			const F32 toMeters = 1.0 / 100.0;
-			ParticleSimulationCpuFeedback feedback = (ParticleSimulationCpuFeedback)0;
-			feedback.m_aabbMin = g_scratch[0].m_aabbMin * toMeters;
-			feedback.m_aabbMax = g_scratch[0].m_aabbMax * toMeters;
-			feedback.m_uuid = emitter.m_uuid;
-			g_cpuFeedback[0] = feedback;
-
-			// Update the GPU scene emitter
-			if(canEmitThisFrame)
-			{
-				SBUFF(g_gpuSceneParticleEmitters, g_consts.m_gpuSceneParticleEmitterIndex).m_timeLeftForNextEmission = emitter.m_emissionPeriod;
-			}
-			else
-			{
-				SBUFF(g_gpuSceneParticleEmitters, g_consts.m_gpuSceneParticleEmitterIndex).m_timeLeftForNextEmission -= g_consts.m_dt;
-			}
-
-			if(reinit)
-			{
-				SBUFF(g_gpuSceneParticleEmitters, g_consts.m_gpuSceneParticleEmitterIndex).m_reinitializeOnNextUpdate = 0;
-			}
-
-			SBUFF(g_gpuSceneParticleEmitters, g_consts.m_gpuSceneParticleEmitterIndex).m_aliveParticleCount = g_scratch[0].m_aliveParticleCount;
-
-			// Reset the scratch struct for next frame
-			g_scratch[0].m_aabbMin = kMaxI32;
-			g_scratch[0].m_aabbMax = kMinI32;
-			g_scratch[0].m_threadgroupCount = 0;
-			g_scratch[0].m_emittedParticleCount = 0;
-			g_scratch[0].m_aliveParticleCount = 0;
-		}
+		const F32 m = getRandomRange(m_props.m_minMass, m_props.m_maxMass);
+
+		Vec3 g = normalize(getRandomRange(m_props.m_minGravityDirection, m_props.m_maxGravityDirection));
+		g = mul(emitterTrf, Vec4(g, 0.0));
+		g = normalize(g); // Because the emitter's trf might have scale
+		g *= getRandomRange(m_props.m_minGravityMagnitude, m_props.m_maxGravityMagnitude);
+
+		const F32 dt = 1.0 / 60;
+		const Vec3 v = g * dt;
+		const Vec3 x = getRandomRange(m_props.m_minStartingPosition, m_props.m_maxStartingPosition) + emitterTrf.getTranslationPart().xyz;
+
+		// Store
+		writeProp(emitter, ParticleProperty::kVelocity, v);
+		writeProp(emitter, ParticleProperty::kLifeFactor, makeAlive ? 0.0 : 1.0);
+		writeProp(emitter, ParticleProperty::kMaxLife, getRandomRange(m_props.m_minParticleLife, m_props.m_maxParticleLife));
+		writeProp(emitter, ParticleProperty::kPosition, x);
+		writeProp(emitter, ParticleProperty::kPreviousPosition, x);
+		writeProp(emitter, ParticleProperty::kMass, m);
+		writeProp(emitter, GRAVITY_PROP, Vec4(g, 0.0));
+
+		const F32 initialAlpha = getRandomRange(m_props.m_minInitialAlpha, m_props.m_maxInitialAlpha);
+		const F32 finalAlpha = getRandomRange(m_props.m_minFinalAlpha, m_props.m_maxFinalAlpha);
+		writeProp(emitter, ALPHA_PROP, Vec4(initialAlpha, finalAlpha, initialAlpha, 0.0));
+
+		const F32 initialSize = getRandomRange(m_props.m_minInitialSize, m_props.m_maxInitialSize);
+		const F32 finalSize = getRandomRange(m_props.m_minFinalSize, m_props.m_maxFinalSize);
+		writeProp(emitter, SIZE_PROP, Vec4(initialSize, finalSize, 0.0, 0.0));
+
+		writeProp(emitter, ParticleProperty::kScale, Vec3(initialSize, initialSize, initialSize));
+
+		particlePosition = x;
+		particleScale = initialSize;
 	}
+};
+
+[numthreads(ANKI_WAVE_SIZE, 1, 1)] void main(COMPUTE_ARGS)
+{
+	ParticleInterface iface;
+	particleMain(svDispatchThreadId.x, svGroupIndex, iface);
 }

+ 65 - 0
AnKi/Shaders/GpuParticlesSparks.ankiprog

@@ -0,0 +1,65 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+// This shader does a particle simulation for spart-like particles
+
+#pragma anki mutator ANKI_WAVE_SIZE 16 32 64
+
+#pragma anki technique comp
+
+#include <AnKi/Shaders/GpuParticlesCommon.hlsl>
+
+// clang-format off
+#pragma anki struct AnKiParticleEmitterProperties
+#	pragma anki member F32 m_minParticleLife 1.0
+#	pragma anki member F32 m_maxParticleLife 1.0
+
+#	pragma anki member F32 m_minMass 0.5
+#	pragma anki member F32 m_maxMass 0.5
+
+#	pragma anki member F32 m_minInitialSize 1.0
+#	pragma anki member F32 m_maxInitialSize 1.0
+#	pragma anki member F32 m_minFinalSize 1.0
+#	pragma anki member F32 m_maxFinalSize 1.0
+
+#	pragma anki member Vec3 m_minStartingPosition 0.0 0.0 0.0
+#	pragma anki member Vec3 m_maxStartingPosition 0.0 0.0 0.0
+
+#	pragma anki member F32 m_forceAngle 1.2
+#	pragma anki member F32 m_minForce 1
+#	pragma anki member F32 m_maxForce 2
+#pragma anki struct_end
+// clang-format on
+
+struct ParticleInterface
+{
+	AnKiParticleEmitterProperties m_props;
+
+	void initAnKiParticleEmitterProperties(GpuSceneParticleEmitter2 emitter)
+	{
+		const AnKiParticleEmitterProperties props = loadAnKiParticleEmitterProperties(g_gpuScene, emitter.m_particleEmitterPropertiesOffset);
+		m_props = props;
+	}
+
+	void simulateParticle(GpuSceneParticleEmitter2 emitter, F32 lifeFactor, out Vec3 particlePosition, out F32 particleScale)
+	{
+		// TODO
+		particlePosition = 0.0;
+		particleScale = 0.0;
+	}
+
+	void initializeParticle(GpuSceneParticleEmitter2 emitter, Mat3x4 emitterTrf, Bool makeAlive, out Vec3 particlePosition, out F32 particleScale)
+	{
+		// TODO
+		particlePosition = 0.0;
+		particleScale = 0.0;
+	}
+};
+
+[numthreads(ANKI_WAVE_SIZE, 1, 1)] void main(COMPUTE_ARGS)
+{
+	ParticleInterface iface;
+	particleMain(svDispatchThreadId.x, svGroupIndex, iface);
+}