Self-shadowing terrain idea - asking for feedback

Started by
9 comments, last by Reitano 10 years, 6 months ago

I've been wrestling for some time on how to produce decent quality, self-shadowing terrain that effectively shadows all other objects in the world. A couple of days ago I had an idea.

For any given point on the terrain, the direct sunlight contribution can be calculated thus:

While the sun is below the horizon or behind occluders (hill, mountains etc.) it's contribution is 0. We can describe this as a function of time - between 9pm and 10am, for example, a point of the terrain is NOT lit by the sun.

While the sun is fully visible it's contribution is 1. The can happen at between say 10.30am and 8.30pm.

The in between times (as the sun appears over distant peaks) would essentially represent the penumbra period - where we could interpolate, based on time of day, a contribution factor between 0 and 1.

However, whilst the above would work well for the ground, no information would be available to correctly light buildings or other objects.

So my idea is to represent sun light using two height values per point on the terrain (probably per vertex in a heightmap). The first value is the height at which the sun begins to fully light a point above the terrain, and the second value the point below which no sunlight has an effect (the first value would always be higher than the second).

So a fragment shader would effectively be:

if fragment is above first value, sunlight contribution = 1;

else if fragment is below second value, sunlight contribution = 0

else interpolate between the two.

This contribution would be included in a g-buffer (after a depth only pass).

The sun changes position slowly over time, so I think it's perfectly possible to recalculate new values by dealing with a fairly small sector of the 'sunlightmap' each frame and storing the results in a 16-bit RG texture.

I haven't really fleshed out this idea, but I thought I'd throw it out there and hopefully get some feedback.

Cheers.

Advertisement

So my idea is to represent sun light using two height values per point on the terrain (probably per vertex in a heightmap). The first value is the height at which the sun begins to fully light a point above the terrain, and the second value the point below which no sunlight has an effect (the first value would always be higher than the second).

I'm not sure I follow. What defines these "height points" and what heightmap are you storing them in? And how does that give you a shadow? All I can think of is it would give you a weird height-based darkening of the world, even if the sun is fully visible (think a beach scene). I guess I'm just confused. :B

