How to create 2D visibility/shadow effects for your game
ncase.github.ioRelated:
1) a tutorial on the same topic by Red Blob Games: http://www.redblobgames.com/articles/visibility/
2) a public domain Javascript visibility polygon library that runs in O(n log n), which is actually used in the Nothing to Hide game: https://code.google.com/p/visibility-polygon-js/
3) a blog post by author of said library discussing the algorithm: http://byronknoll.blogspot.ca/2013/05/on-log-n.html
Shooting a ray to every vertex and then naively computing intersections takes O(n^2) since there are n rays with up to O(n) intersections each. It is interesting to read and think about O(n log n) algorithms for computing the visibility polygon.
I didn't see this mentioned in those articles, so I figured I'd mention it: If the level has a lot of geometry, you could also make a BSP tree, and then at runtime you'd just have an O(log n) lookup, plus an O(m log m) running of the same algorithm on a pruned subset of m vertices stored in the BSP node. Each BSP node would contain only the edges which can possibly be seen from a particular area. The tree might be kind of big, though.
I think this particular idea of storing information in the BSP leaves was the big innovation brought to us by Id Software. Now, the elephant in the room is : how to build a well balanced BSP tree... But well, that's an entirely different question (I was a computer geometry researcher years ago... :-) )
You, sir, must be from the future, what with the uncopyrighted, bitcoin-based crowdfunding campaign. Backing this effort is a no-brainer.
So it turns out you CAN earn money on non-copyrighted work. $40,000 is not a small amount for most people. Well-deserved for the developer, hats off for the idea, implementation, and Bitcoin as a payment option.
A bit OT but what are some of the most successful open source game titles out there? I'm writing a game server backend right now and didn't find very many projects where I could read good quality game code. (I come from the JS web app world where everything is out in the open so I'm super biased)
I found Battle for Wesnoth to be entertaining as a game. It's open source, but I don't know about code quality. If you are willing to read code from when dinosaurs roamed the land and sea, try Nethack.
If you only care about reading code, open source or not, I think the first Deus Ex's in game scripting code was visible in the game's editor. (And the rest was the first Unreal engine, whose might also be available for reading nowadays. Or you just read the source of the various Quakes.)
Cool, thanks for the pointers!
A lovely game idea too. Wonderful commentary on surveillance states.
Nice article. However I think there's a better algorithm: the one actually used in Doom from Id Software.
First the scene must be put into a 2d bsp (that requires slicing some edge). After that, the bsp must be traversed front-to-back and that gives the edges in the right order. Just keep track of which angles are occluded then while traversing. During the process, when an edge is proccessed, cast/create the shadow accordingly. The algorithm stop when the field of vision is fully occluded - and not farther.
that might work for a frustum like directional light source (maybe) but what about an omnidirectional point light like in the example?
you can certainly walk 'outwards' from a node but then you are missing the real benefit to the bsp for this problem imo... which is that you just carved the world into convex volumes.
convex polygons are easier to work with. they allow applying many more algorithms and make others trivial enough to re-invent on the fly as needed... instead of walking a structure at all you could keep a list of which edges are visible at each leaf node - if you have 'loads' of memory (e.g. > 1MB) then you might be able to store the edges themselves rather than indexes and mitigate memory lookup expenses as well (which are so far away from anything in a broswer... but yeah).
Very cool article. Providing actual working demos for each step makes the technique a lot easier to explain to others.
I had fun implementing this effect a long time, in a pre-graphics-shader era [1].
Honestly, I haven't even read it because I'm tired and I'll leave it for tomorrow, but this is the reason I check HN, I've gotten good enough at skimming through and article and the comments to know when an post is worth it and this looks awesome, not much of a point, just happy to see quality material in HN (not a rant, just happy to find a great post).
Really cool tutorial. It also looks like the author has a pledge drive ending today, and he/she is really close to hitting their funding goal. Would be a shame to miss it while being so close, especially for something that looks so promising.
(Note: I have no connection to this developer. Just thought it was worth mentioning.)
Great presentation of the information. The interactive sections are really good at conveying the concepts.
Nice example, but ray casting like this is overkill for this effect and with a low number of rays it's pretty inaccurate.
I prefer using the technique shown in this classic article on 2D soft shadows: http://archive.gamedev.net/archive/reference/programming/fea...
The basic idea is to project the geometry, rather than ray cast. I've implemented this sort of technique in the past and it works well.
I have an open source XNA/MonoGame implementation of a similar lighting technique available here:
https://github.com/sq/Illuminant
It's hardware-accelerated and has a few other useful features; we're using it in Escape Goat 2 and it works on Windows/Linux/Mac on pretty much any D3D9-spec hardware. You can see some of the levels using it here: https://www.youtube.com/watch?v=-9s1PyotS18#t=1m20s
For EG2 we have a pair of separate lighting environments; one for the foreground plane and one for the background plane. Most light sources exist on both planes but have different parameters in order to create a depth effect; some light sources are only on the background plane. We apply a customized ramp texture to the light sources in order to replace the smooth attenuation with quantized attenuation, and we don't use soft shadows (though we do render lighting at a lower resolution and upscale it, because that softens things a little.) We also do a simple HDR camera model by sampling lighting intensity across the scene's foreground/background planes and using that to adjust the range of the final lightmap so that we can automatically tune exposure/white point etc to highlight certain parts of a level.
I also have a similar pure-software-rendering lighting library from around ~8 years ago available here, in C++:
https://github.com/kg/Fury2/blob/master/libraries/SoftFX/mod...
That one's garbage, but may still be of some use.
I'll note that the author's approach (needing extra intersection points at +- 0.00001 radian) is a little perplexing. I'm not sure why that is needed; I've never needed it.
A better approach for 'fuzzy' shadows is to model the penumbra; you can render it as a single triangle that borders on the actual shadow umbra and then make it smooth by using a simple lookup texture. This technique is described here:
http://archive.gamedev.net/archive/reference/programming/fea...
Another great option for 2D lighting is to associate normal maps with your sprites and do per-pixel lighting against each light source using the normal map. This can produce some really great results. I think that SpriteLamp, http://snakehillgames.com/spritelamp/ does this; the Fury2 library I linked above also did per-pixel sprite lighting with normal maps. The upcoming game Full Bore (http://www.wholehog-games.com/fullbore/) is using per-pixel lighting on its sprites and environment art, and I believe Spelunky for PC/XBLA uses it on environment art.
I'm kinda tired of the "cone-of-vision" mechanic and associated visual effect common in modern 2D games. In traditional 2D games, being able to see things that are not visible to the player's avatar is, IMO, a significant part of the charm. Not being restricted to a realistic viewpoint lets you become the larger-than-life hero of cartoons and cheesy action movies that does impossible things like leaping into a room full of bad guys and shooting them all before they have a chance to react, or "sensing" someone behind him and dodging the arrow at the last second. When you make a 2D game that tries to "realistically" limit the player's view, you're getting the downsides of both 2D and 3D games. It says to me, "I really wanted to make an FPS but I don't know how."
No criticism is intended towards OP's game; the "un-stealth game" is a different and interesting idea. I'm just throwing it out there for those dreaming up yet another top-down cone-of-vision stealth shooter to chew on.
You seem to have an unnecessarily narrow view of games and how they're constructed.
Try out Mark of the Ninja and tell me they were 'just trying to make an FPS'.
Picking an appropriate perspective for a game involves a lot of tradeoffs. While certain mechanics are most appropriate for certain perspectives (top-down 2d, isometric, first-person 3d...) a general approach to 'visibility' is not one of them. Visibility (or an approximation of it) applies to AI in almost every action game type, regardless of perspective. Whether they can see you or not is an essential consideration for creating convincing enemies, and AI allies need to know whether they can see foes.
You can also extensively use vision cone mechanics without having anything to do with an FPS. Classic games of the Rogue variety (i.e. nethack, angband...) all leverage vision cones and line-of-sight despite the fact that they predated 3D games of any kind.
I hang out in some indie/amateur game dev communities and it's like everyone and their dog is making a top-down stealth shooter with the exact same effect, the exact same mechanics, and the exact same "FPS in 2D" feeling. That's what I'm tired of, not 2D raycasted lighting/shadows in general (yes, I thought of Nethack while writing the grandparent comment).
Well, it's not like the unlit parts must be completely black.
Having some dynamic lighting does not mean that you must make it part of your game's mechanics. It's perfectly fine to do that kind of thing just for polish.
It's usually quite important if you want to create AI that behaves convincingly in a 2D world. The previous game I built using a 2D lighting system used it explicitly for this (other than making the environments look cool, of course) - the same geometry and light source data that lit the environments and produced shadows also determined whether enemies could see objects at given locations and whether you had hidden yourself by vanishing into the shadows or behind a given surface.
this is interesting (although this sort of effect has never mystified me) - i can't help but think how this can be optimised. the steps gone through here are things i never would have done and the final solution is about where i would start...
just blindly tracing to all of the verts 'feels wrong' its the sort of thing that is intuitively unreasonable with large data sets and is quite obviously going to be constrained by available horsepower at some size.
precomputed vis data is the obvious optimisation and removes the O(n) behaviour. not only do you not have to test everypoint but your raycast itself need not test every edge. i wrote something ages ago about generating PVS in 2d as an example of how to approach the problem in 3D...
(http://jheriko-rtw.blogspot.co.uk/2011/05/visibility-determi...)
the other thing that bugs me here is the jitter. it is the classic 'lazy' way to soften shadows... a better effect can be gotten for less - if you offset the light position perpendicular to the direction toward the test vert, in both directions by the same amount then you can generate two ray casts. triangulating the resulting mesh might be a little painful (unless you cut the world into convex chunks - then it is nearly trivial) but you will get the outline for both edges of the shadow and can then do something like vertex interpolated colour to give you the same visual result as infinite raycasts for the price of two...
I learnt the parametric representation of a circle sometime in highschool but never remember learning the representation of a line segment.
Maybe you forgot it because it's so simple?
Illuminated.js did something very similar:
Brilliant work on the presentation!
This is really awesome. Thanks a lot for breaking down your process, it's both enlightening and inspiring for me just starting out.
This could be achieved a lot simpler using an algorithm like shadow volumes
He uses a nice trick to approximate the real thing, however this algorithm wouldn't be able to deal with curved objects.
I'm downvoting your comment because on technical posts the top rated comment is damn near always of zero value and only serves to make the commenter try to sound smarter than the post author. The more in-depth and complicated the post the more those type of comments appear.
This is a nice little post that reasonably describes a technique that has been used by a fair number of professional, commercial products. That anyone would try to downplay it to make themselves feel superior is silly.
To provide some actual value, most of the the images in the post are actually interactive. It's pretty cool. I somehow read the whole thing and didn't notice.
> That anyone would try to downplay it to make themselves feel superior is silly.
Why would you think that was my goal? Did i say the author was stupid or didn't pay attention? I simply pointed out a limitation that might not be obvious to others at first sight and which wasn't discussed in the article itself, thus also providing for others an easy starting point to improve on the algorithm as presented.
Heck, in the circles* i run the first part of my comment is even praise. Figuring out ways to approximate "real" results so they can be done in realtime is not easy.
*Have an example of EXTREMELY competent faking from those circles: https://www.youtube.com/watch?v=MJfceF0syK8
You didn't read very carefully if you missed that everything was interactive.
"Today, I will show you how to make something like this: (move your mouse around in the box below)"
"The demo below just draws a bunch of line segments and tracks your mouse position."
"Here's what all that math looks like: (move your mouse over the box)"
You are correct. I did not read very carefully. Thank you for pointing that out. I focused on the pretty pictures before reading all of the text. I can say with extreme confidence that I am not the only person who made the mistake of not reading carefully so I thought it worthwhile to mention.
Some of us only skim articles.
It's not a sin.
Agreed. But given the rest of forrestthewoods's post I thought it was worth pointing out.
Couldn't you do something like this for a hack?
http://i.imgur.com/G5U43ZU.png
Calculate the edge points of the circle, draw a line between them, and then your shadow is simply a rectangle. Overlay the circle over the shadow to hide the line.
This will work with circles and ovals, but not more complex curves.
That's not a hack, that's actually the best way to do it. And it can work with more complex shapes (even non-convex ones) as long as you can find the tangent points - which isn't always easy.
You can simply shoot some rays tangent to each curve in addition to the endpoints/vertices.
I'm not too strong with trig. Would this also work with curves that have more than one bump? (sine, etc.)
Yes, with calculus you can find all the tangent lines from a point to any kind of curve. The exact equations vary based on what kind of curve it is (sine, parabola, cubic splines, Bezier curves, non-uniform rational B-splines, etc) and are kinda complicated.
Let P be the point (a,b) and let Q be the point (x(t),y(t)) of a curve for some parameter t. The tangent vector to that curve is (x'(t),y'(t)) and the ray PQ is just (a-x(t),b-y(t)). You need to make sure these vectors are the same (up to a constant multiple) in order to have a tangent ray. That is you are led to two equations
cx'(t) = a-x(t) cy'(x) = b-y(t)
You need to solve these two equations for values of c and t. The difficulty of this task depends mostly on the difficulty of the curve itself.
If the curve has equation y = f(x) (or in other words a simple representation x(t) = t and y(t) = f(x(t)) = f(t)), then you have
c = a-x cf'(x) = b-f(x)
Therefore (a-x)f'(x) = b-f(x). If f(x) is a polynomial of degree n, the problem is reduced to root-finding of a polynomial of degree n so you'll have at most n roots to deal with. So in general it won't be easy. This reduces to the code in the post if you use line segments.
At that point you're no longer dealing with rays and the math gets way more expensive for a goal of 1/60th of a second per render loop.
Any of this is absurdly trivial on modern graphics hardware, you can literally do all of the geometric math to compute your shadow volumes on the GPU and it will be a miniscule portion of your frame time.
Hm, then it becomes a question of whether that can be ran fast enough and remain realtime. :)