Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Window shadows #1329

Open
wajsic opened this issue Sep 17, 2017 · 29 comments
Open

Window shadows #1329

wajsic opened this issue Sep 17, 2017 · 29 comments
Labels

Comments

@wajsic
Copy link

wajsic commented Sep 17, 2017

Does anyone know what would be the best approach to render drop shadow behind the windows?

@ocornut ocornut added the style label Sep 17, 2017
@ocornut
Copy link
Owner

ocornut commented Sep 17, 2017

It's not supported at the moment but here's a few ideas.

The most simple / basic approach would be to draw a transparent rounded rectangle under the window shape. You can hardcore this in Begin() fairly easily. But without a gradient within the shadow it won't look very good.

Adding a gradient would look better but requires more work setting up the vertices.

There's also the question that you may not want the shadow to actually draw behind the window because that is a waste of fill-rate, and solving this (especially as anti-aliased rounded borders are involved) makes the geometry more complex to create.

@Pagghiu
Copy link
Contributor

Pagghiu commented Sep 17, 2017

I have been playing exactly with shadows lately 😃
I am focusing on fast and easy shadows for non-rounded rectangles (for now).
I am trying to avoid using shaders or other fancy techniques for ease of integration into existing imgui based projects.

A few approaches that I have been trying:

  1. Generating a texture
  2. Generating vertices adaptively where the shadow signal is changing a lot, so mainly at the corners
  3. Generating a regular grid of triangles at the corners and color their vertices accordingly

You can see all of them here:

imguishadows

Approach 1) is great but you need to handle textures and generate a new one every time you need a different combination of sigma (shadow kernel) and rectangle size. And probably you will need to merge them inside the main font texture to avoid too many draw calls. You're also wasting some fill rate as pointed out by Omar. You can get really nice looking shadow with very small texture (even 32x32 is great). You could potentially generate only a single corner and re-use it everywhere playing with UV coordinates to even lower down the memory usage.
You could also "adapt" a generated texture for a given sigma to rectangles with different width and height but this requires some work in generating geometry and uv coordinates accordingly.

Approach 2) is probably the best so far, for a small shadow you can do very good approximation with about ~23 triangles for each rectangle with shadow. It's also very easy to implement and you have zero fill rate waste. For a larger sigma you need more triangles, potentially even double them but should be ok with most triangle budgets unless you plan to shadow everything in your UI.
This is great and makes it easy to also support rectangle with rounded corners.

Approach 3) looks great but wastes too many triangles, approach 2) can do the same with a lot less.

If you need a formula to generate the correct shadow for a rounded rectangle, you could take a look at http://madebyevan.com/shaders/fast-rounded-rectangle-shadows/

Beware, there's a typo in the formulas referenced in this article!
I have been wasting at least 1 hour trying to figure out what was wrong with my code before figuring this out ;)

// License: CC0 (http://creativecommons.org/publicdomain/zero/1.0/)

// This approximates the error function, needed for the gaussian integral
vec4 erf(vec4 x) {
  vec4 s = sign(x), a = abs(x);
  x = 1.0 + (0.278393 + (0.230389 + 0.078108 * (a * a)) * a) * a;
  x *= x;
  return s - s / (x * x);
}

// Return the mask for the shadow of a box from lower to upper
float boxShadow(vec2 lower, vec2 upper, vec2 point, float sigma) {
  vec4 query = vec4(point - lower, upper - point);
  vec4 integral = 0.5 + 0.5 * erf(query * (sqrt(0.5) / sigma));
  return (integral.z - integral.x) * (integral.w - integral.y);
}

The line

float boxShadow(vec2 lower, vec2 upper, vec2 point, float sigma) {
  vec4 query = vec4(point - lower, upper - point);
//...
}

should really be

float boxShadow(vec2 lower, vec2 upper, vec2 point, float sigma) {
  vec4 query = vec4(point - lower, point - upper);
//...
}

@wajsic
Copy link
Author

wajsic commented Sep 17, 2017

Thank you for taking your time for such an exhaustive explanation. I had in mind a lot of ideas but all of them included shaders which I'm trying to avoid for the reasons you said. This post pointed me into the right direction.

