Proposal to integrate Shadow Mapping

Hi all,

Built-in shadows is something what most engines have, but what Panda lacks. IMHO, it’s an important feature and should absolutely be integrated into Panda. Currently, it’s hard to have both shadows and the shader generator working at the same time.
So, I have a proposal to integrate this entirely into Panda and the Shader Generator. Let me know how you guys think about it.
The idea is to integrate the camera+buffer setup into the light interface. You’d need to call something like light.setShadowCaster(True). [size=92](Or, would it have to be a renderAttrib or renderEffect or so?)[/size]. This would internally setup a camera sharing the same lens as the spotlight or making a new lens in the case of a directional light. This camera’s transform would be updated on every setTransform call to the pandanode. [size=92](Reparenting would be easier, but I’m not sure whether it’s a good idea to let that flag change people’s scene graph. Or does a camera need to be in the same scene graph as it’s scene?)[/size]
It would also set up a depth buffer and store that internally.
When someone calls render.setLight, it will internally call setScene(render) on the camera as well. Someone would just need to call render.setShaderAuto() then. The shader generator would detect that one of the lights has shadow caster flag set and it will grab the internally stored depth buffer, and use that to calculate the final lighting intensity.

Long story short, here’s what it would take to setup lighting with shadows:

pl = PointLight("light")
pl.setShadowCasterOrSimilarlyNamedMethod(True)
plnp=render.attachNewNode(pl)
render.setLight(plnp)
render.setShaderAuto()

Would this lightNode-based interface become too heavy, for people who don’t want shadows? If so, we could maybe make something like ShadowSpotlight and ShadowDirectionalLight which inherit from Spotlight and DirectionalLight but add the shadow functionality.

I’d need some ideas and advice about this one.

pro-rsoft

Hmm, sounds intriguing. I’m not sure about the setTransform() method; I think it might be better to make the light node be the camera. We could either do this with a ShadowSpotlight class, as you suggest, or just make Spotlight (and DirectionalLight) always inherit from Camera, instead of just from LensNode. I’m not opposed to the latter solution.

David

First of all, pro-rsoft, you are the man! You save my life! At least few months of life :slight_smile:

May I ask for little clarification? Do you mean that it will be needed to call render.setShadowAuto() after lights are set up on the scene? What if the lights are dynamically switched on-off? Like in Oblivion, for example, they only cast light and shadows when the player gets close enough, and fade out when he gets too far. This could be important performance optimization for a non-deferred renderer, I think, and calling render.setShadowAuto() after each light fades in or out could ruin that. Maybe I am wrong…

@drwr: Not a bad idea, but I’m not sure whether people really want their normal spotlights and dlights to be cameras. Maybe we can make ShadowSpotlight double-inherit from Spotlight and Camera? It will end up inheriting LensNode twice though, I don’t know if that works.
If that’s a problem, we could also rename the current Spotlight into SpotlightBase, and don’t make it inherit anything. Then, Spotlight would inherit from SpotlightBase and LightLensNode, while ShadowSpotlight inherits from SpotlightBase and Camera.

Since DirectionalLight doesn’t inherit from LensNode in some way, in that case we can just make a ShadowDirectionalLight that double-inherits from Camera and DirectionalLight.
(Would we make the inherit from Camera a private inherit?)

Another thing: to make a buffer, I would need at least a GraphicsEngine and a GraphicsPipe. How would I access those? Or would I need to let the user pass those in the constructor or so?
Ideally, I think that should be the engine and pipe currently rendering the scene - but I don’t think it’s possible to do it that way, is it?

@birukoff: No, not setShadowAuto(), but setShaderAuto(). Enabling auto-shaders automatically updates the stuff when something changes, so you’ll only need to call setShaderAuto once.

Double-inheriting from LensNode would indeed cause problems, so we’d have to be clever with the inheritance. The convoluted inheritance path you suggest would be the absolutely correct solution, but it would also complicate the inheritance graph considerably. I’m not sure that it’s better than the slightly less correct, but simpler, solution of simply inheriting from Camera instead of LensNode. It’s true that a normal Spotlight doesn’t need to be a Camera, but so what if it is? You don’t have to use those interfaces if you don’t want them. I guess the biggest question is which solution would cause the most confusion to new developers.

But your point about the GraphicsEngine and GraphicsPipe raises some good questions. The need to manage the offscreen buffer means that the act of enabling the shadows has some global implications, more than just local to the particular light node. We can design an interface that attempts to hide this fact from the developer, for instance by attempting to guess the correct GraphicsEngine and GraphicsPipe to use; and by attempting to clean up the buffers at the right time and so forth. This is certainly doable.

But, I think my preference is not to hide such details. I think the developer will be better able to make informed decisions about resource management if he understands which operations have global repercussions. So maybe we should make enabling shadows a little more global? How about there is a ShadowManager class, who will be responsible for creating and destroying buffers as needed. To enable shadows, you would do:

light.setShadowCaster(base.shadowMgr)

and somewhere in ShowBase.py we would have:

base.shadowMgr = ShadowManager(base.graphicsEngine, base.pipe)

This also gives a place to query the total set of active shadow buffers and so on.

David

Hmm, ok, I thought making each light a Camera would add some runtime performance overhead. We’d make the camera inactive by default, right? And this would apply to Spotlight only, because DirectionalLight has no lens, how would it work for that class?

The approach with the ShadowManager would only work if the ShadowManager was a C++ class, since the ShaderGenerator needs to access the buffer.
Your solution sounds fine, but it does make using it somewhat more complex.

Actually, isn’t the idea behind ShadowManager class - a class that keeps track of the shadow buffers - already what GraphicsEngine does? I mean, it would be then the same idea, we could also just pass the engine/pipe/gsg to the ShadowSpotlight constructor, or, like you did it, to the setShadowCaster method.

Cameras don’t do anything by themselves; not until they’ve been added to a DisplayRegion for rendering. (And even then it’s really the DisplayRegion that’s doing the work.) So, making a node a Camera doesn’t automatically increase the runtime overhead, although it does cost some additional memory overhead for the additional data a Camera stores.

The DirectionalLight class would have to be modified to have the same inheritance hierarchy as Spotlight, whatever we decide that ought to be.

It’s true that there would be some similarities between a GraphicsEngine’s reponsibilities, and the ShadowManager’s responsibilities. But the GraphicsEngine doesn’t currently make any decisions for creating and destroying buffers; it just handles requests to do this from other code. In the new auto-shadow world, someone would have to be responsible for deciding when to create a new buffer for a light, and when to clean these up. It wouldn’t necessarily be the right thing to create a buffer for each light in the world, because buffers are heavy, and there might be many, many lights (especially if not all of them are in use at a given time). So someone would have to manage this precious resource wisely. That’s the job I’m imagining assigning to a ShadowManager class. This responsibility could also be folded into the GraphicsEngine, but it seems a little special-purpose for that.

David

Oh, you mean the ShadowManager will have all the functionality of managing the buffers, and not be just a wrapper to point to a graphics engine and pipe?
What I meant is: why don’t we let the ShadowSpotlight handle the buffer management? We could pass the engine/pipe to the constructor which creates the buffer, or, setup the buffer as soon as it gets rendered by a certain gsg.

My only objection against your idea of the ShadowManager is that it kind of defeats the purpose of the ShadowSpotlight. It would become just like the shadow manager I posted at the forums here rather than integrated. The ShadowSpotlight would be degraded to something that just stores a ‘shadowcaster’ flag and inherits Spotlight. Plus we’d only make it heavier.

My point was: if we can pass a ShadowManager to the ShadowSpotlight class, why can’t we pass an engine+pipe to it. Heck, maybe even a ShadowManager class that just stores a pointer to the engine and pipe, and maybe some extra management functions.

Heck, maybe even a ShadowManager class that just stores a pointer to the engine and pipe, and maybe some extra management functions.

I’m not sure how this counter-proposal is different from my proposal. :slight_smile:

Anyway, I don’t mind the idea of letting the Spotlights etc. manage themselves. My only concern is that something more global than the individual lights needs to be responsible for creating and destroying buffers.

David

Hmm, why exactly, if I may ask? For the sake of storing the engine and pipe?

Actually, this approach would make the ShadowSpotlight useless, we could then also just move the flag to Spotlight class, since it will inherit from Camera anyways? Then, we could make the ShadowManager manage the buffers. The only thing is that we’d need to make the buffer destruct when the Spotlight destructs.

Also, would an approach with a ShadowManager still be writable to bam? That’s one of the ideas I had in mind - to be able to load a couple of lights from a .bam just like ordinary lights, and that no postprocessing (i.e. attaching to a global ShadowManager) would be needed.

Each light only knows about itself. It doesn’t know how many other lights there are in the scene, where the light is relative to the camera, or how many buffers have already been created. So if the light itself does all of the its own buffer management, it is limited to very naive management: create a buffer when the light is created (or first used), destroy the buffer when the light is destroyed. This is OK when there are only a few lights in the scene and they are on all the time. It doesn’t give us an option for anything more intelligent, though, for instance to automatically enable shadows only when the camera approaches the lights. (Of course, Panda doesn’t provide this kind of automatic light management anyway, but maybe it should.)

