Dynamic Shadows in AGS - idea for module

Started by Snarky, Fri 31/01/2020 16:42:10

Previous topic - Next topic

Snarky

(Split off from this thread)

Quote from: fernewelten on Fri 31/01/2020 13:05:46
So we've got a light source that throws a conspicuous shadow behind a shovel.
player walks up to the shovel.
So now the light source would need to throw a conspicuous shadow behind player as well, wouldn't it?

And _that_ shadow is going to move around with player and probably change shape and size all the time …

I've sometimes toyed with the idea of making a module for this. It would work with regions to define the parts of the background on which character shadows would be drawn, and there'd be some configuration to define the direction of the shadow (and of the surface, e.g. vertical, horizontal). The module would then take the most appropriate character view (e.g. if facing forward and the shadow is pointing left, take the "face left" view), fill it with black, distort as appropriate, and draw semi-transparent on the BG.

Only very simple effects are possible, because you need full 3D to do it properly (for example, unless you assume an infinitely distant light source like the sun, things get significantly trickier), but it might be good enough for some scenarios.

When I first joined AGS back in 2004 there was a game in production that supposedly was going to do something like this, too. I forget the name…

TheManInBoots

#1
Quote from: Monsieur OUXX on Mon 03/02/2020 12:19:15
QuoteThe character's shadow could be automated with a module
It's not the topic of this discussion, but I've also thought about it and the main difficulty is that shadows are not additive. Only the brightly lit parts of the background would become darker. The parts that are already under a cast shadow from the scene would not get darker. It means you can't just draw the character's shadow over the scene, as is. You'd need to keep the overall shadows in a separate channel. It quickly becomes overly complicated.

