Browse Source

GPU particles: Add spark particles

Panagiotis Christopoulos Charitos 3 weeks ago
parent
commit
524cbab5a4

+ 1 - 1
AnKi/Renderer/IndirectDiffuseClipmaps.h

@@ -14,7 +14,7 @@ namespace anki {
 /// @addtogroup renderer
 /// @{
 
-ANKI_CVAR(BoolCVar, Render, Idc, false, "Enable ray traced indirect diffuse clipmaps")
+ANKI_CVAR(BoolCVar, Render, Idc, true, "Enable ray traced indirect diffuse clipmaps")
 ANKI_CVAR2(BoolCVar, Render, Idc, InlineRt, false, "Use a cheap and less accurate path with inline RT");
 ANKI_CVAR2(BoolCVar, Render, Idc, UseSHL2, !ANKI_PLATFORM_MOBILE, "Use L2 SH for calculations. Else use L1");
 

+ 111 - 0
AnKi/Shaders/ForwardShadingParticlesSparks.ankiprog

@@ -0,0 +1,111 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma anki technique ForwardLegacy vert pixel
+
+#include <AnKi/Shaders/ForwardShadingCommon.hlsl>
+
+#pragma anki struct AnKiLocalConstants
+#pragma anki member Vec3 m_diffColor 1.0 1.0 1.0
+#pragma anki member F32 m_roughness 0.9
+#pragma anki member F32 m_metallic 0.0
+#pragma anki member Vec3 m_initialEmission 0.0 0.0 0.0
+#pragma anki member Vec3 m_finalEmission 0.0 0.0 0.0
+#pragma anki struct_end
+
+struct VertIn
+{
+	U32 m_svVertexId : SV_VERTEXID;
+	U32 m_svInstanceId : SV_INSTANCEID;
+};
+
+struct VertOut
+{
+	ANKI_RELAXED_PRECISION nointerpolation F32 m_lifeFactor : LIFE_FACTOR;
+	nointerpolation U32 m_constantsOffset : CONSTANTS_OFFSET;
+
+	Vec2 m_uv : TEXCOORD;
+
+	Vec4 m_svPosition : SV_POSITION;
+};
+
+#if ANKI_VERTEX_SHADER
+VertOut main(VertIn input)
+{
+	VertOut output;
+
+	const GpuScenePerDraw instance = getGpuScenePerDraw();
+	const GpuSceneParticleEmitter2 particles = SBUFF(g_particleEmitters2, instance.m_particleEmitterIndex);
+	const GpuSceneMeshLod meshLod = SBUFF(g_meshLods, instance.m_meshLodIndex);
+
+	const Vec3 localPos =
+		g_unifiedGeom_R16G16B16A16_Unorm[meshLod.m_vertexOffsets[(U32)VertexStreamId::kPosition] + input.m_svVertexId] * meshLod.m_positionScale
+		+ meshLod.m_positionTranslation;
+
+	U32 offset = particles.m_aliveParticleIndicesOffset + input.m_svInstanceId * sizeof(U32);
+	const U32 particleId = BAB_LOAD(g_gpuScene, U32, offset);
+
+	output.m_uv = g_unifiedGeom_R32G32_Sfloat[meshLod.m_vertexOffsets[(U32)VertexStreamId::kUv] + input.m_svVertexId];
+
+	// Compute pos
+	{
+		offset = particles.m_particleStateSteamOffsets[(U32)ParticleProperty::kPosition] + particleId * sizeof(Vec3);
+		const Vec3 particlePos = BAB_LOAD(g_gpuScene, Vec3, offset);
+
+		offset = particles.m_particleStateSteamOffsets[(U32)ParticleProperty::kVelocity] + particleId * sizeof(Vec3);
+		const Vec3 velocity = BAB_LOAD(g_gpuScene, Vec3, offset);
+
+		offset = particles.m_particleStateSteamOffsets[(U32)ParticleProperty::kScale] + particleId * sizeof(Vec3);
+		const Vec3 particleScale = BAB_LOAD(g_gpuScene, Vec3, offset);
+
+		const Vec3 xAxis = normalize(-velocity);
+		const Vec3 zAxis = normalize(g_globalConstants.m_cameraTransform.getColumn(2));
+		const Vec3 yAxis = normalize(cross(zAxis, xAxis));
+		Mat3 rot;
+		rot.setColumns(xAxis, yAxis, zAxis);
+
+		Vec3 worldPos = localPos;
+		worldPos *= particleScale;
+		worldPos = mul(rot, worldPos);
+		worldPos += particlePos;
+
+		output.m_svPosition = mul(g_globalConstants.m_viewProjectionMatrix, Vec4(worldPos, 1.0));
+	}
+
+	// Life factor
+	offset = particles.m_particleStateSteamOffsets[(U32)ParticleProperty::kLifeFactor] + particleId * sizeof(F32);
+	output.m_lifeFactor = BAB_LOAD(g_gpuScene, F32, offset);
+	output.m_lifeFactor = saturate(output.m_lifeFactor);
+
+	output.m_constantsOffset = instance.m_constantsOffset;
+
+	return output;
+}
+#endif // ANKI_VERTEX_SHADER
+
+#if ANKI_PIXEL_SHADER
+#	include <AnKi/Shaders/PackFunctions.hlsl>
+
+PixelOut main(VertOut input)
+{
+	PixelOut output;
+	const AnKiLocalConstants localConstants = loadAnKiLocalConstants(g_gpuScene, input.m_constantsOffset);
+
+	const Vec2 ndc = uvToNdc(input.m_uv);
+	F32 alpha = min(1.0, length(ndc));
+	alpha = 1.0 - alpha;
+	alpha = pow(alpha, 2.0);
+	alpha *= 1.0 - input.m_lifeFactor;
+
+	F32 emissionFactor = input.m_lifeFactor;
+	// emissionFactor = sqrt(emissionFactor);
+	emissionFactor *= 1.0 - alpha;
+	const Vec3 emission = lerp(localConstants.m_initialEmission, localConstants.m_finalEmission, emissionFactor);
+
+	particleAlpha(Vec4(emission, alpha), 1.0, 0.0, output);
+
+	return output;
+}
+#endif // ANKI_PIXEL_SHADER

+ 0 - 96
AnKi/Shaders/GBufferGpuParticles.ankiprog

@@ -1,96 +0,0 @@
-// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#pragma anki technique GBuffer vert pixel
-
-#include <AnKi/Shaders/MaterialShadersCommon.hlsl>
-
-#pragma anki struct AnKiLocalConstants
-#pragma anki member Vec3 m_diffColor
-#pragma anki member F32 m_roughness
-#pragma anki member Vec3 m_specColor
-#pragma anki member F32 m_metallic
-#pragma anki member Vec3 m_initialEmission
-#pragma anki member Vec3 m_finalEmission
-#pragma anki struct_end
-
-struct VertIn
-{
-	U32 m_svVertexId : SV_VERTEXID;
-};
-
-struct VertOut
-{
-	nointerpolation Vec2 m_velocity : VELOCITY;
-	nointerpolation F32 m_lifeFactor : LIFE_FACTOR;
-	nointerpolation U32 m_constantsOffset : CONSTANTS_OFFSET;
-	Vec4 m_svPosition : SV_POSITION;
-};
-
-#if ANKI_VERTEX_SHADER
-VertOut main(VertIn input)
-{
-	VertOut output;
-
-	const GpuScenePerDraw renderable = getGpuScenePerDraw();
-	const GpuSceneParticleEmitter particles = g_particleEmitters[renderable.m_particleEmitterIndex];
-
-	// Read vertex
-	const U32 particleId = input.m_svVertexId;
-	U32 offset = particles.m_vertexOffsets[(U32)VertexStreamId::kParticlePosition] + particleId * sizeof(Vec3);
-	const Vec3 pos = g_gpuScene.Load<Vec3>(offset);
-
-	offset = particles.m_vertexOffsets[(U32)VertexStreamId::kParticlePreviousPosition] + particleId * sizeof(Vec3);
-	const Vec3 prevPos = g_gpuScene.Load<Vec3>(offset);
-
-	offset = particles.m_vertexOffsets[(U32)VertexStreamId::kParticleLife] + particleId * sizeof(F32);
-	const F32 life = g_gpuScene.Load<F32>(offset);
-
-	offset = particles.m_vertexOffsets[(U32)VertexStreamId::kParticleStartingLife] + particleId * sizeof(F32);
-	const F32 startingLife = g_gpuScene.Load<F32>(offset);
-
-	// Position
-	const Vec4 clipPos = mul(g_globalConstants.m_viewProjectionMatrix, Vec4(pos, 1.0));
-	const Vec4 prevClipPos = mul(g_globalConstants.m_viewProjectionMatrix, Vec4(prevPos, 1.0));
-
-	output.m_svPosition = clipPos;
-
-	// Velocity
-	const Vec2 ndc = clipPos.xy / clipPos.w;
-	const Vec2 prevNdc = prevClipPos.xy / prevClipPos.w;
-	output.m_velocity = (prevNdc - ndc) * 0.5; // It's the opt ver of: ndcToUv(prevNdc) - ndcToUv(ndc)
-
-	// Misc
-	output.m_lifeFactor = saturate(1.0 - (life / startingLife));
-	output.m_constantsOffset = renderable.m_constantsOffset;
-
-	return output;
-}
-#endif // ANKI_VERTEX_SHADER
-
-#if ANKI_PIXEL_SHADER
-#	include <AnKi/Shaders/PackFunctions.hlsl>
-
-GBufferPixelOut main(VertOut input)
-{
-	GBufferPixelOut output;
-	const AnKiLocalConstants localConstants = loadAnKiLocalConstants(g_gpuScene, input.m_constantsOffset);
-
-	GbufferInfo<F16> g;
-	g.m_diffuse = localConstants.m_diffColor;
-
-	const Mat3x4 camTrf = g_globalConstants.m_cameraTransform;
-	g.m_normal = normalize(Vec3(camTrf.m_row0[2], camTrf.m_row1[2], camTrf.m_row2[2]));
-
-	g.m_f0 = localConstants.m_specColor;
-	g.m_roughness = localConstants.m_roughness;
-	g.m_subsurface = 0.0;
-	g.m_emission = lerp(localConstants.m_initialEmission, localConstants.m_finalEmission, input.m_lifeFactor);
-	g.m_metallic = localConstants.m_metallic;
-	g.m_velocity = input.m_velocity;
-
-	return packGBuffer(g);
-}
-#endif // ANKI_PIXEL_SHADER

+ 40 - 18
AnKi/Shaders/GpuParticlesCommon.hlsl

@@ -46,7 +46,7 @@ U32 genHash(U32 x)
 
 U32 getRandomU32()
 {
-	return genHash(g_randomNumber++);
+	return genHash(g_particleIdx + g_randomNumber++);
 }
 
 F32 getRandomRange(F32 min, F32 max)
@@ -74,7 +74,7 @@ void writeProp(GpuSceneParticleEmitter2 emitter, ParticleProperty prop, T value)
 }
 
 // Use the depth buffer and the normal buffer to resolve a collision
