From 0335b6a3b72b4a574d0e38e370080cb65bbf8b5b Mon Sep 17 00:00:00 2001 From: Leon Schittek Date: Fri, 24 Nov 2023 22:50:20 +0100 Subject: [PATCH] UI: Improve menu dropshadow Improvements to the drawing of shadows, used with blocks, menus, nodes, etc. Improvements to shape, especially at the top corner or at extremes of widget roundness. Allows transparent objects to have shadows. This is a nice refactor that removes a lot of code. Pull Request: https://projects.blender.org/blender/blender/pulls/111794 --- .../blender/editors/include/UI_interface_c.hh | 3 +- .../editors/interface/interface_draw.cc | 131 +++-------------- .../editors/interface/interface_intern.hh | 4 - .../editors/interface/interface_widgets.cc | 134 ++---------------- .../blender/editors/space_node/node_draw.cc | 17 ++- .../blender/editors/space_text/text_draw.cc | 2 +- .../gpu_shader_2D_widget_shadow_frag.glsl | 5 +- .../gpu_shader_2D_widget_shadow_vert.glsl | 38 ++++- .../infos/gpu_shader_2D_widget_info.hh | 4 +- 9 files changed, 82 insertions(+), 256 deletions(-) diff --git a/source/blender/editors/include/UI_interface_c.hh b/source/blender/editors/include/UI_interface_c.hh index d2de4d3523f..1d28e64454f 100644 --- a/source/blender/editors/include/UI_interface_c.hh +++ b/source/blender/editors/include/UI_interface_c.hh @@ -493,7 +493,8 @@ void UI_draw_roundbox_4fv_ex(const rctf *rect, int UI_draw_roundbox_corner_get(); #endif -void UI_draw_box_shadow(const rctf *rect, unsigned char alpha); +void ui_draw_dropshadow(const rctf *rct, float radius, float width, float aspect, float alpha); + void UI_draw_text_underline(int pos_x, int pos_y, int len, int height, const float color[4]); /** diff --git a/source/blender/editors/interface/interface_draw.cc b/source/blender/editors/interface/interface_draw.cc index 2ddfdd74a7b..16cad5ef7d9 100644 --- a/source/blender/editors/interface/interface_draw.cc +++ b/source/blender/editors/interface/interface_draw.cc @@ -2178,108 +2178,20 @@ void ui_draw_but_TRACKPREVIEW(ARegion * /*region*/, /* ****************************************************** */ -/* TODO(merwin): high quality UI drop shadows using GLSL shader and single draw call - * would replace / modify the following 3 functions. */ - -static void ui_shadowbox(const rctf *rect, uint pos, uint color, float shadsize, uchar alpha) +void ui_draw_dropshadow( + const rctf *rct, const float radius, const float width, const float aspect, const float alpha) { - /** - *
-   *          v1-_
-   *          |   -_v2
-   *          |     |
-   *          |     |
-   *          |     |
-   * v7_______v3____v4
-   * \        |     /
-   *  \       |   _v5
-   *  v8______v6_-
-   * 
- */ - const float v1[2] = {rect->xmax, rect->ymax - 0.3f * shadsize}; - const float v2[2] = {rect->xmax + shadsize, rect->ymax - 0.75f * shadsize}; - const float v3[2] = {rect->xmax, rect->ymin}; - const float v4[2] = {rect->xmax + shadsize, rect->ymin}; + if (width == 0.0f) { + return; + } - const float v5[2] = {rect->xmax + 0.7f * shadsize, rect->ymin - 0.7f * shadsize}; - - const float v6[2] = {rect->xmax, rect->ymin - shadsize}; - const float v7[2] = {rect->xmin + 0.3f * shadsize, rect->ymin}; - const float v8[2] = {rect->xmin + 0.5f * shadsize, rect->ymin - shadsize}; - - /* right quad */ - immAttr4ub(color, 0, 0, 0, alpha); - immVertex2fv(pos, v3); - immVertex2fv(pos, v1); - immAttr4ub(color, 0, 0, 0, 0); - immVertex2fv(pos, v2); - - immVertex2fv(pos, v2); - immVertex2fv(pos, v4); - immAttr4ub(color, 0, 0, 0, alpha); - immVertex2fv(pos, v3); - - /* corner shape */ - // immAttr4ub(color, 0, 0, 0, alpha); /* Not needed, done above in previous tri. */ - immVertex2fv(pos, v3); - immAttr4ub(color, 0, 0, 0, 0); - immVertex2fv(pos, v4); - immVertex2fv(pos, v5); - - immVertex2fv(pos, v5); - immVertex2fv(pos, v6); - immAttr4ub(color, 0, 0, 0, alpha); - immVertex2fv(pos, v3); - - /* bottom quad */ - // immAttr4ub(color, 0, 0, 0, alpha); /* Not needed, done above in previous tri. */ - immVertex2fv(pos, v3); - immAttr4ub(color, 0, 0, 0, 0); - immVertex2fv(pos, v6); - immVertex2fv(pos, v8); - - immVertex2fv(pos, v8); - immAttr4ub(color, 0, 0, 0, alpha); - immVertex2fv(pos, v7); - immVertex2fv(pos, v3); -} - -void UI_draw_box_shadow(const rctf *rect, uchar alpha) -{ - GPU_blend(GPU_BLEND_ALPHA); - - GPUVertFormat *format = immVertexFormat(); - const uint pos = GPU_vertformat_attr_add(format, "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - uint color = GPU_vertformat_attr_add( - format, "color", GPU_COMP_U8, 4, GPU_FETCH_INT_TO_FLOAT_UNIT); - - immBindBuiltinProgram(GPU_SHADER_3D_SMOOTH_COLOR); - - immBegin(GPU_PRIM_TRIS, 54); - - /* accumulated outline boxes to make shade not linear, is more pleasant */ - ui_shadowbox(rect, pos, color, 11.0, (20 * alpha) >> 8); - ui_shadowbox(rect, pos, color, 7.0, (40 * alpha) >> 8); - ui_shadowbox(rect, pos, color, 5.0, (80 * alpha) >> 8); - - immEnd(); - - immUnbindProgram(); - - GPU_blend(GPU_BLEND_NONE); -} - -void ui_draw_dropshadow(const rctf *rct, float radius, float aspect, float alpha, int /*select*/) -{ /* This undoes the scale of the view for higher zoom factors to clamp the shadow size. */ const float clamped_aspect = smoothminf(aspect, 1.0f, 0.5f); + const float shadow_width = width * clamped_aspect; + const float shadow_offset = min_ff(shadow_width, BLI_rctf_size_y(rct) - 2.0f * radius); - const float shadow_softness = 0.6f * U.widget_unit * clamped_aspect; - const float shadow_offset = 0.5f * U.widget_unit * clamped_aspect; - const float shadow_alpha = 0.5f * alpha; - - const float max_radius = (BLI_rctf_size_y(rct) - shadow_offset) * 0.5f; - const float rad = min_ff(radius, max_radius); + const float inner_radius = max_ff(radius - U.pixelsize, 0.0); + const float shadow_radius = radius + shadow_width - U.pixelsize; GPU_blend(GPU_BLEND_ALPHA); @@ -2287,13 +2199,13 @@ void ui_draw_dropshadow(const rctf *rct, float radius, float aspect, float alpha widget_params.recti.xmin = rct->xmin; widget_params.recti.ymin = rct->ymin; widget_params.recti.xmax = rct->xmax; - widget_params.recti.ymax = rct->ymax - shadow_offset; - widget_params.rect.xmin = rct->xmin - shadow_softness; - widget_params.rect.ymin = rct->ymin - shadow_softness; - widget_params.rect.xmax = rct->xmax + shadow_softness; - widget_params.rect.ymax = rct->ymax - shadow_offset + shadow_softness; - widget_params.radi = rad; - widget_params.rad = rad + shadow_softness; + widget_params.recti.ymax = rct->ymax; + widget_params.rect.xmin = rct->xmin - shadow_width; + widget_params.rect.ymin = rct->ymin - shadow_width; + widget_params.rect.xmax = rct->xmax + shadow_width; + widget_params.rect.ymax = rct->ymax + shadow_width - shadow_offset; + widget_params.radi = inner_radius; + widget_params.rad = shadow_radius; widget_params.round_corners[0] = (roundboxtype & UI_CNR_BOTTOM_LEFT) ? 1.0f : 0.0f; widget_params.round_corners[1] = (roundboxtype & UI_CNR_BOTTOM_RIGHT) ? 1.0f : 0.0f; widget_params.round_corners[2] = (roundboxtype & UI_CNR_TOP_RIGHT) ? 1.0f : 0.0f; @@ -2303,17 +2215,8 @@ void ui_draw_dropshadow(const rctf *rct, float radius, float aspect, float alpha GPUBatch *batch = ui_batch_roundbox_shadow_get(); GPU_batch_program_set_builtin(batch, GPU_SHADER_2D_WIDGET_SHADOW); GPU_batch_uniform_4fv_array(batch, "parameters", 4, (const float(*)[4]) & widget_params); - GPU_batch_uniform_1f(batch, "alpha", shadow_alpha); + GPU_batch_uniform_1f(batch, "alpha", alpha); GPU_batch_draw(batch); - /* outline emphasis */ - const float color[4] = {0.0f, 0.0f, 0.0f, 0.4f}; - rctf rect{}; - rect.xmin = rct->xmin - 0.5f; - rect.xmax = rct->xmax + 0.5f; - rect.ymin = rct->ymin - 0.5f; - rect.ymax = rct->ymax + 0.5f; - UI_draw_roundbox_4fv(&rect, false, radius + 0.5f, color); - GPU_blend(GPU_BLEND_NONE); } diff --git a/source/blender/editors/interface/interface_intern.hh b/source/blender/editors/interface/interface_intern.hh index abf1facc7ec..4d1e4d6fa8b 100644 --- a/source/blender/editors/interface/interface_intern.hh +++ b/source/blender/editors/interface/interface_intern.hh @@ -1028,10 +1028,6 @@ void ui_draw_aligned_panel(const uiStyle *style, bool region_search_filter_active); void ui_panel_tag_search_filter_match(Panel *panel); -/* interface_draw.cc */ - -void ui_draw_dropshadow(const rctf *rct, float radius, float aspect, float alpha, int select); - /** * Draws in resolution of 48x4 colors. */ diff --git a/source/blender/editors/interface/interface_widgets.cc b/source/blender/editors/interface/interface_widgets.cc index 9fdc06f1a3e..31711ecf303 100644 --- a/source/blender/editors/interface/interface_widgets.cc +++ b/source/blender/editors/interface/interface_widgets.cc @@ -599,86 +599,6 @@ static void widget_init(uiWidgetBase *wtb) /** \name Draw Round Box * \{ */ -/* helper call, makes shadow rect, with 'sun' above menu, so only shadow to left/right/bottom */ -/* return tot */ -static int round_box_shadow_edges( - float (*vert)[2], const rcti *rect, float rad, int roundboxalign, float step) -{ - float vec[WIDGET_CURVE_RESOLU][2]; - int tot = 0; - - rad += step; - - if (2.0f * rad > BLI_rcti_size_y(rect)) { - rad = 0.5f * BLI_rcti_size_y(rect); - } - - const float minx = rect->xmin - step; - const float miny = rect->ymin - step; - const float maxx = rect->xmax + step; - const float maxy = rect->ymax + step; - - /* Multiply. */ - for (int a = 0; a < WIDGET_CURVE_RESOLU; a++) { - vec[a][0] = rad * cornervec[a][0]; - vec[a][1] = rad * cornervec[a][1]; - } - - /* start with left-top, anti clockwise */ - if (roundboxalign & UI_CNR_TOP_LEFT) { - for (int a = 0; a < WIDGET_CURVE_RESOLU; a++, tot++) { - vert[tot][0] = minx + rad - vec[a][0]; - vert[tot][1] = maxy - vec[a][1]; - } - } - else { - for (int a = 0; a < WIDGET_CURVE_RESOLU; a++, tot++) { - vert[tot][0] = minx; - vert[tot][1] = maxy; - } - } - - if (roundboxalign & UI_CNR_BOTTOM_LEFT) { - for (int a = 0; a < WIDGET_CURVE_RESOLU; a++, tot++) { - vert[tot][0] = minx + vec[a][1]; - vert[tot][1] = miny + rad - vec[a][0]; - } - } - else { - for (int a = 0; a < WIDGET_CURVE_RESOLU; a++, tot++) { - vert[tot][0] = minx; - vert[tot][1] = miny; - } - } - - if (roundboxalign & UI_CNR_BOTTOM_RIGHT) { - for (int a = 0; a < WIDGET_CURVE_RESOLU; a++, tot++) { - vert[tot][0] = maxx - rad + vec[a][0]; - vert[tot][1] = miny + vec[a][1]; - } - } - else { - for (int a = 0; a < WIDGET_CURVE_RESOLU; a++, tot++) { - vert[tot][0] = maxx; - vert[tot][1] = miny; - } - } - - if (roundboxalign & UI_CNR_TOP_RIGHT) { - for (int a = 0; a < WIDGET_CURVE_RESOLU; a++, tot++) { - vert[tot][0] = maxx - vec[a][1]; - vert[tot][1] = maxy - rad + vec[a][0]; - } - } - else { - for (int a = 0; a < WIDGET_CURVE_RESOLU; a++, tot++) { - vert[tot][0] = maxx; - vert[tot][1] = maxy; - } - } - return tot; -} - /* this call has 1 extra arg to allow mask outline */ static void round_box__edges( uiWidgetBase *wt, int roundboxalign, const rcti *rect, float rad, float radi) @@ -2828,54 +2748,18 @@ static void widget_state_menu_item(uiWidgetType *wt, /* outside of rect, rad to left/bottom/right */ static void widget_softshadow(const rcti *rect, int roundboxalign, const float radin) { - bTheme *btheme = UI_GetTheme(); - uiWidgetBase wtb; - rcti rect1 = *rect; - float triangle_strip[WIDGET_SIZE_MAX * 2 + 2][2]; - const float radout = UI_ThemeMenuShadowWidth(); + const float outline = U.pixelsize; - /* disabled shadow */ - if (radout == 0.0f) { - return; - } + rctf shadow_rect; + BLI_rctf_rcti_copy(&shadow_rect, rect); + BLI_rctf_pad(&shadow_rect, -outline, -outline); - /* prevent tooltips to not show round shadow */ - if (radout > 0.2f * BLI_rcti_size_y(&rect1)) { - rect1.ymax -= 0.2f * BLI_rcti_size_y(&rect1); - } - else { - rect1.ymax -= radout; - } + UI_draw_roundbox_corner_set(roundboxalign); - /* inner part */ - const int totvert = round_box_shadow_edges(wtb.inner_v, - &rect1, - radin, - roundboxalign & - (UI_CNR_BOTTOM_RIGHT | UI_CNR_BOTTOM_LEFT), - 0.0f); + const float shadow_alpha = UI_GetTheme()->tui.menu_shadow_fac; + const float shadow_width = UI_ThemeMenuShadowWidth(); - /* we draw a number of increasing size alpha quad strips */ - const float alphastep = 3.0f * btheme->tui.menu_shadow_fac / radout; - - const uint pos = GPU_vertformat_attr_add( - immVertexFormat(), "pos", GPU_COMP_F32, 2, GPU_FETCH_FLOAT); - - immBindBuiltinProgram(GPU_SHADER_3D_UNIFORM_COLOR); - - for (int step = 1; step <= int(radout); step++) { - const float expfac = sqrtf(step / radout); - - round_box_shadow_edges(wtb.outer_v, &rect1, radin, UI_CNR_ALL, float(step)); - - immUniformColor4f(0.0f, 0.0f, 0.0f, alphastep * (1.0f - expfac)); - - widget_verts_to_triangle_strip(&wtb, totvert, triangle_strip); - - widget_draw_vertex_buffer(pos, 0, GPU_PRIM_TRI_STRIP, triangle_strip, nullptr, totvert * 2); - } - - immUnbindProgram(); + ui_draw_dropshadow(&shadow_rect, radin, shadow_width, 1.0f, shadow_alpha); } static void widget_menu_back( @@ -5534,9 +5418,7 @@ static void ui_draw_widget_back_color(uiWidgetTypeEnum type, uiWidgetType *wt = widget_type(type); if (use_shadow) { - GPU_blend(GPU_BLEND_ALPHA); widget_softshadow(rect, UI_CNR_ALL, 0.25f * U.widget_unit); - GPU_blend(GPU_BLEND_NONE); } rcti rect_copy = *rect; diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index aa729e8200b..3167e9af8b6 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -91,8 +91,6 @@ #include "GEO_fillet_curves.hh" -#include "../interface/interface_intern.hh" /* TODO: Remove */ - #include "node_intern.hh" /* own include */ #include @@ -1857,7 +1855,20 @@ static void node_draw_shadow(const SpaceNode &snode, { const rctf &rct = node.runtime->totr; UI_draw_roundbox_corner_set(UI_CNR_ALL); - ui_draw_dropshadow(&rct, radius, snode.runtime->aspect, alpha, node.flag & SELECT); + + const float shadow_width = 0.6f * U.widget_unit; + const float shadow_alpha = 0.5f * alpha; + + ui_draw_dropshadow(&rct, radius, shadow_width, snode.runtime->aspect, shadow_alpha); + + /* Outline emphasis. Slight darkening _inside_ the outline. */ + const float color[4] = {0.0f, 0.0f, 0.0f, 0.4f}; + rctf rect{}; + rect.xmin = rct.xmin - 0.5f; + rect.xmax = rct.xmax + 0.5f; + rect.ymin = rct.ymin - 0.5f; + rect.ymax = rct.ymax + 0.5f; + UI_draw_roundbox_4fv(&rect, false, radius + 0.5f, color); } static void node_draw_sockets(const View2D &v2d, diff --git a/source/blender/editors/space_text/text_draw.cc b/source/blender/editors/space_text/text_draw.cc index 3a417df8024..aff1cc814e6 100644 --- a/source/blender/editors/space_text/text_draw.cc +++ b/source/blender/editors/space_text/text_draw.cc @@ -1056,7 +1056,7 @@ static void draw_suggestion_list(const SpaceText *st, const TextDrawContext *tdc rect.xmax = x + boxw; rect.ymin = y - boxh; rect.ymax = y; - UI_draw_box_shadow(&rect, 220); + ui_draw_dropshadow(&rect, 0.0f, 8.0f, 1.0f, 0.5f); } uint pos = GPU_vertformat_attr_add( diff --git a/source/blender/gpu/shaders/gpu_shader_2D_widget_shadow_frag.glsl b/source/blender/gpu/shaders/gpu_shader_2D_widget_shadow_frag.glsl index 54b3657c3b2..bdaf9234a4f 100644 --- a/source/blender/gpu/shaders/gpu_shader_2D_widget_shadow_frag.glsl +++ b/source/blender/gpu/shaders/gpu_shader_2D_widget_shadow_frag.glsl @@ -6,5 +6,8 @@ void main() { fragColor = vec4(0.0); /* Manual curve fit of the falloff curve of previous drawing method. */ - fragColor.a = alpha * (shadowFalloff * shadowFalloff * 0.722 + shadowFalloff * 0.277); + float shadow_alpha = alpha * (shadowFalloff * shadowFalloff * 0.722 + shadowFalloff * 0.277); + float inner_alpha = smoothstep(0.0, 0.05, innerMask); + + fragColor.a = inner_alpha * shadow_alpha; } diff --git a/source/blender/gpu/shaders/gpu_shader_2D_widget_shadow_vert.glsl b/source/blender/gpu/shaders/gpu_shader_2D_widget_shadow_vert.glsl index e25bcc5a5f1..a6de1a0de02 100644 --- a/source/blender/gpu/shaders/gpu_shader_2D_widget_shadow_vert.glsl +++ b/source/blender/gpu/shaders/gpu_shader_2D_widget_shadow_vert.glsl @@ -67,17 +67,41 @@ void main() vec2(0.02, -0.805), vec2(0.0, -1.0)); + const vec2 center_offset[4] = vec2[4]( + vec2(1.0, 1.0), vec2(-1.0, 1.0), vec2(-1.0, -1.0), vec2(1.0, -1.0)); + uint cflag = vflag & CNR_FLAG_RANGE; uint vofs = (vflag >> CORNER_VEC_OFS) & CORNER_VEC_RANGE; - vec2 v = cornervec[cflag * 9u + vofs]; - bool is_inner = (vflag & INNER_FLAG) != 0u; - shadowFalloff = (is_inner) ? 1.0 : 0.0; + float shadow_width = rads - radsi; + float shadow_width_top = rect.w - recti.w; - /* Scale by corner radius */ - v *= roundCorners[cflag] * ((is_inner) ? radsi : rads); + float rad_inner = radsi * roundCorners[cflag]; + float rad_outer = rad_inner + shadow_width; + float radius = (is_inner) ? rad_inner : rad_outer; + + float shadow_offset = (is_inner && (cflag > BOTTOM_RIGHT)) ? (shadow_width - shadow_width_top) : + 0.0; + + vec2 c = center_offset[cflag]; + vec2 center_outer = rad_outer * c; + vec2 center = radius * c; + + /* First expand all vertices to the outer shadow border. */ + vec2 v = rad_outer * cornervec[cflag * 9u + vofs]; + + /* Now shrink the inner vertices onto the inner rectangle. + * At the top corners we keep the vertical offset to distribute a few of the vertices along the + * straight part of the rectangle. This allows us to get a better falloff at the top. */ + if (is_inner && (cflag > BOTTOM_RIGHT) && (v.y < (shadow_offset - rad_outer))) { + v.y += shadow_width_top; + v.x = 0.0; + } + else { + v = radius * normalize(v - (center_outer + vec2(0.0, shadow_offset))) + center; + } /* Position to corner */ vec4 rct = (is_inner) ? recti : rect; @@ -94,5 +118,9 @@ void main() v += rct.xw; } + float inner_shadow_strength = min((rect.w - v.y) / rad_outer + 0.1, 1.0); + shadowFalloff = (is_inner) ? inner_shadow_strength : 0.0; + innerMask = (is_inner) ? 0.0 : 1.0; + gl_Position = ModelViewProjectionMatrix * vec4(v, 0.0, 1.0); } diff --git a/source/blender/gpu/shaders/infos/gpu_shader_2D_widget_info.hh b/source/blender/gpu/shaders/infos/gpu_shader_2D_widget_info.hh index 467d8cb0c80..b7cd0419807 100644 --- a/source/blender/gpu/shaders/infos/gpu_shader_2D_widget_info.hh +++ b/source/blender/gpu/shaders/infos/gpu_shader_2D_widget_info.hh @@ -49,7 +49,9 @@ GPU_SHADER_CREATE_INFO(gpu_shader_2D_widget_base_inst) .push_constant(Type::VEC4, "parameters", (MAX_PARAM * MAX_INSTANCE)) .additional_info("gpu_shader_2D_widget_shared"); -GPU_SHADER_INTERFACE_INFO(gpu_widget_shadow_iface, "").smooth(Type::FLOAT, "shadowFalloff"); +GPU_SHADER_INTERFACE_INFO(gpu_widget_shadow_iface, "") + .smooth(Type::FLOAT, "shadowFalloff") + .smooth(Type::FLOAT, "innerMask"); GPU_SHADER_CREATE_INFO(gpu_shader_2D_widget_shadow) .do_static_compilation(true)