diff --git a/base/sources/iron_path.c b/base/sources/iron_path.c index 108dc756..c4460218 100644 --- a/base/sources/iron_path.c +++ b/base/sources/iron_path.c @@ -270,9 +270,14 @@ bool path_is_ext_format(char *path) { return ends_with(p, ".stl") || ends_with(p, ".svg"); } +bool path_is_lut(char *path) { + char *p = to_lower_case(path); + return ends_with(p, ".cube"); +} + bool path_is_known(char *path) { return path_is_mesh(path) || path_is_texture(path) || path_is_font(path) || path_is_project(path) || path_is_plugin(path) || path_is_text(path) || - path_is_ext_format(path); + path_is_ext_format(path) || path_is_lut(path); } bool path_check_ext(char *p, string_array_t *exts) { diff --git a/base/sources/iron_path.h b/base/sources/iron_path.h index 8293531f..5e071034 100644 --- a/base/sources/iron_path.h +++ b/base/sources/iron_path.h @@ -31,6 +31,7 @@ bool path_is_plugin(char *path); bool path_is_json(char *path); bool path_is_text(char *path); bool path_is_ext_format(char *path); +bool path_is_lut(char *path); bool path_is_known(char *path); bool path_is_base_color_tex(char *p); bool path_is_opacity_tex(char *p); diff --git a/paint/assets/Scene.arm b/paint/assets/Scene.arm index 5a677c30..5f214688 100644 Binary files a/paint/assets/Scene.arm and b/paint/assets/Scene.arm differ diff --git a/paint/shaders/compositor_pass.kong b/paint/shaders/compositor_pass.kong index 94ff1a07..1eb64c20 100644 --- a/paint/shaders/compositor_pass.kong +++ b/paint/shaders/compositor_pass.kong @@ -4,6 +4,7 @@ const constants: { vignette_strength: float; grain_strength: float; tonemap_strength: float; // 0.0 or 1.0 + lut_size: float; }; #[set(everything)] @@ -12,6 +13,9 @@ const sampler_linear: sampler; #[set(everything)] const tex: tex2d; +#[set(everything)] +const lut_tex: tex2d; + // #[set(everything)] // const histogram: tex2d; @@ -58,8 +62,28 @@ fun compositor_pass_frag(input: vert_out): float4 { // var expo: float = 2.0 - clamp(length(sample_lod(histogram, sampler_linear, float2(0.5, 0.5), 0.0).rgb), 0.0, 1.0); // color.rgb *= pow(expo, auto_exposure_strength * 2.0); - // Tonemap with gamma - color.rgb = lerp3(color.rgb, tonemap_filmic(color.rgb), constants.tonemap_strength); + if (constants.lut_size > 0.0) { + // .cube lut color grading + color.rgb = pow3(max3(color.rgb, float3(0.0, 0.0, 0.0)), float3(0.4545, 0.4545, 0.4545)); + var r_s: float = min(max(color.x, 0.0), 1.0) * (constants.lut_size - 1.0); + var g_s: float = min(max(color.y, 0.0), 1.0) * (constants.lut_size - 1.0); + var b_s: float = min(max(color.z, 0.0), 1.0) * (constants.lut_size - 1.0); + var b0: float = floor(b_s); + var b1: float = min(b0 + 1.0, constants.lut_size - 1.0); + var b_frac: float = b_s - b0; + var strip_w: float = constants.lut_size * constants.lut_size; + var u0: float = (b0 * constants.lut_size + r_s + 0.5) / strip_w; + var u1: float = (b1 * constants.lut_size + r_s + 0.5) / strip_w; + var v: float = (g_s + 0.5) / constants.lut_size; + var s0: float4 = sample_lod(lut_tex, sampler_linear, float2(u0, v), 0.0); + var s1: float4 = sample_lod(lut_tex, sampler_linear, float2(u1, v), 0.0); + color.rgb = lerp3(s0.rgb, s1.rgb, float3(b_frac, b_frac, b_frac)); + } + else { + // Tonemap with gamma + color.rgb = lerp3(color.rgb, tonemap_filmic(color.rgb), constants.tonemap_strength); + } + return color; } diff --git a/paint/shaders/world_pass.kong b/paint/shaders/world_pass.kong index 92b1545b..cbd0b8d9 100644 --- a/paint/shaders/world_pass.kong +++ b/paint/shaders/world_pass.kong @@ -52,7 +52,7 @@ fun world_pass_frag(input: vert_out): float4 { var color: float4; color.rgb = sample(envmap, sampler_linear, envmap_equirect(-n, constants.envmap_data_world.x)).rgb * constants.envmap_data_world.w; - // Tonemap with gamma + // Tonemap with gamma - non-lit modes color.rgb = lerp3(color.rgb, tonemap_filmic(color.rgb), constants.envmap_data_world.y); color.a = 0.0; // Mark as non-opaque diff --git a/paint/sources/box_preferences.c b/paint/sources/box_preferences.c index 482e4183..8ee81b08 100644 --- a/paint/sources/box_preferences.c +++ b/paint/sources/box_preferences.c @@ -597,6 +597,13 @@ void box_preferences_pen_tab() { } } +void box_preferences_lut_picked(char *path) { + import_lut_run(path); + g_config->lut_path = string_copy(path); + config_save(); + g_context->ddirty = 2; +} + // ██╗ ██╗██╗███████╗██╗ ██╗██████╗ ██████╗ ██████╗ ████████╗ // ██║ ██║██║██╔════╝██║ ██║██╔══██╗██╔═══██╗██╔══██╗╚══██╔══╝ // ██║ ██║██║█████╗ ██║ █╗ ██║██████╔╝██║ ██║██████╔╝ ██║ @@ -708,6 +715,31 @@ void box_preferences_viewport_tab() { g_context->ddirty = 2; make_material_parse_mesh_material(); } + + ui_text(tr(".cube LUT"), UI_ALIGN_LEFT, 0x00000000); + f32_array_t *lut_ar = f32_array_create_from_raw( + (f32[]){ + 7 / 8.0, + 1 / 8.0, + }, + 2); + ui_row(lut_ar); + ui_handle_t *h_lut = ui_handle(__ID__); + h_lut->text = string_copy(g_config->lut_path); + g_config->lut_path = string_copy(ui_text_input(h_lut, "", UI_ALIGN_LEFT, true, false)); + if (h_lut->changed) { + if (string_equals(g_config->lut_path, "")) { + import_lut_free(); + config_save(); + g_context->ddirty = 2; + } + else { + box_preferences_lut_picked(g_config->lut_path); + } + } + if (ui_icon_button("", ICON_FOLDER_OPEN, UI_ALIGN_CENTER)) { + ui_files_show("cube", false, false, &box_preferences_lut_picked); + } } // ██╗ ██╗███████╗██╗ ██╗███╗ ███╗ █████╗ ██████╗ diff --git a/paint/sources/config.c b/paint/sources/config.c index 4b941905..2d73256e 100644 --- a/paint/sources/config.c +++ b/paint/sources/config.c @@ -75,6 +75,7 @@ void config_save() { json_encode_bool("rp_bloom", g_config->rp_bloom); json_encode_f32("rp_vignette", g_config->rp_vignette); json_encode_f32("rp_grain", g_config->rp_grain); + json_encode_string("lut_path", g_config->lut_path); json_encode_string_array("recent_projects", g_config->recent_projects); json_encode_string_array("bookmarks", g_config->bookmarks); json_encode_string_array("plugins", g_config->plugins); @@ -176,6 +177,7 @@ void config_init() { g_config->rp_bloom = false; g_config->rp_vignette = 0.2; g_config->rp_grain = 0.09; + g_config->lut_path = ""; #if defined(IRON_ANDROID) || defined(IRON_IOS) g_config->rp_ssao = false; #else diff --git a/paint/sources/functions.h b/paint/sources/functions.h index e0eeaa5e..a78d5b68 100644 --- a/paint/sources/functions.h +++ b/paint/sources/functions.h @@ -137,6 +137,8 @@ i32 pipes_get_constant_location(char *type); void make_brush_run(node_shader_t *kong); void import_blend_material_run(char *path); void import_texture_run(char *path, bool hdr_as_envmap); +void import_lut_run(const char *path); +void import_lut_free(); void ui_files_show(char *filters, bool is_save, bool open_multiple, void (*files_done)(char *)); char *ui_files_file_browser(ui_handle_t *handle, bool drag_files, char *search, bool refresh, void (*context_menu)(char *)); void ui_files_go_up(ui_handle_t *handle); @@ -547,6 +549,7 @@ void camera_reset(i32 view_index); bool box_preferences_model_exists(char *file_name); char *box_preferences_file_name_from_url(char *url); char *box_preferneces_model_url_from_name(char *name); +void box_preferences_lut_picked(char *path); void box_preferences_show(); void box_preferences_fetch_themes(); void box_preferences_fetch_keymaps(); diff --git a/paint/sources/globals.h b/paint/sources/globals.h index fcb0f168..a5505295 100644 --- a/paint/sources/globals.h +++ b/paint/sources/globals.h @@ -15,6 +15,8 @@ char *manifest_url_ios = "https://apps.apple.com/app/armorpaint/id153396 any_map_t *ui_children; any_map_t *ui_nodes_custom_buttons; +gpu_texture_t *lut_image = NULL; +i32 lut_size = 0; gpu_texture_t *layers_temp_image = NULL; gpu_texture_t *layers_expa = NULL; gpu_texture_t *layers_expb = NULL; diff --git a/paint/sources/import_asset.c b/paint/sources/import_asset.c index 2b7e395b..7dda9eae 100644 --- a/paint/sources/import_asset.c +++ b/paint/sources/import_asset.c @@ -63,6 +63,9 @@ void import_asset_run(char *path, f32 drop_x, f32 drop_y, bool show_box, bool hd g_context->ddirty = 2; } } + else if (path_is_lut(path)) { + box_preferences_lut_picked(path); + } else if (path_is_project(path)) { import_arm_run_project(path); } diff --git a/paint/sources/import_lut.c b/paint/sources/import_lut.c new file mode 100644 index 00000000..5d23a437 --- /dev/null +++ b/paint/sources/import_lut.c @@ -0,0 +1,96 @@ + +#include "global.h" +#include + +void import_lut_free(void) { + if (lut_image != NULL) { + gpu_texture_destroy(lut_image); + free(lut_image); + lut_image = NULL; + lut_size = 0; + } +} + +void import_lut_run(const char *path) { + iron_file_reader_t reader; + iron_file_reader_open(&reader, path, IRON_FILE_TYPE_ASSET); + + i32 file_size = (i32)iron_file_reader_size(&reader); + char *text = (char *)malloc(file_size + 1); + iron_file_reader_read(&reader, text, file_size); + iron_file_reader_close(&reader); + text[file_size] = '\0'; + + i32 n = 0; + f32 *cube = NULL; + i32 data_count = 0; + + char *line = text; + while (*line != '\0') { + char *eol = line; + while (*eol != '\n' && *eol != '\r' && *eol != '\0') + eol++; + char saved = *eol; + *eol = '\0'; + + while (*line == ' ' || *line == '\t') + line++; + + if (*line != '#' && *line != '\0') { + if (strncmp(line, "LUT_3D_SIZE", 11) == 0) { + sscanf(line + 11, "%d", &n); + cube = (f32 *)malloc(n * n * n * 3 * sizeof(f32)); + } + else if (n > 0 && cube != NULL && data_count < n * n * n) { + if ((*line >= '0' && *line <= '9') || *line == '-' || *line == '.') { + f32 r, g, b; + if (sscanf(line, "%f %f %f", &r, &g, &b) == 3) { + cube[data_count * 3 + 0] = r; + cube[data_count * 3 + 1] = g; + cube[data_count * 3 + 2] = b; + data_count++; + } + } + } + } + + *eol = saved; + line = eol; + while (*line == '\n' || *line == '\r') + line++; + } + + free(text); + + if (n == 0 || cube == NULL || data_count != n * n * n) { + free(cube); + return; + } + + // Pack 3d lut into 2d texture + i32 strip_w = n * n; + i32 strip_h = n; + u8 *pixels = (u8 *)malloc(strip_w * strip_h * 4); + + for (i32 b = 0; b < n; b++) { + for (i32 g = 0; g < n; g++) { + for (i32 r = 0; r < n; r++) { + i32 cube_idx = (b * n * n + g * n + r) * 3; + i32 px = b * n + r; + i32 py = g; + i32 pix_idx = (py * strip_w + px) * 4; + pixels[pix_idx + 0] = (u8)(fminf(fmaxf(cube[cube_idx + 0], 0.0f), 1.0f) * 255.0f + 0.5f); + pixels[pix_idx + 1] = (u8)(fminf(fmaxf(cube[cube_idx + 1], 0.0f), 1.0f) * 255.0f + 0.5f); + pixels[pix_idx + 2] = (u8)(fminf(fmaxf(cube[cube_idx + 2], 0.0f), 1.0f) * 255.0f + 0.5f); + pixels[pix_idx + 3] = 255; + } + } + } + + free(cube); + import_lut_free(); + lut_image = (gpu_texture_t *)malloc(sizeof(gpu_texture_t)); + gpu_texture_init_from_bytes(lut_image, pixels, strip_w, strip_h, GPU_TEXTURE_FORMAT_RGBA32); + free(pixels); + lut_size = n; +} diff --git a/paint/sources/kickstart.c b/paint/sources/kickstart.c index 26a69bd7..c0dda03b 100644 --- a/paint/sources/kickstart.c +++ b/paint/sources/kickstart.c @@ -350,6 +350,10 @@ void _kickstart() { plugins_init(); #endif + if (!string_equals(g_config->lut_path, "")) { + import_lut_run(g_config->lut_path); + } + base_init(); #ifdef is_debug diff --git a/paint/sources/main.c b/paint/sources/main.c index 12c9f590..191d42fa 100644 --- a/paint/sources/main.c +++ b/paint/sources/main.c @@ -120,6 +120,7 @@ #include "import_font.c" #include "import_keymap.c" #include "import_legacy.c" +#include "import_lut.c" #include "import_mesh.c" #include "import_obj.c" #include "import_plugin.c" diff --git a/paint/sources/types.h b/paint/sources/types.h index 749be700..8301f95e 100644 --- a/paint/sources/types.h +++ b/paint/sources/types.h @@ -83,11 +83,12 @@ typedef struct config { i32 window_frequency; f32 window_scale; // Render path - f32 rp_supersample; - bool rp_ssao; - bool rp_bloom; - f32 rp_vignette; - f32 rp_grain; + f32 rp_supersample; + bool rp_ssao; + bool rp_bloom; + f32 rp_vignette; + f32 rp_grain; + char *lut_path; // .cube // Application struct string_array *recent_projects; // Recently opened projects struct string_array *bookmarks; // Bookmarked folders in browser @@ -292,102 +293,102 @@ typedef struct context { struct gpu_texture *text_tool_image; char *text_tool_text; i32 layer_filter; - struct object *gizmo; - struct object *gizmo_translate_x; - struct object *gizmo_translate_y; - struct object *gizmo_translate_z; - struct object *gizmo_scale_x; - struct object *gizmo_scale_y; - struct object *gizmo_scale_z; - struct object *gizmo_rotate_x; - struct object *gizmo_rotate_y; - struct object *gizmo_rotate_z; - bool gizmo_started; - f32 gizmo_offset; - f32 gizmo_drag; - f32 gizmo_drag_last; - bool translate_x; - bool translate_y; - bool translate_z; - bool scale_x; - bool scale_y; - bool scale_z; - bool rotate_x; - bool rotate_y; - bool rotate_z; - f32 brush_nodes_radius; - f32 brush_nodes_opacity; - struct gpu_texture *brush_mask_image; - bool brush_mask_image_is_alpha; - struct gpu_texture *brush_stencil_image; - bool brush_stencil_image_is_alpha; - f32 brush_stencil_x; - f32 brush_stencil_y; - f32 brush_stencil_scale; - bool brush_stencil_scaling; - f32 brush_stencil_angle; - bool brush_stencil_rotating; - f32 brush_nodes_scale; - f32 brush_nodes_angle; - f32 brush_nodes_hardness; - bool brush_directional; - f32 brush_radius; - f32 brush_scale_x; - f32 brush_decal_mask_radius; - struct ui_handle *brush_decal_mask_radius_handle; - struct ui_handle *brush_scale_x_handle; - blend_type_t brush_blending; - f32 brush_opacity; - struct ui_handle *brush_opacity_handle; - f32 brush_scale; - f32 brush_angle; - struct ui_handle *brush_angle_handle; - f32 brush_hardness; - f32 brush_lazy_radius; - f32 brush_lazy_step; - f32 brush_lazy_x; - f32 brush_lazy_y; - uv_type_t brush_paint; - f32 brush_angle_reject_dot; - bake_type_t bake_type; - bake_axis_t bake_axis; - bake_up_axis_t bake_up_axis; - i32 bake_samples; - f32 bake_ao_strength; - f32 bake_ao_radius; - f32 bake_ao_offset; - f32 bake_curv_strength; - f32 bake_curv_radius; - f32 bake_curv_offset; - i32 bake_curv_smooth; - i32 bake_high_poly; - bool xray; - bool sym_x; - bool sym_y; - bool sym_z; - struct ui_handle *fill_type_handle; - struct ui_handle *blur_type_handle; - i32 blur_type; - bool paint2d; - i32 maximized_sidebar_width; - i32 drag_dest; - vec4_t coords; - f32 start_x; - f32 start_y; - bool lock_begin; - bool lock_x; - bool lock_y; - f32 lock_start_x; - f32 lock_start_y; - bool registered; - struct object *selected_object; - f32 particle_hit_x; - f32 particle_hit_y; - f32 particle_hit_z; - f32 last_particle_hit_x; - f32 last_particle_hit_y; - f32 last_particle_hit_z; - struct physics_body *paint_body; + struct object *gizmo; + struct object *gizmo_translate_x; + struct object *gizmo_translate_y; + struct object *gizmo_translate_z; + struct object *gizmo_scale_x; + struct object *gizmo_scale_y; + struct object *gizmo_scale_z; + struct object *gizmo_rotate_x; + struct object *gizmo_rotate_y; + struct object *gizmo_rotate_z; + bool gizmo_started; + f32 gizmo_offset; + f32 gizmo_drag; + f32 gizmo_drag_last; + bool translate_x; + bool translate_y; + bool translate_z; + bool scale_x; + bool scale_y; + bool scale_z; + bool rotate_x; + bool rotate_y; + bool rotate_z; + f32 brush_nodes_radius; + f32 brush_nodes_opacity; + struct gpu_texture *brush_mask_image; + bool brush_mask_image_is_alpha; + struct gpu_texture *brush_stencil_image; + bool brush_stencil_image_is_alpha; + f32 brush_stencil_x; + f32 brush_stencil_y; + f32 brush_stencil_scale; + bool brush_stencil_scaling; + f32 brush_stencil_angle; + bool brush_stencil_rotating; + f32 brush_nodes_scale; + f32 brush_nodes_angle; + f32 brush_nodes_hardness; + bool brush_directional; + f32 brush_radius; + f32 brush_scale_x; + f32 brush_decal_mask_radius; + struct ui_handle *brush_decal_mask_radius_handle; + struct ui_handle *brush_scale_x_handle; + blend_type_t brush_blending; + f32 brush_opacity; + struct ui_handle *brush_opacity_handle; + f32 brush_scale; + f32 brush_angle; + struct ui_handle *brush_angle_handle; + f32 brush_hardness; + f32 brush_lazy_radius; + f32 brush_lazy_step; + f32 brush_lazy_x; + f32 brush_lazy_y; + uv_type_t brush_paint; + f32 brush_angle_reject_dot; + bake_type_t bake_type; + bake_axis_t bake_axis; + bake_up_axis_t bake_up_axis; + i32 bake_samples; + f32 bake_ao_strength; + f32 bake_ao_radius; + f32 bake_ao_offset; + f32 bake_curv_strength; + f32 bake_curv_radius; + f32 bake_curv_offset; + i32 bake_curv_smooth; + i32 bake_high_poly; + bool xray; + bool sym_x; + bool sym_y; + bool sym_z; + struct ui_handle *fill_type_handle; + struct ui_handle *blur_type_handle; + i32 blur_type; + bool paint2d; + i32 maximized_sidebar_width; + i32 drag_dest; + vec4_t coords; + f32 start_x; + f32 start_y; + bool lock_begin; + bool lock_x; + bool lock_y; + f32 lock_start_x; + f32 lock_start_y; + bool registered; + struct object *selected_object; + f32 particle_hit_x; + f32 particle_hit_y; + f32 particle_hit_z; + f32 last_particle_hit_x; + f32 last_particle_hit_y; + f32 last_particle_hit_z; + struct physics_body *paint_body; struct { f32 hit_x; f32 hit_y; diff --git a/paint/sources/uniforms.c b/paint/sources/uniforms.c index c263f71d..064837fc 100644 --- a/paint/sources/uniforms.c +++ b/paint/sources/uniforms.c @@ -43,6 +43,9 @@ f32 uniforms_ext_f32_link(object_t *object, material_data_t *mat, char *link) { bool tonemap = g_context->viewport_mode == VIEWPORT_MODE_LIT || g_context->viewport_mode == VIEWPORT_MODE_PATH_TRACE; return tonemap ? 1.0 : 0.0; } + else if (string_equals(link, "_lut_size")) { + return lut_image != NULL ? (f32)lut_size : 0.0; + } else if (string_equals(link, "_bloom_sample_scale")) { return render_path_base_bloom_sample_scale; } @@ -420,6 +423,13 @@ gpu_texture_t *uniforms_ext_tex_link(object_t *object, material_data_t *mat, cha render_target_t *rt = any_map_get(render_path_render_targets, "last"); return rt->_image; } + if (string_equals(link, "_lut_tex")) { + if (lut_image == NULL) { + render_target_t *rt = any_map_get(render_path_render_targets, "empty_white"); + return rt->_image; + } + return lut_image; + } return NULL; }