Placing transparent png images in 3d

Hello, to improve performance, I would like my distant objects to be 2d images. (transparent png).

from direct.showbase.ShowBase import ShowBase
from direct.gui.OnscreenImage import OnscreenImage
from direct.actor.Actor import Actor
from panda3d.core import *
import random
from panda3d.core import loadPrcFileData


class Application(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        self.setFrameRateMeter(True)


        self.panda = Actor("panda", {"walk": "panda-walk"})
        self.panda.reparentTo(render)
        self.panda.loop("walk")
        self.cam.setPos(0, -30, 5)

        files = ["myimage.png"]

        for i in range(30):
            OnscreenImage(random.sample(files, 1)[0], 
                          scale = Vec3(0.1, 0.1, 0.1), 
                          pos = Vec3(random.uniform(-1, 1), 0, random.uniform(-1, 1)),
                          hpr = Vec3(0, 0, random.uniform(0, 360)))
        self.run()

config_data = """
sync-video false
"""
loadPrcFileData("", config_data)

app = Application()

I have 2 problems with my code:
the images remain fixed on the screen, Iā€™d like them to be placed in 3d, like my panda object in 3d. Iā€™d like them to move away when I move backwards and get bigger when I move closer.

the png is displayed in a black rectangle, can I make it transparent?

thank you in advance for your help

In short, the answer two both questions is simply that an OnscreenImageā€“like (I think) all classes that render something on-screenā€“is a NodePath, and can be treated like any other NodePath.

To answer in specific:

This is because, by default, OnscreenImage is a child of the 2D scene-graph (aspect2d, to be specific), I believe.

I see two ways of approaching what you want, broadly speaking:

First and simplest, the constructor for OnscreenImage takes a ā€œparentā€ parameter; just pass into this parameter whatever node in the 3D scene you want the image to be attached to.

Second, and mentioned mainly for illustrative purposes, is that you could simply reparent the OnscreenImage to the desired node in the 3D sceneā€“as I said, itā€™s a NodePath.

You should be able (as again with pretty much any NodePath) to either call ā€œsetTransparentā€ on your OnscreenImage, or reparent it below a node that has been set to use transparency.

The simplest thing you can do is to use lod nodes. By setting the detail node to image plane as the last level.

Presuming that the original poster wants level-of-detail, that is.

(If they do, then I do agree that LODNode is likely the simplest solution.)

thank you for your answers.

I thought that a simple image2d would be more efficient than loading the 3d model.

Iā€™d like to display a huge army (1000000 soldiers) moving around. I thought that the 1st closest models would be in 3d and would have physics.
the far soldiers would be in 2d with no physics

Iā€™ve already tried lodnode but I donā€™t think itā€™s powerful enough.

from panda3d.core import LODNode, NodePath
from direct.showbase.ShowBase import ShowBase

class MyApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        my_model = self.loader.load_model("panda")
        my_model.reparentTo(self.render)

        lod_node = LODNode("my LOD node")
        lod_node_np = self.render.attach_new_node(lod_node)

        lod0_distance = 0.0
        lod_node.add_switch(lod0_distance, 0)

        lod1_distance = 50.0 
        lod_node.add_switch(lod1_distance, 1)

        lod2_distance = 100.0
        lod_node.add_switch(lod2_distance, 2)

        my_model.reparent_to(lod_node_np)

        def update_lod(task):
            screen_pos = my_model.get_screen_pos(self.camera)
            size = my_model.get_sx()
            distance = screen_pos.length() * size
            lod_node.set_switch(lod_node_np, distance)

            return task.cont

        self.taskMgr.add(update_lod, "update_lod")

app = MyApp()
app.run()

is it possible to display 1 million panda object ?
Iā€™ve reduced the size of the distant elements, if you have a code to reduce the geometry of an object Iā€™m interested.

I was thinking, displaying 1 million pandas in 2d shouldnā€™t require a lot of resources compared to 3d objects, should it?

Oh wow; having that many objects is going to call for a rather more advanced approach than this, with LODNode or not! 0_0

In short, having so many on screen at once, and having them move independently, would I think call for some shader-work; I donā€™t know the specifics (not having done such myself), but someone else here might know how itā€™s done.

Of course, there is the simpleā€“but less dynamicā€“approach: have a few 2D objects each showing a large group of soldiers.

If those pandas are independent objects in the scene-graph (or other structure as appropriate), then I think that it would be pretty costly in any engine. o_o

year, I would like to use this approach:
ā€œOf course, there is the simpleā€“but less dynamicā€“approach: have a few 2D objects each showing a large group of soldiers.ā€

to do this I need to be able to position my 2d objects in 3d space.

Yup, and what I described above to that end should do the trick!

That is, create your OnscreenImage (or make a quad via CardMaker and apply your image to it), then parent that into the 3D scene.

I tested it but with 10000 images it becomes very slow, I didnā€™t think it would be so slow.
is there any other solution for displaying lots of objects in 2d efficiently?

Again, the idea is to not create 10000 images, but to create, say, 100 that show the same number of characters.

Much as I said above, there are shader-based techniques I believeā€“but Iā€™m not familiar with the specifics there myself.

Can I use CardMaker to make them transparent?
I thought that CardMaker would have to be rectangular

Indeed, you should be able to make any NodePath transparent, whether made by CardMaker or not!

In 2D or 3D, it does not matter, since 2D costs for processing transparency arise. But in any case, you may like this approach.

I made a code that works quite well

from direct.showbase.ShowBase import ShowBase
from panda3d.core import CardMaker, NodePath, Texture, TextureStage, Vec4, GeomVertexFormat, GeomVertexData, Geom, GeomNode, GeomTriangles, GeomVertexWriter
from panda3d.core import GeomVertexArrayFormat, GeomVertexFormat
from panda3d.core import loadPrcFileData

class MyApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        self.setFrameRateMeter(True)
        texture = self.loader.load_texture("texture.png")
        self.render.set_transparency(True)

        face_maker = CardMaker("face")
        face_maker.set_frame(-1, 1, -1, 1)
        group_np = NodePath("group")

        num_cards = 10000
        cards_per_row = 100
        spacing = 1.5
        row_spacing = 1.5
        for i in range(num_cards):
            x = (i % cards_per_row) * spacing
            y = (i // cards_per_row) * row_spacing
            face_np = NodePath(face_maker.generate())
            face_np.set_color(Vec4(1, 1, 1, 1))
            face_np.set_pos(x, y, 0)
            face_np.reparent_to(group_np)

            face_np.set_texture(texture)

        group_np.set_pos(0, 0, 0)
        group_np.reparent_to(self.render)

config_data = """
sync-video false
"""
loadPrcFileData("", config_data)
app = MyApp()
app.run()

if you have any ideas on how to make it more effective, donā€™t hesitate

To improve performance Iā€™m trying to create a single NodePath containing all the maps using Geom classes. This considerably reduces the number of nodes in the scene and improves performance.

from direct.showbase.ShowBase import ShowBase
from panda3d.core import CardMaker, NodePath, Texture, TextureStage, Vec4

class MyApp(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)
        self.setFrameRateMeter(True)
        texture = self.loader.load_texture("texture.png")
        self.render.set_transparency(True)

        face_maker = CardMaker("face")
        face_maker.set_frame(-1, 1, -1, 1)
        group_np = NodePath("group")

        num_cards = 10000
        cards_per_row = 100
        spacing = 1.5
        row_spacing = 1.5

        vertex_data_format = GeomVertexFormat.get_v3c4()
        vertex_data = GeomVertexData("vertices", vertex_data_format, Geom.UHStatic)
        vertex_writer = GeomVertexWriter(vertex_data, "vertex")
        color_writer = GeomVertexWriter(vertex_data, "color")

        for i in range(num_cards):
            x = (i % cards_per_row) * spacing
            y = (i // cards_per_row) * row_spacing

            vertex_writer.add_data3(x, y, 0)
            color_writer.add_data4(1, 1, 1, 1)

        prim = GeomTriangles(Geom.UHStatic)
        for i in range(num_cards):
            row_length = cards_per_row
            if i // cards_per_row == num_cards // cards_per_row:
                row_length = num_cards % cards_per_row

            if i % cards_per_row != row_length - 1 and i // cards_per_row != num_cards // cards_per_row:
                v0 = i
                v1 = i + 1
                v2 = i + cards_per_row
                v3 = i + cards_per_row + 1

                prim.add_vertices(v0, v1, v2)
                prim.add_vertices(v1, v3, v2)

        geom = Geom(vertex_data)
        geom.add_primitive(prim)
        node = GeomNode("geom_node")
        node.add_geom(geom)

        face_np = NodePath(node)
        face_np.set_texture(texture)
        face_np.reparent_to(group_np)

        group_np.set_pos(0, 0, 0)
        group_np.reparent_to(self.render)

app = MyApp()
app.run()

but Iā€™m getting errors using ā€˜GeomVertexFormatā€™.

You can similarly use the flatten_strong method. And also you can try RigidBodyCombiner.

from panda3d.core import NodePath
from direct.showbase.ShowBase import ShowBase

class MyApp(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)

        all_node = NodePath('all')

        for i in range(0, 10):
            for j in range(0, 10):
                model = loader.load_model("panda")
                model.clear_model_nodes()
                model.set_pos(i*10, j*10, 0)
                model.reparent_to(all_node)

        all_node.flatten_strong()
        all_node.reparent_to(render)

        print(all_node.ls())

app = MyApp()
app.run()
1 Like

this function works miracles !
thank you very much

quick question: once flatten_strong has been applied, is it still possible to delete the 1st object, for example?

I have the impression that theyā€™ve all been grouped together in 1 node, so itā€™s impossible to change the texture or delete the 1st object.

To the best of my knowledge, no, itā€™s not possible. (Or at least, not easily so.) As you say, the objects are I believe combined into a single nodeā€“indeed, a single model.

You could perhaps try the ā€œRigidBodyCombinerā€, which might allow this facility, or implement your cards instead via ā€œMeshDrawerā€.

Hi huitre39,

Apologies for bumping this post a couple of months laterā€¦ :sweat_smile:
But maybe this will be of use to you, or someone searching up in the future!

If youā€™re wanting to show lots of identical images in a line like Iā€™m understanding, an option that might be worth looking into would be something like combining each line into a single image that uses texture wrapping.
https://docs.panda3d.org/1.10/python/programming/texturing/texture-wrap-modes

As a quick proof of concept, I modified your earlier code slightly (from Post 14).
I made each row a single image (that was cards_per_row wide) that had U part of the texture coordinates from 0 to cards_per_row and made the texture wrap in the U dimension.


Note the FPS difference.

Now obviously there are caveats with this approach, like they must be identical, and things like your spacing parameter are probably going to be static. (To get our different approaches to be identical I had to remove the variable spacing.)

Hope thatā€™s useful somewhat :slight_smile:
And best of luck with your project!

Cheers,
H3LLB0Y.

2 Likes