Browse Source

Some decal bits

Panagiotis Christopoulos Charitos 9 years ago
parent
commit
7bd0b7d883

+ 13 - 13
shaders/FsCommonFrag.glsl

@@ -130,21 +130,21 @@ vec3 computeLightColor(vec3 diffCol)
 	}
 
 	// Find the cluster and then the light counts
-	uint idxOffset;
-	uint idx;
-	{
-		uint clusterIdx = computeClusterIndexUsingCustomFragCoord(u_lightingUniforms.nearFarClustererMagicPad1.x,
-			u_lightingUniforms.nearFarClustererMagicPad1.z,
-			fragPos.z,
-			u_lightingUniforms.tileCountPad1.x,
-			u_lightingUniforms.tileCountPad1.y,
-			gl_FragCoord.xy * 2.0);
-
-		idxOffset = u_clusters[clusterIdx];
-	}
+	uint clusterIdx = computeClusterIndexUsingCustomFragCoord(u_lightingUniforms.nearFarClustererMagicPad1.x,
+		u_lightingUniforms.nearFarClustererMagicPad1.z,
+		fragPos.z,
+		u_lightingUniforms.tileCountPad1.x,
+		u_lightingUniforms.tileCountPad1.y,
+		gl_FragCoord.xy * 2.0);
+
+	uint idxOffset = u_clusters[clusterIdx];
+
+	// Skip decals
+	uint count = u_lightIndices[idxOffset];
+	idxOffset += count + 1;
 
 	// Point lights
-	uint count = u_lightIndices[idxOffset++];
+	count = u_lightIndices[idxOffset++];
 	while(count-- != 0)
 	{
 		PointLight light = u_pointLights[u_lightIndices[idxOffset++]];

+ 19 - 1
shaders/Is.frag.glsl

@@ -60,6 +60,15 @@ void debugIncorrectColor(inout vec3 c)
 	}
 }
 