-Bool particleCollision(inout Vec3 x, out Vec3 n, F32 acceptablePenetrationDistance = 0.2)
+Bool particleCollision(inout Vec3 x, out Vec3 n, F32 acceptablePenetrationDistance)
 {
 	n = 0.0;
 
@@ -88,7 +88,7 @@ Bool particleCollision(inout Vec3 x, out Vec3 n, F32 acceptablePenetrationDistan
 
 	Vec2 texSize;
 	g_depthTex.GetDimensions(texSize.x, texSize.y);
-	UVec2 texCoord = ndcToUv(v4.xy) * texSize;
+	UVec2 texCoord = ndcToUv(v3.xy) * texSize;
 	const F32 refDepth = g_depthTex[texCoord].r;
 	const F32 particleDepth = v3.z;
 	if(particleDepth < refDepth)
@@ -107,40 +107,58 @@ Bool particleCollision(inout Vec3 x, out Vec3 n, F32 acceptablePenetrationDistan
 	// Collides, change the position
 
 	g_gbufferRt2Tex.GetDimensions(texSize.x, texSize.y);
-	texCoord = ndcToUv(v4.xy) * texSize;
+	texCoord = ndcToUv(v3.xy) * texSize;
 	n = unpackNormalFromGBuffer(g_gbufferRt2Tex[texCoord]);
 
 	v4 = mul(g_consts.m_invertedViewProjMat, Vec4(v3.xy, refDepth, 1.0));
 	x = v4.xyz / v4.w;
 
 	// Also push it a bit outside the surface
-	x += n * 0.1;
+	x += n * 0.01;
 
 	return true;
 }
 
+struct SimulationArgs
+{
+	Bool m_checkCollision; // Check collision using the depth buffer
+	F32 m_penetrationDistance; // Since collision is checked against the depth buffer add a threshold to avoid falce positives
+
+	U32 m_iterationCount; // The number of interations the simulation will run. Increase it for better accuracy
+	F32 m_e; // The coefficient of restitution. 0 is inelastic, 1 is bouncy
+	F32 m_mu; // The friction coefficient. From ~0.2 to 1.0
+
+	F32 m_velocityDamping; // Decreases the velocity a bit. Set it to 1 to disable damping
+
+	void init()
+	{
+		m_checkCollision = true;
+		m_penetrationDistance = 0.5;
+		m_iterationCount = 1;
+		m_e = 0.5;
+		m_mu = 0.2;
+		m_velocityDamping = 1.0;
+	}
+};
+
 // F Force
 // m Mass
 // dt Delta time
 // v Velocity
 // x Particle position
-// checkCollision Check collision
-// iterationCount The number of interations the simulation will run. Increase it for better accuracy
-// e The coefficient of restitution. 0 is inelastic, 1 is bouncy
-// mu The friction coefficient. From ~0.2 to 1.0
-void simulatePhysics(Vec3 F, F32 m, F32 dt, inout Vec3 v, inout Vec3 x, Bool checkCollision = true, U32 iterationCount = 1, F32 e = 0.5, F32 mu = 0.2)
+void simulatePhysics(Vec3 F, F32 m, F32 dt, inout Vec3 v, inout Vec3 x, SimulationArgs args)
 {
 	const Vec3 a = F / m;
 
-	const F32 sdt = dt / F32(iterationCount);
-	for(U32 i = 0; i < iterationCount; ++i)
+	const F32 sdt = dt / F32(args.m_iterationCount);
+	for(U32 i = 0; i < args.m_iterationCount; ++i)
 	{
 		// Compute the new pos and velocity
 		v += a * sdt; // a = dv/dt
 		x += v * sdt; // v = dx/dt
 
 		Vec3 n;
-		const Bool collides = checkCollision && particleCollision(x, n);
+		const Bool collides = args.m_checkCollision && particleCollision(x, n, args.m_penetrationDistance);
 		if(!collides)
 		{
 			continue;
@@ -153,22 +171,22 @@ void simulatePhysics(Vec3 F, F32 m, F32 dt, inout Vec3 v, inout Vec3 x, Bool che
 		}
 
 		// Restitution
-		v -= (1.0 + e) * vn * n;
+		v -= (1.0 + args.m_e) * vn * n;
 
 		// Friction
 		const Vec3 vt = v - dot(v, n) * n;
 		const F32 vtLen = length(vt);
 		if(vtLen > 0.0)
 		{
-			const F32 jn = -(1.0 + e) * m * vn;
+			const F32 jn = -(1.0 + args.m_e) * m * vn;
 			const F32 jtDesired = m * vtLen;
-			const F32 jt = min(jtDesired, mu * jn);
+			const F32 jt = min(jtDesired, args.m_mu * jn);
 			v -= (jt / m) * (vt / vtLen);
 		}
 	}
 
 	// Add some small damping to avoid jitter
-	// v *= 0.95;
+	v *= args.m_velocityDamping;
 }
 
 void appendAlive(GpuSceneParticleEmitter2 emitter, Vec3 particlePos, F32 particleScale)
@@ -249,7 +267,11 @@ void particleMain(U32 svDispatchThreadId, U32 svGroupIndex, TInterface iface)
 			Vec3 particlePosition;
 			F32 particleScale;
 			iface.initializeParticle(emitter, emitterTrf, makeAlive, particlePosition, particleScale);
-			appendAlive(emitter, particlePosition, particleScale);
+
+			if(makeAlive)
+			{
+				appendAlive(emitter, particlePosition, particleScale);
+			}
 		}
 	}
 

+ 33 - 30
AnKi/Shaders/GpuParticlesGass.ankiprog

@@ -54,36 +54,6 @@ struct ParticleInterface
 		m_props = props;
 	}
 
