Billboard-Maker

This creates billboards for NodePaths which look the same as the original ones from bigger distances.
I made this when trying to put a forrest on screen - 100 trees were unplayable but using billboards for the more distant ones 200 trees are ok.
Maybe you have some use for this on-the-fly billboardmaker, too.

'''
Created on 12.12.2010

@author: Praios
'''
from panda3d.core import NodePath, Vec4, Vec3, OrthographicLens, Point3, TransparencyAttrib

def texOf(np, size=256):
    altBuffer= makeBufferRelCent(np, size)[0]    
    tex = altBuffer.getTexture()
    def dml(task):
        altBuffer.getEngine().removeWindow(altBuffer)
    base.taskMgr.doMethodLater(1, dml, "removeBuffer")
    return tex

def makePosRelCent(np):
    minv = Point3()
    maxv = Point3()
    np.calcTightBounds(minv, maxv)
    
    
    cent = (minv + maxv) / 2
    pos = Vec3(cent)
    pos.setY(minv.getY() - 1)
    
    rel = (abs(minv.getX()) + abs(maxv.getX()), abs(minv.getZ()) + abs(maxv.getZ()))
    return pos, rel, cent
     
    
def makeBufferRelCent(np, size=256):
    #we get a handle to the default window
    mainWindow = base.win

    #we now get buffer thats going to hold the texture of our new scene   
    altBuffer = mainWindow.makeTextureBuffer("hello", size, size)
    altBuffer.setClearColor(Vec4(0.5, 0.5, 0.5, 0))
    #now we have to setup a new scene graph to make this scene
    altRender = NodePath("new render")

    #this takes care of setting up ther camera properly
    altCam = base.makeCamera(altBuffer)
    altCam.reparentTo(altRender)        


    #get the teapot and rotates it for a simple animation
    teapot = NodePath("dummy")
    np.instanceTo(teapot)
    pos, rel, cent = makePosRelCent(teapot)
    lens = OrthographicLens()
    lens.setFilmSize(*rel)
    altCam.setPos(pos)
    altCam.node().setLens(lens)
    
    teapot.reparentTo(altRender)
    altBuffer.setOneShot(True)
    return altBuffer, rel, cent

def cardOf(np, size=256):
    altBuffer= makeBufferRelCent(np, size)[0]
    card = altBuffer.getTextureCard()
    def dml(task):
        altBuffer.getEngine().removeWindow(altBuffer)
    base.taskMgr.doMethodLater(1, dml, "removeBuffer")
    return card

def billboardOf(np, size=256):
    altBuffer, rel, cent = makeBufferRelCent(np, size)
    card = altBuffer.getTextureCard()        
    card.setSx(rel[0] / 2)
    card.setSz(rel[1] / 2)
    dummy = NodePath("dummy")
    card.reparentTo(dummy)
    card.setPos(cent)
    dummy.setTransparency(TransparencyAttrib.MAlpha)
    dummy.setBillboardAxis()
    def dml(task):
        altBuffer.getEngine().removeWindow(altBuffer)
    base.taskMgr.doMethodLater(1, dml, "removeBuffer")
    return dummy

       

edit: better description what it does…

What about using sprites? I think you can flatten them, am I wrong?

I’m not sure if I understand correctly what you are saying.
If you’re talking about particles - I dont get how to use them for that.
If you’re talking about making cards and ppositioning them as an X - maybe it’s faster but my trees lack the symmetry for that so it wouldn’t look very good.

No, Im talking about sprites, its true that spriteparticlerenderer use the same rendering method, but Im sure you dont need to use particles if you want to use sprites.
Have a look at ‘TexGenAttrib.MPointSprite’ texture coordinates here: panda3d.org/manual/index.php … oordinates

So you would need a vertice rendered in point mode, set the size of the point (ends up like a plane) and texture it like that.

I havent tried that as I didnt need sprites.

I think Panda doesnt have a class to do this for you automatically. I always wondered why.

Billboards are just nodes that rotate to face the camera each frame, you cant just flatten few of them for this reason.

I don’t know how exactly pixel-size and size in world-units are related which i would have to for using GeomPoints-based sprites.

import direct.directbase.DirectStart
from pandac.PandaModules import *

model = loader.loadModel('suzanne')
model.reparentTo(render)
model.setY(5)
model.setTransparency(TransparencyAttrib.MDual)

model.setRenderMode(RenderModeAttrib.MPoint, 50)
model.setTexGen(TextureStage.getDefault(), TexGenAttrib.MPointSprite)

texture = loader.loadTexture('cloud.png')
model.setTexture(texture, 1)
			
