浏览代码

Add sky rendering

Panagiotis Christopoulos Charitos 1 年之前
父节点
当前提交
9846731277
共有 66 个文件被更改,包括 965 次插入68 次删除
  1. 9 3
      AnKi/Importer/GltfImporter.cpp
  2. 2 3
      AnKi/Math/Vec.h
  3. 7 0
      AnKi/Renderer/IndirectDiffuseProbes.cpp
  4. 42 6
      AnKi/Renderer/LightShading.cpp
  5. 1 1
      AnKi/Renderer/LightShading.h
  6. 7 0
      AnKi/Renderer/ProbeReflections.cpp
  7. 2 0
      AnKi/Renderer/Renderer.cpp
  8. 1 0
      AnKi/Renderer/RendererObject.def.h
  9. 184 0
      AnKi/Renderer/Sky.cpp
  10. 73 0
      AnKi/Renderer/Sky.h
  11. 22 13
      AnKi/Renderer/Utils/TraditionalDeferredShading.cpp
  12. 3 1
      AnKi/Renderer/Utils/TraditionalDeferredShading.h
  13. 7 1
      AnKi/Scene/Components/SkyboxComponent.h
  14. 64 24
      AnKi/Script/Scene.cpp
  15. 1 0
      AnKi/Script/Scene.xml
  16. 2 2
      AnKi/Shaders/Include/TraditionalDeferredShadingTypes.h
  17. 30 3
      AnKi/Shaders/LightShadingSkybox.ankiprog
  18. 405 0
      AnKi/Shaders/Sky.ankiprog
  19. 80 0
      AnKi/Shaders/Sky.hlsl
  20. 15 3
      AnKi/Shaders/TraditionalDeferredShadingSkybox.ankiprog
  21. 1 1
      Samples/Common/SampleApp.cpp
  22. 7 7
      Samples/Sponza/Assets/Scene.lua
  23. 二进制
      Samples/Sponza/Assets/arc_2_5fe181d03e97a985.ankimesh
  24. 二进制
      Samples/Sponza/Assets/arch_a_2340d230b53e2a69.ankimesh
  25. 二进制
      Samples/Sponza/Assets/arch_support_big_68d8367a811fb94b.ankimesh
  26. 二进制
      Samples/Sponza/Assets/arch_support_med_f674c0ad36e855d5.ankimesh
  27. 二进制
      Samples/Sponza/Assets/arch_support_tiny_6e5678a158e0a576.ankimesh
  28. 二进制
      Samples/Sponza/Assets/carpet_9773eaac1e11dc54.ankimesh
  29. 二进制
      Samples/Sponza/Assets/ceiling_3fd94cde277a48e1.ankimesh
  30. 二进制
      Samples/Sponza/Assets/column_b_c9391d56bff59fc3.ankimesh
  31. 二进制
      Samples/Sponza/Assets/column_c_small_a940cbc4b06b29e0.ankimesh
  32. 二进制
      Samples/Sponza/Assets/column_c_square_34f84a5277d35506.ankimesh
  33. 二进制
      Samples/Sponza/Assets/door_b_43d9d0f054a59e0d.ankimesh
  34. 二进制
      Samples/Sponza/Assets/fabric_a_945c29fc221550fb.ankimesh
  35. 二进制
      Samples/Sponza/Assets/fabric_b_e8dd2769dc642ab7.ankimesh
  36. 二进制
      Samples/Sponza/Assets/flag_pole_b7fcab939d35270d.ankimesh
  37. 二进制
      Samples/Sponza/Assets/hanging_vase_a37dedd7f8c3beeb.ankimesh
  38. 二进制
      Samples/Sponza/Assets/leaf_3a245efd17475037.ankimesh
  39. 二进制
      Samples/Sponza/Assets/leaf_b_686ab977af97774c.ankimesh
  40. 二进制
      Samples/Sponza/Assets/lion_c45d3035db3bc17b.ankimesh
  41. 二进制
      Samples/Sponza/Assets/lion_frame_c8b97a33096fbdb.ankimesh
  42. 二进制
      Samples/Sponza/Assets/list_8b0526c84dd681e3.ankimesh
  43. 二进制
      Samples/Sponza/Assets/list_b_457f406a16f12d4d.ankimesh
  44. 二进制
      Samples/Sponza/Assets/metal_rod_f68ba1d1e70f4801.ankimesh
  45. 二进制
      Samples/Sponza/Assets/small_window_inner_def811d202476946.ankimesh
  46. 二进制
      Samples/Sponza/Assets/sponza_00_ae01670872faa30.ankimesh
  47. 二进制
      Samples/Sponza/Assets/sponza_06_fd85d8293143f003.ankimesh
  48. 二进制
      Samples/Sponza/Assets/sponza_258_9e5285ce7e2189af.ankimesh
  49. 二进制
      Samples/Sponza/Assets/sponza_277_a862a3463155379b.ankimesh
  50. 二进制
      Samples/Sponza/Assets/sponza_278_2814c1fc2c992170.ankimesh
  51. 二进制
      Samples/Sponza/Assets/sponza_279_6912e5f5d4128531.ankimesh
  52. 二进制
      Samples/Sponza/Assets/sponza_280_8dec8aa3e97a7a31.ankimesh
  53. 二进制
      Samples/Sponza/Assets/sponza_281_12fa6f426dc4c559.ankimesh
  54. 二进制
      Samples/Sponza/Assets/sponza_34_af76802cd75f239b.ankimesh
  55. 二进制
      Samples/Sponza/Assets/sponza_35_587c5a72282a0812.ankimesh
  56. 二进制
      Samples/Sponza/Assets/sponza_369.002_6ab77309e0c110ae.ankimesh
  57. 二进制
      Samples/Sponza/Assets/sponza_36_df4619a2b83fb4bb.ankimesh
  58. 二进制
      Samples/Sponza/Assets/sponza_380_752dc70618c5bc97.ankimesh
  59. 二进制
      Samples/Sponza/Assets/sponza_381_d80b7e06247cf847.ankimesh
  60. 二进制
      Samples/Sponza/Assets/sponza_66_5230eeae04fcd528.ankimesh
  61. 二进制
      Samples/Sponza/Assets/sponza_68_921bc07f7acf667.ankimesh
  62. 二进制
      Samples/Sponza/Assets/sponza_69_c96373f43f7e6566.ankimesh
  63. 二进制
      Samples/Sponza/Assets/vase_45c3983f6cc9c489.ankimesh
  64. 二进制
      Samples/Sponza/Assets/vase_flowers_b4fdd6561a1a65fb.ankimesh
  65. 二进制
      Samples/Sponza/Assets/vase_hanger_2a18d1de31dd5e0d.ankimesh
  66. 二进制
      Samples/Sponza/Assets/window_4ac10331d32bff8d.ankimesh

+ 9 - 3
AnKi/Importer/GltfImporter.cpp

@@ -183,11 +183,11 @@ static Error getExtra(const ImporterHashMap<CString, ImporterString>& extras, CS
 
 	if(it != extras.getEnd())
 	{
-		if(*it == "true")
+		if(*it == "true" || *it == "1")
 		{
 			val = true;
 		}
-		else if(*it == "false")
+		else if(*it == "false" || *it == "0")
 		{
 			val = false;
 		}
@@ -678,7 +678,7 @@ Error GltfImporter::visitNode(const cgltf_node& node, const Transform& parentTrf
 			}
 		}
 		else if(stringsExist(extras, {"skybox_solid_color", "skybox_image", "fog_min_density", "fog_max_density", "fog_height_of_min_density",
-									  "fog_height_of_max_density", "fog_diffuse_color"}))
+									  "fog_height_of_max_density", "fog_diffuse_color", "skybox_generated"}))
 		{
 			// Atmosphere
 
@@ -736,6 +736,12 @@ Error GltfImporter::visitNode(const cgltf_node& node, const Transform& parentTrf
 												  extraValueVec3.z()));
 			}
 
+			ANKI_CHECK(getExtra(extras, "skybox_generated", extraValueBool, extraFound));
+			if(extraFound && extraValueBool)
+			{
+				ANKI_CHECK(m_sceneFile.writeTextf("comp:setGeneratedSky()\n"));
+			}
+
 			Transform localTrf;
 			ANKI_CHECK(getNodeTransform(node, localTrf));
 			ANKI_CHECK(writeTransform(parentTrf.combineTransformations(localTrf)));

+ 2 - 3
AnKi/Math/Vec.h

@@ -105,10 +105,9 @@ public:
 		m_simd = simd;
 	}
 
-	TVec(const T x_, const T y_) requires(kTComponentCount == 2)
+	constexpr TVec(const T x_, const T y_) requires(kTComponentCount == 2)
+		: m_arr{x_, y_}
 	{
-		x() = x_;
-		y() = y_;
 	}
 
 	// Vec3 specific

+ 7 - 0
AnKi/Renderer/IndirectDiffuseProbes.cpp

@@ -6,6 +6,7 @@
 #include <AnKi/Renderer/IndirectDiffuseProbes.h>
 #include <AnKi/Renderer/Renderer.h>
 #include <AnKi/Renderer/PrimaryNonRenderableVisibility.h>
+#include <AnKi/Renderer/Sky.h>
 #include <AnKi/Scene/SceneGraph.h>
 #include <AnKi/Scene/Components/GlobalIlluminationProbeComponent.h>
 #include <AnKi/Scene/Components/LightComponent.h>
@@ -416,6 +417,11 @@ void IndirectDiffuseProbes::populateRenderGraph(RenderingContext& rctx)
 					pass.newTextureDependency(shadowsRt, TextureUsageBit::kSampledFragment);
 				}
 