Since you will need to hook up to a GraphicsEngine and GraphicsPipe after load time, there will still need to be some kind of dynamic postprocessing to be performed. One answer is we always hook up to the “global” GraphicsEngine and GraphicsPipe. This will be OK for the simple case, but there needs to be some way to override this default behavior for the more sophisticated user.

David

Another, completely different idea: instead of extending Spotlight and DirectionalLight (beyond the addition of boolean shadow flag), we subsume all of the functionality into the ShaderGenerator. It keeps track of all of the lights that have been used thus far for rendering, and it manages the pool of buffers necessary to support them. It keeps these buffers in a LRU queue, so that when a new buffer is needed and no more can be allocated, it automatically steals a buffer from a light that hasn’t been used recently.

This model would completely support loading lights from bam files without any fancy postprocessing, and it would not require passing a GraphicsPipe or GraphicsEngine to any existing light; nor would it even necessarily require changing the inheritance of Spotlight.

David

Ah, I didn’t even think of that idea. That sounds like a good solution.
The reason why I wanted the light itself to handle the buffer was so the user can keep track of it’s status, or to even see if the buffer was set up correctly.
But that’s just a minor detail.

Hmm, where would you keep track of the camera then? Let the Shader Generator create it and reposition it every frame (or reparent it)?

With “hasn’t been used recently”, do you mean that the buffers won’t be automatically destroyed when a light is turned off? (or if the shadows flag is turned off for the light)

Also, how would we pass the graphics engine/pipe to the Shader Generator now? It still wouldn’t be possible for two different engines/pipes to render the same scene, would it?

Right, this is similar to your first proposal. The camera is really just a placeholder for a transform anyway, so it’s not so bad to call shader_camera.set_transform(light.get_net_transform()) every frame. On the other hand, I suppose this would have to be put into a task, which is getting a little complicated again; I’m still not sure whether re-inheriting Spotlight and DirectionalLight isn’t the best thing to do after all.

This is a minor weakness with this design: it’s difficult to get a callback when the light is destroyed. Not impossible, though. If it is important to have this automatic cleanup feature, we could implement it by assigning a callback pointer to get notified in the light’s destructor.

Presently, there is one global ShaderGenerator. I think we should change this design, so that the ShaderGenerator pointer is stored on the GSG. (It’s only the GSG who ever asks for it anyway.) That way, we could have a different ShaderGenerator for each GSG, and thus the GraphicsPipe and GraphicsEngine could be passed to the ShaderGenerator constructor; and furthermore, it would be possible for two different pipes to render the same scene, each with their own set of shadow buffers.

David

Maybe we can create a ShaderGenerator::update function, that gets called every time the GSG wants to render?

What if we just store a pointer to the buffer on the light, not more? It can then still be managed by the SG. In the destructor, we can check if the pointer isn’t NULL, if so, clean it up.

Perhaps, but then the graphics engine has to figure out which GSG’s it’s about to render. (It knows only the set of windows, really. Each window has a pointer to a GSG. That’s not so bad, but a given GSG will appear on multiple windows, so to avoid calling each GSG multiple times, you’ll have to unify them.)

Right, that’s kind of what I meant by having a callback pointer. It would have to be more than a simple pointer, though; the light would have to be capable of storing a list of pointers, if we are serious about supporting multiple graphics pipes rendering the same scene.

David

A shadow generator would be great–no, awesome–addition to Panda. But are you planning to reparent it to the object itself (sounds like it would make sense) or to the light source?

Hmm, so, to recap, we would need to do the following:

  • Make Spotlight and DirectionalLight inherit from Camera. Also, add a boolean variable saying whether it casts shadows and a pmap<GSG*,GraphicsOutput*> or so. In the destructor, we clean up all the buffers in the pmap.
  • The Shader Generator is stored in the GSG, there is no global generator. Each GSG has its own generator. The SG gets a pointer to the GSG passed in the constructor (where it can also access the engine and pipe from)
  • When the Shader Generator generates a shader for a light with the caster flag set, it will set up the depth buffer (to which a pointer is stored in the light itself). Then, it will make the shader to read out the depth buffer values and dim the lighting for pixels which are occluded according to the depth buffer.

Is that correct? Could I implement it like that, or do you have other ideas?

@King_of_Z: Sorry, I don’t entirely understand the question. What reparenting do you mean? With ‘the object itself’, do you mean the object that is lit?

Yes, this sums up the design, I think. Let the coding begin! :slight_smile:

David

pl = PointLight("light") 
pl.setShadowCasterOrSimilarlyNamedMethod(True) 
plnp=render.attachNewNode(pl) 
render.setLight(plnp) 
render.setShaderAuto()

what I mean is to set the shadow to the object itself instead of the light, unless it is a neccesity to set the shader to the light.