2
0
Эх сурвалжийг харах

Merge pull request #43 from godlikepanos/volumetric_probes

Volumetric probes
Panagiotis Christopoulos Charitos 7 жил өмнө
parent
commit
171983e86d

+ 1 - 0
README.md

@@ -36,6 +36,7 @@ Prerequisites:
 - GCC 5.0 and up or Clang 3.7 and up
 - libx11-dev installed
 - libxrandr-dev installed
+- libx11-xcb-dev installed
 - [Optional] libxinerama-dev if you want proper multi-monitor support
 
 To build the release version:

+ 60 - 0
samples/physics_playground/Main.cpp

@@ -29,6 +29,45 @@ end
 	return Error::NONE;
 }
 
+static Error createFogVolumeFadeEvent(SceneNode* node)
+{
+	CString script = R"(
+density = 15
+radius = 3.5
+
+function update(event, prevTime, crntTime)
+	node = event:getAssociatedSceneNodes():getAt(0)
+	fogComponent = node:getFogDensityComponent()
+
+	dt = crntTime - prevTime
+	density = density - 4.0 * dt
+	radius = radius + 0.5 * dt
+
+	pos = node:getMoveComponent():getLocalOrigin()
+	pos:setY(pos:getY() - 1.1 * dt)
+	node:getMoveComponent():setLocalOrigin(pos)
+
+	if density <= 0.0 or radius <= 0.0 then
+		event:getAssociatedSceneNodes():getAt(0):setMarkedForDeletion()
+	else
+		fogComponent:setSphere(radius)
+		fogComponent:setDensity(density)
+	end
+
+	return 1
+end
+
+function onKilled(event, prevTime, crntTime)
+	return 1
+end
+	)";
+	ScriptEvent* event;
+	ANKI_CHECK(node->getSceneGraph().getEventManager().newEvent(event, -1, 10.0, script));
+	event->addAssociatedSceneNode(node);
+
+	return Error::NONE;
+}
+
 class RayCast : public PhysicsWorldRayCastCallback
 {
 public:
@@ -258,6 +297,7 @@ Error MyApp::userMainLoop(Bool& quit)
 
 			createDestructionEvent(monkey);
 
+#if 1
 			// Create some particles
 			ParticleEmitterNode* particles;
 			ANKI_CHECK(getSceneGraph().newSceneNode(
@@ -266,6 +306,26 @@ Error MyApp::userMainLoop(Bool& quit)
 				"assets/smoke.ankipart"));
 			particles->getComponent<MoveComponent>().setLocalTransform(trf);
 			createDestructionEvent(particles);
+#endif
+
+			// Create some fog volumes
+			for(U i = 0; i < 1; ++i)
+			{
+				static int id = 0;
+				StringAuto name(getSceneGraph().getFrameAllocator());
+				name.sprintf("fog%u", id++);
+
+				FogDensityNode* fogNode;
+				ANKI_CHECK(getSceneGraph().newSceneNode(name.toCString(), fogNode));
+				FogDensityComponent& fogComp = fogNode->getComponent<FogDensityComponent>();
+				fogComp.setSphere(2.1f);
+				fogComp.setDensity(15.0f);
+
+				fogNode->getComponent<MoveComponent>().setLocalTransform(trf);
+
+				createDestructionEvent(fogNode);
+				createFogVolumeFadeEvent(fogNode);
+			}
 		}
 	}
 

+ 6 - 0
samples/physics_playground/assets/sphere_r2.ankicl

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<collisionShape>
+	<type>sphere</type>
+	<value>2.0</value>
+</collisionShape>
+

+ 29 - 16
shaders/ClusteredShadingCommon.glsl

@@ -92,6 +92,8 @@ const U32 _NEXT_TEX_BINDING_3 = _NEXT_TEX_BINDING_2;
 // Decal uniforms
 //
 #if defined(LIGHT_DECALS)
+const U32 _NEXT_UBO_BINDING_4 = _NEXT_UBO_BINDING_3 + 1u;
+
 layout(std140, row_major, ANKI_UBO_BINDING(LIGHT_SET, _NEXT_UBO_BINDING_3)) uniform u4_
 {
 	Decal u_decals[UBO_MAX_SIZE / SIZEOF_DECAL];
@@ -99,6 +101,18 @@ layout(std140, row_major, ANKI_UBO_BINDING(LIGHT_SET, _NEXT_UBO_BINDING_3)) unif
 
 layout(ANKI_TEX_BINDING(LIGHT_SET, _NEXT_TEX_BINDING_3 + 0)) uniform sampler2D u_diffDecalTex;
 layout(ANKI_TEX_BINDING(LIGHT_SET, _NEXT_TEX_BINDING_3 + 1)) uniform sampler2D u_specularRoughnessDecalTex;
+#else
+const U32 _NEXT_UBO_BINDING_4 = _NEXT_UBO_BINDING_3;
+#endif
+
+//
+// Fog density uniforms
+//
+#if defined(LIGHT_FOG_DENSITY_VOLUMES)
+layout(std140, row_major, ANKI_UBO_BINDING(LIGHT_SET, _NEXT_UBO_BINDING_4)) uniform u5_
+{
+	FogDensityVolume u_fogDensityVolumes[UBO_MAX_SIZE / SIZEOF_FOG_DENSITY_VOLUME];
+};
 #endif
 
 //
@@ -115,35 +129,34 @@ layout(std430, ANKI_SS_BINDING(LIGHT_SET, LIGHT_SS_BINDING + 1)) readonly buffer
 };
 
 // Debugging function