@ocornut
Copy link
Owner

ocornut commented Sep 17, 2017

Thanks @Pagghiu for looking into this, amazing stuff.
2) is definitively the way to go here. If we allow for arbitrary 2d offsets for the shadow then picking vertices without overlapping the parent shape is a little more work - if you come up with a helper function to handle it I'm happy to add that to the Styling options (my own drop-shadow git stash got nowhere as far as where yours is). If it's simpler to just support a zero ShadowOffset and fully outward spread it's already a step forward to have it.

I see you have a [ ] Linear Spread checkbox. Wouldn't a simple distance + power curve give good enough results compared to that function you linked above?

@Pagghiu
Copy link
Contributor

Pagghiu commented Sep 17, 2017

Thanks Omar, sure, if I will try to find time to cleanup it a little bit and share.

The linear checkbox activates a linear approximation of the shadow falloff, and it's indistinguishable from the "correct" one for small sigmas. For bigger sigma, the linear approximation is not good, the results are clearly banded and clamped incorrectly.
The box shadow formula is a power formula, as it's the Taylor expansion of the erf error function (https://en.wikipedia.org/wiki/Error_function). One could drop a few terms to save some multiplications of course or approximate it in many ways, even with bezier curves or splines (example http://stereopsis.com/shadowrect/), but I don't think it will be a big impact for a few vertices.
I have not been focusing on fine optimisation yet because I have been trying to figure out the best approach with constraints I have been giving myself.
ShadowOffset is easy to support, you just offset the sampling coordinate to feed into the boxShadow function. To avoid wasting triangles one should generate also triangles/vertices asymmetrically to be more dense in the direction of the shadowOffset and yes, that would be a little more complex to handle but still possible.

@Pagghiu
Copy link
Contributor

Pagghiu commented Sep 26, 2017

I have not been able to spend additional time cleaning and optimising the shadow mesh generation, or adding other features like generation for rounded boxes but as I've been promising, here is the code.

There's a self contained sample in the opengl2_example folder
https://github.com/Pagghiu/imgui/tree/2017-09-box-shadow

screen shot 2017-09-27 at 01 01 00

screen shot 2017-09-27 at 01 01 03

Hope this is useful to someone!

@ocornut
Copy link
Owner

ocornut commented Aug 16, 2018

Toying around with styling and tried your shadows.. they look great with the a right settings and white theme.

20180815_gradient_shadows2

@Pagghiu
Copy link
Contributor

Pagghiu commented Aug 17, 2018

Beautiful shot, it looks quite similar to native macOS windows shadowing.

I hope this makes its way inside the master someday! 😄

@ocornut
Copy link
Owner

ocornut commented Aug 17, 2018

It will definitively, this is still an early draft and I would like to focus on finishing Docking/Viewport before moving to new pastures, then steer the internals to make those changes possible (refactor the Render functions, etc.).

@rg-net
Copy link

rg-net commented Dec 10, 2018

+1 for shadows in master!

@ocornut
Copy link
Owner

ocornut commented Jun 3, 2020

Pushed a branch with work by @ShironekoBen on a different approach for shadows.
When experimenting we found earlier approach had too many edge cases (thin or small shapes) and the thing was redesigned around baking data in atlas texture:

https://github.com/ocornut/imgui/commits/features/shadows

(A)
First commit d2676ad has most of that shadow code as-is.
This time also support shadows behind rounded windows (which required an unusually large and complex amount of polygon clipping code... kudos to Ben for coping with that, kind of made me want to obsolete rounded windows...).

(B)
Here's the catch: baking in texture means changing some shadow settings during runtime requires an update/re-upload of the texture. That alone can get very hairy to support properly in back-ends. Third commit bb8a3cb in the branch has to some code doing just that. This code when it reaches maturity will be useful as a building block for dynamic font atlas. Current code I would say is experimental and doesn't constitute how the final back-end API should look like.

[edit] If we limit the amount of real-time tweaking or limit the scope of (B) or leave it to the app to reload, we can commit most of (A) ahead of time. Doing the full version of (B) will allow for dynami tweaking of shadow settings and be the building block for other features.

