hlsl_shaders = {
	brdf = {
		vp_code = { ref = "code" }
		fp_code = { ref = "code" }
		code="""
			#if defined(RENDERER_GL)
				#define splat(c) vec3(c)
			#else
				#define splat(c) c
			#endif

			float3 diffuse_lambert(float3 diffuse_color) {
				return diffuse_color / splat(PI);
			}

			// Microfacet distribution function
			// Trowbridge-Reitz (GGX) NDF
			float d_ggx(float roughness, float NH) {
				float alpha = roughness * roughness;
				float alpha2 = alpha * alpha;
				float d = (NH * NH) * (alpha2 - 1.f) + 1.f;
				const float eps = 0.0000001f;
				return alpha2 / ((d * d * PI) + eps);
			}

			float d_ggx_anisotropic(float roughness, float anisotropy, float TH, float BH, float NH) {
				const float eps = 0.0000001f;
				float alpha = max(roughness * roughness, eps);
				float t_alpha = alpha;
				float b_alpha = lerp(alpha, eps, anisotropy);
				float dt = TH/t_alpha;
				float db = BH/b_alpha;
				float d = dt*dt + db*db + NH*NH;

				return 1.0 / ((PI * t_alpha * b_alpha * d * d) + eps);
			}

			// Visibility term
			// Schlick approximation of Smith for GGX
			float g_visibility_schlick_smith(float roughness, float NV, float NL) {
				float a = max(roughness * roughness, 0.002);
				float k = a * 0.5;
				float G_L = NL * (1.0 - k) + k;
				float G_V = NV * (1.0 - k) + k;
				float G = 0.25 / ( G_L * G_V );
				return G;
			}

			// Schlick Fresnel approximation
			// Uses spherical gaussians approximation
			float3 f_schlick(float3 F0, float VH) {
				float F = exp2((-5.554730f * VH - 6.983160f) * VH);
				return F0 + (splat(1.f) - F0) * F;
			}

			void bsdf(float3 L, float3 V, float3 N, float3 light_color, float3 diffuse_color, float3 specular_color, half roughness, half attenuation, inout float3 acc_diff, inout float3 acc_spec) {
				float3 H = new_half3_xyz(normalize(L + V));
				float NL = saturate(dot(N, L));
				float NV = saturate(dot(N, V));
				float NH = saturate(dot(N, H));
				float VH = saturate(dot(V, H));

				// Distribution term
				float D = d_ggx(roughness, NH);

				// Visibility term
				float G_VIS = g_visibility_schlick_smith(roughness, NV, NL);

				// Fresnel term
				float3 F = f_schlick(specular_color, VH);

				float3 atten_light_color = light_color * NL * attenuation;

				acc_diff += diffuse_lambert(diffuse_color) * atten_light_color;
				acc_spec += D * G_VIS * F * atten_light_color;
			}

			void bsdf(float3 L, float3 V, float3 T, float3 B, float3 N, float3 light_color, float3 diffuse_color, float3 specular_color, half roughness, float anisotropy, half attenuation, inout float3 acc_diff, inout float3 acc_spec) {
				float3 H = new_half3_xyz(normalize(L + V));
				float NL = saturate(dot(N, L));
				float NV = saturate(dot(N, V));
				float NH = saturate(dot(N, H));
				float VH = saturate(dot(V, H));
				float TH = dot(T, H);
				float BH = dot(B, H);

				// Distribution term
				float D = d_ggx_anisotropic(roughness, anisotropy, TH, BH, NH);

				// Visibility term
				float G_VIS = g_visibility_schlick_smith(roughness, NV, NL);

				// Fresnel term
				float3 F = f_schlick(specular_color, VH);

				float3 atten_light_color = light_color * NL * attenuation;

				acc_diff += diffuse_lambert(diffuse_color) * atten_light_color;
				acc_spec += D * G_VIS * F * atten_light_color;
			}

			// Subsurface approximation
			// Ref: C. Barre-Brisebois, M. Bouchard, "Approximating translucency for a fast, cheap and convincing
			//      subsurface scattering look", GDC 2011
			//
			#define TRANSLUCENCY_AMBIENT 0.35
			#define TRANSLUCENCY_DISTORTION 0.016
			#define TRANSLUCENCY_POWER 11.5
			#define TRANSLUCENCY_LIGHT_SCALE 0.3
			#define TRANSLUCENCY_DIRECTIONAL_SCALE 0.1

			float3 calculate_translucency(float3 L, float3 V, float3 N, float attn, float3 base_color, float3 light_color, half density, half translucency_ambient, half translucency_distortion, half translucency_power, half translucency_light_scale) {
				float3 light_vec   = L + N * translucency_distortion;
				half  v_inv_l_dot = pow(saturate(dot(V, -light_vec)), translucency_power) * translucency_light_scale;
				return attn * (v_inv_l_dot + translucency_ambient) * saturate(1.0 - density) * light_color * base_color;
			}

			float3 calculate_translucency(float3 L, float3 V, float3 N, float attn, float3 base_color, float3 light_color, half density) {
				return calculate_translucency(L, V, N, attn, base_color, light_color, density, TRANSLUCENCY_AMBIENT, TRANSLUCENCY_DISTORTION, TRANSLUCENCY_POWER, TRANSLUCENCY_LIGHT_SCALE);
			}

			half light_attenuation(half d, half near, half inv_far_minus_near) {
				half dist = d-near;
				half a = dist*inv_far_minus_near;
				half a2 = a*a;
				half num = new_half(saturate(1.0 - a2*a2));
				return d < near ? 1.0 : (num*num) / (dist*dist + 1.0);
			}

			float spot_angle_attenuation(float angle, float scale, float bias) {
				// scale=1.f / max(eps, (cos(start_angle*0.5) - cos(stop_angle*0.5)))
				// bias = -cos(stop_angle*0.5) * scale;
				
				float a = saturate(angle * scale + bias);
				// squared for smoother falloff. 
				return a*a;
			}

			#if defined(RENDERER_D3D11) || defined(RENDERER_D3D12) || defined(RENDERER_GNM)
				float2 hammersley_sequence_2d(uint i, uint n) {
					return float2((float)i / n, reversebits(i) * 2.3283064365386963e-10);
				}

				float3 importance_sample_ggx(float2 E, float roughness, float3 N) {
					float a = roughness*roughness;
					float a2 = a*a;

					float phi = 2 * PI * E.x;
					float cos_phi = cos(phi);
					float sin_phi = sin(phi);
					float cos_theta = sqrt((1 - E.y) / ((a2 - 1) * E.y + 1));
					float sin_theta = sqrt(1 - cos_theta * cos_theta);

					float3 H = float3(sin_theta * cos_phi, sin_theta * sin_phi, cos_theta);

					float3 up = abs(N.z) < 0.999 ? float3(0.0,0.0,1.0) : float3(1.0,0.0,0.0);
					float3 tx = normalize(cross(up, N));
					float3 ty = cross(N, tx);

					return tx * H.x + ty * H.y + N * H.z;
				}
			#endif
		"""
	}

	lighting = {
		vp_code = { ref = "code" }
		fp_code = { ref = "code" }
		code="""
			#if defined(CALCULATE_LIGHTING)
				CBUFFER_START(lighting_data)
					UNIFORM float3 ambient_tint;

					UNIFORM float4x4 shadow_rotation;
					UNIFORM float3 shadow_scale_slice0;
					UNIFORM float3 shadow_scale_slice1;
					UNIFORM float3 shadow_scale_slice2;
					UNIFORM float3 shadow_scale_slice3;

					UNIFORM float3 shadow_bias_slice0;
					UNIFORM float3 shadow_bias_slice1;
					UNIFORM float3 shadow_bias_slice2;
					UNIFORM float3 shadow_bias_slice3;

					UNIFORM float3 vp_min_slice0;
					UNIFORM float3 vp_min_slice1;
					UNIFORM float3 vp_min_slice2;
					UNIFORM float3 vp_min_slice3;

					UNIFORM float3 vp_max_slice0;
					UNIFORM float3 vp_max_slice1;
					UNIFORM float3 vp_max_slice2;
					UNIFORM float3 vp_max_slice3;

					UNIFORM float sun_shadows_enabled;
					UNIFORM float sun_enabled;
					
					UNIFORM float ssm_enabled;
					UNIFORM float4x4 ssm_shadow_rotation;

					UNIFORM float  ambient_diffuse_fade_type;
					UNIFORM float3 ambient_diffuse_fade; // min multiplier, height offset, falloff
				CBUFFER_END

				float sample_shadow_map(ComparisonSampler2D shadow_map, float3 sm_pos, float depth, float3 shadow_map_bias)
				{
					sm_pos.z = apply_shadow_depth_comparison_bias(depth, sm_pos.z, shadow_map_bias);
					#if defined(RENDERER_D3D11) || defined(RENDERER_D3D12) || defined(RENDERER_GNM)
						float2 sm_resolution;
						// TODO: get rid of GetDimension, since this a cost on GCN
						shadow_map.tex.GetDimensions(sm_resolution.x, sm_resolution.y);
					#else
						float2 sm_resolution = float2(textureSize(shadow_map, 0));
					#endif
					return shadow_intensity_2d(shadow_map, sm_resolution, sm_pos.xy, sm_pos.z);
				}

				float calculate_static_shadow_intensity(ComparisonSampler2D static_sun_shadow_map, const float3 world_pos, float depth)
				{
					float3 sm_pos = mul(float4(world_pos, 1.0), ssm_shadow_rotation);
					return sample_shadow_map(static_sun_shadow_map, sm_pos, depth, ssm_shadow_map_bias);
				}
				
				float calculate_shadow_intensity(ComparisonSampler2D sun_shadow_map, ComparisonSampler2D static_sun_shadow_map, const float3 world_pos, float depth)
				{
					float3 wp_shadow_space = mul(world_pos, to_mat3(shadow_rotation));
					float3 sm0_pos = wp_shadow_space * shadow_scale_slice0 + shadow_bias_slice0;
					float3 sm1_pos = wp_shadow_space * shadow_scale_slice1 + shadow_bias_slice1;
					float3 sm2_pos = wp_shadow_space * shadow_scale_slice2 + shadow_bias_slice2;
					float3 sm3_pos = wp_shadow_space * shadow_scale_slice3 + shadow_bias_slice3;

					#if defined(RENDERER_D3D11) || defined(RENDERER_D3D12) || defined(RENDERER_GNM)
						float3 sm0_test_pos = sm0_pos;
						float3 sm1_test_pos = sm1_pos;
						float3 sm2_test_pos = sm2_pos;
						float3 sm3_test_pos = sm3_pos;
					#else
						// Need to flip z because of upside down rendering.
						float3 sm0_test_pos = float3(sm0_pos.x, sm0_pos.y, 1.0f - sm0_pos.z);
						float3 sm1_test_pos = float3(sm1_pos.x, sm1_pos.y, 1.0f - sm1_pos.z);
						float3 sm2_test_pos = float3(sm2_pos.x, sm2_pos.y, 1.0f - sm2_pos.z);
						float3 sm3_test_pos = float3(sm3_pos.x, sm3_pos.y, 1.0f - sm3_pos.z);
					#endif
					if (dot(float3(lessThan(sm0_test_pos, vp_min_slice0)) + float3(greaterThan(sm0_test_pos, vp_max_slice0)), float3(1.0f, 1.0f, 1.0f)) == 0.0f)
						return sample_shadow_map(sun_shadow_map, sm0_pos.xzy, depth, sun_shadow_map_bias);
					if (dot(float3(lessThan(sm1_test_pos, vp_min_slice1)) + float3(greaterThan(sm1_test_pos, vp_max_slice1)), float3(1.0f, 1.0f, 1.0f)) == 0.0f)
						return sample_shadow_map(sun_shadow_map, sm1_pos.xzy, depth, sun_shadow_map_bias);
					if (dot(float3(lessThan(sm2_test_pos, vp_min_slice2)) + float3(greaterThan(sm2_test_pos, vp_max_slice2)), float3(1.0f, 1.0f, 1.0f)) == 0.0f)
						return sample_shadow_map(sun_shadow_map, sm2_pos.xzy, depth, sun_shadow_map_bias);
					if (dot(float3(lessThan(sm3_test_pos, vp_min_slice3)) + float3(greaterThan(sm3_test_pos, vp_max_slice3)), float3(1.0f, 1.0f, 1.0f)) == 0.0f)
						return sample_shadow_map(sun_shadow_map, sm3_pos.xzy, depth, sun_shadow_map_bias);
					
					if (ssm_enabled) {
						return calculate_static_shadow_intensity(static_sun_shadow_map, world_pos, depth);
					}

					return 1.0f;
				}

				void calculate_lighting(const float3 world_pos, float depth, ComparisonSampler2D shadow_map, ComparisonSampler2D static_sun_shadow_map, Sampler2D brdf_lut, SamplerCube global_specular_map, const float3 N, const float3 V, const float roughness, const float3 ambient, const float3 diffuse_color, const float3 specular_color, const float density, const float3 base_color, inout float3 acc_diff, inout float3 acc_spec, inout float3 translucency)
				{
					float3 L = normalize(-sun_direction);
					float2 scale_bias = TEX2D(brdf_lut, float2(saturate(dot(N, V)), roughness)).xy;

					#if defined(D3D11)
						const float specular_toggle = 1-capture_cubemap;
						// Remove ambient tint from cube map baking, since will have an exponential effect on the result.
						ambient_tint = (specular_toggle == 1) ? ambient_tint : 1.0;
					#else
						const float specular_toggle = 1.0;
					#endif

					// TODO: make a branchless solution? Do we use this?
					float3 indirect_light_tint = ambient_tint;
					float fade_type = ambient_diffuse_fade_type;
					float3 fade_params = ambient_diffuse_fade;
					[branch]
					if(fade_type != 0.0) {
						if(fade_type == 1.0) {
							indirect_light_tint *= max(fade_params.x, 1.0 - exp((world_pos.z - fade_params.y)/fade_params.z));
						} else if(fade_type == 2.0) {
							indirect_light_tint *= max(fade_params.x, 1.0 - exp((fade_params.y - world_pos.z)/fade_params.z));
						} else {
							indirect_light_tint *= max(fade_params.x, 1.0 - exp(-abs(fade_params.y - world_pos.z)/fade_params.z));
						}
					}

					float mipmap_index = roughness * 7.0;
					acc_diff += ambient * indirect_light_tint * diffuse_color;
					acc_spec += rgbm_decode(TEXCUBELOD(global_specular_map, reflect(-V, N), mipmap_index)) * (specular_color * scale_bias.x + scale_bias.y) * indirect_light_tint * specular_toggle;

					if (sun_enabled == 1.0) {
						float shadow = 1.0;
						if(sun_shadows_enabled && sun_shadows) {
							shadow = saturate(calculate_shadow_intensity(shadow_map, static_sun_shadow_map, world_pos, depth));
						} else if(ssm_enabled) {
							shadow = saturate(calculate_static_shadow_intensity(static_sun_shadow_map, world_pos, depth));
						}
						bsdf(L, V, N, sun_color, diffuse_color, specular_color, roughness, shadow, acc_diff, acc_spec);
						translucency = calculate_translucency(L, V, N, lerp(lerp(0.5, 1.0, shadow), saturate(shadow * TRANSLUCENCY_DIRECTIONAL_SCALE), density), base_color, sun_color, density);
					}
				}
			#endif
		"""
	}

	clustered_shading = {
		vp_code = { ref = "code" }
		fp_code = { ref = "code" }
		code = """
			#if defined(RENDERER_D3D11) || defined(RENDERER_D3D12) || defined(RENDERER_GNM)
				#define USE_RAW_BUFFER_FOR_LIGHT_INDEX_DATA
			#endif

			#if defined(USE_RAW_BUFFER_FOR_LIGHT_INDEX_DATA)
				#define CLUSTER_DATA_TYPE Buffer<uint2>
				#define LIGHT_INDEX_DATA_TYPE Buffer<uint>
				#define LIGHT_DATA_TYPE Buffer<float4>
				#define LIGHT_SHADOW_MATRICES_TYPE Buffer<float4>

				#define DECLARE_CLUSTER_DATA(name) CLUSTER_DATA_TYPE name
				#define DECLARE_LIGHT_INDEX_DATA(name) LIGHT_INDEX_DATA_TYPE name
				#define DECLARE_LIGHT_DATA(name) LIGHT_DATA_TYPE name
				#define DECLARE_LIGHT_DATA(name) LIGHT_DATA_TYPE name
				#define DECLARE_LIGHT_SHADOW_MATRICES(name) LIGHT_SHADOW_MATRICES_TYPE name
			#else
				#define CLUSTER_DATA_TYPE highp usampler2D
				#define LIGHT_INDEX_DATA_TYPE highp usampler2D
				#define LIGHT_DATA_TYPE highp sampler2D
				#define LIGHT_SHADOW_MATRICES_TYPE highp sampler2D

				#define DECLARE_CLUSTER_DATA(name) uniform CLUSTER_DATA_TYPE name
				#define DECLARE_LIGHT_INDEX_DATA(name) uniform LIGHT_INDEX_DATA_TYPE name
				#define DECLARE_LIGHT_DATA(name) uniform LIGHT_DATA_TYPE name
				#define DECLARE_LIGHT_SHADOW_MATRICES(name) uniform LIGHT_SHADOW_MATRICES_TYPE name
			#endif

			// Don't know why, but using texelFetch instead of textureLod is slower, but this define is here in case we want to start using texelFetch.
			//#define USE_TEXEL_FETCH

			CBUFFER_START(clustered_shading_data)
				UNIFORM float4 cs_cluster_size_in_pixels;
				UNIFORM float4 cs_cluster_sizes;
				UNIFORM float4 cs_cluster_data_size;
				UNIFORM float4 cs_light_index_data_size;
				UNIFORM float4 cs_light_data_size;
				UNIFORM float4 cs_light_shadow_matrices_size;
				UNIFORM float2 cs_cluster_max_depth_inv_max_depth;
				UNIFORM float4 cs_shadow_atlas_size;
			CBUFFER_END

			void calculate_sample_uv(uint index, float2 inv_tex_size, out float2 uv)
			{
				float normalized_uv = float(index) * inv_tex_size.x;
				uv.x = frac(normalized_uv);
				uv.y = normalized_uv * inv_tex_size.y;
			}

			void calculate_sample_position(uint index, uint2 tex_size, out uint2 position)
			{
				position.x = index % tex_size.x;
				position.y = index / tex_size.x;
			}

			void sample_cluster_data(CLUSTER_DATA_TYPE cluster_data, float2 screen_pos, float depth, out uint2 light_data)
			{
				float inv_depth_per_cluster = cs_cluster_size_in_pixels.z;
				float layer = (depth - camera_near_far.x) * inv_depth_per_cluster;
				int3 pos_3d = int3(screen_pos * cs_cluster_size_in_pixels.xy, layer);
				int pos_1d = pos_3d.z * int(cs_cluster_sizes.y) + pos_3d.y * int(cs_cluster_sizes.x) + pos_3d.x;
				#if defined(USE_RAW_BUFFER_FOR_LIGHT_INDEX_DATA)
					light_data = cluster_data.Load(pos_1d).xy;
				#else
					#if defined(USE_TEXEL_FETCH)
						uint2 p;
						calculate_sample_position(uint(pos_1d), uint2(cs_cluster_data_size.xy), p);
						light_data = texelFetch(cluster_data, int2(p), 0).xy;
					#else
						float2 uv;
						calculate_sample_uv(uint(pos_1d), cs_cluster_data_size.zw, uv);
						light_data = textureLod(cluster_data, uv, 0.0).xy;
					#endif
				#endif
			}

			void sample_light_index_data(LIGHT_INDEX_DATA_TYPE light_index_data, uint light_index, out uint index)
			{
				#if defined(USE_RAW_BUFFER_FOR_LIGHT_INDEX_DATA)
					index = light_index_data.Load(light_index).r;
				#else
					#if defined(USE_TEXEL_FETCH)
						uint2 p;
						calculate_sample_position(light_index, uint2(cs_light_index_data_size.xy), p);
						index = texelFetch(light_index_data, int2(p), 0).x;
					#else
						float2 uv;
						calculate_sample_uv(light_index, cs_light_index_data_size.zw, uv);
						index = textureLod(light_index_data, uv, 0.0).x;
					#endif
				#endif
			}

			void sample_light_data(LIGHT_DATA_TYPE light_data, uint index, out float3 light_position, out float3 light_color, out float3 light_falloff, out float3 spot_light_falloff, out float3 spot_dir, out float sm_index0, out float sm_index1, out float sm_index2, out float sm_index3, out float sm_index4, out float sm_index5, out float shadow_intensity)
			{
				#if defined(USE_RAW_BUFFER_FOR_LIGHT_INDEX_DATA)
					float4 light_data_0 = light_data.Load(index);
					float4 light_data_1 = light_data.Load(index + 1);
					float4 light_data_2 = light_data.Load(index + 2);
					float4 light_data_3 = light_data.Load(index + 3);
					float4 light_data_4 = light_data.Load(index + 4);
				#else
					#if defined(USE_TEXEL_FETCH)
						uint2 p0, p1, p2, p3;
						calculate_sample_position(index + 0U, uint2(cs_light_data_size.xy), p0);
						calculate_sample_position(index + 1U, uint2(cs_light_data_size.xy), p1);
						calculate_sample_position(index + 2U, uint2(cs_light_data_size.xy), p2);
						calculate_sample_position(index + 3U, uint2(cs_light_data_size.xy), p3);
						float4 light_data_0 = texelFetch(light_data, int2(p0), 0);
						float4 light_data_1 = texelFetch(light_data, int2(p1), 0);
						float4 light_data_2 = texelFetch(light_data, int2(p2), 0);
						float4 light_data_3 = texelFetch(light_data, int2(p3), 0);
					#else
						float2 uv0, uv1, uv2, uv3;
						uint index0 = index + uint(0);
						uint index1 = index + uint(1);
						uint index2 = index + uint(2);
						uint index3 = index + uint(3);
						calculate_sample_uv(index0, cs_light_data_size.zw, uv0);
						calculate_sample_uv(index1, cs_light_data_size.zw, uv1);
						calculate_sample_uv(index2, cs_light_data_size.zw, uv2);
						calculate_sample_uv(index3, cs_light_data_size.zw, uv3);
						float4 light_data_0 = textureLod(light_data, uv0, 0.0);
						float4 light_data_1 = textureLod(light_data, uv1, 0.0);
						float4 light_data_2 = textureLod(light_data, uv2, 0.0);
						float4 light_data_3 = textureLod(light_data, uv3, 0.0);
					#endif
				#endif

				light_position = light_data_0.xyz;
				light_color = light_data_1.xyz;
				light_falloff = light_data_2.xyz;
				spot_light_falloff = light_data_3.xyz;
				spot_dir = normalize(float3(light_data_0.w, light_data_1.w, light_data_2.w));

				sm_index0 = light_data_0.w;
				sm_index1 = light_data_1.w;
				sm_index2 = light_data_3.x;
				sm_index3 = light_data_3.y;
				sm_index4 = light_data_3.z;
				sm_index5 = light_data_3.w;

				shadow_intensity = light_data_4.x;
			}

			void sample_light_shadow_matrix(LIGHT_SHADOW_MATRICES_TYPE light_shadow_matrices, uint index, out float4x4 shadow_matrix)
			{
				#if defined(USE_RAW_BUFFER_FOR_LIGHT_INDEX_DATA)
					shadow_matrix[0] = light_shadow_matrices.Load(index * 4 + 0);
					shadow_matrix[1] = light_shadow_matrices.Load(index * 4 + 1);
					shadow_matrix[2] = light_shadow_matrices.Load(index * 4 + 2);
					shadow_matrix[3] = light_shadow_matrices.Load(index * 4 + 3);
				#else
					#if defined(USE_TEXEL_FETCH)
						uint2 p0, p1, p2, p3;
						calculate_sample_position(index * 4U + 0U, uint2(cs_light_shadow_matrices_size.xy), p0);
						calculate_sample_position(index * 4U + 1U, uint2(cs_light_shadow_matrices_size.xy), p1);
						calculate_sample_position(index * 4U + 2U, uint2(cs_light_shadow_matrices_size.xy), p2);
						calculate_sample_position(index * 4U + 3U, uint2(cs_light_shadow_matrices_size.xy), p3);
						shadow_matrix[0] = texelFetch(light_shadow_matrices, int2(p0), 0);
						shadow_matrix[1] = texelFetch(light_shadow_matrices, int2(p1), 0);
						shadow_matrix[2] = texelFetch(light_shadow_matrices, int2(p2), 0);
						shadow_matrix[3] = texelFetch(light_shadow_matrices, int2(p3), 0);
						shadow_matrix = transpose(shadow_matrix);
					#else
						float2 uv0, uv1, uv2, uv3;
						uint index0 = index * uint(4);
						uint index1 = index * uint(4);
						uint index2 = index * uint(4);
						uint index3 = index * uint(4);
						index0 += uint(0);
						index1 += uint(1);
						index2 += uint(2);
						index3 += uint(3);

						calculate_sample_uv(index0, cs_light_shadow_matrices_size.zw, uv0);
						calculate_sample_uv(index1, cs_light_shadow_matrices_size.zw, uv1);
						calculate_sample_uv(index2, cs_light_shadow_matrices_size.zw, uv2);
						calculate_sample_uv(index3, cs_light_shadow_matrices_size.zw, uv3);
						shadow_matrix[0] = textureLod(light_shadow_matrices, uv0, 0.0);
						shadow_matrix[1] = textureLod(light_shadow_matrices, uv1, 0.0);
						shadow_matrix[2] = textureLod(light_shadow_matrices, uv2, 0.0);
						shadow_matrix[3] = textureLod(light_shadow_matrices, uv3, 0.0);
						shadow_matrix = transpose(shadow_matrix);
					#endif
				#endif
			}

			void clustered_shading(CLUSTER_DATA_TYPE cluster_data,
									LIGHT_INDEX_DATA_TYPE light_index_data,
									LIGHT_DATA_TYPE light_data,
									LIGHT_SHADOW_MATRICES_TYPE light_shadow_matrices,
									ComparisonSampler2D local_lights_shadow_atlas,
									const float3 world_pos,
									const float3 V,
									const float3 N,
									const float3 diffuse_color,
									const float3 specular_color,
									const float roughness,
									const float2 screen_pos,
									const float depth,
									const float density,
									const float3 base_color,
									inout float3 acc_diff,
									inout float3 acc_spec,
									inout float3 translucency)
			{
				if (depth > cs_cluster_max_depth_inv_max_depth.x)
					return;

				const float inv_fade_distance = 0.1; 
				const float fade_attn = min((cs_cluster_max_depth_inv_max_depth.x - depth) * inv_fade_distance, 1.0);

				uint2 cluster_info;
				sample_cluster_data(cluster_data, screen_pos, depth, cluster_info);

				uint light_index = cluster_info.x;
				int point_light_count = int(cluster_info.y & 0x00FFU);
				for (int pl = 0; pl < point_light_count; ++pl) {
					uint index;
					sample_light_index_data(light_index_data, light_index, index);
					float3 light_position, light_color, light_falloff, spot_light_falloff, spot_dir;
					float sm_index0, sm_index1, sm_index2, sm_index3, sm_index4, sm_index5;
					float shadow_fade;
					sample_light_data(light_data, index, light_position, light_color, light_falloff, spot_light_falloff, spot_dir, sm_index0, sm_index1, sm_index2, sm_index3, sm_index4, sm_index5, shadow_fade);

					float3 L = light_position - world_pos;
					float len_L = length(L) + 0.00001f;
					L /= len_L;

					float attn = light_attenuation(len_L, light_falloff.x, light_falloff.y) * fade_attn;
					#ifdef HAS_ANISOTROPY
						bsdf(L, V, T, B, N, light_color, diffuse_color, specular_color, roughness, anisotropy_, attn, acc_diff, acc_spec);
					#else
						bsdf(L, V, N, light_color, diffuse_color, specular_color, roughness, attn, acc_diff, acc_spec);
					#endif

					translucency += calculate_translucency(L, V, N, attn, base_color, light_color, density);
					++light_index;
				}

				int spot_light_count = int((cluster_info.y >> 16U) & 0x00FFU);
				for (int sl = 0; sl < spot_light_count; ++sl) {
					uint index;
					sample_light_index_data(light_index_data, light_index, index);
					float3 light_position, light_color, light_falloff, spot_light_falloff, spot_dir;
					float sm_index0, sm_index1, sm_index2, sm_index3, sm_index4, sm_index5;
					float shadow_fade;
					sample_light_data(light_data, index, light_position, light_color, light_falloff, spot_light_falloff, spot_dir, sm_index0, sm_index1, sm_index2, sm_index3, sm_index4, sm_index5, shadow_fade);

					float3 L = light_position - world_pos;
					float len_L = length(L) + 0.00001f;
					L /= len_L;

					float attn = light_attenuation(len_L, light_falloff.x, light_falloff.y) * fade_attn;
					attn *= spot_angle_attenuation(dot(-L, spot_dir), spot_light_falloff.x, spot_light_falloff.y);
					#ifdef HAS_ANISOTROPY
						bsdf(L, V, T, B, N, light_color, diffuse_color, specular_color, roughness, anisotropy_, attn, acc_diff, acc_spec);
					#else
						bsdf(L, V, N, light_color, diffuse_color, specular_color, roughness, attn, acc_diff, acc_spec);
					#endif
					translucency += calculate_translucency(L, V, N, attn, base_color, light_color, density);

					++light_index;
				}

				int shadow_casting_point_light_count = int((cluster_info.y >> 8U) & 0x00FFU);
				for (int scpl = 0; scpl < shadow_casting_point_light_count; ++scpl) {
					uint index;
					sample_light_index_data(light_index_data, light_index, index);
					float3 light_position, light_color, light_falloff, spot_light_falloff, spot_dir;
					float sm_index0, sm_index1, sm_index2, sm_index3, sm_index4, sm_index5;
					float shadow_fade;
					sample_light_data(light_data, index, light_position, light_color, light_falloff, spot_light_falloff, spot_dir, sm_index0, sm_index1, sm_index2, sm_index3, sm_index4, sm_index5, shadow_fade);

					float3 L = light_position - world_pos;
					float len_L = length(L) + 0.00001f;
					L /= len_L;

					float attn = light_attenuation(len_L, light_falloff.x, light_falloff.y) * fade_attn;

					// Based on the biggest component of the vector from the shaded position to the light source and its sign, chose the correct
					// shadow map index to get the correct world position to shadow map matrix.
					float3 shadow_L = -L;
					int3 is_positive = int3(greaterThan(shadow_L, float3(0.0, 0.0, 0.0)));
					float3 abs_shadow_L = abs(shadow_L);
					int test_index = (abs_shadow_L.x > abs_shadow_L.y && abs_shadow_L.x > abs_shadow_L.z) ? 0 + is_positive[0]: (abs_shadow_L.y > abs_shadow_L.z) ? 2 + is_positive[1] : 4 + is_positive[2];

					// The shadows from the faces of an onmni light are populated in the following order and directions.
					//	float3(-1.0f,  0.0f,  0.0f),
					//	float3( 1.0f,  0.0f,  0.0f),
					//	float3( 0.0f, -1.0f,  0.0f),
					//	float3( 0.0f,  1.0f,  0.0f),
					//	float3( 0.0f,  0.0f, -1.0f)
					//	float3( 0.0f,  0.0f,  1.0f),
					//fix this crap about the sm_index potentially being -1.0 which indicates that the light isn't casting any shadows....
					float sm_index = test_index == 0? sm_index0 :
									test_index == 1? sm_index1 :
									test_index == 2? sm_index2 :
									test_index == 3? sm_index3 :
									test_index == 4? sm_index4 :
									sm_index5;

					float shadow_intensity = 1.0f;
					if (sm_index != -1.0f) {
						float4x4 world_to_sm;
						sample_light_shadow_matrix(light_shadow_matrices, uint(sm_index), world_to_sm);
						float4 sm_pos = mul(float4(world_pos, 1.0), world_to_sm);
						sm_pos.xyz /= sm_pos.w;
						sm_pos.z = apply_shadow_depth_comparison_bias(depth, sm_pos.z, local_shadow_map_bias);
						shadow_intensity = shadow_intensity_2d(local_lights_shadow_atlas, cs_shadow_atlas_size.xy, sm_pos.xy, sm_pos.z);
						shadow_intensity = (shadow_intensity - 1.0) * shadow_fade + 1.0;
					}
					#ifdef HAS_ANISOTROPY
						bsdf(L, V, T, B, N, light_color, diffuse_color, specular_color, roughness, anisotropy_, attn * shadow_intensity, acc_diff, acc_spec);
					#else
						bsdf(L, V, N, light_color, diffuse_color, specular_color, roughness, attn * shadow_intensity, acc_diff, acc_spec);
					#endif
					translucency += calculate_translucency(L, V, N, attn * lerp(lerp(0.5, 1.0, shadow_intensity), shadow_intensity, density), base_color, light_color, density);

					++light_index;
				}

				int shadow_casting_spot_light_count = int((cluster_info.y >> 24U) & 0x00FFU);
				for (int scsl = 0; scsl < shadow_casting_spot_light_count; ++scsl) {
					uint index;
					sample_light_index_data(light_index_data, light_index, index);
					float3 light_position, light_color, light_falloff, spot_light_falloff, spot_dir;
					float sm_index0, sm_index1, sm_index2, sm_index3, sm_index4, sm_index5;
					float shadow_fade;
					sample_light_data(light_data, index, light_position, light_color, light_falloff, spot_light_falloff, spot_dir, sm_index0, sm_index1, sm_index2, sm_index3, sm_index4, sm_index5, shadow_fade);

					float3 L = light_position - world_pos;
					float len_L = length(L) + 0.00001f;
					L /= len_L;

					float attn = light_attenuation(len_L, light_falloff.x, light_falloff.y) * fade_attn;
					attn *= spot_angle_attenuation(dot(-L, spot_dir), spot_light_falloff.x, spot_light_falloff.y);

					float4x4 world_to_sm;
					sample_light_shadow_matrix(light_shadow_matrices, uint(sm_index5), world_to_sm);
					float4 sm_pos = mul(float4(world_pos, 1.0), world_to_sm);
					sm_pos.xyz /= sm_pos.w;
					sm_pos.z = apply_shadow_depth_comparison_bias(depth, sm_pos.z, local_shadow_map_bias);
					float shadow_intensity = shadow_intensity_2d(local_lights_shadow_atlas, cs_shadow_atlas_size.xy, sm_pos.xy, sm_pos.z);
					shadow_intensity = (shadow_intensity - 1.0) * shadow_fade + 1.0;

					#ifdef HAS_ANISOTROPY
						bsdf(L, V, T, B, N, light_color, diffuse_color, specular_color, roughness, anisotropy_, attn * shadow_intensity, acc_diff, acc_spec);
					#else
						bsdf(L, V, N, light_color, diffuse_color, specular_color, roughness, attn * shadow_intensity, acc_diff, acc_spec);
					#endif
					translucency += calculate_translucency(L, V, N, attn * lerp(lerp(0.5, 1.0, shadow_intensity), shadow_intensity, density), base_color, light_color, density);

					++light_index;
				}
			}
		"""
	}
}