+// Compute the colors of a decal.
+void computeDecalColors(in Decal decal, in vec3 fragPos, out vec3 diffuseColor)
+{
+	vec4 texCoords4 = decal.texProjectionMat * vec4(fragPos, 1.0);
+	vec3 texCoords3 = texCoords4.xyz / texCoords4.w;
+
+	diffuseColor = texCoords3;
+}
+
 void readIndirect(in uint idxOffset,
 	in vec3 posVSpace,
 	in vec3 r,
@@ -148,9 +157,18 @@ void main()
 	// Shadowpass sample count
 	uint shadowSampleCount = computeShadowSampleCount(SHADOW_SAMPLE_COUNT, fragPos.z);
 
-	// Point lights
+	// Decals
 	uint count = u_lightIndices[idxOffset++];
 	while(count-- != 0)
+	{
+		Decal decal = u_decals[u_lightIndices[idxOffset++]];
+
+		computeDecalColors(decal, fragPos, diffCol);
+	}
+
+	// Point lights
+	count = u_lightIndices[idxOffset++];
+	while(count-- != 0)
 	{
 		PointLight light = u_pointLights[u_lightIndices[idxOffset++]];
 

+ 12 - 0
shaders/IsFsCommon.glsl

@@ -48,6 +48,13 @@ struct ReflectionProbe
 	vec4 cubemapIndexPad3;
 };
 
+// Decal
+struct Decal
+{
+	vec4 uv;
+	mat4 texProjectionMat;
+};
+
 layout(ANKI_UBO_BINDING(LIGHT_SET, LIGHT_UBO_BINDING), std140, row_major) uniform u0_
 {
 	LightingUniforms u_lightingUniforms;
@@ -70,6 +77,11 @@ layout(std140, row_major, ANKI_UBO_BINDING(LIGHT_SET, LIGHT_UBO_BINDING + 3)) un
 	ReflectionProbe u_reflectionProbes[UBO_MAX_SIZE / (2 * 4 * 4)];
 };
 
+layout(std140, row_major, ANKI_UBO_BINDING(LIGHT_SET, LIGHT_UBO_BINDING + 4)) uniform u4_
+{
+	Decal u_decals[UBO_MAX_SIZE / ((4 + 16) * 4)];
+};
+
 layout(ANKI_SS_BINDING(LIGHT_SET, LIGHT_SS_BINDING + 0), std430) readonly buffer s0_
 {
 	uint u_clusters[];

+ 1 - 2
shaders/LightFunctions.glsl

@@ -50,8 +50,7 @@ vec3 computeSpecularColorBrdf(vec3 v, // view dir
 	D = a2 / (PI * D * D);
 	D = clamp(D, EPSILON, 100.0); // Limit that because it may grow
 
-// G(l,v,h)/(4*dot(n,h)*dot(n,v)) aka Visibility term: Geometric shadowing
-// divided by BRDF denominator
+// G(l,v,h)/(4*dot(n,h)*dot(n,v)) aka Visibility term: Geometric shadowing divided by BRDF denominator
 #if 0
 	float nov = max(EPSILON, dot(n, v));
 	float V_v = nov + sqrt((nov - nov * a2) * nov + a2);

+ 2 - 0
src/anki/Scene.h

@@ -23,3 +23,5 @@
 #include <anki/scene/PlayerNode.h>
 #include <anki/scene/OccluderNode.h>
 #include <anki/scene/OccluderComponent.h>
+#include <anki/scene/DecalComponent.h>
+#include <anki/scene/DecalNode.h>

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

@@ -163,7 +163,7 @@ const U MAX_COLOR_ATTACHMENTS = 4;
 const U MAX_MIPMAPS = 16;
 const U MAX_TEXTURE_LAYERS = 32;
 const U MAX_TEXTURE_BINDINGS = 10;
-const U MAX_UNIFORM_BUFFER_BINDINGS = 4;
+const U MAX_UNIFORM_BUFFER_BINDINGS = 5;
 const U MAX_STORAGE_BUFFER_BINDINGS = 4;
 const U MAX_ATOMIC_BUFFER_BINDINGS = 1;
 const U MAX_IMAGE_BINDINGS = 4;

+ 3 - 0
src/anki/renderer/Is.cpp

@@ -161,6 +161,8 @@ Error Is::initInternal(const ConfigSet& config)
 		init.m_uniformBuffers[2].m_usage = BufferUsageBit::UNIFORM_FRAGMENT | BufferUsageBit::UNIFORM_VERTEX;
 		init.m_uniformBuffers[3].m_uploadedMemory = true;
 		init.m_uniformBuffers[3].m_usage = BufferUsageBit::UNIFORM_FRAGMENT | BufferUsageBit::UNIFORM_VERTEX;
+		init.m_uniformBuffers[4].m_uploadedMemory = true;
+		init.m_uniformBuffers[4].m_usage = BufferUsageBit::UNIFORM_FRAGMENT | BufferUsageBit::UNIFORM_VERTEX;
 
 		init.m_storageBuffers[0].m_uploadedMemory = true;
 		init.m_storageBuffers[0].m_usage = BufferUsageBit::STORAGE_FRAGMENT_READ | BufferUsageBit::STORAGE_VERTEX_READ;
@@ -185,6 +187,7 @@ Error Is::binLights(RenderingContext& ctx)
 		ctx.m_is.m_dynBufferInfo.m_uniformBuffers[P_LIGHTS_LOCATION],
 		ctx.m_is.m_dynBufferInfo.m_uniformBuffers[S_LIGHTS_LOCATION],
 		&ctx.m_is.m_dynBufferInfo.m_uniformBuffers[PROBES_LOCATION],
+		ctx.m_is.m_dynBufferInfo.m_uniformBuffers[DECALS_LOCATION],
 		ctx.m_is.m_dynBufferInfo.m_storageBuffers[CLUSTERS_LOCATION],
 		ctx.m_is.m_dynBufferInfo.m_storageBuffers[LIGHT_IDS_LOCATION]));
 

+ 1 - 0
src/anki/renderer/Is.h

@@ -51,6 +51,7 @@ private:
 	static const U P_LIGHTS_LOCATION = 1;
 	static const U S_LIGHTS_LOCATION = 2;
 	static const U PROBES_LOCATION = 3;
+	static const U DECALS_LOCATION = 4;
 	static const U CLUSTERS_LOCATION = 0;
 	static const U LIGHT_IDS_LOCATION = 1;
 

+ 119 - 22
src/anki/renderer/LightBin.cpp

@@ -8,6 +8,7 @@
 #include <anki/scene/Visibility.h>
 #include <anki/scene/MoveComponent.h>
 #include <anki/scene/LightComponent.h>
+#include <anki/scene/DecalComponent.h>
 #include <anki/scene/ReflectionProbeComponent.h>
 #include <anki/core/Trace.h>
 #include <anki/util/ThreadPool.h>
@@ -15,39 +16,41 @@
 namespace anki
 {
 
-/// This should be the number of light types. For now it's spots & points &
-/// probes.
-const U SIZE_IDX_COUNT = 3;
+/// This should be the number of light types. For now it's spots & points & probes & decals.
+const U SIZE_IDX_COUNT = 4;
 
-// Shader structs and block representations. All positions and directions in
-// viewspace
+// Shader structs and block representations. All positions and directions in viewspace
 // For documentation see the shaders
 
-struct ShaderCluster
+class ShaderCluster
 {
+public:
 	U32 m_firstIdx;
 };
 
-struct ShaderLight
+class ShaderLight
 {
+public:
 	Vec4 m_posRadius;
 	Vec4 m_diffuseColorShadowmapId;
 	Vec4 m_specularColorTexId;
 };
 
-struct ShaderPointLight : ShaderLight
+class ShaderPointLight : public ShaderLight
 {
 };
 
-struct ShaderSpotLight : ShaderLight
+class ShaderSpotLight : public ShaderLight
 {
+public:
 	Vec4 m_lightDir;
 	Vec4 m_outerCosInnerCos;
 	Mat4 m_texProjectionMat; ///< Texture projection matrix
 };
 
-struct ShaderProbe
+class ShaderProbe
 {
+public:
 	Vec3 m_pos;
 	F32 m_radiusSq;
 	F32 m_cubemapIndex;
@@ -60,8 +63,16 @@ struct ShaderProbe
 	}
 };
 
+class ShaderDecal
+{
+public:
+	Vec4 m_uv;
+	Mat4 m_texProjectionMat;
+};
+
 static const U MAX_TYPED_LIGHTS_PER_CLUSTER = 16;
 static const U MAX_PROBES_PER_CLUSTER = 12;
+static const U MAX_DECALS_PER_CLUSTER = 8;
 static const F32 INVALID_TEXTURE_INDEX = 128.0;
 
 class ClusterLightIndex
@@ -138,10 +149,12 @@ public:
 	Atomic<U8> m_pointCount;
 	Atomic<U8> m_spotCount;
 	Atomic<U8> m_probeCount;
+	Atomic<U8> m_decalCount;
 
 	Array<ClusterLightIndex, MAX_TYPED_LIGHTS_PER_CLUSTER> m_pointIds;
 	Array<ClusterLightIndex, MAX_TYPED_LIGHTS_PER_CLUSTER> m_spotIds;
 	Array<ClusterProbeIndex, MAX_PROBES_PER_CLUSTER> m_probeIds;
+	Array<ClusterLightIndex, MAX_DECALS_PER_CLUSTER> m_decalIds;
 
 	ClusterData()
 	{
@@ -159,6 +172,7 @@ public:
 		normalize(m_pointCount, MAX_TYPED_LIGHTS_PER_CLUSTER, "point lights");
 		normalize(m_spotCount, MAX_TYPED_LIGHTS_PER_CLUSTER, "spot lights");
 		normalize(m_probeCount, MAX_PROBES_PER_CLUSTER, "probes");
+		normalize(m_decalCount, MAX_DECALS_PER_CLUSTER, "decals");
 	}
 
 	void sortLightIds()
@@ -185,6 +199,12 @@ public:
 					return a.getProbeRadius() < b.getProbeRadius();
 				});
 		}
+
+		const U decalCount = m_decalCount.get();
+		if(decalCount > 1)
+		{
+			std::sort(&m_decalIds[0], &m_decalIds[0] + decalCount);
+		}
 	}
 
 	Bool operator==(const ClusterData& b) const
@@ -192,11 +212,14 @@ public:
 		const U pointCount = m_pointCount.get();
 		const U spotCount = m_spotCount.get();
 		const U probeCount = m_probeCount.get();
+		const U decalCount = m_decalCount.get();
 		const U pointCount2 = b.m_pointCount.get();
 		const U spotCount2 = b.m_spotCount.get();
 		const U probeCount2 = b.m_probeCount.get();
+		const U decalCount2 = b.m_decalCount.get();
 
-		if(pointCount != pointCount2 || spotCount != spotCount2 || probeCount != probeCount2)
+		if(pointCount != pointCount2 || spotCount != spotCount2 || probeCount != probeCount2
+			|| decalCount != decalCount2)
 		{
 			return false;
 		}
@@ -225,6 +248,14 @@ public:
 			}
 		}
 
+		if(decalCount > 0)
+		{
+			if(memcmp(&m_decalIds[0], &b.m_decalIds[0], sizeof(b.m_decalIds[0]) * decalCount) != 0)
+			{
+				return false;
+			}
+		}
+
 		return true;
 	}
 
@@ -259,6 +290,7 @@ public:
 	WeakArray<ShaderPointLight> m_pointLights;
 	WeakArray<ShaderSpotLight> m_spotLights;
 	WeakArray<ShaderProbe> m_probes;
+	WeakArray<ShaderDecal> m_decals;
 
 	WeakArray<U32> m_lightIds;
 	WeakArray<ShaderCluster> m_clusters;
@@ -266,6 +298,7 @@ public:
 	Atomic<U32> m_pointLightsCount = {0};
 	Atomic<U32> m_spotLightsCount = {0};
 	Atomic<U32> m_probeCount = {0};
+	Atomic<U32> m_decalCount = {0};
 
 	// To fill the tile buffers
 	DynamicArrayAuto<ClusterData> m_tempClusters;
@@ -277,6 +310,7 @@ public:
 	WeakArray<VisibleNode> m_vPointLights;
 	WeakArray<VisibleNode> m_vSpotLights;
 	WeakArray<VisibleNode> m_vProbes;
+	WeakArray<VisibleNode> m_vDecals;
 
 	Atomic<U32> m_count = {0};
 	Atomic<U32> m_count2 = {0};
@@ -323,6 +357,7 @@ Error LightBin::bin(FrustumComponent& frc,
 	TransientMemoryToken& pointLightsToken,
 	TransientMemoryToken& spotLightsToken,
 	TransientMemoryToken* probesToken,
+	TransientMemoryToken& decalsToken,
 	TransientMemoryToken& clustersToken,
 	TransientMemoryToken& lightIndicesToken)
 {
@@ -339,6 +374,7 @@ Error LightBin::bin(FrustumComponent& frc,
 	U visiblePointLightsCount = vi.getCount(VisibilityGroupType::LIGHTS_POINT);
 	U visibleSpotLightsCount = vi.getCount(VisibilityGroupType::LIGHTS_SPOT);
 	U visibleProbeCount = vi.getCount(VisibilityGroupType::REFLECTION_PROBES);
+	U visibleDecalCount = vi.getCount(VisibilityGroupType::DECALS);
 
 	ANKI_TRACE_INC_COUNTER(RENDERER_LIGHTS, visiblePointLightsCount + visibleSpotLightsCount);
 
@@ -400,6 +436,20 @@ Error LightBin::bin(FrustumComponent& frc,
 		}
 	}
 
+	if(visibleDecalCount)
+	{
+		ShaderDecal* data = static_cast<ShaderDecal*>(m_gr->allocateFrameTransientMemory(
+			sizeof(ShaderDecal) * visibleDecalCount, BufferUsageBit::UNIFORM_ALL, decalsToken));
+
+		ctx.m_decals = WeakArray<ShaderDecal>(data, visibleDecalCount);
+
+		ctx.m_vDecals = WeakArray<VisibleNode>(vi.getBegin(VisibilityGroupType::DECALS), visibleDecalCount);
+	}
+	else
+	{
+		decalsToken.markUnused();
+	}
+
 	ctx.m_bin = this;
 
 	// Get mem for clusters
@@ -414,8 +464,7 @@ Error LightBin::bin(FrustumComponent& frc,
 
 	ctx.m_lightIds = WeakArray<U32>(data2, maxLightIndices);
 
-	// Fill the first part of light ids with invalid indices. Will be used
-	// for empty clusters
+	// Fill the first part of light ids with invalid indices. Will be used for empty clusters
 	for(U i = 0; i < SIZE_IDX_COUNT; ++i)
 	{
 		ctx.m_lightIds[i] = 0;
@@ -465,7 +514,7 @@ void LightBin::binLights(U32 threadId, PtrSize threadsCount, LightBinContext& ct
 	ClustererTestResult testResult;
 	m_clusterer.initTestResults(ctx.m_alloc, testResult);
 	U lightCount = ctx.m_vPointLights.getSize() + ctx.m_vSpotLights.getSize();
-	U totalCount = lightCount + ctx.m_vProbes.getSize();
+	U totalCount = lightCount + ctx.m_vProbes.getSize() + ctx.m_vDecals.getSize();
 
 	const U TO_BIN_COUNT = 1;
 	while((start = ctx.m_count2.fetchAdd(TO_BIN_COUNT)) < totalCount)
@@ -474,15 +523,15 @@ void LightBin::binLights(U32 threadId, PtrSize threadsCount, LightBinContext& ct
 
 		for(U j = start; j < end; ++j)
 		{
-			if(j >= lightCount)
+			if(j >= lightCount + ctx.m_vDecals.getSize())
 			{
-				U i = j - lightCount;
+				U i = j - (lightCount + ctx.m_vDecals.getSize());
 				SceneNode& snode = *ctx.m_vProbes[i].m_node;
 				writeAndBinProbe(camfrc, snode, ctx, testResult);
 			}
-			else if(j >= ctx.m_vPointLights.getSize())
+			else if(j >= ctx.m_vPointLights.getSize() + ctx.m_vDecals.getSize())
 			{
-				U i = j - ctx.m_vPointLights.getSize();
+				U i = j - (ctx.m_vPointLights.getSize() + ctx.m_vDecals.getSize());
 
 				SceneNode& snode = *ctx.m_vSpotLights[i].m_node;
 				MoveComponent& move = snode.getComponent<MoveComponent>();
@@ -493,9 +542,9 @@ void LightBin::binLights(U32 threadId, PtrSize threadsCount, LightBinContext& ct
 				I pos = writeSpotLight(light, move, frc, cammove, camfrc, ctx);
 				binLight(sp, pos, 1, ctx, testResult);
 			}
-			else
+			else if(j >= ctx.m_vDecals.getSize())
 			{
-				U i = j;
+				U i = j - ctx.m_vDecals.getSize();
 
 				SceneNode& snode = *ctx.m_vPointLights[i].m_node;
 				MoveComponent& move = snode.getComponent<MoveComponent>();
@@ -505,6 +554,13 @@ void LightBin::binLights(U32 threadId, PtrSize threadsCount, LightBinContext& ct
 				I pos = writePointLight(light, move, camfrc, ctx);
 				binLight(sp, pos, 0, ctx, testResult);
 			}
+			else
+			{
+				U i = j;
+
+				SceneNode& snode = *ctx.m_vDecals[i].m_node;
+				writeAndBinDecal(cammove, snode, ctx, testResult);
+			}
 		}
 	}
 
@@ -529,7 +585,8 @@ void LightBin::binLights(U32 threadId, PtrSize threadsCount, LightBinContext& ct
 			const U countP = cluster.m_pointCount.get();
 			const U countS = cluster.m_spotCount.get();
 			const U countProbe = cluster.m_probeCount.get();
-			const U count = countP + countS + countProbe;
+			const U countDecal = cluster.m_decalCount.get();
+			const U count = countP + countS + countProbe + countDecal;
 
 			auto& c = ctx.m_clusters[i];
 			c.m_firstIdx = 0; // Point to the first empty indices
@@ -541,7 +598,7 @@ void LightBin::binLights(U32 threadId, PtrSize threadsCount, LightBinContext& ct
 			}
 
 			// Check if the previous cluster contains the same lights as this one and if yes then merge them. This will
-			/// avoid allocating new IDs (and thrashing GPU caches).
+			// avoid allocating new IDs (and thrashing GPU caches).
 			cluster.sortLightIds();
 			if(i != start)
 			{
@@ -562,6 +619,12 @@ void LightBin::binLights(U32 threadId, PtrSize threadsCount, LightBinContext& ct
 			{
 				c.m_firstIdx = offset;
 
+				ctx.m_lightIds[offset++] = countDecal;
+				for(U i = 0; i < countDecal; ++i)
+				{
+					ctx.m_lightIds[offset++] = cluster.m_decalIds[i].getIndex();
+				}
+
 				ctx.m_lightIds[offset++] = countP;
 				for(U i = 0; i < countP; ++i)
 				{
@@ -731,4 +794,38 @@ void LightBin::writeAndBinProbe(
 	}
 }
 
+void LightBin::writeAndBinDecal(
+	const MoveComponent& camMovec, const SceneNode& node, LightBinContext& ctx, ClustererTestResult& testResult)
+{
+	const DecalComponent& decalc = node.getComponent<DecalComponent>();
+	const SpatialComponent& sp = node.getComponent<SpatialComponent>();
+
+	I idx = ctx.m_decalCount.fetchAdd(1);
+	ShaderDecal& decal = ctx.m_decals[idx];
+
+	decalc.getDiffuseAtlasInfo(decal.m_uv);
+
+	// bias * proj_l * view_l * world_c
+	decal.m_texProjectionMat = decalc.getBiasProjectionViewMatrix() * Mat4(camMovec.getWorldTransform());
+
+	// Bin it
+	m_clusterer.bin(sp.getSpatialCollisionShape(), sp.getAabb(), testResult);
+
+	auto it = testResult.getClustersBegin();
+	auto end = testResult.getClustersEnd();
+	for(; it != end; ++it)
+	{
+		U x = (*it)[0];
+		U y = (*it)[1];
+		U z = (*it)[2];
+
+		U i = m_clusterer.getClusterCountX() * (z * m_clusterer.getClusterCountY() + y) + x;
+
+		auto& cluster = ctx.m_tempClusters[i];
+
+		i = cluster.m_decalCount.fetchAdd(1) % MAX_DECALS_PER_CLUSTER;
+		cluster.m_decalIds[i].setIndex(idx);
+	}
+}
+
 } // end namespace anki

+ 4 - 0
src/anki/renderer/LightBin.h

@@ -41,6 +41,7 @@ public:
 		TransientMemoryToken& pointLightsToken,
 		TransientMemoryToken& spotLightsToken,
 		TransientMemoryToken* probesToken,
+		TransientMemoryToken& decalsToken,
 		TransientMemoryToken& clustersToken,
 		TransientMemoryToken& lightIndicesToken);
 
@@ -73,6 +74,9 @@ private:
 
 	void writeAndBinProbe(
 		const FrustumComponent& camFrc, const SceneNode& node, LightBinContext& ctx, ClustererTestResult& testResult);
+
+	void writeAndBinDecal(
+		const MoveComponent& camMovec, const SceneNode& node, LightBinContext& ctx, ClustererTestResult& testResult);
 };
 /// @}
 

+ 1 - 0
src/anki/resource/TextureAtlas.h

@@ -27,6 +27,7 @@ namespace anki
 /// 			<uv>0.1 0.2 0.5 0.6</uv>
 /// 		</subTexture>
 /// 		<subTexture>...</subTexture>
+/// 		...
 /// 	</subTextures>
 /// </textureAtlas>
 /// @endcode

+ 2 - 1
src/anki/scene/Camera.cpp

@@ -81,7 +81,8 @@ Error Camera::init(Frustum* frustum)
 		| FrustumComponentVisibilityTestFlag::LENS_FLARE_COMPONENTS
 		| FrustumComponentVisibilityTestFlag::REFLECTION_PROBES
 		| FrustumComponentVisibilityTestFlag::REFLECTION_PROXIES
-		| FrustumComponentVisibilityTestFlag::OCCLUDERS);
+		| FrustumComponentVisibilityTestFlag::OCCLUDERS
+		| FrustumComponentVisibilityTestFlag::DECALS);
 	addComponent(frc, true);
 
 	// Feedback component #2

+ 10 - 0
src/anki/scene/DecalComponent.h

@@ -75,6 +75,16 @@ public:
 		return ErrorCode::NONE;
 	}
 
+	const Mat4& getBiasProjectionViewMatrix() const
+	{
+		return m_biasProjViewMat;
+	}
+
+	void getDiffuseAtlasInfo(Vec4& uv) const
+	{
+		uv = m_layers[LayerType::DIFFUSE].m_uv;
+	}
+
 private:
 	enum class LayerType
 	{

+ 5 - 2
src/anki/scene/FrustumComponent.h

@@ -21,6 +21,7 @@ class VisibilityTestResults;
 /// @{
 
 /// Flags that affect visibility tests.
+/// WARNING!!!!!!!!!!: If you change this remember to change the FrustumComponent::Flags as well
 enum class FrustumComponentVisibilityTestFlag : U8
 {
 	NONE = 0,
@@ -31,9 +32,11 @@ enum class FrustumComponentVisibilityTestFlag : U8
 	REFLECTION_PROBES = 1 << 4,
 	REFLECTION_PROXIES = 1 << 5,
 	OCCLUDERS = 1 << 6,
+	DECALS = 1 << 7,
 
 	ALL_TESTS = RENDER_COMPONENTS | LIGHT_COMPONENTS | LENS_FLARE_COMPONENTS | SHADOW_CASTERS | REFLECTION_PROBES
 		| REFLECTION_PROXIES
+		| DECALS
 };
 ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(FrustumComponentVisibilityTestFlag, inline)
 
@@ -179,8 +182,8 @@ public:
 private:
 	enum Flags
 	{
-		SHAPE_MARKED_FOR_UPDATE = 1 << 7,
-		TRANSFORM_MARKED_FOR_UPDATE = 1 << 8,
+		SHAPE_MARKED_FOR_UPDATE = 1 << 8,
+		TRANSFORM_MARKED_FOR_UPDATE = 1 << 9,
 	};
 	ANKI_ENUM_ALLOW_NUMERIC_OPERATIONS(Flags, friend)
 

+ 14 - 0
src/anki/scene/Visibility.cpp

@@ -12,6 +12,7 @@
 #include <anki/scene/ReflectionProbeComponent.h>
 #include <anki/scene/ReflectionProxyComponent.h>
 #include <anki/scene/OccluderComponent.h>
+#include <anki/scene/DecalComponent.h>
 #include <anki/scene/Light.h>
 #include <anki/scene/MoveComponent.h>
 #include <anki/renderer/MainRenderer.h>
@@ -239,6 +240,8 @@ void VisibilityTestTask::test(ThreadHive& hive)
 	Bool wantsReflectionProxies =
 		testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::REFLECTION_PROXIES);
 
+	Bool wantsDecals = testedFrc.visibilityTestsEnabled(FrustumComponentVisibilityTestFlag::DECALS);
+
 	// Chose the test range and a few other things
 	PtrSize start, end;
 	ThreadPoolTask::choseStartEnd(m_taskIdx, m_taskCount, m_sectorsCtx->getVisibleSceneNodeCount(), start, end);
@@ -288,6 +291,12 @@ void VisibilityTestTask::test(ThreadHive& hive)
 			wantNode = true;
 		}
 
+		DecalComponent* decalc = node.tryGetComponent<DecalComponent>();
+		if(decalc && wantsDecals)
+		{
+			wantNode = true;
+		}
+
 		if(ANKI_UNLIKELY(!wantNode))
 		{
 			// Skip node
@@ -404,6 +413,11 @@ void VisibilityTestTask::test(ThreadHive& hive)
 			visible->moveBack(alloc, VisibilityGroupType::REFLECTION_PROXIES, visibleNode);
 		}
 
+		if(decalc && wantsDecals)
+		{
+			visible->moveBack(alloc, VisibilityGroupType::DECALS, visibleNode);
+		}
+
 		// Add more frustums to the list
 		err = node.iterateComponentsOfType<FrustumComponent>([&](FrustumComponent& frc) {
 			m_visCtx->submitNewWork(frc, hive);

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

@@ -84,6 +84,7 @@ enum class VisibilityGroupType
 	FLARES,
 	REFLECTION_PROBES,
 	REFLECTION_PROXIES,
+	DECALS,
 
 	TYPE_COUNT,
 	FIRST = RENDERABLES_MS

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

@@ -1095,6 +1095,145 @@ static inline void wrapLightComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
+static const char* classnameDecalComponent = "DecalComponent";
+
+template<>
+I64 LuaBinder::getWrappedTypeSignature<DecalComponent>()
+{
+	return -1979693900066114370;
+}
+
+template<>
+const char* LuaBinder::getWrappedTypeName<DecalComponent>()
+{
+	return classnameDecalComponent;
+}
+
+/// Pre-wrap method DecalComponent::setDiffuseDecal.
+static inline int pwrapDecalComponentsetDiffuseDecal(lua_State* l)
+{
+	UserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	LuaBinder::checkArgsCount(l, 4);
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, classnameDecalComponent, -1979693900066114370, ud))
+	{
+		return -1;
+	}
+
+	DecalComponent* self = ud->getData<DecalComponent>();
+
+	// Pop arguments
+	const char* arg0;
+	if(LuaBinder::checkString(l, 2, arg0))
+	{
+		return -1;
+	}
+
+	const char* arg1;
+	if(LuaBinder::checkString(l, 3, arg1))
+	{
+		return -1;
+	}
+
+	F32 arg2;
+	if(LuaBinder::checkNumber(l, 4, arg2))
+	{
+		return -1;
+	}
+
+	// Call the method
+	self->setDiffuseDecal(arg0, arg1, arg2);
+
+	return 0;
+}
+
+/// Wrap method DecalComponent::setDiffuseDecal.
+static int wrapDecalComponentsetDiffuseDecal(lua_State* l)
+{
+	int res = pwrapDecalComponentsetDiffuseDecal(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Pre-wrap method DecalComponent::updateShape.
+static inline int pwrapDecalComponentupdateShape(lua_State* l)
+{
+	UserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	LuaBinder::checkArgsCount(l, 4);
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, classnameDecalComponent, -1979693900066114370, ud))
+	{
+		return -1;
+	}
+
+	DecalComponent* self = ud->getData<DecalComponent>();
+
+	// Pop arguments
+	F32 arg0;
+	if(LuaBinder::checkNumber(l, 2, arg0))
+	{
+		return -1;
+	}
+
+	F32 arg1;
+	if(LuaBinder::checkNumber(l, 3, arg1))
+	{
+		return -1;
+	}
+
+	F32 arg2;
+	if(LuaBinder::checkNumber(l, 4, arg2))
+	{
+		return -1;
+	}
+
+	// Call the method
+	self->updateShape(arg0, arg1, arg2);
+
+	return 0;
+}
+
+/// Wrap method DecalComponent::updateShape.
+static int wrapDecalComponentupdateShape(lua_State* l)
+{
+	int res = pwrapDecalComponentupdateShape(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Wrap class DecalComponent.
+static inline void wrapDecalComponent(lua_State* l)
+{
+	LuaBinder::createClass(l, classnameDecalComponent);
+	LuaBinder::pushLuaCFuncMethod(l, "setDiffuseDecal", wrapDecalComponentsetDiffuseDecal);
+	LuaBinder::pushLuaCFuncMethod(l, "updateShape", wrapDecalComponentupdateShape);
+	lua_settop(l, 0);
+}
+
 static const char* classnameLensFlareComponent = "LensFlareComponent";
 
 template<>
@@ -1471,6 +1610,57 @@ static int wrapSceneNodegetLensFlareComponent(lua_State* l)
 	return 0;
 }
 
+/// Pre-wrap method SceneNode::tryGetComponent<DecalComponent>.
+static inline int pwrapSceneNodegetDecalComponent(lua_State* l)
+{
+	UserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	LuaBinder::checkArgsCount(l, 1);
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, classnameSceneNode, -2220074417980276571, ud))
+	{
+		return -1;
+	}
+
+	SceneNode* self = ud->getData<SceneNode>();
+
+	// Call the method
+	DecalComponent* ret = self->tryGetComponent<DecalComponent>();
+
+	// Push return value
+	if(ANKI_UNLIKELY(ret == nullptr))
+	{
+		lua_pushstring(l, "Glue code returned nullptr");
+		return -1;
+	}
+
+	voidp = lua_newuserdata(l, sizeof(UserData));
+	ud = static_cast<UserData*>(voidp);
+	luaL_setmetatable(l, "DecalComponent");
+	ud->initPointed(-1979693900066114370, const_cast<DecalComponent*>(ret));
+
+	return 1;
+}
+
+/// Wrap method SceneNode::tryGetComponent<DecalComponent>.
+static int wrapSceneNodegetDecalComponent(lua_State* l)
+{
+	int res = pwrapSceneNodegetDecalComponent(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
 /// Wrap class SceneNode.
 static inline void wrapSceneNode(lua_State* l)
 {
@@ -1480,6 +1670,7 @@ static inline void wrapSceneNode(lua_State* l)
 	LuaBinder::pushLuaCFuncMethod(l, "getMoveComponent", wrapSceneNodegetMoveComponent);
 	LuaBinder::pushLuaCFuncMethod(l, "getLightComponent", wrapSceneNodegetLightComponent);
 	LuaBinder::pushLuaCFuncMethod(l, "getLensFlareComponent", wrapSceneNodegetLensFlareComponent);
+	LuaBinder::pushLuaCFuncMethod(l, "getDecalComponent", wrapSceneNodegetDecalComponent);
 	lua_settop(l, 0);
 }
 
@@ -2341,6 +2532,73 @@ static inline void wrapOccluderNode(lua_State* l)
 	lua_settop(l, 0);
 }
 
+static const char* classnameDecalNode = "DecalNode";
+
+template<>
+I64 LuaBinder::getWrappedTypeSignature<DecalNode>()
+{
+	return 1097508121406753350;
+}
+
+template<>
+const char* LuaBinder::getWrappedTypeName<DecalNode>()
+{
+	return classnameDecalNode;
+}
+
+/// Pre-wrap method DecalNode::getSceneNodeBase.
+static inline int pwrapDecalNodegetSceneNodeBase(lua_State* l)
+{
+	UserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	LuaBinder::checkArgsCount(l, 1);
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, classnameDecalNode, 1097508121406753350, ud))
+	{
+		return -1;
+	}
+
+	DecalNode* self = ud->getData<DecalNode>();
+
+	// Call the method
+	SceneNode& ret = *self;
+
+	// Push return value
+	voidp = lua_newuserdata(l, sizeof(UserData));
+	ud = static_cast<UserData*>(voidp);
+	luaL_setmetatable(l, "SceneNode");
+	ud->initPointed(-2220074417980276571, const_cast<SceneNode*>(&ret));
+
+	return 1;
+}
+
+/// Wrap method DecalNode::getSceneNodeBase.
+static int wrapDecalNodegetSceneNodeBase(lua_State* l)
+{
+	int res = pwrapDecalNodegetSceneNodeBase(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
+/// Wrap class DecalNode.
+static inline void wrapDecalNode(lua_State* l)
+{
+	LuaBinder::createClass(l, classnameDecalNode);
+	LuaBinder::pushLuaCFuncMethod(l, "getSceneNodeBase", wrapDecalNodegetSceneNodeBase);
+	lua_settop(l, 0);
+}
+
 static const char* classnameSceneGraph = "SceneGraph";
 
 template<>
@@ -3049,6 +3307,64 @@ static int wrapSceneGraphnewOccluderNode(lua_State* l)
 	return 0;
 }
 
+/// Pre-wrap method SceneGraph::newDecalNode.
+static inline int pwrapSceneGraphnewDecalNode(lua_State* l)
+{
+	UserData* ud;
+	(void)ud;
+	void* voidp;
+	(void)voidp;
+	PtrSize size;
+	(void)size;
+
+	LuaBinder::checkArgsCount(l, 2);
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, classnameSceneGraph, -7754439619132389154, ud))
+	{
+		return -1;
+	}
+
+	SceneGraph* self = ud->getData<SceneGraph>();
+
+	// Pop arguments
+	const char* arg0;
+	if(LuaBinder::checkString(l, 2, arg0))
+	{
+		return -1;
+	}
+
+	// Call the method
+	DecalNode* ret = newSceneNode<DecalNode>(self, arg0);
+
+	// Push return value
+	if(ANKI_UNLIKELY(ret == nullptr))
+	{
+		lua_pushstring(l, "Glue code returned nullptr");
+		return -1;
+	}
+
+	voidp = lua_newuserdata(l, sizeof(UserData));
+	ud = static_cast<UserData*>(voidp);
+	luaL_setmetatable(l, "DecalNode");
+	ud->initPointed(1097508121406753350, const_cast<DecalNode*>(ret));
+
+	return 1;
+}
+
+/// Wrap method SceneGraph::newDecalNode.
+static int wrapSceneGraphnewDecalNode(lua_State* l)
+{
+	int res = pwrapSceneGraphnewDecalNode(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
 /// Pre-wrap method SceneGraph::setActiveCamera.
 static inline int pwrapSceneGraphsetActiveCamera(lua_State* l)
 {
@@ -3112,6 +3428,7 @@ static inline void wrapSceneGraph(lua_State* l)
 	LuaBinder::pushLuaCFuncMethod(l, "newReflectionProbe", wrapSceneGraphnewReflectionProbe);
 	LuaBinder::pushLuaCFuncMethod(l, "newReflectionProxy", wrapSceneGraphnewReflectionProxy);
 	LuaBinder::pushLuaCFuncMethod(l, "newOccluderNode", wrapSceneGraphnewOccluderNode);
+	LuaBinder::pushLuaCFuncMethod(l, "newDecalNode", wrapSceneGraphnewDecalNode);
 	LuaBinder::pushLuaCFuncMethod(l, "setActiveCamera", wrapSceneGraphsetActiveCamera);
 	lua_settop(l, 0);
 }
@@ -3164,6 +3481,7 @@ void wrapModuleScene(lua_State* l)
 {
 	wrapMoveComponent(l);
 	wrapLightComponent(l);
+	wrapDecalComponent(l);
 	wrapLensFlareComponent(l);
 	wrapSceneNode(l);
 	wrapModelNode(l);
@@ -3177,6 +3495,7 @@ void wrapModuleScene(lua_State* l)
 	wrapReflectionProbe(l);
 	wrapReflectionProxy(l);
 	wrapOccluderNode(l);
+	wrapDecalNode(l);
 	wrapSceneGraph(l);
 	LuaBinder::pushLuaCFunc(l, "getSceneGraph", wrapgetSceneGraph);
 }

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

@@ -137,6 +137,24 @@ static SceneGraph* getSceneGraph(lua_State* l)
 				</method>
 			</methods>
 		</class>
+		<class name="DecalComponent">
+			<methods>
+				<method name="setDiffuseDecal">
+					<args>
+						<arg>CString</arg>
+						<arg>CString</arg>
+						<arg>F32</arg>
+					</args>
+				</method>
+				<method name="updateShape">
+					<args>
+						<arg>F32</arg>
+						<arg>F32</arg>
+						<arg>F32</arg>
+					</args>
+				</method>
+			</methods>
+		</class>
 		<class name="LensFlareComponent">
 			<methods>
 				<method name="setFirstFlareSize">
@@ -170,6 +188,9 @@ static SceneGraph* getSceneGraph(lua_State* l)
 				<method name="tryGetComponent&lt;LensFlareComponent&gt;" alias="getLensFlareComponent">
 					<return>LensFlareComponent*</return>
 				</method>
+				<method name="tryGetComponent&lt;DecalComponent&gt;" alias="getDecalComponent">
+					<return>DecalComponent*</return>
+				</method>
 			</methods>
 		</class>
 		<class name="ModelNode">
@@ -274,6 +295,14 @@ static SceneGraph* getSceneGraph(lua_State* l)
 				</method>
 			</methods>
 		</class>
+		<class name="DecalNode">
+			<methods>
+				<method name="getSceneNodeBase">
+					<overrideCall>SceneNode&amp; ret = *self;</overrideCall>
+					<return>SceneNode&amp;</return>
+				</method>
+			</methods>
+		</class>
 		<class name="SceneGraph">
 			<methods>
 				<method name="newPerspectiveCamera">
@@ -362,6 +391,13 @@ static SceneGraph* getSceneGraph(lua_State* l)
 					</args>
 					<return>OccluderNode*</return>
 				</method>
+				<method name="newDecalNode">
+					<overrideCall><![CDATA[DecalNode* ret = newSceneNode<DecalNode>(self, arg0);]]></overrideCall>
+					<args>
+						<arg>const CString&amp;</arg>
+					</args>
+					<return>DecalNode*</return>
+				</method>
 				<method name="setActiveCamera">
 					<args>
 						<arg>SceneNode*</arg>

+ 61 - 0
tools/scene/Exporter.cpp

@@ -155,6 +155,20 @@ static float getUniformScale(const aiMatrix4x4& m)
 	return scale;
 }
 
+static aiVector3D getNonUniformScale(const aiMatrix4x4& m)
+{
+	aiVector3D xAxis(m.a1, m.b1, m.c1);
+	aiVector3D yAxis(m.a2, m.b2, m.c2);
+	aiVector3D zAxis(m.a3, m.b3, m.c3);
+
+	aiVector3D scale;
+	scale[0] = xAxis.Length();
+	scale[1] = yAxis.Length();
+	scale[2] = zAxis.Length();
+
+	return scale;
+}
+
 std::string Exporter::getMaterialName(const aiMaterial& mtl)
 {
 	aiString ainame;
@@ -823,6 +837,34 @@ void Exporter::visitNode(const aiNode* ainode)
 				collisionMesh = prop.second;
 				special = false;
 			}
+			else if(prop.first == "decal" && prop.second == "true")
+			{
+				DecalNode decal;
+				for(const auto& pr : m_scene->mMeshes[meshIndex]->mProperties)
+				{
+					if(pr.first == "decal_diffuse_atlas")
+					{
+						decal.m_diffuseTextureAtlasFilename = pr.second;
+					}
+					else if(pr.first == "decal_diffuse_sub_texture")
+					{
+						decal.m_diffuseSubTextureName = pr.second;
+					}
+				}
+
+				if(decal.m_diffuseTextureAtlasFilename.empty() || decal.m_diffuseSubTextureName.empty())
+				{
+					ERROR("Missing decal information");
+				}
+
+				aiMatrix4x4 trf = ainode->mTransformation;
+				decal.m_size = getNonUniformScale(trf);
+				removeScale(trf);
+				decal.m_transform = trf;
+
+				m_decals.push_back(decal);
+				special = true;
+			}
 		}
 
 		if(special)