-	void simulateParticle(GpuSceneParticleEmitter2 emitter, F32 lifeFactor, out Vec3 particlePosition, out F32 particleScale)
-	{
-		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;
-	}
-
 	void initializeParticle(GpuSceneParticleEmitter2 emitter, Mat3x4 emitterTrf, Bool makeAlive, out Vec3 particlePosition, out F32 particleScale)
 	{
 		const F32 m = getRandomRange(m_props.m_minMass, m_props.m_maxMass);
@@ -119,6 +89,39 @@ struct ParticleInterface
 		particlePosition = x;
 		particleScale = initialSize;
 	}
+
+	void simulateParticle(GpuSceneParticleEmitter2 emitter, F32 lifeFactor, out Vec3 particlePosition, out F32 particleScale)
+	{
+		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;
+
+		SimulationArgs args;
+		args.init();
+		args.m_checkCollision = false;
+		simulatePhysics(F, m, g_consts.m_dt, v, x, args);
+
+		// 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;
+	}
 };
 
 [numthreads(ANKI_WAVE_SIZE, 1, 1)] void main(COMPUTE_ARGS)

+ 64 - 12
AnKi/Shaders/GpuParticlesSparks.ankiprog

@@ -10,6 +10,7 @@
 #pragma anki technique comp
 
 #include <AnKi/Shaders/GpuParticlesCommon.hlsl>
