From b3245782d84dd64d0295bb7ef2e3128ad26a7f20 Mon Sep 17 00:00:00 2001 From: luboslenco Date: Wed, 20 May 2026 19:26:15 +0200 Subject: [PATCH] paint: add envmap spheres --- paint/sources/context.c | 182 ++++++++++++++------------- paint/sources/functions.h | 1 + paint/sources/main.c | 1 + paint/sources/render_envsphere.c | 174 +++++++++++++++++++++++++ paint/sources/render_path_deferred.c | 1 + paint/sources/render_path_forward.c | 1 + paint/sources/render_path_raytrace.c | 2 + paint/sources/types.h | 2 + paint/sources/ui_menubar.c | 18 ++- 9 files changed, 287 insertions(+), 95 deletions(-) create mode 100644 paint/sources/render_envsphere.c diff --git a/paint/sources/context.c b/paint/sources/context.c index 0aae525a..e9895f0f 100644 --- a/paint/sources/context.c +++ b/paint/sources/context.c @@ -2,96 +2,98 @@ #include "global.h" context_t *context_create() { - context_t *c = GC_ALLOC_INIT(context_t, {0}); - c->merged_object_is_atlas = false; // Only objects referenced by atlas are merged - c->ddirty = 0; // depth - c->pdirty = 0; // paint - c->rdirty = 0; // render - c->brush_blend_dirty = true; - c->split_view = false; - c->view_index = -1; - c->view_index_last = -1; - c->picked_color = project_make_swatch(0xffffffff); - c->envmap_loaded = false; - c->show_envmap = false; - c->show_envmap_handle = ui_handle_create(); - c->show_envmap_blur = false; - c->show_envmap_blur_handle = ui_handle_create(); - c->capturing_screenshot = false; - c->capture_background = false; - c->envmap_angle = 0.0; - c->light_angle = 0.0; - c->cull_backfaces = true; - c->texture_filter = true; - c->format_type = TEXTURE_LDR_FORMAT_PNG; - c->format_quality = 100.0; - c->layers_destination = EXPORT_DESTINATION_DISK; - c->split_by = SPLIT_TYPE_OBJECT; - c->select_time = 0.0; - c->viewport_mode = g_config->viewport_mode; - c->hscale_was_changed = false; - c->export_mesh_format = MESH_FORMAT_OBJ; - c->export_mesh_index = 0; - c->pack_assets_on_export = true; - c->pack_assets_on_save = false; - c->paint_vec = (vec4_t){0.0, 0.0, 0.0, 1.0}; - c->last_paint_x = -1.0; - c->last_paint_y = -1.0; - c->foreground_event = false; - c->painted = 0; - c->brush_time = 0.0; - c->clone_start_x = -1.0; - c->clone_start_y = -1.0; - c->clone_delta_x = 0.0; - c->clone_delta_y = 0.0; - c->show_compass = true; - c->project_aspect_ratio = 0; // 1:1, 2:1, 1:2 - c->last_paint_vec_x = -1.0; - c->last_paint_vec_y = -1.0; - c->prev_paint_vec_x = -1.0; - c->prev_paint_vec_y = -1.0; - c->frame = 0; - c->paint2d_view = false; - c->brush_locked = false; - c->camera_type = CAMERA_TYPE_PERSPECTIVE; - c->cam_handle = ui_handle_create(); - c->fov_handle = ui_handle_create(); - c->texture_export_path = ""; - c->last_status_position = 0; - c->camera_pivot = CAMERA_PIVOT_CURSOR; - c->camera_controls = CAMERA_CONTROLS_ORBIT; - c->pen_painting_only = false; // Reject painting with finger when using pen - c->layer_preview_dirty = true; - c->layers_preview_dirty = false; - c->node_preview_name = ""; - c->node_preview_socket_map = any_map_create(); - c->node_preview_map = any_map_create(); - c->selected_node_preview = true; - c->colorid_picked = false; - c->material_preview = false; // Drawing material previews - c->saved_camera = mat4_identity(); - c->materialid_picked = 0; - c->uvx_picked = 0.0; - c->uvy_picked = 0.0; - c->picker_select_material = false; - c->picker_mask_handle = ui_handle_create(); - c->pick_pos_nor_tex = false; - c->pick_object_id = false; - c->posx_picked = 0.0; - c->posy_picked = 0.0; - c->posz_picked = 0.0; - c->norx_picked = 0.0; - c->nory_picked = 0.0; - c->norz_picked = 0.0; - c->draw_wireframe = false; - c->wireframe_handle = ui_handle_create(); - c->draw_texels = false; - c->texels_handle = ui_handle_create(); - c->colorid_handle = ui_handle_create(); - c->layers_export = EXPORT_MODE_VISIBLE; - c->decal_preview = false; - c->decal_x = 0.0; - c->decal_y = 0.0; + context_t *c = GC_ALLOC_INIT(context_t, {0}); + c->merged_object_is_atlas = false; // Only objects referenced by atlas are merged + c->ddirty = 0; // depth + c->pdirty = 0; // paint + c->rdirty = 0; // render + c->brush_blend_dirty = true; + c->split_view = false; + c->view_index = -1; + c->view_index_last = -1; + c->picked_color = project_make_swatch(0xffffffff); + c->envmap_loaded = false; + c->show_envmap = false; + c->show_envmap_handle = ui_handle_create(); + c->show_envmap_blur = false; + c->show_envmap_blur_handle = ui_handle_create(); + c->show_envmap_spheres = false; + c->show_envmap_spheres_handle = ui_handle_create(); + c->capturing_screenshot = false; + c->capture_background = false; + c->envmap_angle = 0.0; + c->light_angle = 0.0; + c->cull_backfaces = true; + c->texture_filter = true; + c->format_type = TEXTURE_LDR_FORMAT_PNG; + c->format_quality = 100.0; + c->layers_destination = EXPORT_DESTINATION_DISK; + c->split_by = SPLIT_TYPE_OBJECT; + c->select_time = 0.0; + c->viewport_mode = g_config->viewport_mode; + c->hscale_was_changed = false; + c->export_mesh_format = MESH_FORMAT_OBJ; + c->export_mesh_index = 0; + c->pack_assets_on_export = true; + c->pack_assets_on_save = false; + c->paint_vec = (vec4_t){0.0, 0.0, 0.0, 1.0}; + c->last_paint_x = -1.0; + c->last_paint_y = -1.0; + c->foreground_event = false; + c->painted = 0; + c->brush_time = 0.0; + c->clone_start_x = -1.0; + c->clone_start_y = -1.0; + c->clone_delta_x = 0.0; + c->clone_delta_y = 0.0; + c->show_compass = true; + c->project_aspect_ratio = 0; // 1:1, 2:1, 1:2 + c->last_paint_vec_x = -1.0; + c->last_paint_vec_y = -1.0; + c->prev_paint_vec_x = -1.0; + c->prev_paint_vec_y = -1.0; + c->frame = 0; + c->paint2d_view = false; + c->brush_locked = false; + c->camera_type = CAMERA_TYPE_PERSPECTIVE; + c->cam_handle = ui_handle_create(); + c->fov_handle = ui_handle_create(); + c->texture_export_path = ""; + c->last_status_position = 0; + c->camera_pivot = CAMERA_PIVOT_CURSOR; + c->camera_controls = CAMERA_CONTROLS_ORBIT; + c->pen_painting_only = false; // Reject painting with finger when using pen + c->layer_preview_dirty = true; + c->layers_preview_dirty = false; + c->node_preview_name = ""; + c->node_preview_socket_map = any_map_create(); + c->node_preview_map = any_map_create(); + c->selected_node_preview = true; + c->colorid_picked = false; + c->material_preview = false; // Drawing material previews + c->saved_camera = mat4_identity(); + c->materialid_picked = 0; + c->uvx_picked = 0.0; + c->uvy_picked = 0.0; + c->picker_select_material = false; + c->picker_mask_handle = ui_handle_create(); + c->pick_pos_nor_tex = false; + c->pick_object_id = false; + c->posx_picked = 0.0; + c->posy_picked = 0.0; + c->posz_picked = 0.0; + c->norx_picked = 0.0; + c->nory_picked = 0.0; + c->norz_picked = 0.0; + c->draw_wireframe = false; + c->wireframe_handle = ui_handle_create(); + c->draw_texels = false; + c->texels_handle = ui_handle_create(); + c->colorid_handle = ui_handle_create(); + c->layers_export = EXPORT_MODE_VISIBLE; + c->decal_preview = false; + c->decal_x = 0.0; + c->decal_y = 0.0; // c.cache_draws = false; c->write_icon_on_export = false; c->particle_hit_x = 0.0; diff --git a/paint/sources/functions.h b/paint/sources/functions.h index a78d5b68..52cc6dac 100644 --- a/paint/sources/functions.h +++ b/paint/sources/functions.h @@ -85,6 +85,7 @@ void operator_update(); bool operator_shortcut(char *s, shortcut_type_t type); void compass_render(); void compass_update(); +void render_envsphere(); void config_load(); void config_save(); void config_init(); diff --git a/paint/sources/main.c b/paint/sources/main.c index 191d42fa..9492d713 100644 --- a/paint/sources/main.c +++ b/paint/sources/main.c @@ -156,6 +156,7 @@ #include "player.c" #include "plugin.c" #include "project.c" +#include "render_envsphere.c" #include "render_path_base.c" #include "render_path_deferred.c" #include "render_path_forward.c" diff --git a/paint/sources/render_envsphere.c b/paint/sources/render_envsphere.c new file mode 100644 index 00000000..2179729a --- /dev/null +++ b/paint/sources/render_envsphere.c @@ -0,0 +1,174 @@ + +#include "global.h" + +extern char *str_sh_irradiance; +extern char *str_envmap_equirect; +extern char *str_envmap_sample; +extern char *str_env_brdf_approx; + +static mesh_object_t *render_envsphere_metallic = NULL; +static mesh_object_t *render_envsphere_diffuse = NULL; + +static node_shader_context_t *make_envsphere_shader(char *name, f32 roughness, f32 metallic) { + material_t *mat = GC_ALLOC_INIT(material_t, {.name = name, .canvas = NULL}); + shader_context_t *props = GC_ALLOC_INIT(shader_context_t, { + .name = "overlay", + .depth_write = false, + .compare_mode = "always", + .cull_mode = "clockwise", + .vertex_elements = any_array_create_from_raw( + (void *[]){ + GC_ALLOC_INIT(vertex_element_t, {.name = "pos", .data = "short4norm"}), + GC_ALLOC_INIT(vertex_element_t, {.name = "nor", .data = "short2norm"}), + GC_ALLOC_INIT(vertex_element_t, {.name = "tex", .data = "short2norm"}), + }, + 3), + .color_attachments = any_array_create_from_raw((void *[]){"RGBA64"}, 1), + .depth_attachment = "NONE", + }); + node_shader_context_t *con = node_shader_context_create(mat, props); + node_shader_t *kong = node_shader_context_make_kong(con); + + kong->frag_n = true; + + node_shader_add_constant(kong, "WVP: float4x4", "_world_view_proj_matrix"); + node_shader_write_vert(kong, "output.pos = constants.WVP * float4(input.pos.xyz, 1.0);"); + + node_shader_add_constant(kong, "cam_look: float3", "_camera_look"); + node_shader_write_attrib_frag(kong, "var vvec: float3 = -constants.cam_look;"); + + node_shader_add_texture(kong, "senvmap_radiance", "_envmap_radiance"); + node_shader_add_texture(kong, "senvmap_radiance0", "_envmap_radiance0"); + node_shader_add_texture(kong, "senvmap_radiance1", "_envmap_radiance1"); + node_shader_add_texture(kong, "senvmap_radiance2", "_envmap_radiance2"); + node_shader_add_texture(kong, "senvmap_radiance3", "_envmap_radiance3"); + node_shader_add_texture(kong, "senvmap_radiance4", "_envmap_radiance4"); + + node_shader_add_constant(kong, "envmap_data: float4", "_envmap_data"); // (angle, sin, cos, strength) + node_shader_add_constant(kong, "shirr0: float4", "_envmap_irradiance0"); + node_shader_add_constant(kong, "shirr1: float4", "_envmap_irradiance1"); + node_shader_add_constant(kong, "shirr2: float4", "_envmap_irradiance2"); + node_shader_add_constant(kong, "shirr3: float4", "_envmap_irradiance3"); + node_shader_add_constant(kong, "shirr4: float4", "_envmap_irradiance4"); + node_shader_add_constant(kong, "shirr5: float4", "_envmap_irradiance5"); + node_shader_add_constant(kong, "shirr6: float4", "_envmap_irradiance6"); + + node_shader_add_function(kong, str_sh_irradiance); + node_shader_add_function(kong, str_envmap_equirect); + node_shader_add_function(kong, str_envmap_sample); + node_shader_add_function(kong, str_env_brdf_approx); + + node_shader_write_frag(kong, "var basecol: float3 = float3(0.8, 0.8, 0.8);"); + node_shader_write_frag(kong, string("var roughness: float = %s;", f32_to_string_with_zeros(roughness))); + node_shader_write_frag(kong, string("var metallic: float = %s;", f32_to_string_with_zeros(metallic))); + + // Indirect lighting + node_shader_write_frag(kong, "var albedo: float3 = lerp3(basecol, float3(0.0, 0.0, 0.0), metallic);"); + node_shader_write_frag(kong, "var f0: float3 = lerp3(float3(0.04, 0.04, 0.04), basecol, metallic);"); + node_shader_write_frag(kong, "var dotnv: float = max(0.0, dot(n, vvec));"); + node_shader_write_frag(kong, "var wreflect: float3 = reflect(-vvec, n);"); + node_shader_write_frag(kong, "var envlod: float = roughness * 5.0;"); + node_shader_write_frag(kong, "var lod0: float = floor(envlod);"); + node_shader_write_frag(kong, "var lod1: float = ceil(envlod);"); + node_shader_write_frag(kong, "var lodf: float = envlod - lod0;"); + node_shader_write_frag(kong, "var envmap_coord: float2 = envmap_equirect(wreflect, constants.envmap_data.x);"); + node_shader_write_frag(kong, "var lodc0: float3 = envmap_sample(lod0, envmap_coord);"); + node_shader_write_frag(kong, "var lodc1: float3 = envmap_sample(lod1, envmap_coord);"); + node_shader_write_frag(kong, "var prefiltered_color: float3 = lerp3(lodc0, lodc1, lodf);"); + // Rotate normal by envmap angle for irradiance + node_shader_write_frag(kong, "var indirect: float3 = albedo * (sh_irradiance(float3(n.x * constants.envmap_data.z - n.y * constants.envmap_data.y, n.x * " + "constants.envmap_data.y + n.y * constants.envmap_data.z, n.z)) / 3.14159265);"); + node_shader_write_frag(kong, "indirect = indirect + prefiltered_color * env_brdf_approx(f0, roughness, dotnv) * 0.5;"); + node_shader_write_frag(kong, "indirect = indirect * constants.envmap_data.w;"); + node_shader_write_frag(kong, "indirect = max3(indirect, float3(0.0, 0.0, 0.0));"); + + // Filmic tone-mapping (matches compositor_pass) + node_shader_write_frag(kong, "var tc: float3 = max3(indirect - float3(0.004, 0.004, 0.004), float3(0.0, 0.0, 0.0));"); + node_shader_write_frag(kong, + "indirect = (tc * (tc * 6.2 + float3(0.5, 0.5, 0.5))) / (tc * (tc * 6.2 + float3(1.7, 1.7, 1.7)) + float3(0.06, 0.06, 0.06));"); + node_shader_write_frag(kong, "output = float4(indirect, 1.0);"); + + parser_material_finalize(con); + con->data->shader_from_source = true; + gpu_create_shaders_from_kong(node_shader_get(kong), &con->data->vertex_shader, &con->data->fragment_shader, &con->data->_->vertex_shader_size, + &con->data->_->fragment_shader_size); + return con; +} + +static mesh_object_t *create_envsphere_object(char *name, f32 roughness, f32 metallic) { + mesh_data_t *md = ((mesh_object_t *)scene_get_child(".Sphere")->ext)->data; + + node_shader_context_t *con = make_envsphere_shader(name, roughness, metallic); + shader_context_load(con->data); + + shader_data_t *sd = GC_ALLOC_INIT(shader_data_t, { + .name = name, + .contexts = any_array_create_from_raw((void *[]){con->data}, 1), + }); + material_context_t *mcon = GC_ALLOC_INIT(material_context_t, { + .name = "overlay", + .bind_constants = any_array_create_from_raw((void *[]){}, 0), + .bind_textures = any_array_create_from_raw((void *[]){}, 0), + }); + material_context_load(mcon); + material_data_t *mat = GC_ALLOC_INIT(material_data_t, { + .name = name, + .shader = "", + .contexts = any_array_create_from_raw((void *[]){mcon}, 1), + ._ = GC_ALLOC_INIT(material_data_runtime_t, {.uid = 0.0, .shader = sd}), + }); + + mesh_object_t *obj = mesh_object_create(md, mat); + obj->base->name = name; + obj->base->visible = false; + obj->frustum_culling = false; + return obj; +} + +static void render_envsphere_init() { + render_envsphere_metallic = create_envsphere_object("render_envsphere_metallic", 0.0, 1.0); + gc_root(render_envsphere_metallic); + render_envsphere_diffuse = create_envsphere_object("render_envsphere_diffuse", 0.9, 0.0); + gc_root(render_envsphere_diffuse); +} + +void render_envsphere() { + if (!g_context->show_envmap_spheres || g_config->workspace == WORKSPACE_PLAYER || g_context->capturing_screenshot) { + return; + } + if (render_envsphere_metallic == NULL) { + render_envsphere_init(); + } + + camera_object_t *cam = scene_camera; + f32 ratio = sys_w() / (float)sys_h(); + mat4_t _P = cam->p; + cam->p = mat4_ortho(-8 * ratio, 8 * ratio, -8, 8, -2, 2); + + // Bottom-right corner + f32 sphere_y = -7.0; + f32 sphere_x_right = 7.4 * ratio; + f32 sphere_x_left = (7.4 - 1.1) * ratio; + f32 sphere_scale = 1.6; + + render_envsphere_metallic->base->visible = true; + render_envsphere_metallic->base->parent = cam->base; + render_envsphere_metallic->base->transform->loc = (vec4_t){sphere_x_right, sphere_y, -1, 1.0}; + render_envsphere_metallic->base->transform->scale = (vec4_t){sphere_scale, sphere_scale, sphere_scale, 1.0}; + transform_build_matrix(render_envsphere_metallic->base->transform); + mesh_object_render(render_envsphere_metallic, "overlay", NULL); + + render_envsphere_diffuse->base->visible = true; + render_envsphere_diffuse->base->parent = cam->base; + render_envsphere_diffuse->base->transform->loc = (vec4_t){sphere_x_left, sphere_y, -1, 1.0}; + render_envsphere_diffuse->base->transform->scale = (vec4_t){sphere_scale, sphere_scale, sphere_scale, 1.0}; + transform_build_matrix(render_envsphere_diffuse->base->transform); + mesh_object_render(render_envsphere_diffuse, "overlay", NULL); + + cam->p = _P; + + render_envsphere_metallic->base->visible = false; + render_envsphere_metallic->base->parent = NULL; + render_envsphere_diffuse->base->visible = false; + render_envsphere_diffuse->base->parent = NULL; +} diff --git a/paint/sources/render_path_deferred.c b/paint/sources/render_path_deferred.c index fb7c3f45..2ae48cb8 100644 --- a/paint/sources/render_path_deferred.c +++ b/paint/sources/render_path_deferred.c @@ -116,6 +116,7 @@ void render_path_deferred_draw_deferred() { render_path_set_target("gbuffer1", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0); compass_render(); + render_envsphere(); render_path_draw_meshes("overlay"); render_path_base_draw_taa("gbuffer1", "buf"); } diff --git a/paint/sources/render_path_forward.c b/paint/sources/render_path_forward.c index 5a74c455..04c4a7e9 100644 --- a/paint/sources/render_path_forward.c +++ b/paint/sources/render_path_forward.c @@ -13,6 +13,7 @@ void render_path_forward_draw_forward() { render_path_set_target("buf", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0); compass_render(); + render_envsphere(); render_path_draw_meshes("overlay"); render_path_base_draw_taa("buf", "gbuffer1"); diff --git a/paint/sources/render_path_raytrace.c b/paint/sources/render_path_raytrace.c index 270da4f7..52e254a1 100644 --- a/paint/sources/render_path_raytrace.c +++ b/paint/sources/render_path_raytrace.c @@ -202,6 +202,8 @@ void render_path_raytrace_draw(bool use_live_layer) { render_path_bind_target("buf", "tex"); render_path_draw_shader("Scene/compositor_pass/compositor_pass"); render_path_base_draw_bloom("buf", "last"); + render_path_set_target("last", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0); + render_envsphere(); render_path_set_target("", NULL, NULL, GPU_CLEAR_NONE, 0, 0.0); render_path_bind_target("last", "tex"); render_path_draw_shader("Scene/copy_pass/copy_pass"); diff --git a/paint/sources/types.h b/paint/sources/types.h index a7d1d919..a865e0a3 100644 --- a/paint/sources/types.h +++ b/paint/sources/types.h @@ -200,6 +200,8 @@ typedef struct context { struct ui_handle *show_envmap_handle; bool show_envmap_blur; struct ui_handle *show_envmap_blur_handle; + bool show_envmap_spheres; + struct ui_handle *show_envmap_spheres_handle; bool capturing_screenshot; bool capture_background; f32 envmap_angle; diff --git a/paint/sources/ui_menubar.c b/paint/sources/ui_menubar.c index ea963032..4a111bf0 100644 --- a/paint/sources/ui_menubar.c +++ b/paint/sources/ui_menubar.c @@ -16,7 +16,7 @@ typedef struct update_info { char *version_name; } update_info_t; -i32 ui_menubar_category = 0; +i32 ui_menubar_category = 0; int ui_menubar_capture_screenshot_frame = 0; void ui_menubar_init() { @@ -470,12 +470,20 @@ void ui_menubar_draw_category_items() { g_context->ddirty = 2; } + if (g_config->experimental) { + g_context->show_envmap_spheres_handle->b = g_context->show_envmap_spheres; + g_context->show_envmap_spheres = ui_check(g_context->show_envmap_spheres_handle, string(" %s", tr("Envmap Spheres")), ""); + if (g_context->show_envmap_spheres_handle->changed) { + g_context->ddirty = 2; + } + } + if (ui_menu_button(tr("Reset Envmap"), "", ICON_NONE)) { project_set_default_envmap(); } if (ui_menu_button(tr("Capture Screenshot"), "", ICON_PHOTO)) { - g_context->capturing_screenshot = true; + g_context->capturing_screenshot = true; ui_menubar_capture_screenshot_frame = 0; sys_notify_on_update(&ui_menubar_capture_screenshot, NULL); ui->changed = false; // Close menu @@ -508,9 +516,9 @@ void ui_menubar_draw_category_items() { } } else if (ui_menubar_category == MENUBAR_CATEGORY_MODE) { - ui_handle_t *mode_handle = ui_handle(__ID__); - mode_handle->i = g_context->viewport_mode; - string_array_t *modes = base_get_viewport_modes(); + ui_handle_t *mode_handle = ui_handle(__ID__); + mode_handle->i = g_context->viewport_mode; + string_array_t *modes = base_get_viewport_modes(); string_array_t *shortcuts = base_get_viewport_mode_shortcuts(); if (g_config->workflow == WORKFLOW_BASE) {