@@ -1002,6 +1044,25 @@ void Exporter::exportAll()
 		++i;
 	}
 
+	//
+	// Export decals
+	//
+	i = 0;
+	for(const DecalNode& decal : m_decals)
+	{
+		std::string name = "decal" + std::to_string(i);
+		file << "\nnode = scene:newDecalNode(\"" << name << "\")\n";
+
+		writeNodeTransform("node", decal.m_transform);
+
+		file << "decalc = node:getSceneNodeBase():getLightComponent()\n";
+		file << "decalc:setDiffuseDecal(\"" << decal.m_diffuseTextureAtlasFilename << "\", \""
+			 << decal.m_diffuseSubTextureName << "\", 1.0)\n";
+		file << "decalc:updateShape(" << decal.m_size.x << ", " << decal.m_size.y << ", " << decal.m_size.z << ")\n";
+
+		++i;
+	}
+
 	//
 	// Export nodes and models.
 	//

+ 10 - 0
tools/scene/Exporter.h

@@ -91,6 +91,15 @@ public:
 	uint32_t m_meshIndex; ///< Points to the scene that is not triangulated.
 };
 
+class DecalNode
+{
+public:
+	aiMatrix4x4 m_transform;
+	std::string m_diffuseTextureAtlasFilename;
+	std::string m_diffuseSubTextureName;
+	aiVector3D m_size;
+};
+
 /// AnKi exporter.
 class Exporter
 {
@@ -119,6 +128,7 @@ public:
 	std::vector<ReflectionProbe> m_reflectionProbes;
 	std::vector<ReflectionProxy> m_reflectionProxies;
 	std::vector<OccluderNode> m_occluders;
+	std::vector<DecalNode> m_decals;
 
 	/// Load the scene.
 	void load();