-Vec3 lightHeatmap(U32 firstIndex, U32 maxLights, Bool decals, Bool plights, Bool slights, Bool probes)
+Vec3 lightHeatmap(U32 firstIndex, U32 maxLights, Bool decals, Bool plights, Bool slights, Bool probes, Bool fogVolumes)
 {
 	U32 count = 0;
+	U32 idx;
+
+	while((idx = u_lightIndices[firstIndex++]) != MAX_U32)
+	{
+		count += (plights) ? 1u : 0u;
+	}
 
-	U32 decalCount = u_lightIndices[firstIndex];
-	firstIndex += decalCount + 1u;
-	if(decals)
+	while((idx = u_lightIndices[firstIndex++]) != MAX_U32)
 	{
-		count += decalCount;
+		count += (slights) ? 1u : 0u;
 	}
 
-	U32 pointLightCount = u_lightIndices[firstIndex];
-	firstIndex += pointLightCount + 1u;
-	if(plights)
+	while((idx = u_lightIndices[firstIndex++]) != MAX_U32)
 	{
-		count += pointLightCount;
+		count += (probes) ? 1u : 0u;
 	}
 
-	U32 spotLightCount = u_lightIndices[firstIndex];
-	firstIndex += spotLightCount + 1u;
-	if(slights)
+	while((idx = u_lightIndices[firstIndex++]) != MAX_U32)
 	{
-		count += spotLightCount;
+		count += (decals) ? 1u : 0u;
 	}
 
-	U32 probeCount = u_lightIndices[firstIndex];
-	if(probes)
+	while((idx = u_lightIndices[firstIndex++]) != MAX_U32)
 	{
-		count += probeCount;
+		count += (fogVolumes) ? 1u : 0u;
 	}
 
 	F32 factor = min(1.0, F32(count) / F32(maxLights));

+ 5 - 12
shaders/ForwardShadingCommonFrag.glsl

@@ -49,16 +49,11 @@ Vec3 computeLightColorHigh(Vec3 diffCol, Vec3 worldPos)
 
 	U32 idxOffset = u_clusters[clusterIdx];
 
-	// Skip decals
-	U32 count = u_lightIndices[idxOffset];
-	idxOffset += count + 1;
-
 	// Point lights
-	count = u_lightIndices[idxOffset++];
-	U32 idxOffsetEnd = idxOffset + count;
-	ANKI_LOOP while(idxOffset < idxOffsetEnd)
+	U32 idx;
+	ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
 	{
-		PointLight light = u_pointLights[u_lightIndices[idxOffset++]];
+		PointLight light = u_pointLights[idx];
 
 		Vec3 diffC = diffCol * light.m_diffuseColorTileSize.rgb;
 
@@ -80,11 +75,9 @@ Vec3 computeLightColorHigh(Vec3 diffCol, Vec3 worldPos)
 	}
 
 	// Spot lights
-	count = u_lightIndices[idxOffset++];
-	idxOffsetEnd = idxOffset + count;
-	ANKI_LOOP while(idxOffset < idxOffsetEnd)
+	ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
 	{
-		SpotLight light = u_spotLights[u_lightIndices[idxOffset++]];
+		SpotLight light = u_spotLights[idx];
 
 		Vec3 diffC = diffCol * light.m_diffuseColorShadowmapId.rgb;
 

+ 6 - 5
shaders/GBufferPost.glslp

@@ -66,7 +66,7 @@ void main()
 	Vec4 worldPos4 = u_invViewProjMat * Vec4(ndc, depth, 1.0);
 	Vec3 worldPos = worldPos4.xyz / worldPos4.w;
 
-	// Get first light index
+	// Get first decal index
 	U32 idxOffset;
 	{
 		U32 k = computeClusterK(u_clustererMagic, worldPos);
@@ -74,18 +74,19 @@ void main()
 			k * (CLUSTER_COUNT_X * CLUSTER_COUNT_Y) + U32(in_clusterIJ.y) * CLUSTER_COUNT_X + U32(in_clusterIJ.x);
 
 		idxOffset = u_clusters[clusterIdx];
+		idxOffset = u_lightIndices[idxOffset - 2u]; // Use the offset metadata
 	}
 
 	// Process decals
-	U32 count = u_lightIndices[idxOffset++];
-	if(count == 0)
+	U32 idx = u_lightIndices[idxOffset];
+	if(idx == MAX_U32)
 	{
 		discard;
 	}
 
-	while(count-- != 0)
+	ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
 	{
-		Decal decal = u_decals[u_lightIndices[idxOffset++]];
+		Decal decal = u_decals[idx];
 
 		// Project pos to decal space
 		Vec4 texCoords4 = decal.m_texProjectionMat * Vec4(worldPos, 1.0);

+ 9 - 17
shaders/LightShading.glslp

@@ -74,11 +74,10 @@ void readReflectionsAndIrradianceFromProbes(U32 idxOffset,
 	F32 totalBlendWeight = EPSILON;
 
 	// Check proxy
-	U32 count = u_lightIndices[idxOffset++];
-	U32 idxOffsetEnd = idxOffset + count;
-	ANKI_LOOP while(idxOffset < idxOffsetEnd)
+	U32 idx;
+	ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
 	{
-		ReflectionProbe probe = u_reflectionProbes[u_lightIndices[idxOffset++]];
+		ReflectionProbe probe = u_reflectionProbes[idx];
 		Vec3 aabbMin = probe.m_aabbMinPad1.xyz;
 		Vec3 aabbMax = probe.m_aabbMaxPad1.xyz;
 		Vec3 probeOrigin = probe.m_positionCubemapIndex.xyz;
@@ -131,7 +130,7 @@ void main()
 
 		idxOffset = u_clusters[clusterIdx];
 
-		// out_color = lightHeatmap(idxOffset, 5, false, false, true, false); return;
+		// out_color = lightHeatmap(idxOffset, 5, false, false, false, false, true); return;
 	}
 
 	// Decode GBuffer
@@ -146,17 +145,12 @@ void main()
 	// Ambient and emissive color
 	out_color = gbuffer.m_diffuse * gbuffer.m_emission;
 
-	// Skip decals
-	U32 count = u_lightIndices[idxOffset];
-	idxOffset += count + 1u;
-
 	// Point lights
 	Vec3 viewDir = normalize(u_cameraPos - worldPos);
-	count = u_lightIndices[idxOffset++];
-	U32 idxOffsetEnd = idxOffset + count;
-	ANKI_LOOP while(idxOffset < idxOffsetEnd)
+	U32 idx;
+	ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
 	{
-		PointLight light = u_pointLights[u_lightIndices[idxOffset++]];
+		PointLight light = u_pointLights[idx];
 
 		LIGHTING_COMMON_BRDF();
 
@@ -171,11 +165,9 @@ void main()
 	}
 
 	// Spot lights
-	count = u_lightIndices[idxOffset++];
-	idxOffsetEnd = idxOffset + count;
-	ANKI_LOOP while(idxOffset < idxOffsetEnd)
+	ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
 	{
-		SpotLight light = u_spotLights[u_lightIndices[idxOffset++]];
+		SpotLight light = u_spotLights[idx];
 
 		LIGHTING_COMMON_BRDF();
 

+ 8 - 7
shaders/VolumetricFogAccumulation.glslp

@@ -55,16 +55,17 @@ void main()
 		// Compute the thikness of this fragment
 		F32 layerThinkness = abs(zVSpaceNear - zVSpaceFar);
 
-		// Scattering & absorption
-		F32 scattering = u_density * u_fogScatteringCoeff * layerThinkness;
-		F32 absorption = u_density * u_fogAbsorptionCoeff * layerThinkness;
+		// Read the light value and the fog density from the fog volumes
+		Vec4 lightAndFogDensity = textureLod(u_lightVolume, Vec3(uv, clusterKFar / F32(FINAL_CLUSTER_Z + 1u)), 0.0);
+		lightAndFogDensity.xyz *= u_fogDiffuse / PI;
+		lightAndFogDensity.w += u_density; // Apply the default density
 
-		// Read the light value
-		Vec3 light = textureLod(u_lightVolume, Vec3(uv, clusterKFar / F32(FINAL_CLUSTER_Z + 1u)), 0.0).rgb;
-		light *= u_fogDiffuse / PI;
+		// Scattering & absorption
+		F32 scattering = lightAndFogDensity.w * u_fogScatteringCoeff * layerThinkness;
+		F32 absorption = lightAndFogDensity.w * u_fogAbsorptionCoeff * layerThinkness;
 
 		// Integrate
-		Vec4 colorAndDensityBack = Vec4(light * scattering, scattering + absorption);
+		Vec4 colorAndDensityBack = Vec4(lightAndFogDensity.xyz * scattering, scattering + absorption);
 
 		Vec3 l = colorAndDensityFront.rgb + saturate(exp(-colorAndDensityFront.a)) * colorAndDensityBack.rgb;
 		colorAndDensityFront = Vec4(l.rgb, colorAndDensityFront.a + colorAndDensityBack.a);

+ 39 - 22
shaders/VolumetricLightingAccumulation.glslp

@@ -42,6 +42,7 @@ ANKI_PUSH_CONSTANTS(PushConsts, u_regs);
 #define LIGHT_LIGHTS
 #define LIGHT_COMMON_UNIS
 #define LIGHT_INDIRECT
+#define LIGHT_FOG_DENSITY_VOLUMES
 #include <shaders/ClusteredShadingCommon.glsl>
 
 Vec3 g_globalInvocationID = Vec3(gl_GlobalInvocationID);
@@ -90,7 +91,7 @@ F32 phaseFunction(Vec3 viewDir, Vec3 lightDir, F32 g)
 	return saturate(a * b);
 }
 
-Vec3 accumulateLights(U32 clusterIdx, Vec3 worldPos)
+Vec4 accumulateLightsAndFog(U32 clusterIdx, Vec3 worldPos)
 {
 	Vec3 color = Vec3(0.0);
 	Vec3 viewDir = normalize(u_cameraPos - worldPos);
@@ -98,16 +99,11 @@ Vec3 accumulateLights(U32 clusterIdx, Vec3 worldPos)
 	// Get ID offset
 	U32 idxOffset = u_clusters[clusterIdx];
 
-	// Skip decals
-	U32 count = u_lightIndices[idxOffset];
-	idxOffset += count + 1u;
-
 	// Point lights
-	count = u_lightIndices[idxOffset++];
-	U32 idxOffsetEnd = idxOffset + count;
-	ANKI_LOOP while(idxOffset < idxOffsetEnd)
+	U32 idx;
+	ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
 	{
-		PointLight light = u_pointLights[u_lightIndices[idxOffset++]];
+		PointLight light = u_pointLights[idx];
 
 		Vec3 frag2Light = light.m_posRadius.xyz - worldPos;
 		F32 factor = computeAttenuationFactor(light.m_posRadius.w, frag2Light);
@@ -126,11 +122,9 @@ Vec3 accumulateLights(U32 clusterIdx, Vec3 worldPos)
 	}
 
 	// Spot lights
-	count = u_lightIndices[idxOffset++];
-	idxOffsetEnd = idxOffset + count;
-	ANKI_LOOP while(idxOffset < idxOffsetEnd)
+	ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
 	{
-		SpotLight light = u_spotLights[u_lightIndices[idxOffset++]];
+		SpotLight light = u_spotLights[idx];
 
 		Vec3 frag2Light = light.m_posRadius.xyz - worldPos;
 		F32 factor = computeAttenuationFactor(light.m_posRadius.w, frag2Light);
@@ -157,11 +151,9 @@ Vec3 accumulateLights(U32 clusterIdx, Vec3 worldPos)
 	// Probes
 	F32 totalBlendWeight = EPSILON;
 	Vec3 diffIndirect = Vec3(0.0);
-	count = u_lightIndices[idxOffset++];
-	idxOffsetEnd = idxOffset + count;
-	ANKI_LOOP while(idxOffset < idxOffsetEnd)
+	ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
 	{
-		ReflectionProbe probe = u_reflectionProbes[u_lightIndices[idxOffset++]];
+		ReflectionProbe probe = u_reflectionProbes[idx];
 		Vec3 aabbMin = probe.m_aabbMinPad1.xyz;
 		Vec3 aabbMax = probe.m_aabbMaxPad1.xyz;
 		Vec3 probeOrigin = probe.m_positionCubemapIndex.xyz;
@@ -178,7 +170,32 @@ Vec3 accumulateLights(U32 clusterIdx, Vec3 worldPos)
 	diffIndirect /= totalBlendWeight;
 	color += diffIndirect;
 
-	return color;
+	// Fog density
+	F32 fogDensity = 0.0;
+	idxOffset = u_clusters[clusterIdx];
+	idxOffset = u_lightIndices[idxOffset - 1u];
+	ANKI_LOOP while((idx = u_lightIndices[idxOffset++]) != MAX_U32)
+	{
+		FogDensityVolume vol = u_fogDensityVolumes[idx];
+
+		F32 factor;
+		ANKI_BRANCH if(vol.m_isBox == 1u)
+		{
+			factor =
+				computeProbeBlendWeight(worldPos, vol.m_aabbMinOrSphereCenter, vol.m_aabbMaxOrSphereRadiusSquared, 0.2);
+		}
+		else
+		{
+			Vec3 diff = worldPos - vol.m_aabbMinOrSphereCenter;
+			F32 distSq = dot(diff, diff) / vol.m_aabbMaxOrSphereRadiusSquared.x;
+			distSq = min(1.0, distSq);
+			factor = 1.0 - distSq;
+		}
+
+		fogDensity += vol.m_density * factor;
+	}
+
+	return Vec4(color, fogDensity);
 }
 
 void main()
@@ -196,7 +213,7 @@ void main()
 	Vec3 worldPos = worldPosInsideCluster(readRand());
 
 	// Get lighting
-	Vec3 color = accumulateLights(clusterIdx, worldPos);
+	Vec4 lightAndFog = accumulateLightsAndFog(clusterIdx, worldPos);
 
 	// Read the prev result
 	{
@@ -215,15 +232,15 @@ void main()
 		Vec3 uvw = Vec3(prevUv, k);
 		if(all(lessThan(abs(uvw), Vec3(1.0))))
 		{
-			Vec3 prev = textureLod(u_prevVolume, uvw, 0.0).rgb;
+			Vec4 prev = textureLod(u_prevVolume, uvw, 0.0);
 
 			// Modulate
-			color = mix(prev, color, 1.0 / 16.0);
+			lightAndFog = mix(prev, lightAndFog, 1.0 / 16.0);
 		}
 	}
 
 	// Write result
-	imageStore(u_volume, IVec3(gl_GlobalInvocationID), Vec4(color, 0.0));
+	imageStore(u_volume, IVec3(gl_GlobalInvocationID), lightAndFog);
 }
 
 #pragma anki end

+ 15 - 0
shaders/glsl_cpp_common/ClusteredShading.h

@@ -9,6 +9,10 @@
 
 ANKI_BEGIN_NAMESPACE
 
+// Consts
+const U32 TYPED_OBJECT_COUNT = 5u;
+const F32 INVALID_TEXTURE_INDEX = -1.0;
+
 // See the documentation in the ClustererBin class.
 struct ClustererMagicValues
 {
@@ -60,6 +64,17 @@ struct Decal
 const U32 SIZEOF_DECAL = 3 * SIZEOF_VEC4 + SIZEOF_MAT4;
 ANKI_SHADER_STATIC_ASSERT(sizeof(Decal) == SIZEOF_DECAL)
 
+// Fog density volume
+struct FogDensityVolume
+{
+	Vec3 m_aabbMinOrSphereCenter;
+	U32 m_isBox;
+	Vec3 m_aabbMaxOrSphereRadiusSquared;
+	F32 m_density;
+};
+const U32 SIZEOF_FOG_DENSITY_VOLUME = 2 * SIZEOF_VEC4;
+ANKI_SHADER_STATIC_ASSERT(sizeof(FogDensityVolume) == SIZEOF_FOG_DENSITY_VOLUME)
+
 // Common uniforms for light shading passes
 struct LightingUniforms
 {

+ 2 - 0
src/anki/Scene.h

@@ -21,6 +21,7 @@
 #include <anki/scene/Octree.h>
 #include <anki/scene/PhysicsDebugNode.h>
 #include <anki/scene/TriggerNode.h>
+#include <anki/scene/FogDensityNode.h>
 
 #include <anki/scene/components/MoveComponent.h>
 #include <anki/scene/components/RenderComponent.h>
@@ -38,6 +39,7 @@
 #include <anki/scene/components/FrustumComponent.h>
 #include <anki/scene/components/JointComponent.h>
 #include <anki/scene/components/TriggerComponent.h>
+#include <anki/scene/components/FogDensityComponent.h>
 
 #include <anki/scene/events/EventManager.h>
 #include <anki/scene/events/Event.h>

+ 6 - 0
src/anki/collision/Sphere.h

@@ -49,9 +49,15 @@ public:
 
 	void setCenter(const Vec4& x)
 	{
+		ANKI_ASSERT(x.w() == 0.0f);
 		m_center = x;
 	}
 
+	void setCenter(const Vec3& x)
+	{
+		m_center = x.xyz0();
+	}
+
 	F32 getRadius() const
 	{
 		return m_radius;

+ 1 - 1
src/anki/gr/Common.h

@@ -44,7 +44,7 @@ const U MAX_TEXTURE_LAYERS = 32;
 const U MAX_SPECIALIZED_CONSTS = 64;
 
 const U MAX_TEXTURE_BINDINGS = 16;
-const U MAX_UNIFORM_BUFFER_BINDINGS = 4;
+const U MAX_UNIFORM_BUFFER_BINDINGS = 6;
 const U MAX_STORAGE_BUFFER_BINDINGS = 4;
 const U MAX_IMAGE_BINDINGS = 4;
 const U MAX_TEXTURE_BUFFER_BINDINGS = 4;

+ 18 - 5
src/anki/gr/vulkan/ShaderImpl.cpp

@@ -120,12 +120,16 @@ void ShaderImpl::doReflection(ConstWeakArray<U8> spirv, SpecConstsVector& specCo
 	}};
 	Array2d<DescriptorBinding, MAX_DESCRIPTOR_SETS, MAX_BINDINGS_PER_DESCRIPTOR_SET> descriptors;
 
-	auto func = [&](const std::vector<spirv_cross::Resource>& resources, DescriptorType type) -> void {
+	auto func = [&](const std::vector<spirv_cross::Resource>& resources,
+					DescriptorType type,
+					U minVkBinding,
+					U maxVkBinding) -> void {
 		for(const spirv_cross::Resource& r : resources)
 		{
 			const U32 id = r.id;
 			const U32 set = spvc.get_decoration(id, spv::Decoration::DecorationDescriptorSet);
 			const U32 binding = spvc.get_decoration(id, spv::Decoration::DecorationBinding);
+			ANKI_ASSERT(binding >= minVkBinding && binding <= maxVkBinding);
 
 			m_descriptorSetMask.set(set);
 			m_activeBindingMask[set].set(set);
@@ -143,10 +147,19 @@ void ShaderImpl::doReflection(ConstWeakArray<U8> spirv, SpecConstsVector& specCo
 		}
 	};
 
-	func(rsrc.uniform_buffers, DescriptorType::UNIFORM_BUFFER);
-	func(rsrc.sampled_images, DescriptorType::TEXTURE);
-	func(rsrc.storage_buffers, DescriptorType::STORAGE_BUFFER);
-	func(rsrc.storage_images, DescriptorType::IMAGE);
+	func(rsrc.uniform_buffers,
+		DescriptorType::UNIFORM_BUFFER,
+		MAX_TEXTURE_BINDINGS,
+		MAX_TEXTURE_BINDINGS + MAX_UNIFORM_BUFFER_BINDINGS - 1);
+	func(rsrc.sampled_images, DescriptorType::TEXTURE, 0, MAX_TEXTURE_BINDINGS - 1);
+	func(rsrc.storage_buffers,
+		DescriptorType::STORAGE_BUFFER,
+		MAX_TEXTURE_BINDINGS + MAX_UNIFORM_BUFFER_BINDINGS,
+		MAX_TEXTURE_BINDINGS + MAX_UNIFORM_BUFFER_BINDINGS + MAX_STORAGE_BUFFER_BINDINGS - 1);
+	func(rsrc.storage_images,
+		DescriptorType::IMAGE,
+		MAX_TEXTURE_BINDINGS + MAX_UNIFORM_BUFFER_BINDINGS + MAX_STORAGE_BUFFER_BINDINGS,
+		MAX_TEXTURE_BINDINGS + MAX_UNIFORM_BUFFER_BINDINGS + MAX_STORAGE_BUFFER_BINDINGS + MAX_IMAGE_BINDINGS - 1);
 
 	for(U set = 0; set < MAX_DESCRIPTOR_SETS; ++set)
 	{

+ 179 - 134
src/anki/renderer/ClusterBin.cpp

@@ -13,9 +13,6 @@
 namespace anki
 {
 
-static const U32 TYPED_OBJECT_COUNT = 4; // Point, spot, decal & probe
-static const F32 INVALID_TEXTURE_INDEX = -1.0;
-
 /// Get a view space point.
 static Vec4 unproject(const F32 zVspace, const Vec2& ndc, const Vec4& unprojParams)
 {
@@ -69,22 +66,36 @@ public:
 class ClusterBin::TileCtx
 {
 public:
+	struct ClusterMetaInfo
+	{
+		Array<U16, TYPED_OBJECT_COUNT> m_counts;
+		U16 m_offset;
+	};
+
+	DynamicArrayAuto<Vec4> m_clusterEdgesWSpace;
+	DynamicArrayAuto<Aabb> m_clusterBoxes;
+	DynamicArrayAuto<Sphere> m_clusterSpheres;
+
+	DynamicArrayAuto<ClusterMetaInfo> m_clusterInfos;
+	DynamicArrayAuto<U32> m_indices;
+
+	U32 m_clusterCountZ = MAX_U32;
+
 	TileCtx(StackAllocator<U8>& alloc)
 		: m_clusterEdgesWSpace(alloc)
 		, m_clusterBoxes(alloc)
 		, m_clusterSpheres(alloc)
+		, m_clusterInfos(alloc)
 		, m_indices(alloc)
-		, m_pIndices(alloc)
-		, m_pCounts(alloc)
 	{
 	}
 
-	DynamicArrayAuto<Vec4> m_clusterEdgesWSpace;
-	DynamicArrayAuto<Aabb> m_clusterBoxes;
-	DynamicArrayAuto<Sphere> m_clusterSpheres;
-	DynamicArrayAuto<U32> m_indices;
-	DynamicArrayAuto<U32*> m_pIndices;
-	DynamicArrayAuto<U32*> m_pCounts;
+	WeakArray<U32> getClusterIndices(const U clusterZ)
+	{
+		ANKI_ASSERT(clusterZ < m_clusterCountZ);
+		const U perClusterCount = m_indices.getSize() / m_clusterCountZ;
+		return WeakArray<U32>(&m_indices[perClusterCount * clusterZ], perClusterCount);
+	}
 };
 
 ClusterBin::~ClusterBin()
@@ -103,7 +114,13 @@ void ClusterBin::init(
 
 	m_totalClusterCount = clusterCountX * clusterCountY * clusterCountZ;
 
-	m_indexCount = m_totalClusterCount * cfg.getNumber("r.avgObjectsPerCluster");
+	m_avgObjectsPerCluster = cfg.getNumber("r.avgObjectsPerCluster");
+
+	// The actual indices per cluster are
+	// - the object indices per cluster
+	// - plus TYPED_OBJECT_COUNT-1 that is the offset per object type minus the first object type
+	// - plus TYPED_OBJECT_COUNT the stopper dummy indices
+	m_indexCount = m_totalClusterCount * (m_avgObjectsPerCluster + TYPED_OBJECT_COUNT - 1 + TYPED_OBJECT_COUNT);
 
 	m_clusterEdges.create(m_alloc, m_clusterCounts[0] * m_clusterCounts[1] * (m_clusterCounts[2] + 1) * 4);
 }
@@ -163,14 +180,13 @@ void ClusterBin::bin(ClusterBinIn& in, ClusterBinOut& out)
 			BinCtx& ctx = *self;
 
 			TileCtx tileCtx(ctx.m_in->m_tempAlloc);
-			const U clusterCountZ = ctx.m_bin->m_clusterCounts[2];
-			const U32 avgIndicesPerCluster = ctx.m_bin->m_indexCount / ctx.m_bin->m_totalClusterCount;
+			const U32 clusterCountZ = ctx.m_bin->m_clusterCounts[2];
 			tileCtx.m_clusterEdgesWSpace.create((clusterCountZ + 1) * 4);
 			tileCtx.m_clusterBoxes.create(clusterCountZ);
 			tileCtx.m_clusterSpheres.create(clusterCountZ);
-			tileCtx.m_indices.create(clusterCountZ * avgIndicesPerCluster);
-			tileCtx.m_pIndices.create(clusterCountZ);
-			tileCtx.m_pCounts.create(clusterCountZ);
+			tileCtx.m_indices.create(clusterCountZ * ctx.m_bin->m_avgObjectsPerCluster);
+			tileCtx.m_clusterInfos.create(clusterCountZ);
+			tileCtx.m_clusterCountZ = clusterCountZ;
 
 			const U tileCount = ctx.m_bin->m_clusterCounts[0] * ctx.m_bin->m_clusterCounts[1];
 			U tileIdx;
@@ -245,7 +261,6 @@ void ClusterBin::binTile(U32 tileIdx, BinCtx& ctx, TileCtx& tileCtx)
 	ANKI_ASSERT(tileIdx < m_clusterCounts[0] * m_clusterCounts[1]);
 	const U tileX = tileIdx % m_clusterCounts[0];
 	const U tileY = tileIdx / m_clusterCounts[0];
-	const U32 avgIndicesPerCluster = m_indexCount / m_totalClusterCount;
 
 	// Compute the tile's cluster edges in view space
 	WeakArray<Vec4> clusterEdgesVSpace(
@@ -316,68 +331,22 @@ void ClusterBin::binTile(U32 tileIdx, BinCtx& ctx, TileCtx& tileCtx)
 		clusterSpheres[clusterZ] = Sphere(sphereCenter, (aabbMin - sphereCenter).getLength());
 	}
 
-	// Set temp indices for each cluster
-	DynamicArrayAuto<U32>& indices = tileCtx.m_indices;
-	DynamicArrayAuto<U32*>& pIndices = tileCtx.m_pIndices;
-	DynamicArrayAuto<U32*>& pCounts = tileCtx.m_pCounts;
+	// Zero the infos
+	memset(&tileCtx.m_clusterInfos[0], 0, tileCtx.m_clusterInfos.getSizeInBytes());
 
-	for(U clusterZ = 0; clusterZ < m_clusterCounts[2]; ++clusterZ)
-	{
-		pIndices[clusterZ] = &indices[clusterZ * avgIndicesPerCluster];
-	}
-
-	// Decals
-	{
-		for(U clusterZ = 0; clusterZ < m_clusterCounts[2]; ++clusterZ)
-		{
-			pCounts[clusterZ] = pIndices[clusterZ];
-			*pCounts[clusterZ] = 0;
-			++pIndices[clusterZ];
-		}
-
-		Obb decalBox;
-		for(U i = 0; i < ctx.m_in->m_renderQueue->m_decals.getSize(); ++i)
-		{
-			const DecalQueueElement& decal = ctx.m_in->m_renderQueue->m_decals[i];
-			decalBox.setCenter(decal.m_obbCenter.xyz0());
-			decalBox.setRotation(Mat3x4(decal.m_obbRotation));
-			decalBox.setExtend(decal.m_obbExtend.xyz0());
-
-			if(!insideClusterFrustum(frustumPlanes, decalBox))
-			{
-				continue;
-			}
-
-			for(U clusterZ = 0; clusterZ < m_clusterCounts[2]; ++clusterZ)
-			{
-				if(!testCollisionShapes(decalBox, clusterBoxes[clusterZ]))
-				{
-					continue;
-				}
-
-				const U32 count = pIndices[clusterZ] - &indices[clusterZ * avgIndicesPerCluster];
-				if(ANKI_UNLIKELY(count + 3 >= avgIndicesPerCluster))
-				{
-					ANKI_R_LOGW("Out of cluster indices. Increase r.avgObjectsPerCluster");
-					continue;
-				}
-
-				*pIndices[clusterZ] = i;
-				*pCounts[clusterZ] += 1;
-				++pIndices[clusterZ];
-			}
-		}
-	}
+#define ANKI_SET_IDX(typeIdx) \
+	ClusterBin::TileCtx::ClusterMetaInfo& inf = tileCtx.m_clusterInfos[clusterZ]; \
+	if(ANKI_UNLIKELY(inf.m_offset + 1 >= m_avgObjectsPerCluster)) \
+	{ \
+		ANKI_R_LOGW("Out of cluster indices. Increase r.avgObjectsPerCluster"); \
+		continue; \
+	} \
+	tileCtx.getClusterIndices(clusterZ)[inf.m_offset++] = i; \
+	++inf.m_counts[typeIdx]; \
+	ANKI_ASSERT(inf.m_counts[typeIdx] <= m_avgObjectsPerCluster)
 
 	// Point lights
 	{
-		for(U clusterZ = 0; clusterZ < m_clusterCounts[2]; ++clusterZ)
-		{
-			pCounts[clusterZ] = pIndices[clusterZ];
-			*pCounts[clusterZ] = 0;
-			++pIndices[clusterZ];
-		}
-
 		Sphere lightSphere;
 		for(U i = 0; i < ctx.m_in->m_renderQueue->m_pointLights.getSize(); ++i)
 		{
@@ -397,29 +366,13 @@ void ClusterBin::binTile(U32 tileIdx, BinCtx& ctx, TileCtx& tileCtx)
 					continue;
 				}
 
-				const U32 count = pIndices[clusterZ] - &indices[clusterZ * avgIndicesPerCluster];
-				if(ANKI_UNLIKELY(count + 2 >= avgIndicesPerCluster))
-				{
-					ANKI_R_LOGW("Out of cluster indices. Increase r.avgObjectsPerCluster");
-					continue;
-				}
-
-				*pIndices[clusterZ] = i;
-				*pCounts[clusterZ] += 1;
-				++pIndices[clusterZ];
+				ANKI_SET_IDX(0);
 			}
 		}
 	}
 
 	// Spot lights
 	{
-		for(U clusterZ = 0; clusterZ < m_clusterCounts[2]; ++clusterZ)
-		{
-			pCounts[clusterZ] = pIndices[clusterZ];
-			*pCounts[clusterZ] = 0;
-			++pIndices[clusterZ];
-		}
-
 		PerspectiveFrustum slightFrustum;
 		for(U i = 0; i < ctx.m_in->m_renderQueue->m_spotLights.getSize(); ++i)
 		{
@@ -442,29 +395,13 @@ void ClusterBin::binTile(U32 tileIdx, BinCtx& ctx, TileCtx& tileCtx)
 					continue;
 				}
 
-				const U32 count = pIndices[clusterZ] - &indices[clusterZ * avgIndicesPerCluster];
-				if(ANKI_UNLIKELY(count + 1 >= avgIndicesPerCluster))
-				{
-					ANKI_R_LOGW("Out of cluster indices. Increase r.avgObjectsPerCluster");
-					continue;
-				}
-
-				*pIndices[clusterZ] = i;
-				*pCounts[clusterZ] += 1;
-				++pIndices[clusterZ];
+				ANKI_SET_IDX(1);
 			}
 		}
 	}
 
 	// Probes
 	{
-		for(U clusterZ = 0; clusterZ < m_clusterCounts[2]; ++clusterZ)
-		{
-			pCounts[clusterZ] = pIndices[clusterZ];
-			*pCounts[clusterZ] = 0;
-			++pIndices[clusterZ];
-		}
-
 		Aabb probeBox;
 		for(U i = 0; i < ctx.m_in->m_renderQueue->m_reflectionProbes.getSize(); ++i)
 		{
@@ -484,49 +421,121 @@ void ClusterBin::binTile(U32 tileIdx, BinCtx& ctx, TileCtx& tileCtx)
 					continue;
 				}
 
-				const U32 count = pIndices[clusterZ] - &indices[clusterZ * avgIndicesPerCluster];
-				if(ANKI_UNLIKELY(count >= avgIndicesPerCluster))
+				ANKI_SET_IDX(2);
+			}
+		}
+	}
+
+	// Decals
+	{
+		Obb decalBox;
+		for(U i = 0; i < ctx.m_in->m_renderQueue->m_decals.getSize(); ++i)
+		{
+			const DecalQueueElement& decal = ctx.m_in->m_renderQueue->m_decals[i];
+			decalBox.setCenter(decal.m_obbCenter.xyz0());
+			decalBox.setRotation(Mat3x4(decal.m_obbRotation));
+			decalBox.setExtend(decal.m_obbExtend.xyz0());
+
+			if(!insideClusterFrustum(frustumPlanes, decalBox))
+			{
+				continue;
+			}
+
+			for(U clusterZ = 0; clusterZ < m_clusterCounts[2]; ++clusterZ)
+			{
+				if(!testCollisionShapes(decalBox, clusterBoxes[clusterZ]))
 				{
-					ANKI_R_LOGW("Out of cluster indices. Increase r.avgObjectsPerCluster");
 					continue;
 				}
 
-				*pIndices[clusterZ] = i;
-				*pCounts[clusterZ] += 1;
-				++pIndices[clusterZ];
+				ANKI_SET_IDX(3);
 			}
 		}
 	}
 
-	// Upload the indices for all clusters of the tile
-	for(U clusterZ = 0; clusterZ < m_clusterCounts[2]; ++clusterZ)
+	// Fog volumes
 	{
-		const U indexCount = pIndices[clusterZ] - &indices[clusterZ * avgIndicesPerCluster];
-		ANKI_ASSERT(indexCount <= avgIndicesPerCluster);
-		ANKI_ASSERT(indexCount >= TYPED_OBJECT_COUNT);
-
-		U firstIndex;
-		if(indexCount > TYPED_OBJECT_COUNT)
+		Aabb box;
+		Sphere sphere;
+		for(U i = 0; i < ctx.m_in->m_renderQueue->m_fogDensityVolumes.getSize(); ++i)
 		{
-			// Have some objects to bin
+			const FogDensityQueueElement& fogVol = ctx.m_in->m_renderQueue->m_fogDensityVolumes[i];
 
-			firstIndex = ctx.m_allocatedIndexCount.fetchAdd(indexCount);
-			ANKI_ASSERT(firstIndex + indexCount <= ctx.m_lightIds.getSize());
+			CollisionShape* shape;
+			if(fogVol.m_isBox)
+			{
+				box.setMin(fogVol.m_aabbMin);
+				box.setMax(fogVol.m_aabbMax);
+				shape = &box;
+			}
+			else
+			{
+				sphere.setCenter(fogVol.m_sphereCenter.xyz0());
+				sphere.setRadius(fogVol.m_sphereRadius);
+				shape = &sphere;
+			}
+
+			if(!insideClusterFrustum(frustumPlanes, *shape))
+			{
+				continue;
+			}
 
-			memcpy(&ctx.m_lightIds[firstIndex],
-				&indices[clusterZ * avgIndicesPerCluster],
-				sizeof(ctx.m_lightIds[firstIndex]) * indexCount);
+			for(U clusterZ = 0; clusterZ < m_clusterCounts[2]; ++clusterZ)
+			{
+				if(!testCollisionShapes(*shape, clusterBoxes[clusterZ]))
+				{
+					continue;
+				}
+
+				ANKI_SET_IDX(4);
+			}
 		}
-		else
+	}
+
+	// Upload the indices for all clusters of the tile
+	for(U clusterZ = 0; clusterZ < m_clusterCounts[2]; ++clusterZ)
+	{
+		WeakArray<U32> inIndices = tileCtx.getClusterIndices(clusterZ);
+		const ClusterBin::TileCtx::ClusterMetaInfo& inf = tileCtx.m_clusterInfos[clusterZ];
+
+		const U other = (TYPED_OBJECT_COUNT - 1) + TYPED_OBJECT_COUNT;
+		const U indexCountPlusOther = inf.m_offset + other;
+		ANKI_ASSERT(indexCountPlusOther <= m_avgObjectsPerCluster + other);
+		ANKI_ASSERT(indexCountPlusOther >= other);
+
+		// Write indices
+		const U32 firstIndex = ctx.m_allocatedIndexCount.fetchAdd(indexCountPlusOther);
+		ANKI_ASSERT(firstIndex + indexCountPlusOther <= ctx.m_lightIds.getSize());
+		WeakArray<U32> outIndices(&ctx.m_lightIds[firstIndex], indexCountPlusOther);
+
+		// Write the offsets
+		U offset = firstIndex + TYPED_OBJECT_COUNT - 1;
+		for(U i = 1; i < TYPED_OBJECT_COUNT; ++i)
+		{
+			offset += inf.m_counts[i - 1] + 1; // Count plus the stop
+			outIndices[i - 1] = offset;
+		}
+
+		// Write indices
+		U outIndicesOffset = TYPED_OBJECT_COUNT - 1;
+		U inIndicesOffset = 0;
+		for(U i = 0; i < TYPED_OBJECT_COUNT; ++i)
 		{
-			// No typed objects, point to the preallocated cluster
-			firstIndex = 0;
+			for(U c = 0; c < inf.m_counts[i]; ++c)
+			{
+				outIndices[outIndicesOffset++] = inIndices[inIndicesOffset++];
+			}
+
+			// Stop
+			outIndices[outIndicesOffset++] = MAX_U32;
 		}
+		ANKI_ASSERT(inIndicesOffset == inf.m_offset);
+		ANKI_ASSERT(outIndicesOffset == indexCountPlusOther);
 
 		// Write the cluster
 		const U clusterIndex =
 			clusterZ * (m_clusterCounts[0] * m_clusterCounts[1]) + tileY * m_clusterCounts[0] + tileX;
-		ctx.m_clusters[clusterIndex] = firstIndex;
+		ctx.m_clusters[clusterIndex] = firstIndex + TYPED_OBJECT_COUNT - 1; // Points to the first object
 	}
 }
 
@@ -684,6 +693,42 @@ void ClusterBin::writeTypedObjectsToGpuBuffers(BinCtx& ctx) const
 	{
 		ctx.m_out->m_probesToken.markUnused();
 	}
+
+	// Fog volumes
+	const U visibleFogVolumeCount = rqueue.m_fogDensityVolumes.getSize();
+	if(visibleFogVolumeCount)
+	{
+		FogDensityVolume* data = static_cast<FogDensityVolume*>(
+			ctx.m_in->m_stagingMem->allocateFrame(sizeof(FogDensityVolume) * visibleFogVolumeCount,
+				StagingGpuMemoryType::UNIFORM,
+				ctx.m_out->m_fogDensityVolumesToken));
+
+		WeakArray<FogDensityVolume> gpuFogVolumes(data, visibleFogVolumeCount);
+
+		for(U i = 0; i < visibleFogVolumeCount; ++i)
+		{
+			const FogDensityQueueElement& in = rqueue.m_fogDensityVolumes[i];
+			FogDensityVolume& out = gpuFogVolumes[i];
+
+			out.m_density = in.m_density;
+			if(in.m_isBox)
+			{
+				out.m_isBox = 1;
+				out.m_aabbMinOrSphereCenter = in.m_aabbMin;
+				out.m_aabbMaxOrSphereRadiusSquared = in.m_aabbMax;
+			}
+			else
+			{
+				out.m_isBox = 0;
+				out.m_aabbMinOrSphereCenter = in.m_sphereCenter;
+				out.m_aabbMaxOrSphereRadiusSquared = Vec3(in.m_sphereRadius * in.m_sphereRadius);
+			}
+		}
+	}
+	else
+	{
+		ctx.m_out->m_fogDensityVolumesToken.markUnused();
+	}
 }
 
 } // end namespace anki

+ 2 - 0
src/anki/renderer/ClusterBin.h

@@ -40,6 +40,7 @@ public:
 	StagingGpuMemoryToken m_spotLightsToken;
 	StagingGpuMemoryToken m_probesToken;
 	StagingGpuMemoryToken m_decalsToken;
+	StagingGpuMemoryToken m_fogDensityVolumesToken;
 	StagingGpuMemoryToken m_clustersToken;
 	StagingGpuMemoryToken m_indicesToken;
 
@@ -68,6 +69,7 @@ private:
 	Array<U32, 3> m_clusterCounts = {};
 	U32 m_totalClusterCount = 0;
 	U32 m_indexCount = 0;
+	U32 m_avgObjectsPerCluster = 0;
 
 	DynamicArray<Vec4> m_clusterEdges; ///< Cache those for opt. [tileCount][K+1][4]
 	Vec4 m_prevUnprojParams = Vec4(0.0f); ///< To check if m_tiles is dirty.

+ 29 - 1
src/anki/renderer/RenderQueue.h

@@ -208,6 +208,33 @@ public:
 
 static_assert(std::is_trivially_destructible<UiQueueElement>::value == true, "Should be trivially destructible");
 
+/// Fog density queue element.
+class FogDensityQueueElement final
+{
+public:
+	union
+	{
+		Vec3 m_aabbMin;
+		Vec3 m_sphereCenter;
+	};
+
+	union
+	{
+		Vec3 m_aabbMax;
+		F32 m_sphereRadius;
+	};
+
+	F32 m_density;
+	Bool8 m_isBox;
+
+	FogDensityQueueElement()
+	{
+	}
+};
+
+static_assert(
+	std::is_trivially_destructible<FogDensityQueueElement>::value == true, "Should be trivially destructible");
+
 /// A callback to fill a coverage buffer.
 using FillCoverageBufferCallback = void (*)(void* userData, F32* depthValues, U32 width, U32 height);
 
@@ -225,9 +252,10 @@ public:
 	WeakArray<ReflectionProbeQueueElement> m_reflectionProbes;
 	WeakArray<LensFlareQueueElement> m_lensFlares;
 	WeakArray<DecalQueueElement> m_decals;
+	WeakArray<FogDensityQueueElement> m_fogDensityVolumes;
 	WeakArray<UiQueueElement> m_uis;
 
-	/// Applies only if the RenderQueue holds shadow casters. It's the timesamp that modified
+	/// Applies only if the RenderQueue holds shadow casters. It's the max timesamp of all shadow casters
 	Timestamp m_shadowRenderablesLastUpdateTimestamp = 0;
 
 	F32 m_cameraNear;

+ 2 - 1
src/anki/renderer/VolumetricLightingAccumulation.cpp

@@ -63,7 +63,7 @@ Error VolumetricLightingAccumulation::init(const ConfigSet& config)
 	// Create RTs
 	TextureInitInfo texinit = m_r->create2DRenderTargetInitInfo(m_volumeSize[0],
 		m_volumeSize[1],
-		LIGHT_SHADING_COLOR_ATTACHMENT_PIXEL_FORMAT,
+		Format::R16G16B16A16_SFLOAT,
 		TextureUsageBit::IMAGE_COMPUTE_READ_WRITE | TextureUsageBit::SAMPLED_FRAGMENT
 			| TextureUsageBit::SAMPLED_COMPUTE,
 		"VolLight");
@@ -124,6 +124,7 @@ void VolumetricLightingAccumulation::run(RenderPassWorkContext& rgraphCtx)
 	bindUniforms(cmdb, 0, 1, rsrc.m_pointLightsToken);
 	bindUniforms(cmdb, 0, 2, rsrc.m_spotLightsToken);
 	bindUniforms(cmdb, 0, 3, rsrc.m_probesToken);
+	bindUniforms(cmdb, 0, 4, rsrc.m_fogDensityVolumesToken);
 	bindStorage(cmdb, 0, 0, rsrc.m_clustersToken);
 	bindStorage(cmdb, 0, 1, rsrc.m_indicesToken);
 

+ 1 - 1
src/anki/scene/CameraNode.cpp

@@ -79,7 +79,7 @@ Error CameraNode::init(Frustum* frustum)
 		| FrustumComponentVisibilityTestFlag::LENS_FLARE_COMPONENTS
 		| FrustumComponentVisibilityTestFlag::REFLECTION_PROBES | FrustumComponentVisibilityTestFlag::REFLECTION_PROXIES
 		| FrustumComponentVisibilityTestFlag::OCCLUDERS | FrustumComponentVisibilityTestFlag::DECALS
-		| FrustumComponentVisibilityTestFlag::EARLY_Z);
+		| FrustumComponentVisibilityTestFlag::FOG_DENSITY_COMPONENTS | FrustumComponentVisibilityTestFlag::EARLY_Z);
 
 	// Feedback component #2
 	newComponent<CameraFrustumFeedbackComponent>();

+ 83 - 0
src/anki/scene/FogDensityNode.cpp

@@ -0,0 +1,83 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/scene/FogDensityNode.h>
+#include <anki/scene/components/MoveComponent.h>
+#include <anki/scene/components/FogDensityComponent.h>
+#include <anki/scene/components/SpatialComponent.h>
+
+namespace anki
+{
+
+class FogDensityNode::FeedbackComponent : public SceneComponent
+{
+public:
+	FeedbackComponent(SceneNode* node)
+		: SceneComponent(SceneComponentType::NONE, node)
+	{
+	}
+
+	ANKI_USE_RESULT Error update(Second, Second, Bool& updated) override
+	{
+		updated = false;
+
+		const MoveComponent& movec = m_node->getComponent<MoveComponent>();
+		if(movec.getTimestamp() == m_node->getGlobalTimestamp())
+		{
+			static_cast<FogDensityNode*>(m_node)->moveUpdated(movec);
+		}
+
+		return Error::NONE;
+	}
+};
+
+FogDensityNode::FogDensityNode(SceneGraph* scene, CString name)
+	: SceneNode(scene, name)
+{
+	// Create components
+	newComponent<MoveComponent>(MoveComponentFlag::NONE);
+	newComponent<FeedbackComponent>();
+	newComponent<FogDensityComponent>();
+	newComponent<SpatialComponent>(&m_spatialBox);
+}
+
+FogDensityNode::~FogDensityNode()
+{
+}
+
+void FogDensityNode::moveUpdated(const MoveComponent& movec)
+{
+	// Update the fog component
+	FogDensityComponent& fogc = getComponent<FogDensityComponent>();
+	fogc.updatePosition(movec.getWorldTransform().getOrigin());
+
+	// Update the spatial component
+	SpatialComponent& spatialc = getComponent<SpatialComponent>();
+
+	Vec4 min, max;
+	if(fogc.isAabb())
+	{
+		fogc.getAabb(min, max);
+	}
+	else
+	{
+		F32 radius;
+		fogc.getSphere(radius);
+
+		min = Vec4(-radius, -radius, -radius, 0.0f);
+		max = Vec4(radius, radius, radius, 0.0f);
+	}
+
+	min += movec.getWorldTransform().getOrigin();
+	max += movec.getWorldTransform().getOrigin();
+
+	m_spatialBox.setMin(min);
+	m_spatialBox.setMax(max);
+
+	spatialc.setSpatialOrigin(movec.getWorldTransform().getOrigin());
+	spatialc.markForUpdate();
+}
+
+} // end namespace anki

+ 39 - 0
src/anki/scene/FogDensityNode.h

@@ -0,0 +1,39 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/scene/SceneNode.h>
+#include <anki/collision/Aabb.h>
+
+namespace anki
+{
+
+/// @addtogroup scene
+/// @{
+
+/// Fog density node.
+class FogDensityNode : public SceneNode
+{
+public:
+	FogDensityNode(SceneGraph* scene, CString name);
+
+	~FogDensityNode();
+
+	ANKI_USE_RESULT Error init()
+	{
+		return Error::NONE;
+	}
+
+private:
+	class FeedbackComponent;
+
+	Aabb m_spatialBox;
+
+	void moveUpdated(const MoveComponent& movec);
+};
+/// @}
+
+} // end namespace anki

+ 52 - 39
src/anki/scene/Visibility.cpp

@@ -12,8 +12,9 @@
 #include <anki/scene/components/ReflectionProxyComponent.h>
 #include <anki/scene/components/OccluderComponent.h>
 #include <anki/scene/components/DecalComponent.h>
-#include <anki/scene/LightNode.h>
 #include <anki/scene/components/MoveComponent.h>
+#include <anki/scene/components/FogDensityComponent.h>
+#include <anki/scene/components/LightComponent.h>
 #include <anki/renderer/MainRenderer.h>
 #include <anki/util/Logger.h>
 #include <anki/util/ThreadHive.h>
@@ -221,6 +222,9 @@ void VisibilityTestTask::test(ThreadHive& hive, U32 taskId)
 
 	const Bool wantsDecals = testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::DECALS);
 