When the character stands between the object and the wall/floor the shadows ARE additive.
And for the non-additive shadows, I do not believe that it would become overly complicated.
From how I understood Snarky the areas upon which the shadows fall (I'll call them "Shadow Areas") would be drawn, just like you draw the walkable areas or walk behinds.
In that case it's very easy to simply cut a hole into the shadow area where the object shadow is placed. (Illustrations and Explanations below)
Spoiler

I understood it that you'd draw the shadow areas because he said, there would be horizontal and vertical areas. You would have to define those areas.
And the best way is to draw them.



Because, what if there is a hole or a window in the wall?



You wouldn't want to the hole to be covered by the shadow either!

Also you would need to define a Baseline for the Shadow Area (just like for the walk-behinds). Because the distance between the wall and the character defines the distance between the character and the shadow. (And maybe also the intensity/transparency of the shadow)


Those are supposedly flying men with wings!

Let's say you have a shovel object in the room with it's object shadow.



Then you just need to leave a hole where the shadow object is when drawing the shadow area. (Similar as when you draw walk-behinds)
And you can fill up the "hole" with another Shadow Area ID, in order to restore or remove it, so that character's shadow is cast or not cast on the place, depending on weather the shovel is picked up or not.



It's exactly like walkable areas/walk-behinds. And those are not complicated. And it would create a perfect shadow, because the overlaying shadow is cut out:



The real complications maybe only start when you have angled walls and different distances for the light source, but the module wouldn't need to include that.

Unless implementing the option to draw the shadow area is too complex, I say a simple shadow module/plugin is worth it and totally possible. Even if you couldn't draw the exact outlines of the shadow area, I wonder if depending on how the shadow sprites are drawn- as dynamic sprites I assume- if you could simply create a walk behind that cuts the "shadow hole" (at least in simple cases where you can play around with baselines)...
[close]

Snarky

Thanks for the illustrations, ManInBoots! Yes, that's more or less what I had in mind.

I also considered how to do shadows on side-walls (vertical, but at an angle to the observer): I think it mainly requires defining a diagonal "baseline" (essentially, a line along the intersection of the wall and floor, pointing towards the vanishing point). And to get "correct" perspective (mainly for floor shadows, though it also applies to wall shadows), you'd also need to define the height of the horizon. Perspective would probably be limited to direction and foreshortening, not converging towards the vanishing point, since that would require perspective transformation of the sprite.

The biggest problem for this idea is that it requires a skew/shear operation on the sprites, which AGS does not offer. It would be possible to create one manually (by cropping a sprite to its individual rows/columns and drawing them displaced onto a new canvas), but it would probably be very slow.

Kweepa

#3
I played around with simple cast shadows that just sliced and skewed the main character sprite about ten years ago. It wasn't horribly slow.
[EDIT] SSH also created a module https://www.adventuregamestudio.co.uk/forums/index.php?topic=28905 (which might give you a leg up)
Still waiting for Purity of the Surf II

Snarky


Vincent

I really would love to have a module which handle dynamic shadows, I hope you will do it one day. I remember that once Khris has done something like this but it was only for the character and it was never released as far as I know. Here's the video of the dynamic shadows, what you think?

Monsieur OUXX

I would still like to know how your would implement the shadow of the player being cast o top of the shadow of an object. Let's say for simplicity's sake that both shadows should be 50% opaque. Well, then when on top of each other, the combined shadow should still be 50% opaque, not 100%.


 

Crimson Wizard

#7
Quote from: Monsieur OUXX on Tue 04/02/2020 09:52:21
I would still like to know how your would implement the shadow of the player being cast o top of the shadow of an object. Let's say for simplicity's sake that both shadows should be 50% opaque. Well, then when on top of each other, the combined shadow should still be 50% opaque, not 100%.

If we are using hardware accelerated driver, then we need full sprite transform support, and then just draw transformed translucent shadow sprites. (We still would have to calculate the shape of transformation of these sprites somehow though)


If we are talking about software method, one solution that comes to mind is to have an array, corresponding to pixels, where for each pixel (or group of pixels) there's a number, which defines shadow IDs. When shadow moves around the place, these IDs are adjusted as it covers and frees some area.

One could limit dynamic shadows to a number of bits in the N-bit number (32 for regular int, but you may have much more if you allow multiple ints per area), and then store the bitmask, where each bit means a shadow ID. When calculating actual opacity you check for each shadow's opacity level and combine them for particular pixel.

I keep saying "for pixel", but, of course, one could optimize this and store this shadow number per group of pixels, like 2x2, or 4x4 pixels.

Monsieur OUXX

Quote from: Crimson Wizard on Tue 04/02/2020 10:16:32
If we are talking about software method...

Are you discussing the technical means of computing the shadow's pixel values? I'm not 100% sure I understood. But if that's what you're doing, then it's not my question. Computing transparency is just a technicality. What I was asking was : "how do you tell your shadows engine that 'this background pixel does represent a shadow, and the player's shadow should not make it darker, while that other background pixel does not represent a shadow, so the player's shadow must make it darker' ?".
 

Crimson Wizard

#9
Quote from: Monsieur OUXX on Tue 04/02/2020 10:38:48What I was asking was : "how do you tell your shadows engine that 'this background pixel does represent a shadow, and the player's shadow should not make it darker, while that other background pixel does not represent a shadow, so the player's shadow must make it darker' ?".

this is what my suggestion was about.

And yes, this suggestion assumes that you do all the work yourself, as opposed to delegating it to hardware-accelerated gfx driver, for which it would be simplier, and fast enough, to just draw all shadows each frame.

Snarky

Quote from: Monsieur OUXX on Tue 04/02/2020 09:52:21
I would still like to know how your would implement the shadow of the player being cast o top of the shadow of an object. Let's say for simplicity's sake that both shadows should be 50% opaque. Well, then when on top of each other, the combined shadow should still be 50% opaque, not 100%.

Well first of all, drawing with transparency is multiplicative, not additive, so 50% on top of 50% is 75%, not 100%.

Second, as TheManInBoots explained, you'd define the regions where shadows should be drawn, ideally by drawing a mask (if it turns out regions are not suited to this, you might have to provide the shadow mask as a separate sprite). So if some part of the region is already in shadow and shouldn't have the dynamic shadow applied, you just omit it from the mask.

Snarky

Oh, and if the question is combining multiple dynamic shadows, you simply draw them all in opaque black on an intermediate canvas, then draw that with alpha-transparency onto the room.

Monsieur OUXX

Quote from: Snarky on Tue 04/02/2020 12:29:57
Oh, and if the question is combining multiple dynamic shadows, you simply draw them all in opaque black on an intermediate canvas, then draw that with alpha-transparency onto the room.

So if some of them are already part of the background, you just leave them out of that mask you were talking about?
 

Crimson Wizard

I guess, this is also a question of optimization: do you redraw all the shadows every time? That is much simplier, but also slower if you use software drawing.

My above reply was containing a suggestion about optimization. Guess, I forgot to explain better...

Snarky

Quote from: Monsieur OUXX on Tue 04/02/2020 12:46:51
Quote from: Snarky on Tue 04/02/2020 12:29:57
Oh, and if the question is combining multiple dynamic shadows, you simply draw them all in opaque black on an intermediate canvas, then draw that with alpha-transparency onto the room.

So if some of them are already part of the background, you just leave them out of that mask you were talking about?

Yes.

Monsieur OUXX

#15
Quote from: Crimson Wizard on Tue 04/02/2020 12:47:32
I guess, this is also a question of optimization: do you redraw all the shadows every time? That is much simplier, but also slower if you use software drawing.

My above reply was containing a suggestion about optimization. Guess, I forgot to explain better...

I find that usually for low-res games, per-pixel computations are not that big if a deal. For bigger amounts of pixels I use tricks relying on the build-in functions (Tint, redraw the same sprite several times, slice the sprite into smaller sprites, etc.). But usually I bump into the lack of flexibility of the functions used to manipulate the sprite's alpha mask. I wish I could intersect sprites, or intersect a sprite with another alpha mask (not just entirely overwrite it), for example, to produce cutouts of sprites.

 

Crimson Wizard

Quote from: Monsieur OUXX on Tue 04/02/2020 13:27:05I wish I could intersect sprites, or intersect a sprite with another alpha mask, for example, to produce cutouts of sprites.

See DynamicSprite.CopyTransparencyMask:
https://github.com/adventuregamestudio/ags-manual/wiki/DynamicSprite#dynamicspritecopytransparencymask

If i remember correctly, that should cut the transparent holes in one sprite using another.

Monsieur OUXX

Quote from: Crimson Wizard on Tue 04/02/2020 13:34:59
Quote from: Monsieur OUXX on Tue 04/02/2020 13:27:05I wish I could intersect sprites, or intersect a sprite with another alpha mask, for example, to produce cutouts of sprites.

See DynamicSprite.CopyTransparencyMask:
https://github.com/adventuregamestudio/ags-manual/wiki/DynamicSprite#dynamicspritecopytransparencymask

If i remember correctly, that should cut the transparent holes in one sprite using another.

I know, but I meant without entirely replacing the sprite's current mask.
To be honest I need to dive back into it but I remember it not quite working in a way that would make it useful. I don't want to talk more about it as I don't have more constructive feedback than that fron the top of my head.
 

TheManInBoots

#18
Great you moved the thread Snarky!
Quote from: Snarky on Mon 03/02/2020 18:13:18
Thanks for the illustrations, ManInBoots! Yes, that's more or less what I had in mind.
Sure thing. When I read that you wanted to create a shadow module, I just immediately could imagine it how it would work!
Yesterday I kept thinking about this throughout the day and came up with ideas on how to manipulate the dynamic sprites to create the shadow shape.

I use a lot of illustrations, so open the panels to look at it. I hoped using the panels "show/hide" gives for a better overview?...

Spoiler


First I want to mention that I believe that it would be better to use a shadow character and draw on it's view sprite instead of the background. This would allow to draw shadows on other objects as well. You would assign the character you choose as the shadow character or it's ID to the module with a certain function for example. And the characters baseline would repeatedly be Playercharacter.baseline-1, so it would cover everything behind the player character, but nothing in front, and not the player character himself. Otherwise how can it throw shadows on any object that the character walks past?

I want to describe a method to transform the sprites in a way so you can determine exactly from which direction, or which angle the light falls unto the character.

And I mean, as well from the x-direction:



As from the y-direction:



EXPLANATION OF TERM "LINE"
Spoiler

A quick explanation for when I talk about shearing.
Here we have a simple box 11x11 pixels (strongly magnified):



And now I am moving the "Y-LINES" to the right (since every line has an y value)



Now I move the "X-LINES" of the original box down (every line has an x-value)



This just to show what I mean with the terms "x-line" and "y-line".
[close]

MANIPULATION ON THE X-AXIS
Spoiler

Now, back to the transformation:

How would you manipulate the shadow sprite into the right shape when changing only the x-direction of the light source? (As those three different shadow examples in this picture below)



First you would have to choose the x-angle from which the light comes. In the illustration you see the red x-axis.
I marked the angles 45 degrees and 135 degrees. But obviously you could choose any angle between 1 and 179 degrees.
I also marked the baseline of the "vertical wall shadow area" with a dotted line.



Now let's take the example of 135 degrees.



First you would calculate the intersection point of the baseline with line that leaves from the characters base point (characters x,y coordinate) with 135 degrees.
I marked the intersection point with a blue X:



Now the x-value of that intersection point is the x-value where you place the shadow sprite.



Now you cut off (yellow line) the lower part of the shadow sprite and shear it into place.



There you have it! The shadow is finished!



Now when you actually draw the Dynamic Sprite the best way, from how I understand it, would be to draw it from bottom to top.
So you start with the sheared part. The first y-line is drawn just like the character sprite on the characters x-postion.
The second y-line then is moved/sheared to the right in this example. How much to the right?
Well you would calculate the difference between the x-coordinate of the original character and the x-value of the intersection point. Then you divide that by the number of "y-lines" that are being sheared. That number equals the distance between the character's y-coordinate and the shadow area baseline.
Let's say hypothetically in this case the y-line will be sheared by 10 pixels. The the second then will be moved and re-drawn with 20 pixel offset. The third 30 pixels, and so on.
So the y-lines are being sheared until we reach the baseline.
So from now on the y-lines won't be sheared anymore.
Let's say the last shear has been moved by 200 pixels. That means from now on every y-line of the sprite above the baseline will be drawn 200 pixels to the right. And thus the upper part will look normally drawn, just further to the right, as it should.
[close]

MANIPULATION ON THE Y-AXIS
Spoiler

Now how would you manipulate the sprite for the y-axis angle?

Let's say you have the angle of 135 degrees on the x-axis, and now also you want to transform the shadow with 135 degrees on the y-axis.



(Note that if x-angle<90degrees, the the y-angle would automatically switch to the left side of the y-axis, in case you understand what I mean by that. So again we don't need 360 degrees for the y-axis, but only the values 1-179)

So, y-angle set to 135 degrees, now we need to calculate the intersection point of the y-angle line with the x-value of the formerly mentioned intersection point with the baseline. I marked this second intersection point with an orange X.



As before, now we place the shadow sprite on the X-intersection value with the baseline.


But this time, before shearing the lower part as we did for the x-axis, we will SCALE the shadow sprite first! But only for the y-value! We basically "squish" or negatively stretch it.
And we stretch it until it has exactly the height of the y-angle intersection point:



(I assume one could use the same code as the engine uses for character scaling, but do it only for the "y-lines" and not the "x-lines")

Once this is done, we shear the part below the baseline like we did for the x-axis.



And voila! The shadow is finished. Now the light is coming in from the high left:




So for the sprite drawing:
You would have to stretch (in this case negatively stretch) the character to the right size first. Then you would start with the shearing and continue like you did when transforming on the x-axis.
Let's say the negative stretching is achieved by skipping certain y-lines. Then it would be advisable to make the process of stretching (skipping y-lines) and shearing at the SAME time, in order to safe drawing time and possibly avoid using an additional canvas.

There you have it. Those are my ideas on how to angle the shadow on the x- and y-axis!




I also have an idea for the angled walls! How to manipulate the sprites to create such a shadow:



But it's getting late here. I'll explain it more thoroughly another time.
[close]

SHEARING
Spoiler

Concerning shearing I want to mention a detail. Moving the y-lines when shearing to the left for example (as happened in the examples above) works fine.
HOWEVER, when you shear too much, it can create gaps between the pixels. Take for example a simple one pixel thick line (magnified in the illustration):



Now when you shear it strongly, this would happen (right side of the image cut out):



So the module would have to recognize gaps between the y-lines and automatically fill them up. Fortunately it would have to fill them up only with "black" pixels since the shadow is one-colored.
And how many pixels should it add to the right of the previous y-line?
Basically when drawing the new sheared y-line on top of the previous y-line, the module should:
add the 1/2 the length of the first y-line and 1/2 the length of the second y-line.
If that addition is shorter than the shearing distance, than it needs to draw additional pixels.
(the amount of pixels would be the difference: shear distance MINUS 1/2 * (y-line 1 PLUS y-line 2). Not sure if this was clearly enough explained.
(side note: for this to work, the module would have to recognize which pixels are "drawn" and which ones are transparent, in order to properly define the length of the y-line)

In the example:
The first y-line is one sprite one long.
The second as well.
The shearing distance is 2 sprites.
So the module needs to fill out:
2-(0,5 PLUS 0,5)=1
Since every y-line has the same length, the module needs to draw ONE pixel to the right of each y-line:


[close]

[close]

You are guys more advanced than I am with the programming, but maybe this can give you some ideas and some inspiration.

Anyways, I think it's an exciting project!

2ND EDIT: For your idea about the horizon Snarky, the vanity point etc. actually I think it's possible. But you wouldn't use a vanity point or horizon, but simply scale the shadow depending on it's y-value. That's the efficient way to do it I suppose.

Snarky

I love your enthusiasm, Man!
I think your constructions are a little bit off, though. They look more or less OK in these particular scenarios (though I'm not convinced you're being 100% consistent), but if you try some other setups I believe you'll find that the rules you're using are wrong.

Most obviously, the vertical scaling of the wall shadow is not correct. Assuming a distant lightsource and a vertical character, the shadow cast on a vertical surface will be exactly as tall as the character. (Also, consider how it works if the shadow is cast sideways.)

However, I don't have time right now to work through all the details or attempt any code, so I'll leave it.

Quote from: Monsieur OUXX on Tue 04/02/2020 13:27:05
But usually I bump into the lack of flexibility of the functions used to manipulate the sprite's alpha mask. I wish I could intersect sprites, or intersect a sprite with another alpha mask (not just entirely overwrite it), for example, to produce cutouts of sprites.

I agree that the limitations are annoying, but what you describe here is definitely possible.

SMF spam blocked by CleanTalk