+				if(getRenderer().getSky().isEnabled())
+				{
+					pass.newTextureDependency(getRenderer().getSky().getSkyLutRt(), TextureUsageBit::kSampledFragment);
+				}
+
 				pass.setWork(1, [this, visibleLightsBuffer = lightVis.m_visiblesBuffer, viewProjMat = frustum.getViewProjectionMatrix(), cellCenter,
 								 gbufferColorRts, gbufferDepthRt, probeToRefresh, cascadeViewProjMat, shadowsRt,
 								 faceIdx = f](RenderPassWorkContext& rgraphCtx) {
@@ -457,6 +463,7 @@ void IndirectDiffuseProbes::populateRenderGraph(RenderingContext& rctx)
 					dsInfo.m_gbufferRenderTargetSubresourceInfos[2].m_firstFace = faceIdx;
 					dsInfo.m_gbufferDepthRenderTarget = gbufferDepthRt;
 					dsInfo.m_directionalLightShadowmapRenderTarget = shadowsRt;
+					dsInfo.m_skyLutRenderTarget = getRenderer().getSky().getSkyLutRt();
 					dsInfo.m_renderpassContext = &rgraphCtx;
 
 					m_lightShading.m_deferred.drawLights(dsInfo);

+ 42 - 6
AnKi/Renderer/LightShading.cpp

@@ -13,6 +13,7 @@
 #include <AnKi/Renderer/DepthDownscale.h>
 #include <AnKi/Renderer/ShadowmapsResolve.h>
 #include <AnKi/Renderer/RtShadows.h>
+#include <AnKi/Renderer/Sky.h>
 #include <AnKi/Renderer/VrsSriGeneration.h>
 #include <AnKi/Renderer/ClusterBinning.h>
 #include <AnKi/Renderer/Ssao.h>
@@ -20,6 +21,7 @@
 #include <AnKi/Core/CVarSet.h>
 #include <AnKi/Util/Tracer.h>
 #include <AnKi/Scene/Components/SkyboxComponent.h>
+#include <AnKi/Scene/Components/LightComponent.h>
 
 namespace anki {
 
@@ -82,7 +84,7 @@ Error LightShading::initSkybox()
 {
 	ANKI_CHECK(ResourceManager::getSingleton().loadResource("ShaderBinaries/LightShadingSkybox.ankiprogbin", m_skybox.m_prog));
 
-	for(MutatorValue method = 0; method < 2; ++method)
+	for(MutatorValue method = 0; method < 3; ++method)
 	{
 		ANKI_CHECK(
 			loadShaderProgram("ShaderBinaries/LightShadingSkybox.ankiprogbin", {{"METHOD", method}}, m_skybox.m_prog, m_skybox.m_grProgs[method]));
@@ -149,8 +151,10 @@ void LightShading::run(const RenderingContext& ctx, RenderPassWorkContext& rgrap
 		cmdb.setDepthCompareOperation(CompareOperation::kEqual);
 
 		const SkyboxComponent* sky = SceneGraph::getSingleton().getSkybox();
+		const LightComponent* dirLight = SceneGraph::getSingleton().getDirectionalLight();
 
-		const Bool isSolidColor = (sky) ? sky->getSkyboxType() == SkyboxType::kSolidColor : true;
+		const Bool isSolidColor =
+			(!sky || sky->getSkyboxType() == SkyboxType::kSolidColor || (!dirLight && sky->getSkyboxType() == SkyboxType::kGenerated));
 
 		if(isSolidColor)
 		{
@@ -159,17 +163,17 @@ void LightShading::run(const RenderingContext& ctx, RenderPassWorkContext& rgrap
 			const Vec4 color((sky) ? sky->getSolidColor() : Vec3(0.0f), 0.0);
 			cmdb.setPushConstants(&color, sizeof(color));
 		}
-		else
+		else if(sky->getSkyboxType() == SkyboxType::kImage2D)
 		{
 			cmdb.bindShaderProgram(m_skybox.m_grProgs[1].get());
 
 			class
 			{
 			public:
-				Mat4 m_invertedViewProjectionJitter;
+				Mat4 m_invertedViewProjectionJitterMat;
 
 				Vec3 m_cameraPos;
-				F32 m_padding = 0.0;
+				F32 m_padding;
 
 				Vec3 m_scale;
 				F32 m_padding1;
@@ -178,7 +182,7 @@ void LightShading::run(const RenderingContext& ctx, RenderPassWorkContext& rgrap
 				F32 m_padding2;
 			} pc;
 
-			pc.m_invertedViewProjectionJitter = ctx.m_matrices.m_invertedViewProjectionJitter;
+			pc.m_invertedViewProjectionJitterMat = ctx.m_matrices.m_invertedViewProjectionJitter;
 			pc.m_cameraPos = ctx.m_matrices.m_cameraTransform.getTranslationPart().xyz();
 			pc.m_scale = sky->getImageScale();
 			pc.m_bias = sky->getImageBias();
@@ -188,6 +192,32 @@ void LightShading::run(const RenderingContext& ctx, RenderPassWorkContext& rgrap
 			cmdb.bindSampler(0, 0, getRenderer().getSamplers().m_trilinearRepeatAnisoResolutionScalingBias.get());
 			cmdb.bindTexture(0, 1, &sky->getImageResource().getTextureView());
 		}
+		else
+		{
+			cmdb.bindShaderProgram(m_skybox.m_grProgs[2].get());
+
+			class
+			{
+			public:
+				Mat4 m_invertedViewProjectionJitterMat;
+
+				Vec3 m_cameraPos;
+				F32 m_padding1;
+
+				Vec3 m_dirToSun;
+				F32 m_sunPower;
+			} pc;
+
+			pc.m_invertedViewProjectionJitterMat = ctx.m_matrices.m_invertedViewProjectionJitter;
+			pc.m_cameraPos = ctx.m_matrices.m_cameraTransform.getTranslationPart().xyz();
+			pc.m_dirToSun = -dirLight->getDirection();
+			pc.m_sunPower = dirLight->getDiffuseColor().xyz().dot(Vec3(0.30f, 0.59f, 0.11f));
+
+			cmdb.setPushConstants(&pc, sizeof(pc));
+
+			cmdb.bindSampler(0, 0, getRenderer().getSamplers().m_trilinearClamp.get());
+			rgraphCtx.bindColorTexture(0, 1, getRenderer().getSky().getSkyLutRt());
+		}
 
 		drawQuad(cmdb);
 
@@ -316,6 +346,12 @@ void LightShading::populateRenderGraph(RenderingContext& ctx)
 	// Fog
 	pass.newTextureDependency(getRenderer().getVolumetricFog().getRt(), readUsage);
 
+	// Sky
+	if(getRenderer().getSky().isEnabled())
+	{
+		pass.newTextureDependency(getRenderer().getSky().getSkyLutRt(), readUsage);
+	}
+
 	// For forward shading
 	getRenderer().getForwardShading().setDependencies(pass);
 }

+ 1 - 1
AnKi/Renderer/LightShading.h

@@ -47,7 +47,7 @@ private:
 	{
 	public:
 		ShaderProgramResourcePtr m_prog;
-		Array<ShaderProgramPtr, 2> m_grProgs;
+		Array<ShaderProgramPtr, 3> m_grProgs;
 	} m_skybox;
 
 	class

+ 7 - 0
AnKi/Renderer/ProbeReflections.cpp

@@ -8,6 +8,7 @@
 #include <AnKi/Renderer/LightShading.h>
 #include <AnKi/Renderer/FinalComposite.h>
 #include <AnKi/Renderer/GBuffer.h>
+#include <AnKi/Renderer/Sky.h>
 #include <AnKi/Renderer/PrimaryNonRenderableVisibility.h>
 #include <AnKi/Core/CVarSet.h>
 #include <AnKi/Util/Tracer.h>
@@ -409,6 +410,11 @@ void ProbeReflections::populateRenderGraph(RenderingContext& rctx)
 				pass.newTextureDependency(shadowMapRt, TextureUsageBit::kSampledFragment);
 			}
 
+			if(getRenderer().getSky().isEnabled())
+			{
+				pass.newTextureDependency(getRenderer().getSky().getSkyLutRt(), TextureUsageBit::kSampledFragment);
+			}
+
 			pass.setWork([this, visResult = lightVis.m_visiblesBuffer, viewProjMat = frustum.getViewProjectionMatrix(),
 						  cascadeViewProjMat = cascadeViewProjMat, probeToRefresh, gbufferColorRts, gbufferDepthRt, shadowMapRt,
 						  faceIdx = f](RenderPassWorkContext& rgraphCtx) {
@@ -436,6 +442,7 @@ void ProbeReflections::populateRenderGraph(RenderingContext& rctx)
 				{
 					dsInfo.m_directionalLightShadowmapRenderTarget = shadowMapRt;
 				}
+				dsInfo.m_skyLutRenderTarget = getRenderer().getSky().getSkyLutRt();
 				dsInfo.m_renderpassContext = &rgraphCtx;
 
 				m_lightShading.m_deferred.drawLights(dsInfo);

+ 2 - 0
AnKi/Renderer/Renderer.cpp

@@ -43,6 +43,7 @@
 #include <AnKi/Renderer/ClusterBinning.h>
 #include <AnKi/Renderer/Ssao.h>
 #include <AnKi/Renderer/Ssr.h>
+#include <AnKi/Renderer/Sky.h>
 #include <AnKi/Core/StatsSet.h>
 
 namespace anki {
@@ -305,6 +306,7 @@ Error Renderer::populateRenderGraph(RenderingContext& ctx)
 	m_gbuffer->populateRenderGraph(ctx);
 	m_shadowMapping->populateRenderGraph(ctx);
 	m_clusterBinning2->populateRenderGraph(ctx);
+	m_sky->populateRenderGraph(ctx);
 	m_indirectDiffuseProbes->populateRenderGraph(ctx);
 	m_probeReflections->populateRenderGraph(ctx);
 	m_volumetricLightingAccumulation->populateRenderGraph(ctx);

+ 1 - 0
AnKi/Renderer/RendererObject.def.h

@@ -34,5 +34,6 @@ ANKI_RENDERER_OBJECT_DEF(PrimaryNonRenderableVisibility, primaryNonRenderableVis
 ANKI_RENDERER_OBJECT_DEF(ClusterBinning, clusterBinning2, 1)
 ANKI_RENDERER_OBJECT_DEF(Ssao, ssao, 1)
 ANKI_RENDERER_OBJECT_DEF(Ssr, ssr, 1)
+ANKI_RENDERER_OBJECT_DEF(Sky, sky, 1)
 
 #undef ANKI_RENDERER_OBJECT_DEF

+ 184 - 0
AnKi/Renderer/Sky.cpp

@@ -0,0 +1,184 @@
+// Copyright (C) 2009-2023, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <AnKi/Renderer/Sky.h>
+#include <AnKi/Renderer/Renderer.h>
+#include <AnKi/Util/Tracer.h>
+#include <AnKi/Scene/Components/SkyboxComponent.h>
+#include <AnKi/Scene/Components/LightComponent.h>
+
+namespace anki {
+
+Error Sky::init()
+{
+	const Error err = initInternal();
+	if(err)
+	{
+		ANKI_R_LOGE("Failed to initialize sky passes");
+	}
+
+	return Error::kNone;
+}
+
+Error Sky::initInternal()
+{
+	ANKI_R_LOGV("Initializing sky passes");
+
+	ANKI_CHECK(loadShaderProgram("ShaderBinaries/Sky.ankiprogbin", {}, m_prog, m_transmittanceLutGrProg, "SkyTransmittanceLut"));
+	ANKI_CHECK(loadShaderProgram("ShaderBinaries/Sky.ankiprogbin", {}, m_prog, m_multipleScatteringLutGrProg, "SkyMultipleScatteringLut"));
+	ANKI_CHECK(loadShaderProgram("ShaderBinaries/Sky.ankiprogbin", {}, m_prog, m_skyLutGrProg, "SkyLut"));
+
+	const TextureUsageBit usage = TextureUsageBit::kAllCompute;
+	const TextureUsageBit initialUsage = TextureUsageBit::kAllCompute;
+	const Format formatB =
+		(GrManager::getSingleton().getDeviceCapabilities().m_unalignedBbpTextureFormats) ? Format::kR16G16B16_Unorm : Format::kR16G16B16A16_Unorm;
+
+	m_transmittanceLut = getRenderer().createAndClearRenderTarget(
+		getRenderer().create2DRenderTargetInitInfo(kTransmittanceLutSize.x(), kTransmittanceLutSize.y(), formatB, usage, "SkyTransmittanceLut"),
+		initialUsage);
+
+	// TODO check formats
+	m_multipleScatteringLut = getRenderer().createAndClearRenderTarget(
+		getRenderer().create2DRenderTargetInitInfo(kMultipleScatteringLutSize.x(), kMultipleScatteringLutSize.y(), formatB, usage,
+												   "SkyMultipleScatteringLut"),
+		initialUsage);
+
+	m_skyLut = getRenderer().createAndClearRenderTarget(
+		getRenderer().create2DRenderTargetInitInfo(kSkyLutSize.x(), kSkyLutSize.y(), formatB, usage | TextureUsageBit::kSampledFragment, "SkyLut"),
+		initialUsage);
+
+	return Error::kNone;
+}
+
+void Sky::populateRenderGraph(RenderingContext& ctx)
+{
+	ANKI_TRACE_SCOPED_EVENT(Sky);
+
+	if(!isEnabled())
+	{
+		m_runCtx = {};
+		return;
+	}
+
+	RenderGraphDescription& rgraph = ctx.m_renderGraphDescr;
+
+	const LightComponent* dirLightc = SceneGraph::getSingleton().getDirectionalLight();
+	ANKI_ASSERT(dirLightc);
+	if(dirLightc->getDirection() == m_sunDir)
+	{
+		// Same light dir, no need to generate the sky LUT
+
+		m_runCtx.m_skyLutRt = rgraph.importRenderTarget(m_skyLut.get());
+		return;
+	}
+
+	// Create render targets
+	const Bool generateLuts = !m_transmittanceAndMultiScatterLutsGenerated;
+	RenderTargetHandle transmittanceLutRt;
+	RenderTargetHandle multipleScatteringLutRt;
+	if(generateLuts)
+	{
+		transmittanceLutRt = rgraph.importRenderTarget(m_transmittanceLut.get(), TextureUsageBit::kAllCompute);
+		multipleScatteringLutRt = rgraph.importRenderTarget(m_multipleScatteringLut.get(), TextureUsageBit::kAllCompute);
+		m_transmittanceAndMultiScatterLutsGenerated = true;
+	}
+	else
+	{
+		transmittanceLutRt = rgraph.importRenderTarget(m_transmittanceLut.get(), TextureUsageBit::kSampledCompute);
+		multipleScatteringLutRt = rgraph.importRenderTarget(m_multipleScatteringLut.get(), TextureUsageBit::kSampledCompute);
+	}
+
+	if(m_skyLutImportedOnce) [[likely]]
+	{
+		m_runCtx.m_skyLutRt = rgraph.importRenderTarget(m_skyLut.get());
+	}
+	else
+	{
+		m_runCtx.m_skyLutRt = rgraph.importRenderTarget(m_skyLut.get(), TextureUsageBit::kAllCompute);
+		m_skyLutImportedOnce = true;
+	}
+
+	// Transmittance LUT
+	if(generateLuts)
+	{
+		ComputeRenderPassDescription& rpass = rgraph.newComputeRenderPass("SkyTransmittanceLut");
+
+		rpass.newTextureDependency(transmittanceLutRt, TextureUsageBit::kUavComputeWrite);
+
+		rpass.setWork([this, transmittanceLutRt](RenderPassWorkContext& rgraphCtx) {
+			ANKI_TRACE_SCOPED_EVENT(SkyTransmittanceLut);
+
+			CommandBuffer& cmdb = *rgraphCtx.m_commandBuffer;
+
+			cmdb.bindShaderProgram(m_transmittanceLutGrProg.get());
+
+			rgraphCtx.bindUavTexture(0, 0, transmittanceLutRt);
+
+			dispatchPPCompute(cmdb, 8, 8, kTransmittanceLutSize.x(), kTransmittanceLutSize.y());
+		});
+	}
+
+	//  Multiple scattering LUT
+	if(generateLuts)
+	{
+		ComputeRenderPassDescription& rpass = rgraph.newComputeRenderPass("SkyMultipleScatteringLut");
+
+		rpass.newTextureDependency(transmittanceLutRt, TextureUsageBit::kSampledCompute);
+		rpass.newTextureDependency(multipleScatteringLutRt, TextureUsageBit::kUavComputeWrite);
+
+		rpass.setWork([this, transmittanceLutRt, multipleScatteringLutRt](RenderPassWorkContext& rgraphCtx) {
+			ANKI_TRACE_SCOPED_EVENT(SkyMultipleScatteringLut);
+
+			CommandBuffer& cmdb = *rgraphCtx.m_commandBuffer;
+
+			cmdb.bindShaderProgram(m_multipleScatteringLutGrProg.get());
+
+			rgraphCtx.bindColorTexture(0, 0, transmittanceLutRt);
+			cmdb.bindSampler(0, 1, getRenderer().getSamplers().m_trilinearClamp.get());
+			rgraphCtx.bindUavTexture(0, 2, multipleScatteringLutRt);
+
+			dispatchPPCompute(cmdb, 8, 8, kMultipleScatteringLutSize.x(), kMultipleScatteringLutSize.y());
+		});
+	}
+
+	// Sky LUT
+	{
+		ComputeRenderPassDescription& rpass = rgraph.newComputeRenderPass("SkyLut");
+
+		rpass.newTextureDependency(transmittanceLutRt, TextureUsageBit::kSampledCompute);
+		rpass.newTextureDependency(multipleScatteringLutRt, TextureUsageBit::kSampledCompute);
+		rpass.newTextureDependency(m_runCtx.m_skyLutRt, TextureUsageBit::kUavComputeWrite);
+
+		rpass.setWork([this, transmittanceLutRt, multipleScatteringLutRt](RenderPassWorkContext& rgraphCtx) {
+			ANKI_TRACE_SCOPED_EVENT(SkyLut);
+
+			const LightComponent* dirLightc = SceneGraph::getSingleton().getDirectionalLight();
+
+			CommandBuffer& cmdb = *rgraphCtx.m_commandBuffer;
+
+			cmdb.bindShaderProgram(m_skyLutGrProg.get());
+
+			rgraphCtx.bindColorTexture(0, 0, transmittanceLutRt);
+			rgraphCtx.bindColorTexture(0, 1, multipleScatteringLutRt);
+			cmdb.bindSampler(0, 2, getRenderer().getSamplers().m_trilinearClamp.get());
+			rgraphCtx.bindUavTexture(0, 3, m_runCtx.m_skyLutRt);
+
+			const Vec4 dir = -dirLightc->getDirection().xyz0();
+			cmdb.setPushConstants(&dir, sizeof(dir));
+
+			dispatchPPCompute(cmdb, 8, 8, kSkyLutSize.x(), kSkyLutSize.y());
+		});
+	}
+}
+
+Bool Sky::isEnabled() const
+{
+	const SkyboxComponent* skyc = SceneGraph::getSingleton().getSkybox();
+	const LightComponent* dirLightc = SceneGraph::getSingleton().getDirectionalLight();
+
+	return skyc && skyc->getSkyboxType() == SkyboxType::kGenerated && dirLightc;
+}
+
+} // end namespace anki

+ 73 - 0
AnKi/Renderer/Sky.h

@@ -0,0 +1,73 @@
+// Copyright (C) 2009-2023, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <AnKi/Renderer/RendererObject.h>
+#include <AnKi/Resource/ImageResource.h>
+#include <AnKi/Gr.h>
+
+namespace anki {
+
+/// @addtogroup renderer
+/// @{
+
+/// Resolves shadowmaps into a single texture.
+class Sky : public RendererObject
+{
+public:
+	Sky()
+	{
+		registerDebugRenderTarget("SkyLut");
+	}
+
+	Error init();
+
+	void populateRenderGraph(RenderingContext& ctx);
+
+	void getDebugRenderTarget([[maybe_unused]] CString rtName, Array<RenderTargetHandle, kMaxDebugRenderTargets>& handles,
+							  [[maybe_unused]] ShaderProgramPtr& optionalShaderProgram) const override
+	{
+		handles[0] = m_runCtx.m_skyLutRt;
+	}
+
+	RenderTargetHandle getSkyLutRt() const
+	{
+		ANKI_ASSERT(isEnabled());
+		return m_runCtx.m_skyLutRt;
+	}
+
+	ANKI_PURE Bool isEnabled() const;
+
+public:
+	ShaderProgramResourcePtr m_prog;
+	ShaderProgramPtr m_transmittanceLutGrProg;
+	ShaderProgramPtr m_multipleScatteringLutGrProg;
+	ShaderProgramPtr m_skyLutGrProg;
+
+	static constexpr UVec2 kTransmittanceLutSize{256, 64};
+	static constexpr UVec2 kMultipleScatteringLutSize{32, 32};
+	static constexpr UVec2 kSkyLutSize{256, 256};
+
+	TexturePtr m_transmittanceLut;
+	TexturePtr m_multipleScatteringLut;
+	TexturePtr m_skyLut;
+
+	Vec3 m_sunDir = Vec3(0.0f);
+
+	Bool m_transmittanceAndMultiScatterLutsGenerated = false;
+	Bool m_skyLutImportedOnce = false;
+
+	class
+	{
+	public:
+		RenderTargetHandle m_skyLutRt;
+	} m_runCtx;
+
+	Error initInternal();
+};
+/// @}
+
+} // namespace anki

+ 22 - 13
AnKi/Renderer/Utils/TraditionalDeferredShading.cpp

@@ -54,27 +54,38 @@ void TraditionalDeferredLightShading::drawLights(TraditionalDeferredLightShading
 
 	// Skybox first
 	const SkyboxComponent* skyc = SceneGraph::getSingleton().getSkybox();
-	if(skyc)
+	const LightComponent* dirLightc = SceneGraph::getSingleton().getDirectionalLight();
+	if(skyc && !(skyc->getSkyboxType() == SkyboxType::kGenerated && !dirLightc))
 	{
-		const Bool isSolidColor = (skyc->getSkyboxType() == SkyboxType::kSolidColor);
-
-		cmdb.bindShaderProgram(m_skyboxGrProgs[!isSolidColor].get());
+		cmdb.bindShaderProgram(m_skyboxGrProgs[skyc->getSkyboxType()].get());
 
 		cmdb.bindSampler(0, 0, getRenderer().getSamplers().m_nearestNearestClamp.get());
 		rgraphCtx.bindTexture(0, 1, info.m_gbufferDepthRenderTarget, TextureSubresourceInfo(DepthStencilAspectBit::kDepth));
 
-		if(!isSolidColor)
+		TraditionalDeferredSkyboxConstants unis = {};
+		unis.m_invertedViewProjectionMat = info.m_invViewProjectionMatrix;
+		unis.m_cameraPos = info.m_cameraPosWSpace.xyz();
+		unis.m_scale = skyc->getImageScale();
+		unis.m_bias = skyc->getImageBias();
+
+		if(skyc->getSkyboxType() == SkyboxType::kSolidColor)
+		{
+			unis.m_solidColorOrDirToLight = skyc->getSolidColor();
+		}
+		else if(skyc->getSkyboxType() == SkyboxType::kImage2D)
 		{
 			cmdb.bindSampler(0, 2, getRenderer().getSamplers().m_trilinearRepeatAniso.get());
 			cmdb.bindTexture(0, 3, &skyc->getImageResource().getTextureView());
 		}
+		else
+		{
+			unis.m_solidColorOrDirToLight = -dirLightc->getDirection().xyz();
+			unis.m_dirLightPower = dirLightc->getDiffuseColor().xyz().dot(Vec3(0.30f, 0.59f, 0.11f));
+
+			cmdb.bindSampler(0, 2, getRenderer().getSamplers().m_trilinearClamp.get());
+			rgraphCtx.bindColorTexture(0, 3, info.m_skyLutRenderTarget);
+		}
 
-		TraditionalDeferredSkyboxConstants unis;
-		unis.m_solidColor = (isSolidColor) ? skyc->getSolidColor() : Vec3(0.0f);
-		unis.m_invertedViewProjectionMat = info.m_invViewProjectionMatrix;
-		unis.m_cameraPos = info.m_cameraPosWSpace.xyz();
-		unis.m_scale = skyc->getImageScale();
-		unis.m_bias = skyc->getImageBias();
 		cmdb.setPushConstants(&unis, sizeof(unis));
 
 		drawQuad(cmdb);
@@ -82,8 +93,6 @@ void TraditionalDeferredLightShading::drawLights(TraditionalDeferredLightShading
 
 	// Light shading
 	{
-		const LightComponent* dirLightc = SceneGraph::getSingleton().getDirectionalLight();
-
 		TraditionalDeferredShadingConstants* unis = allocateAndBindConstants<TraditionalDeferredShadingConstants>(cmdb, 0, 0);
 
 		unis->m_invViewProjMat = info.m_invViewProjectionMatrix;

+ 3 - 1
AnKi/Renderer/Utils/TraditionalDeferredShading.h

@@ -37,6 +37,8 @@ public:
 	RenderTargetHandle m_directionalLightShadowmapRenderTarget;
 	TextureSubresourceInfo m_directionalLightShadowmapRenderTargetSubresourceInfo = {DepthStencilAspectBit::kDepth};
 
+	RenderTargetHandle m_skyLutRenderTarget;
+
 	RenderPassWorkContext* m_renderpassContext = nullptr;
 };
 
@@ -54,7 +56,7 @@ private:
 	Array<ShaderProgramPtr, 2> m_lightGrProg;
 
 	ShaderProgramResourcePtr m_skyboxProg;
-	Array<ShaderProgramPtr, 2> m_skyboxGrProgs;
+	Array<ShaderProgramPtr, 3> m_skyboxGrProgs;
 
 	SamplerPtr m_shadowSampler;
 };

+ 7 - 1
AnKi/Scene/Components/SkyboxComponent.h

@@ -21,7 +21,8 @@ class SkyboxQueueElement;
 enum class SkyboxType : U8
 {
 	kSolidColor,
-	kImage2D
+	kImage2D,
+	kGenerated
 };
 
 /// Skybox config.
@@ -79,6 +80,11 @@ public:
 		return m_imageBias;
 	}
 
+	void setGeneratedSky()
+	{
+		m_type = SkyboxType::kGenerated;
+	}
+
 	void setMinFogDensity(F32 density)
 	{
 		m_fog.m_minDensity = clamp(density, 0.0f, 100.0f);

+ 64 - 24
AnKi/Script/Scene.cpp

@@ -61,7 +61,7 @@ static EventManager* getEventManager(lua_State* l)
 using WeakArraySceneNodePtr = WeakArray<SceneNode*>;
 using WeakArrayBodyComponentPtr = WeakArray<BodyComponent*>;
 
-LuaUserDataTypeInfo luaUserDataTypeInfoLightComponentType = {1918372895594684481, "LightComponentType", 0, nullptr, nullptr};
+LuaUserDataTypeInfo luaUserDataTypeInfoLightComponentType = {4219517956328838712, "LightComponentType", 0, nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<LightComponentType>()
@@ -97,7 +97,7 @@ static inline void wrapLightComponentType(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoWeakArraySceneNodePtr = {
-	-8393853726430350519, "WeakArraySceneNodePtr", LuaUserData::computeSizeForGarbageCollected<WeakArraySceneNodePtr>(), nullptr, nullptr};
+	-89195344190135263, "WeakArraySceneNodePtr", LuaUserData::computeSizeForGarbageCollected<WeakArraySceneNodePtr>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<WeakArraySceneNodePtr>()
@@ -216,7 +216,7 @@ static inline void wrapWeakArraySceneNodePtr(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoWeakArrayBodyComponentPtr = {
-	-1025709696283503968, "WeakArrayBodyComponentPtr", LuaUserData::computeSizeForGarbageCollected<WeakArrayBodyComponentPtr>(), nullptr, nullptr};
+	8690829719095736348, "WeakArrayBodyComponentPtr", LuaUserData::computeSizeForGarbageCollected<WeakArrayBodyComponentPtr>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<WeakArrayBodyComponentPtr>()
@@ -334,7 +334,7 @@ static inline void wrapWeakArrayBodyComponentPtr(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoLightComponent = {-3675274546931224198, "LightComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoLightComponent = {-5271648520904895104, "LightComponent",
 														 LuaUserData::computeSizeForGarbageCollected<LightComponent>(), nullptr, nullptr};
 
 template<>
@@ -945,7 +945,7 @@ static inline void wrapLightComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoDecalComponent = {-2073814368319048649, "DecalComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoDecalComponent = {5995036315092354567, "DecalComponent",
 														 LuaUserData::computeSizeForGarbageCollected<DecalComponent>(), nullptr, nullptr};
 
 template<>
@@ -1117,7 +1117,7 @@ static inline void wrapDecalComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoLensFlareComponent = {5474230361879199779, "LensFlareComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoLensFlareComponent = {3035215901600321039, "LensFlareComponent",
 															 LuaUserData::computeSizeForGarbageCollected<LensFlareComponent>(), nullptr, nullptr};
 
 template<>
@@ -1280,7 +1280,7 @@ static inline void wrapLensFlareComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoBodyComponent = {-2846863010728672642, "BodyComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoBodyComponent = {-9159963251664499177, "BodyComponent",
 														LuaUserData::computeSizeForGarbageCollected<BodyComponent>(), nullptr, nullptr};
 
 template<>
@@ -1433,7 +1433,7 @@ static inline void wrapBodyComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoTriggerComponent = {4126023717417036406, "TriggerComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoTriggerComponent = {-7425360375740373469, "TriggerComponent",
 														   LuaUserData::computeSizeForGarbageCollected<TriggerComponent>(), nullptr, nullptr};
 
 template<>
@@ -1596,7 +1596,7 @@ static inline void wrapTriggerComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoFogDensityComponent = {-8065432833529453691, "FogDensityComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoFogDensityComponent = {-449397768782810738, "FogDensityComponent",
 															  LuaUserData::computeSizeForGarbageCollected<FogDensityComponent>(), nullptr, nullptr};
 
 template<>
@@ -1799,7 +1799,7 @@ static inline void wrapFogDensityComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoCameraComponent = {-1108575505958935399, "CameraComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoCameraComponent = {8960080176973402871, "CameraComponent",
 														  LuaUserData::computeSizeForGarbageCollected<CameraComponent>(), nullptr, nullptr};
 
 template<>
@@ -1881,7 +1881,7 @@ static inline void wrapCameraComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoGlobalIlluminationProbeComponent = {
-	8571128079366460170, "GlobalIlluminationProbeComponent", LuaUserData::computeSizeForGarbageCollected<GlobalIlluminationProbeComponent>(), nullptr,
+	3424589126774403182, "GlobalIlluminationProbeComponent", LuaUserData::computeSizeForGarbageCollected<GlobalIlluminationProbeComponent>(), nullptr,
 	nullptr};
 
 template<>
@@ -2128,7 +2128,7 @@ static inline void wrapGlobalIlluminationProbeComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoReflectionProbeComponent = {
-	2942902628021820888, "ReflectionProbeComponent", LuaUserData::computeSizeForGarbageCollected<ReflectionProbeComponent>(), nullptr, nullptr};
+	-6758135295013264798, "ReflectionProbeComponent", LuaUserData::computeSizeForGarbageCollected<ReflectionProbeComponent>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<ReflectionProbeComponent>()
@@ -2243,7 +2243,7 @@ static inline void wrapReflectionProbeComponent(lua_State* l)
 }
 
 LuaUserDataTypeInfo luaUserDataTypeInfoParticleEmitterComponent = {
-	1295664338858625731, "ParticleEmitterComponent", LuaUserData::computeSizeForGarbageCollected<ParticleEmitterComponent>(), nullptr, nullptr};
+	5706885704346443564, "ParticleEmitterComponent", LuaUserData::computeSizeForGarbageCollected<ParticleEmitterComponent>(), nullptr, nullptr};
 
 template<>
 const LuaUserDataTypeInfo& LuaUserData::getDataTypeInfoFor<ParticleEmitterComponent>()
@@ -2305,7 +2305,7 @@ static inline void wrapParticleEmitterComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoModelComponent = {-3217880167307335193, "ModelComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoModelComponent = {97249979289646973, "ModelComponent",
 														 LuaUserData::computeSizeForGarbageCollected<ModelComponent>(), nullptr, nullptr};
 
 template<>
@@ -2368,7 +2368,7 @@ static inline void wrapModelComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoSkinComponent = {-1893988325997574862, "SkinComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoSkinComponent = {-7585993549028037320, "SkinComponent",
 														LuaUserData::computeSizeForGarbageCollected<SkinComponent>(), nullptr, nullptr};
 
 template<>
@@ -2431,7 +2431,7 @@ static inline void wrapSkinComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoSkyboxComponent = {-4950185802732061592, "SkyboxComponent",
+LuaUserDataTypeInfo luaUserDataTypeInfoSkyboxComponent = {-6878543670401624158, "SkyboxComponent",
 														  LuaUserData::computeSizeForGarbageCollected<SkyboxComponent>(), nullptr, nullptr};
 
 template<>
@@ -2535,6 +2535,45 @@ static int wrapSkyboxComponentloadImageResource(lua_State* l)
 	return 0;
 }
 
+/// Pre-wrap method SkyboxComponent::setGeneratedSky.
+static inline int pwrapSkyboxComponentsetGeneratedSky(lua_State* l)
+{
+	[[maybe_unused]] LuaUserData* ud;
+	[[maybe_unused]] void* voidp;
+	[[maybe_unused]] PtrSize size;
+
+	if(LuaBinder::checkArgsCount(l, 1)) [[unlikely]]
+	{
+		return -1;
+	}
+
+	// Get "this" as "self"
+	if(LuaBinder::checkUserData(l, 1, luaUserDataTypeInfoSkyboxComponent, ud))
+	{
+		return -1;
+	}
+
+	SkyboxComponent* self = ud->getData<SkyboxComponent>();
+
+	// Call the method
+	self->setGeneratedSky();
+
+	return 0;
+}
+
+/// Wrap method SkyboxComponent::setGeneratedSky.
+static int wrapSkyboxComponentsetGeneratedSky(lua_State* l)
+{
+	int res = pwrapSkyboxComponentsetGeneratedSky(l);
+	if(res >= 0)
+	{
+		return res;
+	}
+
+	lua_error(l);
+	return 0;
+}
+
 /// Pre-wrap method SkyboxComponent::setMinFogDensity.
 static inline int pwrapSkyboxComponentsetMinFogDensity(lua_State* l)
 {
@@ -2872,6 +2911,7 @@ static inline void wrapSkyboxComponent(lua_State* l)
 	LuaBinder::createClass(l, &luaUserDataTypeInfoSkyboxComponent);
 	LuaBinder::pushLuaCFuncMethod(l, "setSolidColor", wrapSkyboxComponentsetSolidColor);
 	LuaBinder::pushLuaCFuncMethod(l, "loadImageResource", wrapSkyboxComponentloadImageResource);
+	LuaBinder::pushLuaCFuncMethod(l, "setGeneratedSky", wrapSkyboxComponentsetGeneratedSky);
 	LuaBinder::pushLuaCFuncMethod(l, "setMinFogDensity", wrapSkyboxComponentsetMinFogDensity);
 	LuaBinder::pushLuaCFuncMethod(l, "setMaxFogDensity", wrapSkyboxComponentsetMaxFogDensity);
 	LuaBinder::pushLuaCFuncMethod(l, "setHeightOfMinFogDensity", wrapSkyboxComponentsetHeightOfMinFogDensity);
@@ -2882,7 +2922,7 @@ static inline void wrapSkyboxComponent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoSceneNode = {-8850241204806571619, "SceneNode", LuaUserData::computeSizeForGarbageCollected<SceneNode>(),
+LuaUserDataTypeInfo luaUserDataTypeInfoSceneNode = {-1295485195147561206, "SceneNode", LuaUserData::computeSizeForGarbageCollected<SceneNode>(),
 													nullptr, nullptr};
 
 template<>
@@ -4719,7 +4759,7 @@ static inline void wrapSceneNode(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoSceneGraph = {-4636742016121079003, "SceneGraph", LuaUserData::computeSizeForGarbageCollected<SceneGraph>(),
+LuaUserDataTypeInfo luaUserDataTypeInfoSceneGraph = {-7805560184770748431, "SceneGraph", LuaUserData::computeSizeForGarbageCollected<SceneGraph>(),
 													 nullptr, nullptr};
 
 template<>
@@ -4905,7 +4945,7 @@ static inline void wrapSceneGraph(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoEvent = {-1645267055986152358, "Event", LuaUserData::computeSizeForGarbageCollected<Event>(), nullptr,
+LuaUserDataTypeInfo luaUserDataTypeInfoEvent = {-6825970229082400759, "Event", LuaUserData::computeSizeForGarbageCollected<Event>(), nullptr,
 												nullptr};
 
 template<>
@@ -4970,7 +5010,7 @@ static inline void wrapEvent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoLightEvent = {6526760604254386170, "LightEvent", LuaUserData::computeSizeForGarbageCollected<LightEvent>(),
+LuaUserDataTypeInfo luaUserDataTypeInfoLightEvent = {-2438111026101797635, "LightEvent", LuaUserData::computeSizeForGarbageCollected<LightEvent>(),
 													 nullptr, nullptr};
 
 template<>
@@ -5089,7 +5129,7 @@ static inline void wrapLightEvent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoScriptEvent = {-6276976464258063819, "ScriptEvent", LuaUserData::computeSizeForGarbageCollected<ScriptEvent>(),
+LuaUserDataTypeInfo luaUserDataTypeInfoScriptEvent = {5136611228223914887, "ScriptEvent", LuaUserData::computeSizeForGarbageCollected<ScriptEvent>(),
 													  nullptr, nullptr};
 
 template<>
@@ -5105,7 +5145,7 @@ static inline void wrapScriptEvent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoJitterMoveEvent = {-5524576077885319542, "JitterMoveEvent",
+LuaUserDataTypeInfo luaUserDataTypeInfoJitterMoveEvent = {-4365713583592201088, "JitterMoveEvent",
 														  LuaUserData::computeSizeForGarbageCollected<JitterMoveEvent>(), nullptr, nullptr};
 
 template<>
@@ -5180,7 +5220,7 @@ static inline void wrapJitterMoveEvent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoAnimationEvent = {-3440619397170119690, "AnimationEvent",
+LuaUserDataTypeInfo luaUserDataTypeInfoAnimationEvent = {4765740095150022724, "AnimationEvent",
 														 LuaUserData::computeSizeForGarbageCollected<AnimationEvent>(), nullptr, nullptr};
 
 template<>
@@ -5196,7 +5236,7 @@ static inline void wrapAnimationEvent(lua_State* l)
 	lua_settop(l, 0);
 }
 
-LuaUserDataTypeInfo luaUserDataTypeInfoEventManager = {-3352184178300328573, "EventManager",
+LuaUserDataTypeInfo luaUserDataTypeInfoEventManager = {8040133071572423967, "EventManager",
 													   LuaUserData::computeSizeForGarbageCollected<EventManager>(), nullptr, nullptr};
 
 template<>

+ 1 - 0
AnKi/Script/Scene.xml

@@ -347,6 +347,7 @@ using WeakArrayBodyComponentPtr = WeakArray<BodyComponent*>;
 						<arg>CString</arg>
 					</args>
 				</method>
+				<method name="setGeneratedSky" />
 				<method name="setMinFogDensity">
 					<args>
 						<arg>F32</arg>

+ 2 - 2
AnKi/Shaders/Include/TraditionalDeferredShadingTypes.h

@@ -36,13 +36,13 @@ struct TraditionalDeferredShadingConstants
 
 struct TraditionalDeferredSkyboxConstants
 {
-	RVec3 m_solidColor;
+	RVec3 m_solidColorOrDirToLight;
 	F32 m_padding1;
 
 	Mat4 m_invertedViewProjectionMat;
 
 	Vec3 m_cameraPos;
-	F32 m_padding2;
+	F32 m_dirLightPower;
 
 	Vec3 m_scale;
 	F32 m_padding3;

+ 30 - 3
AnKi/Shaders/LightShadingSkybox.ankiprog

@@ -3,7 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#pragma anki mutator METHOD 0 1 // 0: solid colod, 1: 2D image
+#pragma anki mutator METHOD 0 1 2 // 0: solid colod, 1: 2D image, 2: generated
 
 #pragma anki technique_start vert
 
@@ -30,6 +30,8 @@ VertOut main(U32 vertId : SV_VERTEXID)
 #pragma anki technique_start frag
 
 #include <AnKi/Shaders/Functions.hlsl>
+#include <AnKi/Shaders/TonemappingFunctions.hlsl>
+#include <AnKi/Shaders/Sky.hlsl>
 
 #if METHOD == 0
 struct Constants
@@ -39,7 +41,7 @@ struct Constants
 };
 
 [[vk::push_constant]] ConstantBuffer<Constants> g_consts;
-#else
+#elif METHOD == 1
 [[vk::binding(0)]] SamplerState g_trilinearAnySampler;
 [[vk::binding(1)]] Texture2D<RVec4> g_envMapTex;
 
@@ -57,6 +59,22 @@ struct Constants
 	F32 m_padding2;
 };
 
+[[vk::push_constant]] ConstantBuffer<Constants> g_consts;
+#else
+[[vk::binding(0)]] SamplerState g_linearAnyClampSampler;
+[[vk::binding(1)]] Texture2D<Vec4> g_skyLut;
+
+struct Constants
+{
+	Mat4 m_invertedViewProjectionJitterMat;
+
+	Vec3 m_cameraPos;
+	F32 m_padding1;
+
+	Vec3 m_dirToSun;
+	F32 m_sunPower;
+};
+
 [[vk::push_constant]] ConstantBuffer<Constants> g_consts;
 #endif
 
@@ -65,7 +83,7 @@ RVec3 main([[vk::location(0)]] Vec2 uv : TEXCOORD) : SV_TARGET0
 #if METHOD == 0
 	ANKI_MAYBE_UNUSED(uv);
 	return g_consts.m_solidColor;
-#else
+#elif METHOD == 1
 	const F32 depth = 1.0;
 	const Vec2 ndc = uvToNdc(uv);
 	const Vec4 worldPos4 = mul(g_consts.m_invertedViewProjectionJitterMat, Vec4(ndc, depth, 1.0));
@@ -84,6 +102,15 @@ RVec3 main([[vk::location(0)]] Vec2 uv : TEXCOORD) : SV_TARGET0
 	const F32 bias = (maxD > 0.9) ? -100.0f : 0.0f;
 
 	return g_envMapTex.SampleBias(g_trilinearAnySampler, uv3, bias).rgb * g_consts.m_scale + g_consts.m_bias;
+#else
+	const F32 depth = 1.0;
+	const Vec2 ndc = uvToNdc(uv);
+	const Vec4 worldPos4 = mul(g_consts.m_invertedViewProjectionJitterMat, Vec4(ndc, depth, 1.0));
+	const Vec3 worldPos = worldPos4.xyz / worldPos4.w;
+
+	const Vec3 eyeToFrag = normalize(worldPos - g_consts.m_cameraPos);
+
+	return computeSkyColor(g_skyLut, g_linearAnyClampSampler, eyeToFrag, g_consts.m_dirToSun, g_consts.m_sunPower, true);
 #endif
 }
 

+ 405 - 0
AnKi/Shaders/Sky.ankiprog

@@ -0,0 +1,405 @@
+// Copyright (C) 2009-2023, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+// Shamelessly copied from https://www.shadertoy.com/view/slSXRW
+
+#include <AnKi/Shaders/Sky.hlsl>
+
+constexpr F32 kAtmosphereRadiusMM = 6.460f;
+
+// These are per megameter.
+constexpr Vec3 kRayleighScatteringBase = Vec3(5.802f, 13.558f, 33.1f);
+constexpr F32 kRayleighAbsorptionBase = 0.0f;
+
+constexpr F32 kMieScatteringBase = 3.996f;
+constexpr F32 kMieAbsorptionBase = 4.4f;
+
+constexpr Vec3 kOzoneAbsorptionBase = Vec3(0.650f, 1.881f, 0.085f);
+
+constexpr F32 kMiltipleScatteringSteps = 20.0f;
+constexpr F32 kSqrtSamples = 8.0f;
+
+constexpr F32 kSunTransmittanceSteps = 40.0f;
+
+constexpr F32 kScatteringSteps = 32.0f;
+
+constexpr Vec3 kGroundAlbedo = 0.3f;
+
+// From https://gamedev.stackexchange.com/questions/96459/fast-ray-sphere-collision-code.
+F32 rayIntersectSphere(Vec3 ro, Vec3 rd, F32 rad)
+{
+	const F32 b = dot(ro, rd);
+	const F32 c = dot(ro, ro) - rad * rad;
+
+	if(c > 0.0f && b > 0.0)
+	{
+		return -1.0;
+	}
+
+	const F32 discr = b * b - c;
+	if(discr < 0.0)
+	{
+		return -1.0;
+	}
+
+	// Special case: inside sphere, use far discriminant
+	if(discr > b * b)
+	{
+		return (-b + sqrt(discr));
+	}
+
+	return -b - sqrt(discr);
+}
+
+void getScatteringValues(Vec3 pos, out Vec3 rayleighScattering, out F32 mieScattering, out Vec3 extinction)
+{
+	const F32 altitudeKM = (length(pos) - kGroundRadiusMM) * 1000.0f;
+	// Note: Paper gets these switched up.
+	const F32 rayleighDensity = exp(-altitudeKM / 8.0f);
+	const F32 mieDensity = exp(-altitudeKM / 1.2f);
+
+	rayleighScattering = kRayleighScatteringBase * rayleighDensity;
+	const F32 rayleighAbsorption = kRayleighAbsorptionBase * rayleighDensity;
+
+	mieScattering = kMieScatteringBase * mieDensity;
+	F32 mieAbsorption = kMieAbsorptionBase * mieDensity;
+
+	const Vec3 ozoneAbsorption = kOzoneAbsorptionBase * max(0.0f, 1.0f - abs(altitudeKM - 25.0f) / 15.0f);
+
+	extinction = rayleighScattering + rayleighAbsorption + mieScattering + mieAbsorption + ozoneAbsorption;
+}
+
+/// Get value from transmittance LUT.
+Vec3 getValFromTLut(Texture2D<Vec4> tex, SamplerState linearAnyClampSampler, Vec3 pos, Vec3 dirToSun)
+{
+	const F32 height = length(pos);
+	const Vec3 up = pos / height;
+	const F32 sunCosZenithAngle = dot(dirToSun, up);
+	const Vec2 uv = Vec2(saturate(0.5f + 0.5f * sunCosZenithAngle), saturate((height - kGroundRadiusMM) / (kAtmosphereRadiusMM - kGroundRadiusMM)));
+	return tex.SampleLevel(linearAnyClampSampler, uv, 0.0f).xyz;
+}
+
+Vec3 getValFromMultiScattLut(Texture2D<Vec4> tex, SamplerState linearAnyClampSampler, Vec3 pos, Vec3 dirToSun)
+{
+	const F32 height = length(pos);
+	const Vec3 up = pos / height;
+	const F32 sunCosZenithAngle = dot(dirToSun, up);
+	const Vec2 uv = Vec2(saturate(0.5 + 0.5 * sunCosZenithAngle), saturate((height - kGroundRadiusMM) / (kAtmosphereRadiusMM - kGroundRadiusMM)));
+	return tex.SampleLevel(linearAnyClampSampler, uv, 0.0f).xyz;
+}
+
+F32 getSunAltitude(F32 hour24)
+{
+	const F32 periodSec = 24.0f;
+	const F32 halfPeriod = periodSec / 2.0f;
+	const F32 sunriseShift = 0.8f; // The closer it is to 1 the more time the sun will stay hidden
+	F32 cyclePoint = (1.0f - abs((fmod(hour24, periodSec) - halfPeriod) / halfPeriod));
+	cyclePoint = (cyclePoint * (1.0f + sunriseShift)) - sunriseShift;
+	return (0.5f * kPi) * cyclePoint;
+}
+
+Vec3 getSunDir(F32 time)
+{
+	const F32 altitude = getSunAltitude(time);
+	return normalize(Vec3(0.0f, sin(altitude), -cos(altitude)));
+}
+
+F32 getMiePhase(F32 cosTheta)
+{
+	const F32 g = 0.8f;
+	const F32 scale = 3.0f / (8.0f * kPi);
+
+	const F32 num = (1.0f - g * g) * (1.0f + cosTheta * cosTheta);
+	const F32 denom = (2.0f + g * g) * pow((1.0f + g * g - 2.0f * g * cosTheta), 1.5f);
+
+	return scale * num / denom;
+}
+
+F32 getRayleighPhase(F32 cosTheta)
+{
+	const F32 k = 3.0f / (16.0f * kPi);
+	return k * (1.0f + cosTheta * cosTheta);
+}
+
+// ===========================================================================
+// Transmitance LUT                                                          =
+// ===========================================================================
+#pragma anki technique_start comp SkyTransmittanceLut
+
+[[vk::binding(0)]] RWTexture2D<Vec4> g_tLutUav;
+
+Vec3 getSunTransmittance(Vec3 pos, Vec3 dirToSun)
+{
+	if(rayIntersectSphere(pos, dirToSun, kGroundRadiusMM) > 0.0f)
+	{
+		return 0.0f;
+	}
+
+	const F32 atmoDist = rayIntersectSphere(pos, dirToSun, kAtmosphereRadiusMM);
+	F32 t = 0.0f;
+
+	Vec3 transmittance = 1.0f;
+	for(F32 i = 0.0f; i < kSunTransmittanceSteps; i += 1.0f)
+	{
+		const F32 newT = ((i + 0.3f) / kSunTransmittanceSteps) * atmoDist;
+		const F32 dt = newT - t;
+		t = newT;
+
+		const Vec3 newPos = pos + t * dirToSun;
+
+		Vec3 rayleighScattering, extinction;
+		F32 mieScattering;
+		getScatteringValues(newPos, rayleighScattering, mieScattering, extinction);
+
+		transmittance *= exp(-dt * extinction);
+	}
+	return transmittance;
+}
+
+[numthreads(8, 8, 1)] void main(UVec2 svDispatchThreadId : SV_DISPATCHTHREADID)
+{
+	Vec2 lutSize;
+	g_tLutUav.GetDimensions(lutSize.x, lutSize.y);
+
+	const Vec2 uv = (Vec2(svDispatchThreadId) + 0.5f) / lutSize;
+
+	const F32 sunCosTheta = 2.0f * uv.x - 1.0f;
+	const F32 sunTheta = safeacos(sunCosTheta);
+	const F32 height = lerp(kGroundRadiusMM, kAtmosphereRadiusMM, uv.y);
+
+	const Vec3 pos = Vec3(0.0f, height, 0.0f);
+	const Vec3 dirToSun = normalize(Vec3(0.0f, sunCosTheta, -sin(sunTheta)));
+
+	g_tLutUav[svDispatchThreadId] = Vec4(getSunTransmittance(pos, dirToSun), 1.0f);
+}
+
+#pragma anki technique_end comp SkyTransmittanceLut
+
+// ===========================================================================
+// Multiple scattering LUT                                                   =
+// ===========================================================================
+#pragma anki technique_start comp SkyMultipleScatteringLut
+
+[[vk::binding(0)]] Texture2D<Vec4> g_tLutTex;
+[[vk::binding(1)]] SamplerState g_linearAnyClampSampler;
+[[vk::binding(2)]] RWTexture2D<Vec4> g_mLutUav;
+
+Vec3 getSphericalDir(F32 theta, F32 phi)
+{
+	const F32 cosPhi = cos(phi);
+	const F32 sinPhi = sin(phi);
+	const F32 cosTheta = cos(theta);
+	const F32 sinTheta = sin(theta);
+	return Vec3(sinPhi * sinTheta, cosPhi, sinPhi * cosTheta);
+}
+
+// Calculates Equation (5) and (7) from the paper.
+void getMulScattValues(Vec3 pos, Vec3 dirToSun, out Vec3 lumTotal, out Vec3 fms)
+{
+	lumTotal = 0.0f;
+	fms = 0.0f;
+
+	const F32 invSamples = 1.0f / F32(kSqrtSamples * kSqrtSamples);
+	for(F32 i = 0.0; i < kSqrtSamples; i += 1.0f)
+	{
+		for(F32 j = 0.0f; j < kSqrtSamples; j += 1.0f)
+		{
+			// This integral is symmetric about theta = 0 (or theta = PI), so we only need to integrate from zero to PI, not zero to 2*PI.
+			const F32 theta = kPi * (i + 0.5f) / kSqrtSamples;
+			const F32 phi = safeacos(1.0f - 2.0f * (j + 0.5f) / kSqrtSamples);
+			const Vec3 rayDir = getSphericalDir(theta, phi);
+
+			const F32 atmoDist = rayIntersectSphere(pos, rayDir, kAtmosphereRadiusMM);
+			const F32 groundDist = rayIntersectSphere(pos, rayDir, kGroundRadiusMM);
+			F32 tMax = atmoDist;
+			if(groundDist > 0.0f)
+			{
+				tMax = groundDist;
+			}
+
+			const F32 cosTheta = dot(rayDir, dirToSun);
+
+			const F32 miePhaseValue = getMiePhase(cosTheta);
+			const F32 rayleighPhaseValue = getRayleighPhase(-cosTheta);
+
+			Vec3 lum = 0.0f;
+			Vec3 lumFactor = 0.0f;
+			Vec3 transmittance = 1.0f;
+			F32 t = 0.0f;
+			for(F32 stepI = 0.0f; stepI < kMiltipleScatteringSteps; stepI += 1.0f)
+			{
+				const F32 newT = ((stepI + 0.3f) / kMiltipleScatteringSteps) * tMax;
+				const F32 dt = newT - t;
+				t = newT;
+
+				const Vec3 newPos = pos + t * rayDir;
+
+				Vec3 rayleighScattering, extinction;
+				F32 mieScattering;
+				getScatteringValues(newPos, rayleighScattering, mieScattering, extinction);
+
+				const Vec3 sampleTransmittance = exp(-dt * extinction);
+
+				// Integrate within each segment.
+				const Vec3 scatteringNoPhase = rayleighScattering + mieScattering;
+				const Vec3 scatteringF = (scatteringNoPhase - scatteringNoPhase * sampleTransmittance) / extinction;
+				lumFactor += transmittance * scatteringF;
+
+				// This is slightly different from the paper, but I think the paper has a mistake? In equation (6), I think S(x,w_s) should be
+				// S(x-tv,w_s).
+				const Vec3 sunTransmittance = getValFromTLut(g_tLutTex, g_linearAnyClampSampler, newPos, dirToSun);
+
+				const Vec3 rayleighInScattering = rayleighScattering * rayleighPhaseValue;
+				const F32 mieInScattering = mieScattering * miePhaseValue;
+				const Vec3 inScattering = (rayleighInScattering + mieInScattering) * sunTransmittance;
+
+				// Integrated scattering within path segment.
+				const Vec3 scatteringIntegral = (inScattering - inScattering * sampleTransmittance) / extinction;
+
+				lum += scatteringIntegral * transmittance;
+				transmittance *= sampleTransmittance;
+			}
+
+			if(groundDist > 0.0f)
+			{
+				Vec3 hitPos = pos + groundDist * rayDir;
+				if(dot(pos, dirToSun) > 0.0f)
+				{
+					hitPos = normalize(hitPos) * kGroundRadiusMM;
+					lum += transmittance * kGroundAlbedo * getValFromTLut(g_tLutTex, g_linearAnyClampSampler, hitPos, dirToSun);
+				}
+			}
+
+			fms += lumFactor * invSamples;
+			lumTotal += lum * invSamples;
+		}
+	}
+}
+
+[numthreads(8, 8, 1)] void main(UVec2 svDispatchThreadId : SV_DISPATCHTHREADID)
+{
+	Vec2 lutSize;
+	g_mLutUav.GetDimensions(lutSize.x, lutSize.y);
+
+	const Vec2 uv = (Vec2(svDispatchThreadId) + 0.5f) / lutSize;
+
+	const F32 sunCosTheta = 2.0f * uv.x - 1.0f;
+	const F32 sunTheta = safeacos(sunCosTheta);
+	const F32 height = lerp(kGroundRadiusMM, kAtmosphereRadiusMM, uv.y);
+
+	const Vec3 pos = Vec3(0.0f, height, 0.0f);
+	const Vec3 dirToSun = normalize(Vec3(0.0f, sunCosTheta, -sin(sunTheta)));
+
+	Vec3 lum, f_ms;
+	getMulScattValues(pos, dirToSun, lum, f_ms);
+
+	// Equation 10 from the paper.
+	const Vec3 psi = lum / (1.0f - f_ms);
+
+	g_mLutUav[svDispatchThreadId] = Vec4(psi, 0.0f);
+}
+
+#pragma anki technique_end comp SkyMultipleScatteringLut
+
+// ===========================================================================
+// Sky LUT                                                                   =
+// ===========================================================================
+#pragma anki technique_start comp SkyLut
+
+[[vk::binding(0)]] Texture2D<Vec4> g_tLutTex;
+[[vk::binding(1)]] Texture2D<Vec4> g_mLutTex;
+[[vk::binding(2)]] SamplerState g_linearAnyClampSampler;
+[[vk::binding(3)]] RWTexture2D<Vec4> g_skyLutUav;
+
+struct Consts
+{
+	Vec3 m_dirLightDirection;
+	F32 m_padding;
+};
+
+[[vk::push_constant]] ConstantBuffer<Consts> g_consts;
+
+Vec3 raymarchScattering(Vec3 pos, Vec3 rayDir, Vec3 dirToSun, F32 tMax, F32 numSteps)
+{
+	const F32 cosTheta = dot(rayDir, dirToSun);
+
+	const F32 miePhaseValue = getMiePhase(cosTheta);
+	const F32 rayleighPhaseValue = getRayleighPhase(-cosTheta);
+
+	Vec3 lum = 0.0f;
+	Vec3 transmittance = 1.0f;
+	F32 t = 0.0f;
+	for(F32 i = 0.0; i < numSteps; i += 1.0)
+	{
+		const F32 newT = ((i + 0.3f) / numSteps) * tMax;
+		const F32 dt = newT - t;
+		t = newT;
+
+		const Vec3 newPos = pos + t * rayDir;
+
+		Vec3 rayleighScattering, extinction;
+		F32 mieScattering;
+		getScatteringValues(newPos, rayleighScattering, mieScattering, extinction);
+
+		const Vec3 sampleTransmittance = exp(-dt * extinction);
+
+		const Vec3 sunTransmittance = getValFromTLut(g_tLutTex, g_linearAnyClampSampler, newPos, dirToSun);
+		const Vec3 psiMS = getValFromMultiScattLut(g_mLutTex, g_linearAnyClampSampler, newPos, dirToSun);
+
+		const Vec3 rayleighInScattering = rayleighScattering * (rayleighPhaseValue * sunTransmittance + psiMS);
+		const Vec3 mieInScattering = mieScattering * (miePhaseValue * sunTransmittance + psiMS);
+		const Vec3 inScattering = (rayleighInScattering + mieInScattering);
+
+		// Integrated scattering within path segment.
+		const Vec3 scatteringIntegral = (inScattering - inScattering * sampleTransmittance) / extinction;
+
+		lum += scatteringIntegral * transmittance;
+		transmittance *= sampleTransmittance;
+	}
+
+	return lum;
+}
+
+[numthreads(8, 8, 1)] void main(UVec2 svDispatchThreadId : SV_DISPATCHTHREADID)
+{
+	Vec2 lutSize;
+	g_skyLutUav.GetDimensions(lutSize.x, lutSize.y);
+
+	const Vec2 uv = (Vec2(svDispatchThreadId) + 0.5f) / lutSize;
+
+	const F32 azimuthAngle = (uv.x - 0.5f) * 2.0f * kPi;
+	// Non-linear mapping of altitude. See Section 5.3 of the paper.
+	F32 adjV;
+	if(uv.y < 0.5f)
+	{
+		const F32 coord = 1.0f - 2.0f * uv.y;
+		adjV = -coord * coord;
+	}
+	else
+	{
+		const F32 coord = uv.y * 2.0f - 1.0f;
+		adjV = coord * coord;
+	}
+
+	const F32 height = length(kViewPos);
+	const Vec3 up = kViewPos / height;
+	const F32 horizonAngle = safeacos(sqrt(height * height - kGroundRadiusMM * kGroundRadiusMM) / height) - 0.5f * kPi;
+	const F32 altitudeAngle = adjV * 0.5f * kPi - horizonAngle;
+
+	const F32 cosAltitude = cos(altitudeAngle);
+	const Vec3 rayDir = Vec3(cosAltitude * sin(azimuthAngle), sin(altitudeAngle), -cosAltitude * cos(azimuthAngle));
+
+	const F32 sunAltitude = (0.5f * kPi) - acos(dot(g_consts.m_dirLightDirection, up));
+	const Vec3 dirToSun = Vec3(0.0f, sin(sunAltitude), -cos(sunAltitude));
+
+	const F32 atmoDist = rayIntersectSphere(kViewPos, rayDir, kAtmosphereRadiusMM);
+	const F32 groundDist = rayIntersectSphere(kViewPos, rayDir, kGroundRadiusMM);
+	const F32 tMax = (groundDist < 0.0f) ? atmoDist : groundDist;
+	const Vec3 lum = raymarchScattering(kViewPos, rayDir, dirToSun, tMax, kScatteringSteps);
+
+	g_skyLutUav[svDispatchThreadId] = Vec4(lum, 0.0);
+}
+
+#pragma anki technique_end comp SkyLut

+ 80 - 0
AnKi/Shaders/Sky.hlsl

@@ -0,0 +1,80 @@
+// Copyright (C) 2009-2023, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <AnKi/Shaders/Common.hlsl>
+
+constexpr F32 kGroundRadiusMM = 6.360f;
+
+constexpr Vec3 kViewPos = Vec3(0.0f, kGroundRadiusMM + 0.0002f, 0.0f); // 200M above the ground.
+
+F32 safeacos(F32 x)
+{
+	return acos(clamp(x, -1.0f, 1.0f));
+}
+
+Vec3 getValFromSkyLut(Texture2D<Vec4> skyLut, SamplerState linearAnyClampSampler, Vec3 rayDir, Vec3 dirToSun)
+{
+	const F32 height = length(kViewPos);
+	const Vec3 up = kViewPos / height;
+
+	const F32 horizonAngle = safeacos(sqrt(height * height - kGroundRadiusMM * kGroundRadiusMM) / height);
+	const F32 altitudeAngle = horizonAngle - acos(dot(rayDir, up)); // Between -PI/2 and PI/2
+	F32 azimuthAngle; // Between 0 and 2*PI
+	if(abs(altitudeAngle) > (0.5f * kPi - 0.0001f))
+	{
+		// Looking nearly straight up or down.
+		azimuthAngle = 0.0f;
+	}
+	else
+	{
+		const Vec3 right = cross(dirToSun, up);
+		const Vec3 forward = cross(up, right);
+
+		const Vec3 projectedDir = normalize(rayDir - up * (dot(rayDir, up)));
+		const F32 sinTheta = dot(projectedDir, right);
+		const F32 cosTheta = dot(projectedDir, forward);
+		azimuthAngle = atan2(sinTheta, cosTheta) + kPi;
+	}
+
+	// Non-linear mapping of altitude angle. See Section 5.3 of the paper.
+	const F32 v = 0.5 + 0.5 * sign(altitudeAngle) * sqrt(abs(altitudeAngle) * 2.0 / kPi);
+	const Vec2 uv = Vec2(azimuthAngle / (2.0f * kPi), v);
+
+	return skyLut.SampleLevel(linearAnyClampSampler, uv, 0.0f).xyz;
+}
+
+Vec3 sunWithBloom(Vec3 rayDir, Vec3 dirToSun)
+{
+	const F32 sunSolidAngle = 0.53f * kPi / 180.0f;
+	const F32 minSunCosTheta = cos(sunSolidAngle);
+
+	const F32 cosTheta = dot(rayDir, dirToSun);
+	if(cosTheta >= minSunCosTheta)
+	{
+		return 1.0f;
+	}
+
+	const F32 offset = minSunCosTheta - cosTheta;
+	const F32 gaussianBloom = exp(-offset * 50000.0f) * 0.5f;
+	const F32 invBloom = 1.0f / (0.02f + offset * 300.0f) * 0.01f;
+	return gaussianBloom + invBloom;
+}
+
+/// Compute the sky color.
+/// @param rayDir It's the vector: normalize(frag-cameraOrigin)
+/// @param dirToSun It's opposite direction the light travels.
+Vec3 computeSkyColor(Texture2D<Vec4> skyLut, SamplerState linearAnyClampSampler, Vec3 rayDir, Vec3 dirToSun, F32 sunPower, Bool drawSun)
+{
+	Vec3 col = getValFromSkyLut(skyLut, linearAnyClampSampler, rayDir, dirToSun);
+
+	if(drawSun)
+	{
+		col += sunWithBloom(rayDir, dirToSun);
+	}
+
+	col *= sunPower;
+
+	return col;
+}

+ 15 - 3
AnKi/Shaders/TraditionalDeferredShadingSkybox.ankiprog

@@ -3,7 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#pragma anki mutator METHOD 0 1 // 0: solid colod, 1: 2D image
+#pragma anki mutator METHOD 0 1 2 // 0: solid colod, 1: 2D image, 2: Generated
 
 #pragma anki technique_start vert
 #include <AnKi/Shaders/QuadVert.hlsl>
@@ -12,6 +12,7 @@
 #pragma anki technique_start frag
 
 #include <AnKi/Shaders/Functions.hlsl>
+#include <AnKi/Shaders/Sky.hlsl>
 #include <AnKi/Shaders/Include/TraditionalDeferredShadingTypes.h>
 
 [[vk::push_constant]] ConstantBuffer<TraditionalDeferredSkyboxConstants> g_consts;
@@ -22,6 +23,9 @@
 #if METHOD == 1
 [[vk::binding(2)]] SamplerState g_trilinearAnySampler;
 [[vk::binding(3)]] Texture2D<RVec4> g_envMapTex;
+#elif METHOD == 2
+[[vk::binding(2)]] SamplerState g_linearAnyClampSampler;
+[[vk::binding(3)]] Texture2D<Vec4> g_skyLut;
 #endif
 
 RVec3 main(Vec2 uv : TEXCOORD) : SV_TARGET0
@@ -34,8 +38,8 @@ RVec3 main(Vec2 uv : TEXCOORD) : SV_TARGET0
 
 #if METHOD == 0
 	ANKI_MAYBE_UNUSED(uv);
-	return g_consts.m_solidColor;
-#else
+	return g_consts.m_solidColorOrDirToLight;
+#elif METHOD == 1
 	const F32 depthFar = 1.0;
 	const Vec2 ndc = uvToNdc(uv);
 	const Vec4 worldPos4 = mul(g_consts.m_invertedViewProjectionMat, Vec4(ndc, depthFar, 1.0));
@@ -45,6 +49,14 @@ RVec3 main(Vec2 uv : TEXCOORD) : SV_TARGET0
 
 	const Vec2 uv2 = equirectangularMapping(eyeToFrag);
 	return g_envMapTex.Sample(g_trilinearAnySampler, uv2).rgb * g_consts.m_scale + g_consts.m_bias;
+#else
+	const Vec2 ndc = uvToNdc(uv);
+	const Vec4 worldPos4 = mul(g_consts.m_invertedViewProjectionMat, Vec4(ndc, depth, 1.0));
+	const Vec3 worldPos = worldPos4.xyz / worldPos4.w;
+
+	const Vec3 eyeToFrag = normalize(worldPos - g_consts.m_cameraPos);
+
+	return computeSkyColor(g_skyLut, g_linearAnyClampSampler, eyeToFrag, g_consts.m_solidColorOrDirToLight, g_consts.m_dirLightPower, false);
 #endif
 }
 

+ 1 - 1
Samples/Common/SampleApp.cpp

@@ -63,7 +63,7 @@ Error SampleApp::userMainLoop(Bool& quit, Second elapsedTime)
 
 	if(in.getKey(KeyCode::kY) == 1)
 	{
-		renderer.setCurrentDebugRenderTarget((renderer.getCurrentDebugRenderTarget() == "GBufferNormals") ? "" : "GBufferNormals");
+		renderer.setCurrentDebugRenderTarget((renderer.getCurrentDebugRenderTarget() == "SkyLut") ? "" : "SkyLut");
 	}
 
 	if(in.getKey(KeyCode::kU) == 1)

+ 7 - 7
Samples/Sponza/Assets/Scene.lua

@@ -1,13 +1,13 @@
--- Generated by: C:\src\anki\out\build\x64-Debug\Binaries\GltfImporter.exe sponza_crytek_7_pbr_3.0.gltf C:/src/anki/Samples/Sponza/Assets/ -rpath Assets -texrpath Assets -lod-count 2 -light-scale 0.01 -v
+-- Generated by: C:\src\anki\out\build\x64-Debug\Binaries\GltfImporter.exe sponza_crytek_7_pbr_3.0.gltf C:/src/anki/Samples/Sponza/Assets/ -rpath Assets -texrpath Assets -lod-count 2 -light-scale 0.0001839 -v
 local scene = getSceneGraph()
 local events = getEventManager()
 
 node = scene:newSceneNode("Cube.017")
 comp = node:newSkyboxComponent()
-comp:loadImageResource("EngineAssets/DefaultSkybox.ankitex")
 comp:setMinFogDensity(0.000000)
 comp:setMaxFogDensity(0.900000)
 comp:setHeightOfMaxFogDensity(10.000000)
+comp:setGeneratedSky()
 trf = Transform.new()
 trf:setOrigin(Vec4.new(0.000000, 23.077700, 25.287918, 0))
 rot = Mat3x4.new()
@@ -207,7 +207,7 @@ node:setLocalTransform(trf)
 node = scene:newSceneNode("Point.003")
 lcomp = node:newLightComponent()
 lcomp:setLightComponentType(LightComponentType.kPoint)
-lcomp:setDiffuseColor(Vec4.new(20.007532, 8.208058, 5.410148, 1))
+lcomp:setDiffuseColor(Vec4.new(19.997978, 8.204139, 5.407565, 1))
 lcomp:setShadowEnabled(1)
 lcomp:setRadius(3.000000)
 event = events:newLightEvent(0.0, -1.0, node)
@@ -224,7 +224,7 @@ node:setLocalTransform(trf)
 node = scene:newSceneNode("Point.002")
 lcomp = node:newLightComponent()
 lcomp:setLightComponentType(LightComponentType.kPoint)
-lcomp:setDiffuseColor(Vec4.new(20.007532, 8.208058, 5.410148, 1))
+lcomp:setDiffuseColor(Vec4.new(19.997978, 8.204139, 5.407565, 1))
 lcomp:setShadowEnabled(1)
 lcomp:setRadius(3.000000)
 event = events:newLightEvent(0.0, -1.0, node)
@@ -241,7 +241,7 @@ node:setLocalTransform(trf)
 node = scene:newSceneNode("Point.001")
 lcomp = node:newLightComponent()
 lcomp:setLightComponentType(LightComponentType.kPoint)
-lcomp:setDiffuseColor(Vec4.new(20.007532, 8.208058, 5.410148, 1))
+lcomp:setDiffuseColor(Vec4.new(19.997978, 8.204139, 5.407565, 1))
 lcomp:setShadowEnabled(1)
 lcomp:setRadius(3.000000)
 event = events:newLightEvent(0.0, -1.0, node)
@@ -258,7 +258,7 @@ node:setLocalTransform(trf)
 node = scene:newSceneNode("Point")
 lcomp = node:newLightComponent()
 lcomp:setLightComponentType(LightComponentType.kPoint)
-lcomp:setDiffuseColor(Vec4.new(20.007532, 8.208058, 5.410148, 1))
+lcomp:setDiffuseColor(Vec4.new(19.997978, 8.204139, 5.407565, 1))
 lcomp:setShadowEnabled(1)
 lcomp:setRadius(3.000000)
 event = events:newLightEvent(0.0, -1.0, node)
@@ -2865,7 +2865,7 @@ node:setLocalTransform(trf)
 node = scene:newSceneNode("Lamp")
 lcomp = node:newLightComponent()
 lcomp:setLightComponentType(LightComponentType.kDirectional)
-lcomp:setDiffuseColor(Vec4.new(15.000000, 15.000000, 15.000000, 1))
+lcomp:setDiffuseColor(Vec4.new(15.072444, 15.072444, 15.072444, 1))
 lcomp:setShadowEnabled(1)
 trf = Transform.new()
 trf:setOrigin(Vec4.new(-0.637397, 34.433578, -6.746824, 0))

二进制
Samples/Sponza/Assets/arc_2_5fe181d03e97a985.ankimesh


二进制
Samples/Sponza/Assets/arch_a_2340d230b53e2a69.ankimesh


二进制
Samples/Sponza/Assets/arch_support_big_68d8367a811fb94b.ankimesh


二进制
Samples/Sponza/Assets/arch_support_med_f674c0ad36e855d5.ankimesh


二进制
Samples/Sponza/Assets/arch_support_tiny_6e5678a158e0a576.ankimesh


二进制
Samples/Sponza/Assets/carpet_9773eaac1e11dc54.ankimesh


二进制
Samples/Sponza/Assets/ceiling_3fd94cde277a48e1.ankimesh


二进制
Samples/Sponza/Assets/column_b_c9391d56bff59fc3.ankimesh


二进制
Samples/Sponza/Assets/column_c_small_a940cbc4b06b29e0.ankimesh


二进制
Samples/Sponza/Assets/column_c_square_34f84a5277d35506.ankimesh


二进制
Samples/Sponza/Assets/door_b_43d9d0f054a59e0d.ankimesh


二进制
Samples/Sponza/Assets/fabric_a_945c29fc221550fb.ankimesh


二进制
Samples/Sponza/Assets/fabric_b_e8dd2769dc642ab7.ankimesh


二进制
Samples/Sponza/Assets/flag_pole_b7fcab939d35270d.ankimesh


二进制
Samples/Sponza/Assets/hanging_vase_a37dedd7f8c3beeb.ankimesh


二进制
Samples/Sponza/Assets/leaf_3a245efd17475037.ankimesh


二进制
Samples/Sponza/Assets/leaf_b_686ab977af97774c.ankimesh


二进制
Samples/Sponza/Assets/lion_c45d3035db3bc17b.ankimesh


二进制
Samples/Sponza/Assets/lion_frame_c8b97a33096fbdb.ankimesh


二进制
Samples/Sponza/Assets/list_8b0526c84dd681e3.ankimesh


二进制
Samples/Sponza/Assets/list_b_457f406a16f12d4d.ankimesh


二进制
Samples/Sponza/Assets/metal_rod_f68ba1d1e70f4801.ankimesh


二进制
Samples/Sponza/Assets/small_window_inner_def811d202476946.ankimesh


二进制
Samples/Sponza/Assets/sponza_00_ae01670872faa30.ankimesh


二进制
Samples/Sponza/Assets/sponza_06_fd85d8293143f003.ankimesh


二进制
Samples/Sponza/Assets/sponza_258_9e5285ce7e2189af.ankimesh


二进制
Samples/Sponza/Assets/sponza_277_a862a3463155379b.ankimesh


二进制
Samples/Sponza/Assets/sponza_278_2814c1fc2c992170.ankimesh


二进制
Samples/Sponza/Assets/sponza_279_6912e5f5d4128531.ankimesh


二进制
Samples/Sponza/Assets/sponza_280_8dec8aa3e97a7a31.ankimesh


二进制
Samples/Sponza/Assets/sponza_281_12fa6f426dc4c559.ankimesh


二进制
Samples/Sponza/Assets/sponza_34_af76802cd75f239b.ankimesh


二进制
Samples/Sponza/Assets/sponza_35_587c5a72282a0812.ankimesh


二进制
Samples/Sponza/Assets/sponza_369.002_6ab77309e0c110ae.ankimesh


二进制
Samples/Sponza/Assets/sponza_36_df4619a2b83fb4bb.ankimesh


二进制
Samples/Sponza/Assets/sponza_380_752dc70618c5bc97.ankimesh


二进制
Samples/Sponza/Assets/sponza_381_d80b7e06247cf847.ankimesh


二进制
Samples/Sponza/Assets/sponza_66_5230eeae04fcd528.ankimesh


二进制
Samples/Sponza/Assets/sponza_68_921bc07f7acf667.ankimesh


二进制
Samples/Sponza/Assets/sponza_69_c96373f43f7e6566.ankimesh


二进制
Samples/Sponza/Assets/vase_45c3983f6cc9c489.ankimesh


二进制
Samples/Sponza/Assets/vase_flowers_b4fdd6561a1a65fb.ankimesh


二进制
Samples/Sponza/Assets/vase_hanger_2a18d1de31dd5e0d.ankimesh


二进制
Samples/Sponza/Assets/window_4ac10331d32bff8d.ankimesh