+#include <AnKi/Shaders/ImportanceSampling.hlsl>
 
 // clang-format off
 #pragma anki struct AnKiParticleEmitterProperties
@@ -19,17 +20,18 @@
 #	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 Vec2 m_size 1.0 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 Vec3 m_forceDir 0 1 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 member F32 m_coefOfRestitution 0.1
+#	pragma anki member F32 m_coefFriction 0.1
 #pragma anki struct_end
 // clang-format on
 
@@ -43,18 +45,68 @@ struct ParticleInterface
 		m_props = props;
 	}
 
-	void simulateParticle(GpuSceneParticleEmitter2 emitter, F32 lifeFactor, out Vec3 particlePosition, out F32 particleScale)
+	void initializeParticle(GpuSceneParticleEmitter2 emitter, Mat3x4 emitterTrf, Bool makeAlive, out Vec3 particlePosition, out F32 particleScale)
 	{
-		// TODO
-		particlePosition = 0.0;
-		particleScale = 0.0;
+		const F32 m = getRandomRange(m_props.m_minMass, m_props.m_maxMass);
+
+		// Compute the force
+		const F32 randAng = getRandomRange(0.0, m_props.m_forceAngle);
+		const F32 adj = 10.0; // In meters. Something big
+		const F32 opposite = tan(randAng / 2.0) * adj; // Compute the opposite in the orthogonal triangle. tan(phi) = opposite/adj
+		const Vec3 sphereCenter = m_props.m_forceDir * adj;
+		const F32 sphereRadius = opposite;
+		const Vec3 randomPointInSphere = sphereCenter + normalize(getRandomRange((Vec3)-1.0, (Vec3)1.0)) * sphereRadius;
+		const Vec3 forceDir = normalize(randomPointInSphere);
+		const Vec3 F = getRandomRange(m_props.m_minForce, m_props.m_maxForce) * forceDir;
+
+		// Initial stuff
+		const Vec3 g = Vec3(0.0, -9.8, 0.0);
+		const Vec3 a = F / m + g;
+		const F32 dt = 1.0 / 60.0;
+		const Vec3 v = a * 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, ParticleProperty::kScale, Vec3(m_props.m_size, 1.0));
+
+		particlePosition = x;
+		particleScale = max2(m_props.m_size);
 	}
 
