include = [ "core/shaders/common.shader" "core/shaders/shadow_map.shader" ] bgfx_shaders = { lighting = { includes = [ "shadow_mapping" "fog" ] code = """ #if !defined(NO_LIGHT) # define LIGHT_SIZE 8 // In vec4 units. # define MAX_NUM_LIGHTS 32 # define MAX_NUM_CASCADES 4 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_shadow_maps_texel_sizes; # define sun_sm_texel_size u_shadow_maps_texel_sizes.xy # define local_lights_sm_texel_size u_shadow_maps_texel_sizes.zw SAMPLER2DSHADOW(u_cascaded_shadow_map, 10); SAMPLER2DSHADOW(u_local_lights_shadow_map, 11); uniform vec4 u_local_lights_params; # define local_lights_distance_culling u_local_lights_params.x # define local_lights_distance_culling_fade u_local_lights_params.y # define local_lights_distance_culling_cutoff u_local_lights_params.z uniform vec4 u_lighting_params; # define ambient_color u_lighting_params.xyz CONST(float PI) = 3.14159265358979323846; float length_squared(vec3 a) { return dot(a, a); } vec3 fresnel(float dot, vec3 f0) { float a = clamp(1.0 - dot, 0.0, 1.0); return f0 + (1.0 - f0) * (a*a*a*a*a); } float dist_GGX(float ndoth, float roughness) { float r = roughness*roughness; float rr = r*r; float d = ((ndoth*ndoth) * (rr - 1.0) + 1.0); return rr / (PI * (d*d)); } float geom_schlick_GGX(float ndotv, float roughness) { float r = roughness + 1.0; float k = r*r * (1.0/8.0); return ndotv / (ndotv * (1.0 - k) + k); } float geom_smith(float ndotv, float ndotl, float roughness) { float ggx2 = geom_schlick_GGX(ndotv, roughness); float ggx1 = geom_schlick_GGX(ndotl, roughness); return ggx1 * ggx2; } vec3 calc_radiance(vec3 n, vec3 l, vec3 v, vec3 h, vec3 albedo, vec3 radiance, float metallic, float roughness, vec3 f0) { float ndotl = max(0.0, dot(n, l)); float ndotv = max(0.0, dot(n, v)); float ndoth = max(0.0, dot(n, h)); float hdotv = max(0.0, dot(h, v)); vec3 f = fresnel(hdotv, f0); float ndf = dist_GGX(ndoth, roughness); float g = geom_smith(ndotv, ndotl, roughness); // Cook-Torrance BRDF. vec3 num = ndf * g * f; float den = 4.0 * ndotv * ndotl + 0.0001; vec3 specular = num / den; vec3 ks = f; vec3 kd = (vec3_splat(1.0) - ks) * (1.0 - metallic); return (kd * albedo / PI + specular) * radiance * ndotl; } vec3 calc_dir_light(vec3 n, vec3 v, vec3 color, float intensity, vec3 direction, vec3 albedo, float metallic, float roughness, vec3 f0) { vec3 l = normalize(-direction); // Direction to light. vec3 h = normalize(v + l); // Half-vector betwen v and l. vec3 radiance = color * intensity; return calc_radiance(n, l, v, h, albedo, radiance, metallic, roughness, f0); } vec3 calc_omni_light(vec3 n, vec3 v, vec3 frag_pos, vec3 color, float intensity, vec3 position, float range, vec3 albedo, float metallic, float roughness, vec3 f0) { vec3 dpos = position - frag_pos; vec3 l = normalize(dpos); // Direction to light. vec3 h = normalize(v + l); // Half-vector betwen v and l. float dd = length_squared(dpos); float attenuation_std = 1.0 / dd; float k = 0.15; float a = 1.0 - dd / (range*range); float t = clamp(a/k, 0.0, 1.0); float attenuation = max(mix(0.0, attenuation_std, t), 0.0); vec3 radiance = color * intensity * attenuation; return calc_radiance(n, l, v, h, albedo, radiance, metallic, roughness, f0); } vec3 calc_spot_light(vec3 n , vec3 v , vec3 frag_pos , vec3 color , float intensity , vec3 direction , float spot_angle , vec3 position , float range , vec3 albedo , float metallic , float roughness , vec3 f0 ) { vec3 dpos = position - frag_pos; vec3 l = normalize(dpos); // Direction to light. vec3 h = normalize(v + l); // Half-vector betwen v and l. float dd = length_squared(dpos); float ldotd = dot(l, -direction); float r = range / ldotd; float rr = r*r; float attenuation_std = 1.0 / dd; float k = 0.03; if (ldotd >= cos(spot_angle) && dd <= rr) { float a = ldotd - cos(spot_angle); float t = clamp(a/k, 0.0, 1.0); float attenuation = mix(0.0, attenuation_std, t); vec3 radiance = color * intensity * attenuation; return calc_radiance(n, l, v, h, albedo, radiance, metallic, roughness, f0); } else { return vec3_splat(0.0); } } float fade_smoothstep(float camera_dist, float fade_dist, float cutoff_dist) { if (cutoff_dist == fade_dist) return camera_dist <= fade_dist ? 1.0 : 0.0; return 1.0 - smoothstep(fade_dist, cutoff_dist, camera_dist); } vec3 apply_distance_fading(vec3 radiance, vec3 light_pos, vec3 camera_pos) { if (local_lights_distance_culling == 0.0) return radiance; float fade_dist = local_lights_distance_culling_fade; float cutoff_dist = local_lights_distance_culling_cutoff; float camera_dist = length(light_pos - camera_pos); return mix(vec3_splat(0.0), radiance, fade_smoothstep(camera_dist, fade_dist, cutoff_dist)); } vec3 calc_lighting(mat3 tbn , vec3 n , vec3 v , vec3 frag_pos , vec3 camera_frag_pos , vec3 camera_pos , vec4 shadow_pos0 , vec4 shadow_pos1 , vec4 shadow_pos2 , vec4 shadow_pos3 , vec4 shadow_local , vec3 albedo , float metallic , float roughness , float ao , vec3 emission , vec3 f0 ) { vec3 radiance = ao * toLinearAccurate(ambient_color) * albedo; int loffset = 0; int num_dir = int(u_lights_num.x); int num_omni = int(u_lights_num.y); int num_spot = int(u_lights_num.z); vec3 sun_color = vec3(1, 1, 1); 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; sun_color = light_color; 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))); if (atlas0) local_radiance *= PCF(u_cascaded_shadow_map, shadow_pos0, shadow_bias, sun_sm_texel_size, vec3(atlas_u , atlas_v , atlas_size)); else if (atlas1) local_radiance *= PCF(u_cascaded_shadow_map, shadow_pos1, shadow_bias, sun_sm_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, sun_sm_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, sun_sm_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; vec3 direction = u_lights_data[loffset + 2].xyz; float shadow_bias = u_lights_data[loffset + 3].r; radiance += calc_dir_light(n , v , toLinearAccurate(light_color) , intensity , mul(direction, tbn) , albedo , metallic , roughness , f0 ); } for (int oi = 0; oi < num_omni; ++oi, loffset += LIGHT_SIZE) { vec3 light_color = u_lights_data[loffset + 0].rgb; float intensity = u_lights_data[loffset + 0].w; vec3 position = u_lights_data[loffset + 1].xyz; float range = u_lights_data[loffset + 1].w; float shadow_bias = u_lights_data[loffset + 3].r; vec3 local_radiance = calc_omni_light(n , v , frag_pos , toLinearAccurate(light_color) , intensity , mul(position, tbn) , range , albedo , metallic , roughness , f0 ); radiance += apply_distance_fading(local_radiance, position, camera_pos); } for (int si = 0; si < num_spot; ++si, loffset += LIGHT_SIZE) { vec3 light_color = u_lights_data[loffset + 0].rgb; float intensity = u_lights_data[loffset + 0].w; vec3 position = u_lights_data[loffset + 1].xyz; float range = u_lights_data[loffset + 1].w; vec3 direction = u_lights_data[loffset + 2].xyz; float spot_angle = u_lights_data[loffset + 2].w; 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; vec4 axis_x = u_lights_data[loffset + 4]; vec4 axis_y = u_lights_data[loffset + 5]; vec4 axis_z = u_lights_data[loffset + 6]; vec4 axis_t = u_lights_data[loffset + 7]; vec3 local_radiance = calc_spot_light(n , v , frag_pos , toLinearAccurate(light_color) , intensity , mul(direction, tbn) , spot_angle , mul(position, tbn) , range , albedo , metallic , roughness , f0 ); mat4 mvp = mtxFromCols(axis_x, axis_y, axis_z, axis_t); vec4 shadow_pos0 = mul(mvp, shadow_local); local_radiance *= PCF(u_local_lights_shadow_map, shadow_pos0, shadow_bias, local_lights_sm_texel_size, vec3(atlas_u, atlas_v, atlas_size)); radiance += apply_distance_fading(local_radiance, position, camera_pos); } return apply_fog(emission + radiance, length(camera_frag_pos), sun_color); } #endif """ } fog = { code = """ uniform vec4 u_fog_data[2]; #define fog_color u_fog_data[0].rgb #define fog_density u_fog_data[0].w #define fog_range_min u_fog_data[1].x #define fog_range_max u_fog_data[1].y #define fog_sun_blend u_fog_data[1].z #define fog_enabled u_fog_data[1].w vec3 apply_fog(vec3 radiance, float d, vec3 sun_color) { if (fog_enabled == 0.0) return radiance; if (d < fog_range_min || d > fog_range_max) return radiance; float d2 = d - fog_range_min; float f = exp(-fog_density * d2); return mix(mix(fog_color, sun_color, fog_sun_blend), radiance, f); } """ } }