Shots

Previous "hacked style" with those shadows (replicating screenshots above)

image

With regular "Light" style as-is from master (same shadows, just a less sexy style)

image

There's probably more work to provide here. The irony is that this may over time become somehow less useful for apps transitioning to use more of the platform/multi-viewport feature. However one of the aim for this is to also provide a low-level ImDrawList API which can be also used for custom widgets.

@ocornut ocornut changed the title Window shadow Window shadows Jun 4, 2020
@camplowell
Copy link

Noticed the window shadows feature branch is gone (https://github.com/ocornut/imgui/commits/features/shadows is a dead link now), and the changes have not been pulled into master.
Any news on this feature? Has it been dropped?

@ocornut
Copy link
Owner

ocornut commented Jul 17, 2020

@camplowell We've been working on this, I only removed it from public repo to avoid pushing to two locations as I forgot I mentioned it here. Pushed again now.

@ocornut
Copy link
Owner

ocornut commented Jul 17, 2020

Note that Demo>Examples>Custom Rendering demonstrates some of it including new primitives:

image

We're still working on the API, but the ImDrawShadowFlags_CutOutShapeBackground shape allows cutting out a hole inside the shadows to avoid filling pixels twice. See the left-most rounded rectangle showing background color behind: the shadows triangles are clipped.

image

@Pagghiu
Copy link
Contributor

Pagghiu commented Jul 17, 2020

that's seriously beautiful 😃

@frink
Copy link
Contributor

frink commented Nov 1, 2020

Makes you want the docking/viewport thing to be done already!!!

@codecat
Copy link
Contributor

codecat commented Sep 16, 2021

I haven't tested this branch yet or looked at the API for it, but would this also allow adding shadows to text rendered via eg. drawlists?

@ShironekoBen
Copy link
Collaborator

Hm, in it's current state, only if the text was being drawn with convex primitives as opposed to the font texture... and even then I wouldn't be surprised if trying to deal with text brought out all the worst kinds of edge-cases!
For texture font shadows the best approach is probably (in an ideal world where everything was working and finished and beautiful!) the SDF stuff, or for a simpler take on it a static version of that with a pre-generated expanded/blurred font texture, I imagine.

@bvgastel
Copy link

I have made a merge request for text and window shadows using SDF, see MR 4056. In that MR rounded rectangle shadows (such as for windows) are also calculated with SDF and same shaders as used for text glyphs. Another upside from this is that there are no artifacts (as can be the case with some pre-generated shadow solutions).

@AlexvZyl
Copy link

This would really take dear imgui to the next level!

@ocornut
Copy link
Owner

ocornut commented Mar 15, 2022

It’s not as impactful as I hoped for three reasons:

  • on dark themes shadows are generally less relevant.
  • when docking shadows are generally less relevant.
  • when using multi-viewports with standalone windows we currently can’t get the shadows working.

@frink
Copy link
Contributor

frink commented Mar 16, 2022

I'd agree. If we allow shadows on other stuff like headers buttons that becomes more interesting but windows only makes sense if we are in control of drawing a large portion of the screen or if we're rending over a game which is still the prime use-case.

In docking they can be nice if you do like tiling window managers do and force spacing between window tiles. However, that's not currently how docking works as far as I know and I doubt anybody wants to redo the docking component one more time for something like this.

@flarive
Copy link

flarive commented Oct 10, 2022

Shadows in imGUI are very cool !
I have already made a neumorphism theme for Avalonia UI framework (.net C#) and i would like to try to make the same kind of Neumorphism theme for imGUI.
https://github.com/flarive/Neumorphism.Avalonia

@ocornut, do you think it would be a good idea ?
I know imGUI was not intended for that of the begining but with imGUI shadows it seems to be possible to create a very nice Neumorphism theme.

I have just 2 problems for the moment :

  • Shadows are not rounded when rectangle is rounded (see screenshot)
  • How can i create an inner shadow (shadows are only outer shadows for the moment)

2022-10-10 08_53_26-Dear ImGui DirectX9 Example

Thanks a lot !

@frink
Copy link
Contributor

frink commented Oct 20, 2022

I haven't looked but I'm guessing that the shadow geometry is not taking rounding into account.

@Ena-Shepherd
Copy link

Shots

Previous "hacked style" with those shadows (replicating screenshots above)

image

With regular "Light" style as-is from master (same shadows, just a less sexy style)

image

There's probably more work to provide here. The irony is that this may over time become somehow less useful for apps transitioning to use more of the platform/multi-viewport feature. However one of the aim for this is to also provide a low-level ImDrawList API which can be also used for custom widgets.

Tried shadow branch and it's working amazingly well on my window components !
I didn't find how you rendered shadows inside your elements, though, but it seems to be a custom texture rendering you're doing, right (with ImDraw) ? (I'm a newbie in code).
Is there a way to replicate this "hacked style" ?
It would be nice to have shadows per element features on shadow branch by default.
And also what @flarive commented #1329 (comment)

Thank you !

@kpcftsz
Copy link

kpcftsz commented May 22, 2023

If anybody is looking for a quick and dirty way of doing this with minimal set up you can use this 9 slice (technically 8 slice) method.

Preview:

image

From my gist (which includes the texture I'm using):

/*
 * This function assumes the existence of an active Dear ImGui window
 */
void RenderDropShadow(ImTextureID tex_id, float size, ImU8 opacity)
{
    ImVec2 p = ImGui::GetWindowPos();
    ImVec2 s = ImGui::GetWindowSize();
    ImVec2 m = {p.x + s.x, p.y + s.y};
    float uv0 = 0.0f;      // left/top region
    float uv1 = 0.333333f; // leftward/upper region
    float uv2 = 0.666666f; // rightward/lower region
    float uv3 = 1.0f;      // right/bottom region
    ImU32 col = (opacity << 24) | 0xFFFFFF;
    ImDrawList* dl = ImGui::GetWindowDrawList();
    dl->PushClipRectFullScreen();
    dl->AddImage(tex_id, {p.x - size, p.y - size}, {p.x,        p.y       }, {uv0, uv0}, {uv1, uv1}, col);
    dl->AddImage(tex_id, {p.x,        p.y - size}, {m.x,        p.y       }, {uv1, uv0}, {uv2, uv1}, col);
    dl->AddImage(tex_id, {m.x,        p.y - size}, {m.x + size, p.y       }, {uv2, uv0}, {uv3, uv1}, col);
    dl->AddImage(tex_id, {p.x - size, p.y       }, {p.x,        m.y       }, {uv0, uv1}, {uv1, uv2}, col);
    dl->AddImage(tex_id, {m.x,        p.y       }, {m.x + size, m.y       }, {uv2, uv1}, {uv3, uv2}, col);
    dl->AddImage(tex_id, {p.x - size, m.y       }, {p.x,        m.y + size}, {uv0, uv2}, {uv1, uv3}, col);
    dl->AddImage(tex_id, {p.x,        m.y       }, {m.x,        m.y + size}, {uv1, uv2}, {uv2, uv3}, col);
    dl->AddImage(tex_id, {m.x,        m.y       }, {m.x + size, m.y + size}, {uv2, uv2}, {uv3, uv3}, col);
    dl->PopClipRect();
}

You can call this after your window's Begin() and the shadow will draw around it. The only caveat with this is that it won't look good if WindowRounding isn't 0. You could probably work around this by drawing the middle segment of the 9 slice texture across the window and adjusting the code to use the background draw list, but then your shadows wouldn't properly overlap other windows (at least if my understanding is right; I haven't tested the workaround).

@dromer
Copy link

dromer commented Sep 1, 2023

It was mentioned that shadows are less relevant for dark themes, however: what about reusing this effort to also be able to render a "glow" behind the window?

@frink
Copy link
Contributor

frink commented Sep 3, 2023

@dromer - precisely my usecase.

@ocornut
Copy link
Owner

ocornut commented Sep 4, 2023

to also be able to render a "glow" behind the window?

This is exactly what shadows are doing.

This cannot be finished and merged until #3761 is done, and the polygon clipper of current shadow impl. is ihmo too complex for its own good. Without further resources I can't expect this to be usable anytime soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests