I’m studying how we can add light and shadow to widgets and items. I want to hear what you think :-). So I’ll just throw out my ideas and see what happens.
Light and shadow are special effects that follow and decorate items, and affect how they are rendered, at the same time as they’re a bit different from regular items / subitems, or subwidgets. For both light and shadow effects, there’s sometimes a need to render outside the item’s boundaries and blend into surroundings. For widgets, we then need to do something to break out of the box model… to make that happen. For inside-widget light effects, we’re limited to what we can do inside paintEvent() or inside the paint() function. I don’t know about you, but I think both light and shadow effects should be primary citizens of the scene graph, which essentially means they are also items. Stack-above / always-on-top (overlays) or stack-below / always-below items (underlays?).
Here’s a flash video showing a sample of what I’m working on. This is based on Graphics View.
The scene consists of 150 elliptoid items with shadows, and one light source that’s flying over it. It’s a bit psychedelic; anyone who loves colliding mice will love this.
Haha.
Let’s start with shadows. In 2D, shadows can be pretty simple. Shadows can be thought of as transformed filled outlines of an object with a dark semitransparent fill and possibly fuzzy/blurred edges, stacked below the object itself. It has an offset, and/or an angle. The offset can be fixed for all items, or relative to one or more light sources. Ideally two shadows on top of each other don’t make a darker shadow, rather they blend with each other along the edges. Exact shadows are expensive to do accurately, and in many cases they’re completely pointless because they’re hard to see in the first place; at least the types of shadows we foresee being used in 2D / 2.5D UIs… Extreme shadows are cool but useless, subtle shadows, however, to me, are/can be beautiful. There seems to be a “market” for simple stupid fast shadows (e.g., bounding rect / bounding region based), pretty good medium-speed shadows (e.g., shape based), and custom shadows. And possibly perfect shadows (e.g., based on paint()) but I don’t really think that market is very big :-)).
As for how shadows are stacked, the easiest way is to just say that an item with a shadow always renders the shadow before itself. This works fine for most cases. But as soon as sibling items come close, and one’s shadow renders on top of the other, it starts looking wrong.
|
|
| Take 1: Sibling shadow overlaps | Take 2: Sibling shadows behind both |
It would be nice if I could set a shadow on our favorite “Drag And Drop Robot” without having lower leg shadows cast on the upper legs, or a shadow from the left leg draw on top of the right leg. I can see the need for both, but what would the API look like? And how would the items be stacked? My solution right now is to add a special flag to QGraphicsItem called QGraphicsItem::ItemStacksBehindParent (the child and its children are behind the parent), which is certainly one step on the way.
The other thing is how the shadow is placed. I don’t know about you, but I hate it how some presentation tools rotate the shadow relative to the item when you rotate and item that has a shadow on it. A picture says more than a thousand words:
|
|
| No |
Yes! Shadow follows scene :-)). |
It’s a bit complicated though because you want the shadow to follow the item, but you wants its offset to be done scene-relative. Turns out it’s just that the item’s local transform is prepended to instead of appended to the parent item’s scene transform. So I added a flag for that: QGraphicsItem::ItemBeforeSceneTransform. Btw this is just research, it’s not in Qt snapshots or anything.
Now light effects come in many different shapes and colors. Light can affect shadows, or it can just add a glow to an item. Light is typically represented by an abstract member of the scene, to which you can calculate distance and angle and apply a suitable effect to your item, or the background, and so on. In styling, we usually fake light big-time by just applying light and dark effects to our button bevels, or use pixmaps that look shiny and sparkly. Which, of course, is in many cases much faster than doing “correct lighting”. I recall once Zack Rusin and I were talking crap, I don’t know who brought it up but if the whole UI was a complex detailed 3D scene graph, lighting could come by itself (I think the discussion started with why buttons in RTL mode don’t have shadows cast as if the sun shine from the upper right).
For blend effects it’s also necessary to apply aftereffects that combine the source and destination. That can be done in two ways - either just via an offscreen buffer, or directly onto the destination device / framebuffer / or so. If we want blend effects I think we need some type of shader integration. Let’s not digress though.
So, ball in your court. What are your thoughts about light and shadow?
12 Responses to “Decoration items, light and shadow effects”
In the past, I have accomplished this in GraphcisView with about 8 QLinearGradients around the perimeter of a rectangle to get both semi-transparent and blurred edges. I had to extend the boundingRect() to include the drop shadow, and the end result looked pretty good but was very computationally expensive. Here’s what mine looked like:
http://thesmithfam.org/images/graphicsview-shadow.png
So I am very much interested in this idea, and would love for GV to do it for me! As long as it looks good and has rounded, soft edges (optionally).
Good work Andreas!
Hmmm….I think the sibling shadow could be a bit more subtle than what you have in either case. One sibling is definitely above the other so I think there could be a small shadow (3-4px) of the upper sibling on the lower sibling, and a large shadow of the upper sibling on the background. The upper->background shadow should either be the same size as the lower->background shadow (I imagine this would be easier to implement), or the size of the upper->lower shadow added to the size of the lower->background shadow (more “accurate” in terms of how shadows work, but I imagine trickier to implement).
If there were a QGraphicsShadowItem class, it would greatly simplify that, as all the hard work can be done there. It would use its parent’s bounding rect or shape, have a stacking behavior attribute, know where the light source is, etc.
What about adding a “z” property, and then build the shadows accordingly, an mockup at http://www.pnpitalia.it/images/image3392.png
having a “z” coordinate may be useful for further purposes too ![]()
Btw this is just research, it’s not in Qt snapshots or anything. –> what about the dojo ?!
I think bump mapping would be interesting for those especially if the shadow from a high bump can extend past the object. Have you considered this already? I’m thinking like a button with a beveled edge and raise center.
Honestly this posting made me immediately think of “focus glows” that you see on OSX. I’ve been very unhappy with how Qt handles these. It’s consistent. It seems when various widgets are placed in QGridLayouts some items demand that extra focus H/V margin and others do not, so I end up asking the style for the pixel size of these margins and manually getting stuff to all line up. I wish I could just not think about focus glows at all, that Qt would just work, but this still is not the case.
I bring this up because it would be nice if focus glows could be fixed at the same time this light and shadow stuff is implemented. It seems very much related since focus glows, like shadows, do not fit within the items geometry, and yet you don’t want to manually add padding all over the place so that your shadows/glows show up nicely, you wan to retain standard native spacing and margins. These problems, at least with focus glows at the moment, really start to stack up when you placed QGridLayouts in other QGridLayouts. Things get nasty fast. You can get around this somtimes by using one big complicated QGridLayout, but the grid cell layout, expansion, minimize widths and heights, etc, all get very complicated very fast and the code you write becomes very hard to maintain.
@Will Stokes: You took the words right out of my mouth!
Dave & Karelleen: The sibling-sibling shadows and shadows that hit multiple targets with an offset are certainly possible. But how can we make it fast? It’s hard to do these kinds of “perfect” shadow effects without it getting expensive very quickly… I’m open for suggestions at the algorithmic level ;-).
Dave & David: Yes, the shadow needs properties so you can tune how it’s rendered - and being able to create a custom shadow is also useful, so yes, I think QGraphicsShadowItem is a good dea; it would serve as a good container for these kinds of things.
Francesco: There is a z property, QGraphicsItem::zValue(), but this only applies to sibling order. Introducing a global Z property for widgets and items I think everybody’s code would be littered with setZ(N) where N is a random constant, and I really want to avoid that. Today, Qt chooses a good stacking order based on parent and child, and then creation order, and you can override sibling order. I hope we can avoid having to force users to hardcode Z values.
greg: We might post snapshots of this in a while - the changes are inside Qt though so it’s non-trivial to post compontents like this in the Dojo :-).
Michael: Bump mapping is a very cool type of offscreen effect, yes, it would be awesome if we could support it. But bump mapping is a per-pixel operation, and bumps cannot cast shadows (then it’s not bump mapping anymore, it’s true shadow casting)…
Will & Aidan: Yes, with this approach we could have fixed the focus glows to work perfectly with Mac OS X. Graphics View’s separation between the geometry and the “clip rect”, and how children can render outside the parent’s boundaries, are a very powerful tool for these types of effects. However, it’s extremely hard to change QWidget’s box model without breaking binary compatibility, or more importantly, behavior compatibility. However, we will certainly work towards this direction if we ever start looking at what Qt 5 might be.
This looks fancy, but i doubt it would be very useful for the average UI.
I remember this kind of effect from Populous: The Beginning (Back from 1998!) - it had a start menu screen with a similar effect: http://www.game-over.com/shot.php3?page=reviews&id=86&number=1
They tucked the light source on the mousecursor, which made for a nice effect.
Zandru: Hm, most modern styles use glow and shadow effects to some extent, many canvas applications like to use dropshadows (even the chip demo draws a crude 1-pixel shadow on each chip), and many embedded UIs use shadows to get a more authentic feel. The pad navigator example shows how subtle shadows on each item can “lift” the UI. I actually think most UIs can make use of shadows, in some way or another. But naturally embedded UI are the ones that will pull the most out of it (at least now).
I’m not saying shadows are bad. I just think that a moving light source, while looking fancy, is something that won’t really help the average UI.
Also you identified the biggest problem with shadows yourself: Finding a way to assign Z-Order automatically. I think it’s virtually impossible to find an algorithm that creates good-looking Z-Order for a majority of the Qt UIs that are out there.