+	const Bool wantsFogDensityComponents =
+		testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::FOG_DENSITY_COMPONENTS);
+
 	const Bool wantsEarlyZ = testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::EARLY_Z)
 							 && m_frcCtx->m_visCtx->m_earlyZDist > 0.0f;
 
@@ -241,43 +245,49 @@ void VisibilityTestTask::test(ThreadHive& hive, U32 taskId)
 		// Check what components the frustum needs
 		Bool wantNode = false;
 
-		const RenderComponent* rc = node.tryGetComponent<RenderComponent>();
-		if(rc && wantsRenderComponents)
+		const RenderComponent* rc = nullptr;
+		if(wantsRenderComponents && (rc = node.tryGetComponent<RenderComponent>()))
+		{
+			wantNode = true;
+		}
+
+		if(wantsShadowCasters && (rc = node.tryGetComponent<RenderComponent>()) && rc->getCastsShadow())
 		{
 			wantNode = true;
 		}
 
-		if(rc && rc->getCastsShadow() && wantsShadowCasters)
+		const LightComponent* lc = nullptr;
+		if(wantsLightComponents && (lc = node.tryGetComponent<LightComponent>()))
 		{
 			wantNode = true;
 		}
 
-		const LightComponent* lc = node.tryGetComponent<LightComponent>();
-		if(lc && wantsLightComponents)
+		const LensFlareComponent* lfc = nullptr;
+		if(wantsFlareComponents && (lfc = node.tryGetComponent<LensFlareComponent>()))
 		{
 			wantNode = true;
 		}
 
-		const LensFlareComponent* lfc = node.tryGetComponent<LensFlareComponent>();
-		if(lfc && wantsFlareComponents)
+		const ReflectionProbeComponent* reflc = nullptr;
+		if(wantsReflectionProbes && (reflc = node.tryGetComponent<ReflectionProbeComponent>()))
 		{
 			wantNode = true;
 		}
 
-		const ReflectionProbeComponent* reflc = node.tryGetComponent<ReflectionProbeComponent>();
-		if(reflc && wantsReflectionProbes)
+		const ReflectionProxyComponent* proxyc = nullptr;
+		if(wantsReflectionProxies && (proxyc = node.tryGetComponent<ReflectionProxyComponent>()))
 		{
 			wantNode = true;
 		}
 
-		const ReflectionProxyComponent* proxyc = node.tryGetComponent<ReflectionProxyComponent>();
-		if(proxyc && wantsReflectionProxies)
+		DecalComponent* decalc = nullptr;
+		if(wantsDecals && (decalc = node.tryGetComponent<DecalComponent>()))
 		{
 			wantNode = true;
 		}
 
-		DecalComponent* decalc = node.tryGetComponent<DecalComponent>();
-		if(decalc && wantsDecals)
+		const FogDensityComponent* fogc = nullptr;
+		if(wantsFogDensityComponents && (fogc = node.tryGetComponent<FogDensityComponent>()))
 		{
 			wantNode = true;
 		}
@@ -336,34 +346,30 @@ void VisibilityTestTask::test(ThreadHive& hive, U32 taskId)
 
 		if(rc)
 		{
-			if(wantsRenderComponents || (wantsShadowCasters && rc->getCastsShadow()))
+			RenderableQueueElement* el;
+			if(rc->isForwardShading())
 			{
-				RenderableQueueElement* el;
-				if(rc->isForwardShading())
-				{
-					el = result.m_forwardShadingRenderables.newElement(alloc);
-				}
-				else
-				{
-					el = result.m_renderables.newElement(alloc);
-				}
+				el = result.m_forwardShadingRenderables.newElement(alloc);
+			}
+			else
+			{
+				el = result.m_renderables.newElement(alloc);
+			}
 
-				rc->setupRenderableQueueElement(*el);
+			rc->setupRenderableQueueElement(*el);
 
-				// Compute distance from the frustum
-				const Plane& nearPlane = testedFrc.getFrustum().getPlanesWorldSpace()[FrustumPlaneType::NEAR];
-				el->m_distanceFromCamera = max(0.0f, sps[0].m_sp->getAabb().testPlane(nearPlane));
+			// Compute distance from the frustum
+			const Plane& nearPlane = testedFrc.getFrustum().getPlanesWorldSpace()[FrustumPlaneType::NEAR];
+			el->m_distanceFromCamera = max(0.0f, sps[0].m_sp->getAabb().testPlane(nearPlane));
 
-				if(wantsEarlyZ && el->m_distanceFromCamera < m_frcCtx->m_visCtx->m_earlyZDist
-					&& !rc->isForwardShading())
-				{
-					RenderableQueueElement* el2 = result.m_earlyZRenderables.newElement(alloc);
-					*el2 = *el;
-				}
+			if(wantsEarlyZ && el->m_distanceFromCamera < m_frcCtx->m_visCtx->m_earlyZDist && !rc->isForwardShading())
+			{
+				RenderableQueueElement* el2 = result.m_earlyZRenderables.newElement(alloc);
+				*el2 = *el;
 			}
 		}
 