On the topic though, I think my favorite approach so far is what was done for Crysis. For the sun, you render the terrain (only the terrain, low LODs too if you want) to a low-resolution VSM shadow map and blur it. Then during your main sun shadow pass you use the darkest of both shadow terms as your shadow term (so that you don't get a multiplying effect).

Edit: Oh you added a graph, sweet. How do you get the highest points though? Wouldn't this break anywhere the terrain was uneven?

I've added an image to illustrate.

You'd have a normal heightmap which contains a single height value. A second heightmap would have two values, the height above which is fully lit (shown in blue), and the height below which is fully in shadow (shown in red). A fragment would get it's derive it's lighting calculation depending on it's vertical location in the world - so a point half way between blue and red would get 50% sunlight (or 50% shadow, depending on your viewpoint!).

This second 'sunlight heightmap' would get updated over time as the time-of-day progresses.

I don't know what you mean by "the height at which is fully lit" and "the height below which is fully in shadow". Are you just randomly picking numbers based on the time of day and interpolating or are you reading the depth of the heightmap to compute these values? And are they local to each point?

It sounds like a lot of work for a really coarse approximation. If you need 2 heightmap values for such an approximation, it would probably be easier and visually more pleasing to simply render a terrain shadow map, cached over a few frames and rendered over the world - you'll get proper terrain occlusion and all for pretty cheap.

Though I'm still a bit confused on this so I'll wait to see how this goes. Maybe you can try implementing your idea and posting some results as a proof of concept?

If I understood correctly, you have a shadow function that depends on time (let's call it s(t)) and you want to describe this function. For this, you want to make is a smooth step function (_/---) so you just need to store the coefficients. Keep in mind though, that you need more than 2 values. s(t) looks more like this (sunrise ___/--day--\___ sunset), so you need at least 4 values. Still, it should be pretty easy to implement. This, of course, assumes fixed relation between the ground and the sun.

This reminds me of Polynomial texture maps. You can store the coefficients of a quadratic function at^2 + bt + c such that at any time t you can calculate a sunlight value. This works perfectly as long as you have only two transitions, from dark->light->dark or light->dark->light. More transitions require more terms. But for terrain without holes ( like a tunnel or a building with translucent windows ), this should work.

http://www.hpl.hp.com/research/ptm/papers/ptm.pdf

If I understood correctly, you have a shadow function that depends on time (let's call it s(t)) and you want to describe this function. For this, you want to make is a smooth step function (_/---) so you just need to store the coefficients. Keep in mind though, that you need more than 2 values. s(t) looks more like this (sunrise ___/--day--\___ sunset), so you need at least 4 values. Still, it should be pretty easy to implement. This, of course, assumes fixed relation between the ground and the sun.

Totally correct. I envisage having 'snapshots' of the shadow information on the GPU as a two channel texture, updated at regular intervals on the CPU. This wouldn't be a problem because, in reality, many regions wouldn't change very often - only as a shadow passes over them.

This reminds me of Polynomial texture maps. You can store the coefficients of a quadratic function at^2 + bt + c such that at any time t you can calculate a sunlight value. This works perfectly as long as you have only two transitions, from dark->light->dark or light->dark->light. More transitions require more terms. But for terrain without holes ( like a tunnel or a building with translucent windows ), this should work.

http://www.hpl.hp.com/research/ptm/papers/ptm.pdf

I wasn't aware of that paper, interesting.

You are correct about only two transitions. I'm basing my assumptions on the sun rising and setting vertically to guarantee the simplest shadow information.

This is called "Horizon Mapping" (http://research.microsoft.com/en-us/um/people/cohen/bs.pdf) iirc. Your implementation of 2 values per point will only work for 1 axis of light though. Take your image for example, what happens when the sun moves over the sky to the other side and begins to set? The occlusion heights from that direction are different, and now your precomputed 2 values are no longer correct.

Waramp.Before you insult a man, walk a mile in his shoes.That way, when you do insult him, you'll be a mile away, and you'll have his shoes.


We can describe this as a function of time - between 9pm and 10am, for example, a point of the terrain is NOT lit by the sun.

While the sun is fully visible it's contribution is 1. The can happen at between say 10.30am and 8.30pm.
I've you've not read this already, then check out the GPU gems chapter on occlusion interval maps; it's a very simillar idea biggrin.png

https://developer.nvidia.com/content/gpu-gems-chapter-13-generating-soft-shadows-using-occlusion-interval-maps


So my idea is to represent sun light using two height values per point on the terrain (probably per vertex in a heightmap). The first value is the height at which the sun begins to fully light a point above the terrain, and the second value the point below which no sunlight has an effect (the first value would always be higher than the second).
The nice thing about the above technique is that you can precompute it once, and then use that data to perform shadowing all day long without any further occlusion computation.

With this idea, your two height values change over the course of the day, so whenever the sun moves, you need to generate new height values for the whole terrain... That's fine, but you're just giving up one nice advantage of horizon or occlusion-interval maps.

I imagine that when the light moves, you'll basically have to run a real-time light baker over the scene, which, for each terrain lightmap texel, will find the two height values.

You could do this by rendering two directional shadowmaps from the sun's point of view, facing along the two lines in your diagram. For each lightmap texel, you'd then check if it is in shadow, and if so, raycast upwards until you find a height that is not in shadow.

That rebaking might be expensive, but you could split it up over multiple frames -- generating part of the shadow map and/or part of the lightmap each frame. A game that I'm currently working on does this with regular lightmaps. Over the course of a few minutes, I use 1ms of GPU time each frame to bake a new lightmap, and when it's complete, I swap it with the current lightmap and start baking the next one. This let us add dynamic time-of-day to the game, while keeping our very, very cheap to render light-mapped shadows.

This topic is closed to new replies.

Advertisement