-	void initializeParticle(GpuSceneParticleEmitter2 emitter, Mat3x4 emitterTrf, Bool makeAlive, out Vec3 particlePosition, out F32 particleScale)
+	void simulateParticle(GpuSceneParticleEmitter2 emitter, F32 lifeFactor, out Vec3 particlePosition, out F32 particleScale)
 	{
-		// TODO
-		particlePosition = 0.0;
-		particleScale = 0.0;
+		const F32 m = readProp<F32>(emitter, ParticleProperty::kMass);
+		const Vec3 g = Vec3(0.0, -9.8, 0.0);
+		const Vec3 F = m * g;
+		Vec3 v = readProp<Vec3>(emitter, ParticleProperty::kVelocity);
+		Vec3 x = readProp<Vec3>(emitter, ParticleProperty::kPosition);
+		const Vec3 prevX = x;
+
+		SimulationArgs args;
+		args.init();
+		args.m_checkCollision = true;
+		args.m_penetrationDistance = 0.4;
+		args.m_iterationCount = 2;
+		args.m_e = saturate(m_props.m_coefOfRestitution);
+		args.m_mu = clamp(m_props.m_coefFriction, 0.1, 1.0);
+		simulatePhysics(F, m, g_consts.m_dt, v, x, args);
+
+		// 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);
+		writeProp(emitter, ParticleProperty::kScale, Vec3(m_props.m_size, 1.0));
+
+		particlePosition = x;
+		particleScale = max2(m_props.m_size);
 	}
 };
 

