paintball texturestage limit?

I’ve heard you can’t have more than 4 texture stages for a NodePath, but what about decal effects like tire traces or paintball? I would use texture projection for this, but it seems I would hit the limit very quick like this.

There are various techniques for this, including painting on an existing texture, but you can also just create a plane at the appropriate location and put a texture on that, and use smart z-sorting and depth offset to make it appear as part of the surface.

For the record, you can use more than 4/8 textures if you use the shader generator, but there are still limits of course, dependent on your GPU’s limit.

The plane approach could work if I had even walls and even then it could be a problem for the corners of the walls.
I’m not sure how messing with the z-buffer would work.

The painting to an existing texture could work, but there are some problems.

  1. After doing some search it doesn’t seem like Panda allows you to modify a texture on the fly, at least what this manual page says: … nvas_Class
    I don’t see Canvas class in the API docs, so it was probably never implemented.
    I don’t know if PNMWriter is meant to be used for this kind of stuff.

  2. For a paintball effect you would want to apply it on all of the textures of your level. And even then it could be too slow…
    I thought you could like “bake” texture stages to one or something.

The Canvas class was an idea by one of our community developers who has since gone on to other projects, and you’re right–it was never completed. But it was just meant to be a convenience tool; there are many other ways to draw directly into a Texture object. PNMWriter is one such way. Other people have done other approaches. They all have benefits and drawbacks. Try searching the forums for more ideas.

There actually is a tool that can bake multiple texture stages together into one, it’s called the MultitexReducer. I’m not sure I can recommend its use right now, though–it’s got a few issues, including a known memory leak. If you use it only occasionally it would probably be OK, but it’s probably not great for a paintball game.

But for paintball, you only need to make splats onto a texture, and splats are easy to do procedurally, just by directly manipulating pixels. You don’t need to get all sophisticated. Have you seen my Tagger game?

You don’t need to apply paint onto all the textures of your level. Just put a transparent texture on top of everything else, and apply paint onto that one texture. Of course, it all depends on the details of how your levels are set up.

Layering planes on front of your surfaces may also be a viable approach. There are plenty of tricks you can play with the depth buffer to make things always render on top. Again, it depends on the details of your level.


I didn’t quite get what he meant in the manual page. Why is PNMWriter slower than what he intended to make? Does it edit the texture pixels directly, or does it create a new texture and replace the old one?

I’m not having too much luck with the forum’s search engine (and embeeded google).

Good and bad to know at the same time. I could think of other cases I would also want something like that.

I dont think a splat like this will be easy to generate procedurally.

The depth buffer is one of the things I’m really not good at. Perhaps a small example on what you mean?

I don’t quite understand how you can directly edit textures by using PNMImage/PNMPainter/PNMBrush.
Please have a look at the below code. It seems I can only get the changes if I do texture.load(pnmimage) every time I paint something.

from panda3d.core import *
import direct.directbase.DirectStart

# create a PNMImage
myimage = PNMImage(256, 256)

# create a PNMPainter and pass your PNMIMage to it
mypainter = PNMPainter(myimage)

# create a PNMBrush and assign it to your PNMPainter
mypen = PNMBrush.makeSpot(color = (1,1,1,1), radius = 12, fuzzy = True)

# draw a line with the PNMBrush on your PNMPainter, args: fromx, fromy, tox, toy
mypainter.drawLine(0,0, 128,128)

# create a Texture from the PNMImage
mytexture = Texture()

# assign texture to a plane
cm = CardMaker('')
card = aspect2d.attachNewNode(cm.generate())

# drawing again won't get updated...
mypainter.drawLine(0,0, 32,128)


That’s right, PNMWriter etc. requires you to re-copy the texture with each change.

If that’s too expensive, you have to find some other approach. Or I guess you can experiment with putting the upload in a background thread, if you’re using Panda3D 1.8.


Oh, it sounds like MultitexReducer would be the best option then. I would want the paint splat to animate a bit which can easily be achieved by just scaling the UVs along the y axis and moving it down along the y axis, before finally flattening it with the rest.
(I thought I could do that “melting” effect with PNMPainter, but it’s too slow)

Seems like I always tend to end up needing features which have issues or are bugged, hehe.

For paintball effect on the wall I just created a plane with texture on it and placed it in front of the wall.

This effect has a little problem, when you shoot in the corner of the wall.

Yes, that can be done if you have mostly flat walls, you just create a plane and use the collision origin and surfaces normals to position the plane on the wall.

However that’s hardly an option when you want a paintball effect on animated Actors (other players).

On a side note, it felt weird that PNMBrush.make_* didn’t use default argument values.