-		if(lc && wantsLightComponents)
+		if(lc)
 		{
 			switch(lc->getLightComponentType())
 			{
@@ -420,13 +426,13 @@ void VisibilityTestTask::test(ThreadHive& hive, U32 taskId)
 			}
 		}
 
-		if(lfc && wantsFlareComponents)
+		if(lfc)
 		{
 			LensFlareQueueElement* el = result.m_lensFlares.newElement(alloc);
 			lfc->setupLensFlareQueueElement(*el);
 		}
 
-		if(reflc && wantsReflectionProbes)
+		if(reflc)
 		{
 			ReflectionProbeQueueElement* el = result.m_reflectionProbes.newElement(alloc);
 			reflc->setupReflectionProbeQueueElement(*el);
@@ -449,17 +455,23 @@ void VisibilityTestTask::test(ThreadHive& hive, U32 taskId)
 			}
 		}
 
-		if(proxyc && wantsReflectionProxies)
+		if(proxyc)
 		{
 			ANKI_ASSERT(!"TODO");
 		}
 
-		if(decalc && wantsDecals)
+		if(decalc)
 		{
 			DecalQueueElement* el = result.m_decals.newElement(alloc);
 			decalc->setupDecalQueueElement(*el);
 		}
 
+		if(fogc)
+		{
+			FogDensityQueueElement* el = result.m_fogDensityVolumes.newElement(alloc);
+			fogc->setupFogDensityQueueElement(*el);
+		}
+
 		// Add more frustums to the list
 		if(nextQueues.getSize() > 0)
 		{
@@ -532,6 +544,7 @@ void CombineResultsTask::combine()
 	ANKI_VIS_COMBINE(ReflectionProbeQueueElement, m_reflectionProbes);
 	ANKI_VIS_COMBINE(LensFlareQueueElement, m_lensFlares);
 	ANKI_VIS_COMBINE(DecalQueueElement, m_decals);
+	ANKI_VIS_COMBINE(FogDensityQueueElement, m_fogDensityVolumes);
 
 #undef ANKI_VIS_COMBINE
 #undef ANKI_VIS_COMBINE_AND_PTR

+ 1 - 0
src/anki/scene/VisibilityInternal.h

@@ -113,6 +113,7 @@ public:
 	TRenderQueueElementStorage<ReflectionProbeQueueElement> m_reflectionProbes;
 	TRenderQueueElementStorage<LensFlareQueueElement> m_lensFlares;
 	TRenderQueueElementStorage<DecalQueueElement> m_decals;
+	TRenderQueueElementStorage<FogDensityQueueElement> m_fogDensityVolumes;
 
 	Timestamp m_timestamp = 0;
 };

+ 115 - 0
src/anki/scene/components/FogDensityComponent.h