+ 5 - 5
AnKi/Shaders/GpuVisibilityStage2And3.ankiprog

@@ -213,11 +213,6 @@ Bool cullMeshlet(GpuSceneRenderable renderable, const MeshletBoundingVolume mesh
 	}
 #	endif
 
-#	if MESHLET_HZB_CULLING
-	meshletCulledByHzb = cullHzb(minNdc, maxNdc, aabbMinDepth, g_hzbTexture, g_nearestClampSampler);
-	return meshletCulledByHzb;
-#	endif
-
 #	if MESHLET_BACKFACE_CULLING
 	{
 		Vec3 center = (meshletBoundingVol.m_aabbMin + meshletBoundingVol.m_aabbMax) * 0.5;
@@ -240,6 +235,11 @@ Bool cullMeshlet(GpuSceneRenderable renderable, const MeshletBoundingVolume mesh
 	}
 #	endif
 
+#	if MESHLET_HZB_CULLING
+	meshletCulledByHzb = cullHzb(minNdc, maxNdc, aabbMinDepth, g_hzbTexture, g_nearestClampSampler);
+	return meshletCulledByHzb;
+#	endif
+
 	return false;
 }
 

+ 5 - 0
AnKi/Shaders/Include/Common.h

@@ -300,6 +300,11 @@ struct Mat3x4
 		m_row1[i] = c.y;
 		m_row2[i] = c.z;
 	}
+
+	Vec3 getColumn(U32 i)
+	{
+		return Vec3(m_row0[i], m_row1[i], m_row2[i]);
+	}
 };
 
 Vec3 mul(Mat3x4 m, Vec4 v)

+ 52 - 0
Samples/Sponza/Assets/Scene.lua

@@ -3265,6 +3265,58 @@ trf:setRotation(rot)
 trf:setScale(Vec3.new(0.664928, 1.646613, 0.265592))
 node:setLocalTransform(trf)
 
+node = scene:newSceneNode("Cube.019")
+comp = node:newParticleEmitter2Component()
+comp:setParticleEmitterFilename("Assets/Sparks.ankipart")
+comp = node:newMaterialComponent()
+comp:setMaterialFilename("Assets/Sparks.ankimtl")
+trf = Transform.new()
+trf:setOrigin(Vec3.new(8.578157, 2.320148, -3.843734))
+rot = Mat3.new()
+rot:setAll(1.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 1.000000)
+trf:setRotation(rot)
+trf:setScale(Vec3.new(0.259678, 0.259678, 0.259678))
+node:setLocalTransform(trf)
+
+node = scene:newSceneNode("Cube.020")
+comp = node:newParticleEmitter2Component()
+comp:setParticleEmitterFilename("Assets/Sparks.ankipart")
+comp = node:newMaterialComponent()
+comp:setMaterialFilename("Assets/Sparks.ankimtl")
+trf = Transform.new()
+trf:setOrigin(Vec3.new(-10.755020, 2.320148, -3.843734))
+rot = Mat3.new()
+rot:setAll(1.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 1.000000)
+trf:setRotation(rot)
+trf:setScale(Vec3.new(0.259678, 0.259678, 0.259678))
+node:setLocalTransform(trf)
+
+node = scene:newSceneNode("Cube.021")
+comp = node:newParticleEmitter2Component()
+comp:setParticleEmitterFilename("Assets/Sparks.ankipart")
+comp = node:newMaterialComponent()
+comp:setMaterialFilename("Assets/Sparks.ankimtl")
+trf = Transform.new()
+trf:setOrigin(Vec3.new(-10.760053, 2.320148, 2.487105))
+rot = Mat3.new()
+rot:setAll(1.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 1.000000)
+trf:setRotation(rot)
+trf:setScale(Vec3.new(0.259678, 0.259678, 0.259678))
+node:setLocalTransform(trf)
+
+node = scene:newSceneNode("Cube.022")
+comp = node:newParticleEmitter2Component()
+comp:setParticleEmitterFilename("Assets/Sparks.ankipart")
+comp = node:newMaterialComponent()
+comp:setMaterialFilename("Assets/Sparks.ankimtl")
+trf = Transform.new()
+trf:setOrigin(Vec3.new(8.566397, 2.320148, 2.494902))
+rot = Mat3.new()
+rot:setAll(1.000000, 0.000000, 0.000000, 0.000000, 1.000000, 0.000000, 0.000000, 0.000000, 1.000000)
+trf:setRotation(rot)
+trf:setScale(Vec3.new(0.259678, 0.259678, 0.259678))
+node:setLocalTransform(trf)
+
 node = scene:tryFindSceneNode("mixamorig:RightArm")
 if node ~= nil then
 	getEventManager():newAnimationEvent("Assets/Armature|mixamo.com|Layer0_2ff0b9b4a30af3d0.ankianim", "mixamorig:RightArm", node)

+ 14 - 0
Samples/Sponza/Assets/Sparks.ankimtl

@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<material shadow="0">
+	<shaderProgram name="ForwardShadingParticlesSparks" />
+
+	<inputs>
+		<input name="m_diffColor" value="0 0 0"/>
+		<input name="m_roughness" value="0.9"/>
+		<input name="m_metallic" value="0.0"/>
+
+		<input name="m_initialEmission" value="15 2 0"/>
+		<input name="m_finalEmission" value="0.1 0.0 0"/>
+	</inputs>
+</material>
+

+ 21 - 0
Samples/Sponza/Assets/Sparks.ankipart

@@ -0,0 +1,21 @@
+<particleEmitter>
+	<shaderProgram name="GpuParticlesSparks"/>
+	<particleCount value="10"/>
+	<emissionPeriod value="0.300000"/>
+	<particlesPerEmission value="2"/>
+	<inputs>
+		<input name="m_minParticleLife" value="0.500000"/>
+		<input name="m_maxParticleLife" value="0.700000"/>
+		<input name="m_minMass" value="0.500000"/>
+		<input name="m_maxMass" value="0.500000"/>
+		<input name="m_size" value="0.150000 0.040000 "/>
+		<input name="m_minStartingPosition" value="0.000000 0.000000 0.000000 "/>
+		<input name="m_maxStartingPosition" value="0.100000 0.000000 0.000000 "/>
+		<input name="m_forceDir" value="0.000000 1.000000 0.000000 "/>
+		<input name="m_forceAngle" value="1.000000"/>
+		<input name="m_minForce" value="150.000000"/>
+		<input name="m_maxForce" value="250.000000"/>
+		<input name="m_coefOfRestitution" value="1.000000"/>
+		<input name="m_coefFriction" value="0.500000"/>
+	</inputs>
+</particleEmitter>