calc_tight_light_projection :: (camera: Camera, light_direction: Vector3) -> Matrix4 { // View space camera frustum aspect_ratio := cast(float)engine.renderer.render_target_height / cast(float)engine.renderer.render_target_width; frustum := get_frustum(camera.fov, aspect_ratio, camera.z_near, camera.z_far); // View frustum back to world space inv_camera_view := inverse(camera.view_matrix); view_frustum_in_world_space := transform(frustum, inv_camera_view); light_view := look_at_lh(.{0,0,0}, light_direction, .{0,1,0}); light_space_frustum := transform(view_frustum_in_world_space, light_view); aabb := get_frustum_aabb(light_space_frustum); bottom_left := aabb.min; top_right := Vector3.{aabb.max.x, aabb.max.y, aabb.min.z}; light_pos_world := (bottom_left + top_right) * 0.5; inverse_light_view := inverse(light_view); light_pos_world = transform_position(light_pos_world, inverse_light_view); light_view = look_at_lh(light_pos_world, light_pos_world + light_direction, .{0,1,0}); light_space_frustum = transform(view_frustum_in_world_space, light_view); final_aabb := get_frustum_aabb(light_space_frustum); // Texel size calculation based on shadow map resolution (e.g., 2048x2048) texel_size_x := (final_aabb.max.x - final_aabb.min.x) / 4096.0; texel_size_y := (final_aabb.max.y - final_aabb.min.y) / 4096.0; // Snap the minimum bounds of the AABB to the nearest texel final_aabb.min.x = floor(final_aabb.min.x / texel_size_x) * texel_size_x; final_aabb.min.y = floor(final_aabb.min.y / texel_size_y) * texel_size_y; // Recalculate the max bounds based on snapped min final_aabb.max.x = final_aabb.min.x + (texel_size_x * 4096.0); final_aabb.max.y = final_aabb.min.y + (texel_size_y * 4096.0); // Padding final_aabb.min -= .{8,5,8}; final_aabb.max += .{8,50, 8}; light_projection := orthographic_lh_projection_matrix(final_aabb.min.x, final_aabb.max.x, final_aabb.min.y, final_aabb.max.y, final_aabb.min.z, final_aabb.max.z); return light_projection * light_view; } update_light_buffer :: () { scene := engine.current_scene; #if EDITOR { camera := ifx engine.mode == .EDITING then engine.editor.camera else scene.camera; } else { camera := *engine.current_scene.camera; } light_data : Directional_Light_Buffer_Data; light_data.direction = scene.directional_light.direction; light_data.color_and_intensity = scene.directional_light.color_and_intensity; light_matrix := calc_tight_light_projection(camera, scene.directional_light.direction.xyz); light_data.light_matrix = light_matrix; upload_data_to_buffer(engine.renderer, engine.directional_light_buffer, *light_data, size_of(Directional_Light_Buffer_Data)); point_light_array: Point_Light_Array; for light: engine.current_scene.by_type._Point_Light { shd_point_light := to_shader_point_light(light); point_light_array.point_lights[point_light_array.num_point_lights] = shd_point_light; point_light_array.num_point_lights += 1; } upload_data_to_buffer(engine.renderer, engine.point_light_buffer, *point_light_array, size_of(Point_Light_Array)); } sync_engine_buffers :: () { update_light_buffer(); // Camera buffer #if EDITOR { camera := ifx engine.mode == .EDITING then *engine.editor.camera else *engine.current_scene.camera; } else { camera := *engine.current_scene.camera; } camera_data : Camera_Data; camera_data.projection_matrix = camera.projection_matrix; camera_data.view_matrix = camera.view_matrix; camera_data.position = to_v4(camera.position); upload_data_to_buffer(engine.renderer, engine.camera_buffer, *camera_data, size_of(Camera_Data)); shader_time : Shader_Time; shader_time.time = time; upload_data_to_buffer(engine.renderer, engine.time_buffer, *shader_time, size_of(Shader_Time)); // Sync entity transforms for engine.current_scene.entities { if it.flags & .RENDERABLE { if it.renderable.type == { case .MODEL; { for n, i: it.renderable.model.nodes { if n.meshes.count > 0 { node_data := *it.renderable.nodes[i]; upload_data_to_buffer(engine.renderer, node_data.transform_buffer, *node_data.transform.world_matrix, size_of(Matrix4)); if node_data.num_bones > 0 { for handle, mesh_index: n.meshes { m := parray_get(*engine.renderer.meshes, handle); bones : [MAX_BONES] Matrix4; for bone_index: 0..m.num_bones-1 { bone := *it.renderable.nodes[m.bone_indices[bone_index]]; bones[bone_index] = bone.transform.world_matrix * m.bone_matrices[bone_index]; } upload_data_to_buffer(engine.renderer, node_data.bone_buffers[mesh_index], bones.data, size_of(Matrix4) * m.num_bones); } } } } } } } } }