run()

Hm, youre right, you will need to set the size of the point manually depending on the distance from the camera, but then all the billboarder trees will have the same size…
This is worth an extra topic. The particles dont have this problem, so there must be a way.

If it was only a single sprite you could just scale it, though.

The manual says you could use nodePath.setRenderModePerspective() (allthoght i dont see much difference)
The problem is more that when billboardOf-ing a nodepath it should keep its size on screen. Nodepath-sizes are specified in world-units and sprites-size in pixels.
How can that be converted?

Posted as Pixel-size of a NodePath in Scripting Issues

Nice find, it works.

For the size, I dont know. You could set the point size according to the bounding box size of the node, maybe?

But how is it related?
If i get a size of eg 20x10 world-units how many pixels corresponds that to?

Looks like there’s three different topics going on this in different forums. I guess I’ll post this here since it’s a “code snippet”. :confused:

Inspired by these posts I was also playing around with this effect last night. I had some trouble getting render-to-texture working from the Teapot-On-Tv sample, probably due to outdated hardware/drivers on my end. I have to use the gl-force-pixfmt setting you see commented at the top of the snippet or else I don’t get the transparency effect. Also, my teapot sprites render upside-down, even though the bufferViewer displays it right-side-up. So I’d just like you guys to run my code and tell me what you see. Pull the camera back a little bit, default camera controls.

v = view texture buffer
p = toggle perspective mode
1-9 = change point thickness (default 8 )

from direct.showbase.ShowBase import ShowBase
from panda3d.core import GeomVertexFormat, GeomVertexData, GeomVertexWriter
from panda3d.core import Geom, GeomPoints, GeomNode, NodePath
from panda3d.core import TextureStage, TexGenAttrib, TransparencyAttrib
from panda3d.core import AmbientLight, DirectionalLight, Vec4
from random import uniform

# try uncommenting these 2 lines if the sprites have opaque gray background
#from pandac.PandaModules import loadPrcFileData 
#loadPrcFileData('', 'gl-force-pixfmt 6') 

app = ShowBase()
app.setBackgroundColor(0.6, 0.65, 1.0)

# render-to-texture stuff
altBuffer=app.win.makeTextureBuffer("spritebuf", 256, 256)
altRender=NodePath("alt render")
altCam=app.makeCamera(altBuffer)
altCam.reparentTo(altRender)
altCam.setPos(0.25, -12, 0)
teapot=loader.loadModel('models/teapot')
teapot.reparentTo(altRender)
teapot.setPos(0, 0, -1)
app.accept("v", app.bufferViewer.toggleEnable)
app.bufferViewer.setPosition("llcorner")
app.bufferViewer.setCardSize(1.0, 0.0)

# lighting
dlight = DirectionalLight('dlight')
alight = AmbientLight('alight')
dlnp = altRender.attachNewNode(dlight) 
alnp = altRender.attachNewNode(alight)
dlight.setColor(Vec4(0.8, 0.8, 0.5, 1))
alight.setColor(Vec4(0.2, 0.2, 0.2, 1))
dlnp.setHpr(0, -60, 0) 
altRender.setLight(dlnp)
altRender.setLight(alnp)

# vertex writer
vdata = GeomVertexData('points', GeomVertexFormat.getV3(), Geom.UHDynamic)
vwriter = GeomVertexWriter(vdata, 'vertex')

# 100 randomly generated vertex coordinates
for i in range(100):
    vwriter.addData3f(uniform(-100,100), uniform(-100,100), uniform(-100,100))

# create geom
points = GeomPoints(Geom.UHDynamic)
points.addNextVertices(100)
points.closePrimitive()
geo = Geom(vdata)
geo.addPrimitive(points)
gnode = GeomNode('points')
gnode.addGeom(geo)
np = render.attachNewNode(gnode)

# point sprite effect
np.setTransparency(TransparencyAttrib.MDual)
np.setTexGen(TextureStage.getDefault(), TexGenAttrib.MPointSprite)
np.setTexture(altBuffer.getTexture())
np.setRenderModePerspective(True)
np.setRenderModeThickness(8)

# additional controls
def toggle_perspective():
    np.setRenderModePerspective(not np.getRenderModePerspective())

app.accept('p', toggle_perspective)

for i in range(1,10):
    app.accept(str(i), np.setRenderModeThickness, [i])

app.run()

You can use buffer.setInverted(True) to force the offscreen buffer to render upside-down and compensate for the upside-down nature of sprites.

David

I don`t understand, is there any solution to scale sprite size to screen size when camera near to particle? Is there any examples of billboard shader in the net?