Selaa lähdekoodia

world: add cascaded shadow mapping

Daniele Bartolini 8 kuukautta sitten
vanhempi
sitoutus
ed5ef66e86

+ 1 - 0
docs/changelog.rst

@@ -16,6 +16,7 @@ Changelog
 
 **Runtime**
 
+* Added cascaded shadow mapping.
 * Added sounds streaming and OGG/Vorbis audio files support.
 * Fixed spurious activations of physics bodies.
 * HTML5: added Touch input device support.

+ 18 - 3
samples/core/shaders/default.shader

@@ -206,6 +206,10 @@ bgfx_shaders = {
 			vec2 v_texcoord0 : TEXCOORD0 = vec2(0.0, 0.0);
 			vec3 v_position  : TEXCOORD1 = vec3(0.0, 0.0, 0.0);
 			vec3 v_camera    : TEXCOORD2 = vec3(0.0, 0.0, 0.0);
+			vec4 v_shadow0   : TEXCOORD3 = vec4(0.0, 0.0, 0.0, 0.0);
+			vec4 v_shadow1   : TEXCOORD4 = vec4(0.0, 0.0, 0.0, 0.0);
+			vec4 v_shadow2   : TEXCOORD5 = vec4(0.0, 0.0, 0.0, 0.0);
+			vec4 v_shadow3   : TEXCOORD6 = vec4(0.0, 0.0, 0.0, 0.0);
 
 			vec3 a_position  : POSITION;
 			vec3 a_normal    : NORMAL;
@@ -222,7 +226,7 @@ bgfx_shaders = {
 		#else
 			$input a_position, a_normal, a_tangent, a_bitangent, a_texcoord0
 		#endif
-			$output v_normal, v_tangent, v_bitangent, v_texcoord0, v_position, v_camera
+			$output v_normal, v_tangent, v_bitangent, v_texcoord0, v_position, v_camera, v_shadow0, v_shadow1, v_shadow2, v_shadow3
 		"""
 
 		vs_code = """
@@ -259,11 +263,22 @@ bgfx_shaders = {
 				v_position = mul(v_position, tbn);
 
 				v_texcoord0 = a_texcoord0;
+
+		#if !defined(NO_LIGHT)
+				vec3 pos_offset = a_position + a_normal * 0.01;
+				v_shadow0 = mul(mul(u_cascaded_lights[0], model), vec4(pos_offset, 1.0));
+				v_shadow1 = mul(mul(u_cascaded_lights[1], model), vec4(pos_offset, 1.0));
+				v_shadow2 = mul(mul(u_cascaded_lights[2], model), vec4(pos_offset, 1.0));
+				v_shadow3 = mul(mul(u_cascaded_lights[3], model), vec4(pos_offset, 1.0));
+		#endif
 			}
 		"""
 
 		fs_input_output = """
-			$input v_normal, v_tangent, v_bitangent, v_texcoord0, v_position, v_camera
+			$input v_normal, v_tangent, v_bitangent, v_texcoord0, v_position, v_camera, v_shadow0, v_shadow1, v_shadow2, v_shadow3
+		"""
+
+		code = """
 		"""
 
 		fs_code = """
@@ -303,7 +318,7 @@ bgfx_shaders = {
 				vec3 n = normalize(normal); // Fragment normal.
 				vec3 v = normalize(v_camera); // Versor from fragment to camera pos.
 				vec3 f0 = mix(vec3_splat(0.04), albedo, metallic);
-				vec3 radiance = calc_lighting(tbn, n, v, v_position, albedo, metallic, roughness, f0);
+				vec3 radiance = calc_lighting(tbn, n, v, v_position, v_shadow0, v_shadow1, v_shadow2, v_shadow3, albedo, metallic, roughness, f0);
 				radiance = radiance / (radiance + vec3_splat(1.0)); // Tone-mapping.
 		#endif // !defined(NO_LIGHT)
 

+ 71 - 2
samples/core/shaders/lighting.shader

@@ -1,13 +1,23 @@
-include = ["core/shaders/common.shader"]
+include = [
+	"core/shaders/common.shader"
+	"core/shaders/shadow_map.shader"
+]
 
 bgfx_shaders = {
 	lighting = {
+		includes = [ "shadow_mapping" ]
+
 		code = """
 		#if !defined(NO_LIGHT)
 		#	define LIGHT_SIZE 4 // In vec4 units.
 		#	define MAX_NUM_LIGHTS 32
+		#	define MAX_NUM_CASCADES 4
+		#	define CASCADED_SHADOW_MAP_SLOT 10
 			uniform vec4 u_lights_num;        // num_dir, num_omni, num_spot
 			uniform vec4 u_lights_data[LIGHT_SIZE * MAX_NUM_LIGHTS]; // dir_0, .., dir_n-1, omni_0, .., omni_n-1, spot_0, .., spot_n-1
+			uniform mat4 u_cascaded_lights[MAX_NUM_CASCADES]; // View-proj-crop matrices for cascaded shadow maps.
+			uniform vec4 u_cascaded_texel_size;
+			SAMPLER2DSHADOW(u_cascaded_shadow_map, CASCADED_SHADOW_MAP_SLOT);
 
 			CONST(float PI) = 3.14159265358979323846;
 
@@ -126,7 +136,19 @@ bgfx_shaders = {
 				}
 			}
 
-			vec3 calc_lighting(mat3 tbn, vec3 n, vec3 v, vec3 frag_pos, vec3 albedo, float metallic, float roughness, vec3 f0)
+			vec3 calc_lighting(mat3 tbn
+				, vec3 n
+				, vec3 v
+				, vec3 frag_pos
+				, vec4 shadow_pos0
+				, vec4 shadow_pos1
+				, vec4 shadow_pos2
+				, vec4 shadow_pos3
+				, vec3 albedo
+				, float metallic
+				, float roughness
+				, vec3 f0
+				)
 			{
 				vec3 radiance = vec3_splat(0.0);
 
@@ -135,6 +157,53 @@ bgfx_shaders = {
 				int num_omni = int(u_lights_num.y);
 				int num_spot = int(u_lights_num.z);
 
+				if (num_dir > 0) {
+					// Brightest directional light (index == 0) generates cascaded shadow maps.
+					vec3 light_color  = u_lights_data[loffset + 0].rgb;
+					float intensity   = u_lights_data[loffset + 0].w;
+					vec3 direction    = u_lights_data[loffset + 2].xyz;
+					float shadow_bias = u_lights_data[loffset + 3].r;
+					float atlas_u     = u_lights_data[loffset + 3].g;
+					float atlas_v     = u_lights_data[loffset + 3].b;
+					float atlas_size  = u_lights_data[loffset + 3].a;
+					loffset += LIGHT_SIZE;
+
+					vec3 local_radiance = calc_dir_light(n
+						, v
+						, toLinearAccurate(light_color)
+						, intensity
+						, mul(direction, tbn)
+						, albedo
+						, metallic
+						, roughness
+						, f0
+						);
+
+					vec2 shadow0 = shadow_pos0.xy/shadow_pos0.w;
+					vec2 shadow1 = shadow_pos1.xy/shadow_pos1.w;
+					vec2 shadow2 = shadow_pos2.xy/shadow_pos2.w;
+					vec2 shadow3 = shadow_pos3.xy/shadow_pos3.w;
+
+					bool atlas0 = all(lessThan(shadow0, vec2_splat(0.99))) && all(greaterThan(shadow0, vec2_splat(0.01)));
+					bool atlas1 = all(lessThan(shadow1, vec2_splat(0.99))) && all(greaterThan(shadow1, vec2_splat(0.01)));
+					bool atlas2 = all(lessThan(shadow2, vec2_splat(0.99))) && all(greaterThan(shadow2, vec2_splat(0.01)));
+					bool atlas3 = all(lessThan(shadow3, vec2_splat(0.99))) && all(greaterThan(shadow3, vec2_splat(0.01)));
+
+					vec2 texel_size = vec2_splat(1.0/u_cascaded_texel_size.x);
+
+					if (atlas0)
+						local_radiance *= PCF(u_cascaded_shadow_map, shadow_pos0, shadow_bias, texel_size, vec3(atlas_u             , atlas_v             , atlas_size));
+					else if (atlas1)
+						local_radiance *= PCF(u_cascaded_shadow_map, shadow_pos1, shadow_bias, texel_size, vec3(atlas_u + atlas_size, atlas_v             , atlas_size));
+					else if (atlas2)
+						local_radiance *= PCF(u_cascaded_shadow_map, shadow_pos2, shadow_bias, texel_size, vec3(atlas_u             , atlas_v + atlas_size, atlas_size));
+					else if (atlas3)
+						local_radiance *= PCF(u_cascaded_shadow_map, shadow_pos3, shadow_bias, texel_size, vec3(atlas_u + atlas_size, atlas_v + atlas_size, atlas_size));
+
+					radiance += local_radiance;
+				}
+
+				// Others directional lights just add to radiance.
 				for (int di = 1; di < num_dir; ++di, loffset += LIGHT_SIZE) {
 					vec3 light_color  = u_lights_data[loffset + 0].rgb;
 					float intensity   = u_lights_data[loffset + 0].w;

+ 125 - 0
samples/core/shaders/shadow_map.shader

@@ -0,0 +1,125 @@
+include = [ "core/shaders/common.shader" ]
+
+render_states = {
+	shadow = {
+		states = {
+			rgb_write_enable = false
+			alpha_write_enable = false
+			depth_write_enable = true
+		}
+	}
+}
+
+bgfx_shaders = {
+	shadow_mapping = {
+		includes = [ "common" ]
+
+		code = """
+			#define Sampler sampler2DShadow
+			#define map_offt atlas_offset.xy
+			#define map_size atlas_offset.z
+
+			float hard_shadow(Sampler sampler, vec4 shadow_coord, float bias, vec3 atlas_offset)
+			{
+				vec3 tex_coord = shadow_coord.xyz/shadow_coord.w;
+
+				return shadow2D(sampler, vec3(tex_coord.xy * map_size + map_offt, tex_coord.z - bias));
+			}
+
+			float PCF(Sampler sampler, vec4 shadow_coord, float bias, vec2 texel_size, vec3 atlas_offset)
+			{
+				vec2 tex_coord = shadow_coord.xy/shadow_coord.w;
+				tex_coord = tex_coord * atlas_offset.z + atlas_offset.xy;
+
+				bool outside = any(greaterThan(tex_coord, map_offt + vec2_splat(map_size)))
+							|| any(lessThan   (tex_coord, map_offt))
+							 ;
+
+				if (outside)
+					return 0.0;
+
+				float result = 0.0;
+				vec2 offset = texel_size * shadow_coord.w;
+
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(-1.5, -1.5) * offset, 0.0, 0.0), bias, atlas_offset);
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(-1.5, -0.5) * offset, 0.0, 0.0), bias, atlas_offset);
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(-1.5,  0.5) * offset, 0.0, 0.0), bias, atlas_offset);
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(-1.5,  1.5) * offset, 0.0, 0.0), bias, atlas_offset);
+
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(-0.5, -1.5) * offset, 0.0, 0.0), bias, atlas_offset);
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(-0.5, -0.5) * offset, 0.0, 0.0), bias, atlas_offset);
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(-0.5,  0.5) * offset, 0.0, 0.0), bias, atlas_offset);
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(-0.5,  1.5) * offset, 0.0, 0.0), bias, atlas_offset);
+
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(0.5, -1.5) * offset, 0.0, 0.0), bias, atlas_offset);
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(0.5, -0.5) * offset, 0.0, 0.0), bias, atlas_offset);
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(0.5,  0.5) * offset, 0.0, 0.0), bias, atlas_offset);
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(0.5,  1.5) * offset, 0.0, 0.0), bias, atlas_offset);
+
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(1.5, -1.5) * offset, 0.0, 0.0), bias, atlas_offset);
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(1.5, -0.5) * offset, 0.0, 0.0), bias, atlas_offset);
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(1.5,  0.5) * offset, 0.0, 0.0), bias, atlas_offset);
+				result += hard_shadow(sampler, shadow_coord + vec4(vec2(1.5,  1.5) * offset, 0.0, 0.0), bias, atlas_offset);
+
+				return result / 16.0;
+			}
+		"""
+	}
+
+	shadow = {
+		includes = [ "common" ]
+
+		varying = """
+			vec3 a_position : POSITION;
+			vec4 a_indices  : BLENDINDICES;
+			vec4 a_weight   : BLENDWEIGHT;
+		"""
+
+		vs_input_output = """
+		#if defined(SKINNING)
+			$input a_position, a_indices, a_weight
+		#else
+			$input a_position
+		#endif
+		"""
+
+		vs_code = """
+			void main()
+			{
+		#if defined(SKINNING)
+				mat4 model;
+				model  = a_weight.x * u_model[int(a_indices.x)];
+				model += a_weight.y * u_model[int(a_indices.y)];
+				model += a_weight.z * u_model[int(a_indices.z)];
+				model += a_weight.w * u_model[int(a_indices.w)];
+				gl_Position = mul(mul(u_modelViewProj, model), vec4(a_position, 1.0));
+		#else
+				gl_Position = mul(mul(u_viewProj, u_model[0]), vec4(a_position, 1.0));
+		#endif
+			}
+		"""
+
+		fs_input_output = """
+		"""
+
+		fs_code = """
+			void main()
+			{
+				gl_FragColor = vec4_splat(0.0);
+			}
+		"""
+	}
+}
+
+shaders = {
+	shadow = {
+		bgfx_shader = "shadow"
+		render_state = "shadow"
+	}
+}
+
+static_compile = [
+	{ shader = "shadow" defines = [] }
+	{ shader = "shadow" defines = ["SKINNING"] }
+
+]

+ 3 - 1
src/device/device.cpp

@@ -810,6 +810,8 @@ void Device::render(World &world, UnitId camera_unit)
 			bgfx::setViewMode(id, bgfx::ViewMode::DepthAscending);
 			bgfx::setViewFrameBuffer(id, _pipeline->_main_frame_buffer);
 			bgfx::touch(id);
+		} else if (id >= View::CASCADE_0 && id < View::CASCADE_LAST) {
+			view_name = "sm_cascade";
 		} else if (id == View::LIGHTS) {
 			view_name = "lights_data";
 		} else if (id == View::MESH) {
@@ -903,7 +905,7 @@ void Device::render(World &world, UnitId camera_unit)
 	bgfx::setTexture(0, _pipeline->_html5_default_sampler, _pipeline->_html5_default_texture);
 #endif
 
-	world.render(view);
+	world.render(view, proj);
 }
 
 World *Device::create_world()

+ 2 - 0
src/device/pipeline.cpp

@@ -102,6 +102,7 @@ static void lookup_default_shaders(Pipeline &pl)
 	pl._debug_line_shader = pl._shader_manager->shader(STRING_ID_32("debug_line", UINT32_C(0xbc06e973)));
 	pl._outline_shader = pl._shader_manager->shader(STRING_ID_32("outline", UINT32_C(0xb6b58d80)));
 	pl._selection_shader = pl._shader_manager->shader(STRING_ID_32("selection", UINT32_C(0x17c0bc11)));
+	pl._shadow_shader = pl._shader_manager->shader(STRING_ID_32("shadow", UINT32_C(0xaceb94a8)));
 }
 
 Pipeline::Pipeline(ShaderManager &sm)
@@ -117,6 +118,7 @@ Pipeline::Pipeline(ShaderManager &sm)
 	, _selection_texture_sampler(BGFX_INVALID_HANDLE)
 	, _selection_depth_texture_sampler(BGFX_INVALID_HANDLE)
 	, _outline_color_uniform(BGFX_INVALID_HANDLE)
+	, _cascaded_shadow_map_size(4096)
 {
 	lookup_default_shaders(*this);
 }

+ 10 - 2
src/device/pipeline.h

@@ -11,14 +11,18 @@
 
 #define MAX_NUM_LIGHTS 32
 #define MAX_NUM_SPRITE_LAYERS 8
+#define MAX_NUM_CASCADES 4
+#define CASCADED_SHADOW_MAP_SLOT 10
 
 struct View
 {
 	enum Enum
 	{
 		SPRITE_0,
-		SPRITE_LAST = SPRITE_0 + MAX_NUM_SPRITE_LAYERS,
-		LIGHTS      = SPRITE_LAST,
+		SPRITE_LAST  = SPRITE_0 + MAX_NUM_SPRITE_LAYERS,
+		CASCADE_0    = SPRITE_LAST,
+		CASCADE_LAST = CASCADE_0 + MAX_NUM_CASCADES,
+		LIGHTS       = CASCADE_LAST,
 		MESH,
 		WORLD_GUI,
 		SELECTION,
@@ -71,6 +75,10 @@ struct Pipeline
 	ShaderData _debug_line_shader;
 	ShaderData _outline_shader;
 	ShaderData _selection_shader;
+	ShaderData _shadow_shader;
+
+	// Global settings.
+	u16 _cascaded_shadow_map_size;
 
 	///
 	Pipeline(ShaderManager &sm);

+ 156 - 9
src/world/render_world.cpp

@@ -10,6 +10,7 @@
 #include "core/math/aabb.h"
 #include "core/math/color4.inl"
 #include "core/math/constants.h"
+#include "core/math/frustum.inl"
 #include "core/math/intersection.h"
 #include "core/math/matrix4x4.inl"
 #include "core/strings/string_id.inl"
@@ -24,8 +25,9 @@
 #include "world/scene_graph.h"
 #include "world/shader_manager.h"
 #include "world/unit_manager.h"
-#include <bgfx/bgfx.h>
 #include <algorithm> // std::sort
+#include <bgfx/bgfx.h>
+#include <bx/math.h>
 
 namespace crown
 {
@@ -60,6 +62,13 @@ static void selection_draw_override(u8 view_id, UnitId unit_id, RenderWorld *rw)
 	bgfx::submit(view_id, rw->_pipeline->_selection_shader.program);
 }
 
+static void shadow_draw_override(u8 view_id, UnitId unit_id, RenderWorld *rw)
+{
+	CE_UNUSED(unit_id);
+	bgfx::setState(rw->_pipeline->_shadow_shader.state);
+	bgfx::submit(view_id, rw->_pipeline->_shadow_shader.program);
+}
+
 RenderWorld::RenderWorld(Allocator &a
 	, ResourceManager &rm
 	, ShaderManager &sm
@@ -93,10 +102,31 @@ RenderWorld::RenderWorld(Allocator &a
 	// Lighting.
 	_u_lights_num = bgfx::createUniform("u_lights_num", bgfx::UniformType::Vec4, 1);
 	_u_lights_data = bgfx::createUniform("u_lights_data", bgfx::UniformType::Vec4, 3*32);
+
+	// Create cascaded shadow map resources.
+	bgfx::TextureHandle fbtex = bgfx::createTexture2D(pl._cascaded_shadow_map_size
+		, pl._cascaded_shadow_map_size
+		, false
+		, 1
+		, bgfx::TextureFormat::D32F
+		, BGFX_TEXTURE_RT | BGFX_SAMPLER_COMPARE_LEQUAL
+		);
+	bgfx::TextureHandle fbtextures[] = { fbtex };
+	_cascaded_shadow_map_frame_buffer = bgfx::createFrameBuffer(countof(fbtextures), fbtextures, true);
+
+	_u_cascaded_shadow_map = bgfx::createUniform("u_cascaded_shadow_map", bgfx::UniformType::Sampler);
+	_u_cascaded_texel_size = bgfx::createUniform("u_cascaded_texel_size", bgfx::UniformType::Vec4);
+	_u_cascaded_lights = bgfx::createUniform("u_cascaded_lights", bgfx::UniformType::Mat4, MAX_NUM_CASCADES);
 }
 
 RenderWorld::~RenderWorld()
 {
+	// Destroy cascaded shadow map resources.
+	bgfx::destroy(_u_cascaded_lights);
+	bgfx::destroy(_u_cascaded_texel_size);
+	bgfx::destroy(_u_cascaded_shadow_map);
+	bgfx::destroy(_cascaded_shadow_map_frame_buffer);
+
 	// Destroy lighting uniforms.
 	bgfx::destroy(_u_lights_data);
 	bgfx::destroy(_u_lights_num);
@@ -452,13 +482,116 @@ void RenderWorld::update_transforms(const UnitId *begin, const UnitId *end, cons
 
 void sort(RenderWorld::LightManager &m);
 
-void RenderWorld::render(const Matrix4x4 &view)
+void RenderWorld::render(const Matrix4x4 &view, const Matrix4x4 &proj)
 {
 	LightManager::LightInstanceData &lid = _light_manager._data;
 
-	// Set lighting data.
 	sort(_light_manager);
 
+	const bgfx::Caps *caps = bgfx::getCaps();
+	Matrix4x4 cascaded_lights[MAX_NUM_CASCADES];
+	Matrix4x4 inv_view = view;
+	invert(inv_view);
+
+	// Render cascaded shadow maps.
+	// CSMs are only computed for the brightest directional light (index = 0) in the scene.
+	if (lid.num[LightType::DIRECTIONAL] > 0) {
+		Matrix4x4 light_proj;
+		Matrix4x4 light_view;
+		Frustum splits[MAX_NUM_CASCADES];
+		Frustum frustum;
+
+		// Compute light view matrix.
+		const Vector3 &light_dir = lid.shader[0].direction;
+		const bx::Vec3 at  = { -light_dir.x,  -light_dir.y, -light_dir.z };
+		const bx::Vec3 eye = { 0.0, 0.0, 0.0 };
+		const bx::Vec3 up = { 0.0f, 0.0f, 1.0f };
+		bx::mtxLookAt(to_float_ptr(light_view), eye, at, up, bx::Handedness::Right);
+
+		// Split the view frustum into MAX_NUM_CASCADES frustums.
+		frustum::from_matrix(frustum, proj, caps->homogeneousDepth, bx::Handedness::Right);
+		frustum::split(splits, MAX_NUM_CASCADES, frustum, 0.75f);
+
+		// Render the scene once per cascade.
+		for (u32 i = 0; i < MAX_NUM_CASCADES; ++i) {
+			Frustum split_i_world;
+			frustum::transform(split_i_world, splits[i], inv_view);
+
+			// Compute light projection matrix.
+			// Get frustum corners in view space.
+			Vector3 vertices[8];
+			frustum::vertices(vertices, splits[i]);
+
+			for (u32 j = 0; j < 8; ++j) {
+				// Transform frustum corners to light space.
+				vertices[j] = vertices[j] * inv_view;   // To world space.
+				vertices[j] = vertices[j] * light_view; // To light space.
+			}
+
+			// Compute frustum bounding box in light space.
+			AABB box;
+			aabb::from_points(box, countof(vertices), vertices);
+
+			bx::mtxOrtho(to_float_ptr(light_proj)
+				, box.min.x
+				, box.max.x
+				, box.min.y
+				, box.max.y
+				, -1000.0f
+				,  1000.0f
+				, 0.0f
+				, caps->homogeneousDepth
+				, bx::Handedness::Right
+				);
+
+			const float sy = caps->originBottomLeft ? 0.5f : -0.5f;
+			const float sz = caps->homogeneousDepth ? 0.5f :  1.0f;
+			const float tz = caps->homogeneousDepth ? 0.5f :  0.0f;
+			Matrix4x4 crop = {
+				{ 0.5f, 0.0f, 0.0f, 0.0f },
+				{ 0.0f,   sy, 0.0f, 0.0f },
+				{ 0.0f, 0.0f, sz,   0.0f },
+				{ 0.5f, 0.5f, tz,   1.0f }
+			};
+
+			cascaded_lights[i] = light_view * light_proj * crop;
+
+			// Render scene into atlas.
+			//
+			// Screen-space     Texture-space
+			//
+			// (0;0)  (w;0)     (0;1)
+			//   +------>         |
+			//   |                |
+			//   |                |
+			//   |                +------>
+			// (0;h)            (0;0)  (1;0)
+			//
+			const f32 tile_size = 0.5f * _pipeline->_cascaded_shadow_map_size;
+			Vector4 rects[] =
+			{
+				{         0, tile_size, tile_size, tile_size },
+				{ tile_size, tile_size, tile_size, tile_size },
+				{         0,    0,      tile_size, tile_size },
+				{ tile_size,    0,      tile_size, tile_size },
+			};
+			CE_STATIC_ASSERT(countof(rects) == MAX_NUM_CASCADES);
+
+			lid.shader[0].atlas_u = rects[0].x / _pipeline->_cascaded_shadow_map_size;
+			lid.shader[0].atlas_v = 1.0f - ((rects[0].y + rects[0].z) / _pipeline->_cascaded_shadow_map_size);
+			lid.shader[0].map_size = rects[0].w / _pipeline->_cascaded_shadow_map_size;
+
+			bgfx::setViewRect(View::CASCADE_0 + i, rects[i].x, rects[i].y, rects[i].z, rects[i].w);
+			bgfx::setViewFrameBuffer(View::CASCADE_0 + i, _cascaded_shadow_map_frame_buffer);
+			bgfx::setViewClear(View::CASCADE_0 + i, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0xffffffff, 1.0f, 0);
+
+			bgfx::setViewTransform(View::CASCADE_0 + i, to_float_ptr(light_view), to_float_ptr(light_proj));
+
+			_mesh_manager.draw(View::CASCADE_0 + i, *_scene_graph, NULL, shadow_draw_override);
+		}
+	}
+
+	// Set lighting data.
 	Vector4 h;
 	h.x = lid.num[LightType::DIRECTIONAL];
 	h.y = lid.num[LightType::OMNI];
@@ -469,11 +602,11 @@ void RenderWorld::render(const Matrix4x4 &view)
 	bgfx::touch(View::LIGHTS);
 
 	// Render objects.
-	_mesh_manager.draw(View::MESH, *_scene_graph);
+	_mesh_manager.draw(View::MESH, *_scene_graph, &cascaded_lights[0]);
 	_sprite_manager.draw(View::SPRITE_0);
 
 	// Render outlines.
-	_mesh_manager.draw(View::SELECTION, *_scene_graph, selection_draw_override);
+	_mesh_manager.draw(View::SELECTION, *_scene_graph, NULL, selection_draw_override);
 	_sprite_manager.draw(View::SELECTION, selection_draw_override);
 }
 
@@ -747,7 +880,7 @@ void RenderWorld::MeshManager::destroy()
 	_allocator->deallocate(_data.buffer);
 }
 
-void RenderWorld::MeshManager::draw(u8 view_id, SceneGraph &scene_graph, DrawOverride draw_override)
+void RenderWorld::MeshManager::draw(u8 view_id, SceneGraph &scene_graph, const Matrix4x4 *cascaded_lights, DrawOverride draw_override)
 {
 	for (u32 ii = 0; ii < _data.first_hidden; ++ii) {
 		if (_data.skeleton[ii] != NULL) {
@@ -772,10 +905,16 @@ void RenderWorld::MeshManager::draw(u8 view_id, SceneGraph &scene_graph, DrawOve
 		bgfx::setVertexBuffer(0, _data.mesh[ii].vbh);
 		bgfx::setIndexBuffer(_data.mesh[ii].ibh);
 
-		if (draw_override)
+		if (draw_override) {
 			draw_override(view_id, _data.unit[ii], _render_world);
-		else
+		} else {
+			bgfx::setTexture(CASCADED_SHADOW_MAP_SLOT, _render_world->_u_cascaded_shadow_map, bgfx::getTexture(_render_world->_cascaded_shadow_map_frame_buffer));
+			Vector4 size = { (f32)_render_world->_pipeline->_cascaded_shadow_map_size, 0.0f, 0.0f, 0.0f };
+			bgfx::setUniform(_render_world->_u_cascaded_texel_size, &size);
+			bgfx::setUniform(_render_world->_u_cascaded_lights, cascaded_lights, MAX_NUM_CASCADES);
+
 			_data.material[ii]->bind(view_id);
+		}
 	}
 }
 
@@ -1220,7 +1359,15 @@ void sort(RenderWorld::LightManager &m)
 
 	std::sort(m._data.index
 		, m._data.index + m._data.size
-		, [](const RenderWorld::LightManager::Index &in_a, const RenderWorld::LightManager::Index &in_b) { return in_a.type < in_b.type; });
+		, [m](const RenderWorld::LightManager::Index &in_a, const RenderWorld::LightManager::Index &in_b) {
+			const RenderWorld::LightManager::ShaderData &a = m._data.shader[in_a.index];
+			const RenderWorld::LightManager::ShaderData &b = m._data.shader[in_b.index];
+
+			if (in_a.type == in_b.type)
+				return a.intensity > b.intensity;
+
+			return in_a.type < in_b.type;
+		});
 
 	for (u32 i = 0; i < m._data.size; ++i) {
 		m._data.new_shader[i] = m._data.shader[m._data.index[i].index];

+ 11 - 3
src/world/render_world.h

@@ -164,7 +164,7 @@ struct RenderWorld
 	void update_transforms(const UnitId *begin, const UnitId *end, const Matrix4x4 *world);
 
 	///
-	void render(const Matrix4x4 &view);
+	void render(const Matrix4x4 &view, const Matrix4x4 &proj);
 
 	/// Sets whether to @a enable debug drawing
 	void enable_debug_drawing(bool enable);
@@ -256,7 +256,7 @@ struct RenderWorld
 		void swap(u32 inst_a, u32 inst_b);
 
 		///
-		void draw(u8 view, SceneGraph &scene_graph, DrawOverride draw_override = NULL);
+		void draw(u8 view, SceneGraph &scene_graph, const Matrix4x4 *cascaded_lights = NULL, DrawOverride draw_override = NULL);
 
 		///
 		MeshInstance make_instance(u32 i)
@@ -355,7 +355,9 @@ struct RenderWorld
 			Vector3 direction;
 			f32 spot_angle;
 			f32 shadow_bias;
-			f32 pad[3];
+			f32 atlas_u;   // U-coord in shadow map atlas.
+			f32 atlas_v;   // V-coord in shadow map atlas.
+			f32 map_size;  // Tile size in shadow map atlas.
 		};
 
 		struct Index
@@ -446,6 +448,12 @@ struct RenderWorld
 	// Lighting.
 	bgfx::UniformHandle _u_lights_num;
 	bgfx::UniformHandle _u_lights_data;
+
+	// Shadow mapping.
+	bgfx::FrameBufferHandle _cascaded_shadow_map_frame_buffer;
+	bgfx::UniformHandle _u_cascaded_shadow_map;
+	bgfx::UniformHandle _u_cascaded_texel_size;
+	bgfx::UniformHandle _u_cascaded_lights;
 };
 
 } // namespace crown

+ 2 - 2
src/world/world.cpp

@@ -280,9 +280,9 @@ void World::update(f32 dt)
 	update_scene(dt);
 }
 
-void World::render(const Matrix4x4 &view)
+void World::render(const Matrix4x4 &view, const Matrix4x4 &proj)
 {
-	_render_world->render(view);
+	_render_world->render(view, proj);
 
 	_physics_world->debug_draw();
 	_render_world->debug_draw(*_lines);

+ 1 - 1
src/world/world.h

@@ -166,7 +166,7 @@ struct World
 	void update(f32 dt);
 
 	/// Renders the world using @a view.
-	void render(const Matrix4x4 &view);
+	void render(const Matrix4x4 &view, const Matrix4x4 &proj);
 
 	/// @copydoc SoundWorld::play().
 	SoundInstanceId play_sound(StringId64 name, const bool loop, const f32 volume, const Vector3 &pos, const f32 range, StringId32 group);