@@ -0,0 +1,115 @@
+// Copyright (C) 2009-2018, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/scene/components/SceneComponent.h>
+#include <anki/renderer/RenderQueue.h>
+#include <anki/collision/Aabb.h>
+#include <anki/collision/Sphere.h>
+
+namespace anki
+{
+
+/// @addtogroup scene
+/// @{
+
+/// Fog density component. Controls the fog density.
+class FogDensityComponent : public SceneComponent
+{
+public:
+	static const SceneComponentType CLASS_TYPE = SceneComponentType::FOG_DENSITY;
+
+	FogDensityComponent(SceneNode* node)
+		: SceneComponent(CLASS_TYPE, node)
+	{
+	}
+
+	void setAabb(const Vec4& aabbMin, const Vec4& aabbMax)
+	{
+		m_aabbMin = aabbMin;
+		m_aabbMax = aabbMax;
+		m_box = true;
+	}
+
+	void setSphere(F32 radius)
+	{
+		m_sphereRadius = radius;
+		m_box = false;
+	}
+
+	Bool isAabb() const
+	{
+		return m_box == true;
+	}
+
+	void getAabb(Vec4& aabbMin, Vec4& aabbMax) const
+	{
+		ANKI_ASSERT(isAabb());
+		aabbMin = m_aabbMin;
+		aabbMax = m_aabbMax;
+	}
+
+	void getSphere(F32& radius) const
+	{
+		ANKI_ASSERT(!isAabb());
+		radius = m_sphereRadius;
+	}
+
+	void setDensity(F32 d)
+	{
+		ANKI_ASSERT(d >= 0.0f);
+		m_density = d;
+	}
+
+	F32 getDensity() const
+	{
+		return m_density;
+	}
+
+	void updatePosition(const Vec4& pos)
+	{
+		m_worldPos = pos;
+	}
+
+	void setupFogDensityQueueElement(FogDensityQueueElement& el) const
+	{
+		el.m_density = m_density;
+		el.m_isBox = m_box;
+		if(m_box)
+		{
+			el.m_aabbMin = (m_aabbMin + m_worldPos).xyz();
+			el.m_aabbMax = (m_aabbMax + m_worldPos).xyz();
+		}
+		else
+		{
+			el.m_sphereCenter = m_worldPos.xyz();
+			el.m_sphereRadius = m_sphereRadius;
+		}
+	}
+
+	/// Implements SceneComponent::update.
+	ANKI_USE_RESULT Error update(Second, Second, Bool& updated) override
+	{
+		updated = false;
+		return Error::NONE;
+	}
+
+private:
+	Vec4 m_aabbMin{0.0f};
+
+	union
+	{
+		Vec4 m_aabbMax{1.0f};
+		F32 m_sphereRadius;
+	};
+
+	Vec4 m_worldPos{0.0f};
+
+	F32 m_density = 1.0f;
+	Bool8 m_box = false;
+};
+
+} // end namespace anki

+ 5 - 4
src/anki/scene/components/FrustumComponent.h

@@ -30,10 +30,11 @@ enum class FrustumComponentVisibilityTestFlag : U16
 	REFLECTION_PROXIES = 1 << 5,
 	OCCLUDERS = 1 << 6,
 	DECALS = 1 << 7,
-	EARLY_Z = 1 << 8,
+	FOG_DENSITY_COMPONENTS = 1 << 8,
+	EARLY_Z = 1 << 9,
 
 	ALL_TESTS = RENDER_COMPONENTS | LIGHT_COMPONENTS | LENS_FLARE_COMPONENTS | SHADOW_CASTERS | REFLECTION_PROBES
-				| REFLECTION_PROXIES | DECALS | EARLY_Z
+				| REFLECTION_PROXIES | DECALS | FOG_DENSITY_COMPONENTS | EARLY_Z
 };
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(FrustumComponentVisibilityTestFlag, inline)
 
@@ -184,8 +185,8 @@ public:
 private:
 	enum Flags
 	{
-		SHAPE_MARKED_FOR_UPDATE = 1 << 9,
-		TRANSFORM_MARKED_FOR_UPDATE = 1 << 10,
+		SHAPE_MARKED_FOR_UPDATE = 1 << 10,
+		TRANSFORM_MARKED_FOR_UPDATE = 1 << 12,
 	};
 	ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(Flags, friend)
 

+ 2 - 0
src/anki/scene/components/SceneComponent.h

@@ -34,6 +34,7 @@ enum class SceneComponentType : U16
 	SCRIPT,
 	JOINT,
 	TRIGGER,
+	FOG_DENSITY,
 	PLAYER_CONTROLLER,
 
 	COUNT,
@@ -74,6 +75,7 @@ public:
 	/// Called only by the SceneGraph
 	ANKI_USE_RESULT Error updateReal(Second prevTime, Second crntTime, Bool& updated);
 
+	/// Unique ID in the same run.
 	U64 getUuid() const
 	{
 		return m_uuid;

+ 30 - 5
src/anki/script/LuaBinder.cpp

@@ -10,6 +10,24 @@
 namespace anki
 {
 
+// Forward
+#define ANKI_SCRIPT_CALL_WRAP(x_) void wrapModule##x_(lua_State*)
+ANKI_SCRIPT_CALL_WRAP(Logger);
+ANKI_SCRIPT_CALL_WRAP(Math);
+ANKI_SCRIPT_CALL_WRAP(Renderer);
+ANKI_SCRIPT_CALL_WRAP(Scene);
+#undef ANKI_SCRIPT_CALL_WRAP
+
+static void wrapModules(lua_State* l)
+{
+#define ANKI_SCRIPT_CALL_WRAP(x_) wrapModule##x_(l)
+	ANKI_SCRIPT_CALL_WRAP(Logger);
+	ANKI_SCRIPT_CALL_WRAP(Math);
+	ANKI_SCRIPT_CALL_WRAP(Renderer);
+	ANKI_SCRIPT_CALL_WRAP(Scene);
+#undef ANKI_SCRIPT_CALL_WRAP
+}
+
 static int luaPanic(lua_State* l)
 {
 	ANKI_SCRIPT_LOGE("Lua panic attack: %s", lua_tostring(l, -1));
@@ -37,6 +55,8 @@ Error LuaBinder::create(ScriptAllocator alloc, void* parent)
 	luaL_openlibs(m_l);
 	lua_atpanic(m_l, &luaPanic);
 
+	wrapModules(m_l);
+
 	return Error::NONE;
 }
 
@@ -256,8 +276,15 @@ LuaThread LuaBinder::newLuaThread()
 {
 	LuaThread out;
 
-	out.m_luaState = lua_newthread(m_l);
-	out.m_reference = luaL_ref(m_l, LUA_REGISTRYINDEX);
+	void* ud;
+	auto allocCallback = lua_getallocf(m_l, &ud);
+	ANKI_ASSERT(ud && allocCallback);
+
+	out.m_luaState = lua_newstate(allocCallback, ud);
+	luaL_openlibs(out.m_luaState);
+	lua_atpanic(out.m_luaState, &luaPanic);
+
+	wrapModules(out.m_luaState);
 
 	return out;
 }
@@ -266,12 +293,10 @@ void LuaBinder::destroyLuaThread(LuaThread& luaThread)
 {
 	if(luaThread.m_luaState)
 	{
-		// Unref it, garbage collector will take care of it
-		luaL_unref(m_l, LUA_REGISTRYINDEX, luaThread.m_reference);
+		lua_close(luaThread.m_luaState);
 	}
 
 	luaThread.m_luaState = nullptr;
-	luaThread.m_reference = -1;
 }
 
 void LuaBinder::serializeGlobals(lua_State* l, LuaBinderSerializeGlobalsCallback& callback)

+ 0 - 6
src/anki/script/LuaBinder.h

@@ -154,14 +154,8 @@ public:
 		ANKI_ASSERT(m_luaState == nullptr);
 		m_luaState = b.m_luaState;
 		b.m_luaState = nullptr;
-
-		m_reference = b.m_reference;
-		b.m_reference = -1;
 		return *this;
 	}
-
-private:
-	int m_reference = -1;
 };
 
 /// @memberof LuaBinder

+ 354 - 0
src/anki/script/Scene.cpp

@@ -1634,6 +1634,233 @@ static inline void wrapTriggerComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
+LuaUserDataTypeInfo luaUserDataTypeInfoFogDensityComponent = {3433641273323722630,
+	"FogDensityComponent",
+	LuaUserData::computeSizeForGarbageCollected<FogDensityComponent>(),
+	nullptr,
+	nullptr};
+
+template<>
+const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<FogDensityComponent>()
+{
+	return luaUserDataTypeInfoFogDensityComponent;
+}
+
+/// Pre-wrap method FogDensityComponent::setAabb.
+static inline int pwrapFogDensityComponentsetAabb(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 3)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoFogDensityComponent, ud))
+	{
+		return -1;
+	}
+
+	FogDensityComponent* self = ud->getData<FogDensityComponent>();
+
+	// Pop arguments
+	extern LuaUserDataTypeInfo luaUserDataTypeInfoVec4;
+	if(ANKI_UNLIKELY(LuaBinder::checkUserData(l, 2, luaUserDataTypeInfoVec4, ud)))
+	{
+		return -1;
+	}
+
+	Vec4* iarg0 = ud->getData<Vec4>();
+	const Vec4& arg0(*iarg0);
+
+	extern LuaUserDataTypeInfo luaUserDataTypeInfoVec4;
+	if(ANKI_UNLIKELY(LuaBinder::checkUserData(l, 3, luaUserDataTypeInfoVec4, ud)))
+	{
+		return -1;
+	}
+
+	Vec4* iarg1 = ud->getData<Vec4>();
+	const Vec4& arg1(*iarg1);
+
+	// Call the method
+	self->setAabb(arg0, arg1);
+
+	return 0;
+}
+
+/// Wrap method FogDensityComponent::setAabb.
+static int wrapFogDensityComponentsetAabb(lua_State* l)
+{
+	int res = pwrapFogDensityComponentsetAabb(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Pre-wrap method FogDensityComponent::setSphere.
+static inline int pwrapFogDensityComponentsetSphere(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 2)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoFogDensityComponent, ud))
+	{
+		return -1;
+	}
+
+	FogDensityComponent* self = ud->getData<FogDensityComponent>();
+
+	// Pop arguments
+	F32 arg0;
+	if(ANKI_UNLIKELY(LuaBinder::checkNumber(l, 2, arg0)))
+	{
+		return -1;
+	}
+
+	// Call the method
+	self->setSphere(arg0);
+
+	return 0;
+}
+
+/// Wrap method FogDensityComponent::setSphere.
+static int wrapFogDensityComponentsetSphere(lua_State* l)
+{
+	int res = pwrapFogDensityComponentsetSphere(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Pre-wrap method FogDensityComponent::setDensity.
+static inline int pwrapFogDensityComponentsetDensity(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 2)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoFogDensityComponent, ud))
+	{
+		return -1;
+	}
+
+	FogDensityComponent* self = ud->getData<FogDensityComponent>();
+
+	// Pop arguments
+	F32 arg0;
+	if(ANKI_UNLIKELY(LuaBinder::checkNumber(l, 2, arg0)))
+	{
+		return -1;
+	}
+
+	// Call the method
+	self->setDensity(arg0);
+
+	return 0;
+}
+
+/// Wrap method FogDensityComponent::setDensity.
+static int wrapFogDensityComponentsetDensity(lua_State* l)
+{
+	int res = pwrapFogDensityComponentsetDensity(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Pre-wrap method FogDensityComponent::getDensity.
+static inline int pwrapFogDensityComponentgetDensity(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 1)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoFogDensityComponent, ud))
+	{
+		return -1;
+	}
+
+	FogDensityComponent* self = ud->getData<FogDensityComponent>();
+
+	// Call the method
+	F32 ret = self->getDensity();
+
+	// Push return value
+	lua_pushnumber(l, ret);
+
+	return 1;
+}
+
+/// Wrap method FogDensityComponent::getDensity.
+static int wrapFogDensityComponentgetDensity(lua_State* l)
+{
+	int res = pwrapFogDensityComponentgetDensity(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Wrap class FogDensityComponent.
+static inline void wrapFogDensityComponent(lua_State* l)
+{
+	LuaBinder::createClass(l, &luaUserDataTypeInfoFogDensityComponent);
+	LuaBinder::pushLuaCFuncMethod(l, "setAabb", wrapFogDensityComponentsetAabb);
+	LuaBinder::pushLuaCFuncMethod(l, "setSphere", wrapFogDensityComponentsetSphere);
+	LuaBinder::pushLuaCFuncMethod(l, "setDensity", wrapFogDensityComponentsetDensity);
+	LuaBinder::pushLuaCFuncMethod(l, "getDensity", wrapFogDensityComponentgetDensity);
+	lua_settop(l, 0);
+}
+
 LuaUserDataTypeInfo luaUserDataTypeInfoSceneNode = {
 	-2220074417980276571, "SceneNode", LuaUserData::computeSizeForGarbageCollected<SceneNode>(), nullptr, nullptr};
 
@@ -2057,6 +2284,61 @@ static int wrapSceneNodegetTriggerComponent(lua_State* l)
 	return 0;
 }
 
+/// Pre-wrap method SceneNode::tryGetComponent<FogDensityComponent>.
+static inline int pwrapSceneNodegetFogDensityComponent(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 1)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoSceneNode, ud))
+	{
+		return -1;
+	}
+
+	SceneNode* self = ud->getData<SceneNode>();
+
+	// Call the method
+	FogDensityComponent* ret = self->tryGetComponent<FogDensityComponent>();
+
+	// Push return value
+	if(ANKI_UNLIKELY(ret == nullptr))
+	{
+		lua_pushstring(l, "Glue code returned nullptr");
+		return -1;
+	}
+
+	voidp = lua_newuserdata(l, sizeof(LuaUserData));
+	ud = static_cast<LuaUserData*>(voidp);
+	luaL_setmetatable(l, "FogDensityComponent");
+	extern LuaUserDataTypeInfo luaUserDataTypeInfoFogDensityComponent;
+	ud->initPointed(&luaUserDataTypeInfoFogDensityComponent, const_cast<FogDensityComponent*>(ret));
+
+	return 1;
+}
+
+/// Wrap method SceneNode::tryGetComponent<FogDensityComponent>.
+static int wrapSceneNodegetFogDensityComponent(lua_State* l)
+{
+	int res = pwrapSceneNodegetFogDensityComponent(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
 /// Wrap class SceneNode.
 static inline void wrapSceneNode(lua_State* l)
 {
@@ -2069,6 +2351,7 @@ static inline void wrapSceneNode(lua_State* l)
 	LuaBinder::pushLuaCFuncMethod(l, "getLensFlareComponent", wrapSceneNodegetLensFlareComponent);
 	LuaBinder::pushLuaCFuncMethod(l, "getDecalComponent", wrapSceneNodegetDecalComponent);
 	LuaBinder::pushLuaCFuncMethod(l, "getTriggerComponent", wrapSceneNodegetTriggerComponent);
+	LuaBinder::pushLuaCFuncMethod(l, "getFogDensityComponent", wrapSceneNodegetFogDensityComponent);
 	lua_settop(l, 0);
 }
 
@@ -2949,6 +3232,75 @@ static inline void wrapTriggerNode(lua_State* l)
 	lua_settop(l, 0);
 }
 
+LuaUserDataTypeInfo luaUserDataTypeInfoFogDensityNode = {-2463430472135938886,
+	"FogDensityNode",
+	LuaUserData::computeSizeForGarbageCollected<FogDensityNode>(),
+	nullptr,
+	nullptr};
+
+template<>
+const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<FogDensityNode>()
+{
+	return luaUserDataTypeInfoFogDensityNode;
+}
+
+/// Pre-wrap method FogDensityNode::getSceneNodeBase.
+static inline int pwrapFogDensityNodegetSceneNodeBase(lua_State* l)
+{
+	LuaUserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	if(ANKI_UNLIKELY(LuaBinder::checkArgsCount(l, 1)))
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoFogDensityNode, ud))
+	{
+		return -1;
+	}
+
+	FogDensityNode* self = ud->getData<FogDensityNode>();
+
+	// Call the method
+	SceneNode& ret = *self;
+
+	// Push return value
+	voidp = lua_newuserdata(l, sizeof(LuaUserData));
+	ud = static_cast<LuaUserData*>(voidp);
+	luaL_setmetatable(l, "SceneNode");
+	extern LuaUserDataTypeInfo luaUserDataTypeInfoSceneNode;
+	ud->initPointed(&luaUserDataTypeInfoSceneNode, const_cast<SceneNode*>(&ret));
+
+	return 1;
+}
+
+/// Wrap method FogDensityNode::getSceneNodeBase.
+static int wrapFogDensityNodegetSceneNodeBase(lua_State* l)
+{
+	int res = pwrapFogDensityNodegetSceneNodeBase(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Wrap class FogDensityNode.
+static inline void wrapFogDensityNode(lua_State* l)
+{
+	LuaBinder::createClass(l, &luaUserDataTypeInfoFogDensityNode);
+	LuaBinder::pushLuaCFuncMethod(l, "getSceneNodeBase", wrapFogDensityNodegetSceneNodeBase);
+	lua_settop(l, 0);
+}
+
 LuaUserDataTypeInfo luaUserDataTypeInfoSceneGraph = {
 	-7754439619132389154, "SceneGraph", LuaUserData::computeSizeForGarbageCollected<SceneGraph>(), nullptr, nullptr};
 
@@ -4167,6 +4519,7 @@ void wrapModuleScene(lua_State* l)
 	wrapDecalComponent(l);
 	wrapLensFlareComponent(l);
 	wrapTriggerComponent(l);
+	wrapFogDensityComponent(l);
 	wrapSceneNode(l);
 	wrapModelNode(l);
 	wrapPerspectiveCameraNode(l);
@@ -4179,6 +4532,7 @@ void wrapModuleScene(lua_State* l)
 	wrapOccluderNode(l);
 	wrapDecalNode(l);
 	wrapTriggerNode(l);
+	wrapFogDensityNode(l);
 	wrapSceneGraph(l);
 	wrapEvent(l);
 	wrapLightEvent(l);

+ 34 - 0
src/anki/script/Scene.xml

@@ -215,6 +215,29 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 				</method>
 			</methods>
 		</class>
+		<class name="FogDensityComponent">
+			<methods>
+				<method name="setAabb">
+					<args>
+						<arg>const Vec4&amp;</arg>
+						<arg>const Vec4&amp;</arg>
+					</args>
+				</method>
+				<method name="setSphere">
+					<args>
+						<arg>F32</arg>
+					</args>
+				</method>
+				<method name="setDensity">
+					<args>
+						<arg>F32</arg>
+					</args>
+				</method>
+				<method name="getDensity">
+					<return>F32</return>
+				</method>
+			</methods>
+		</class>
 
 		<!-- Nodes -->
 		<class name="SceneNode">
@@ -243,6 +266,9 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 				<method name="tryGetComponent&lt;TriggerComponent&gt;" alias="getTriggerComponent">
 					<return>TriggerComponent*</return>
 				</method>
+				<method name="tryGetComponent&lt;FogDensityComponent&gt;" alias="getFogDensityComponent">
+					<return>FogDensityComponent*</return>
+				</method>
 			</methods>
 		</class>
 		<class name="ModelNode">
@@ -347,6 +373,14 @@ using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 				</method>
 			</methods>
 		</class>
+		<class name="FogDensityNode">
+			<methods>
+				<method name="getSceneNodeBase">
+					<overrideCall>SceneNode&amp; ret = *self;</overrideCall>
+					<return>SceneNode&amp;</return>
+				</method>
+			</methods>
+		</class>
 		<class name="SceneGraph">
 			<methods>
 				<method name="newPerspectiveCameraNode">

+ 0 - 18
src/anki/script/ScriptManager.cpp

@@ -10,14 +10,6 @@
 namespace anki
 {
 
-// Forward
-#define ANKI_SCRIPT_CALL_WRAP(x_) void wrapModule##x_(lua_State*)
-ANKI_SCRIPT_CALL_WRAP(Logger);
-ANKI_SCRIPT_CALL_WRAP(Math);
-ANKI_SCRIPT_CALL_WRAP(Renderer);
-ANKI_SCRIPT_CALL_WRAP(Scene);
-#undef ANKI_SCRIPT_CALL_WRAP
-
 ScriptManager::ScriptManager()
 {
 }
@@ -35,16 +27,6 @@ Error ScriptManager::init(AllocAlignedCallback allocCb, void* allocCbData)
 
 	ANKI_CHECK(m_lua.create(m_alloc, this));
 
-	// Wrap stuff
-	lua_State* l = m_lua.getLuaState();
-
-#define ANKI_SCRIPT_CALL_WRAP(x_) wrapModule##x_(l)
-	ANKI_SCRIPT_CALL_WRAP(Logger);
-	ANKI_SCRIPT_CALL_WRAP(Math);
-	ANKI_SCRIPT_CALL_WRAP(Renderer);
-	ANKI_SCRIPT_CALL_WRAP(Scene);
-#undef ANKI_SCRIPT_CALL_WRAP
-
 	return Error::NONE;
 }