[SOLVED] Render only one frame and other questions


#1

Hello,

I would like to use Panda3D for my personal project, but after reading the documentation and some example sourcecodes, I still have a few questions:

  • How can I render just one frame and save it in a file?
    In fact I would need to render 2 different images: a single object, and a scene of multiple objects including the previous single object, but just one frame for each and they both need to be saved as image files.

  • The application will be coded in Python, and needs to be very scalable (be used by thousand of users). Would Panda3D fit the bill here? (about my program in Python, it’s almost a constant complexity so no problem here, and 3D models will be low-poly and about 5 to 20 per scene).

  • I need to calculate the perspective projection of every object to the camera. Is it possible to directly access the vertexes and faces (position, parameters, etc…)?

  • Can I recolor my 3D objects? I need to set a simple color for the whole object, but a different color per object. Is it possible?

Please also note that I’m quite a newbie in the field of graphical and game development, but I know some bits of 3D modelling and 3D theory, as well as computer imaging theory.

Thank you for reading me.

PS: My main alternative currently is to use Soya3D or PySoy, but they don’t seem to be very actively developped nor optimized, so although they would both have a smaller memory footprints, I don’t know if they would really perform faster than Panda3D since they’re not very optimized…


#2

Take a look at ShowBase.screenshot() and ShowBase.graphicsEngine.renderFrame(). (A global instance of ShowBase is usually stored in “base”.)

Nobody can really answer such a question, but if it makes you sleep better… Panda has been used for MMOG by Disney for Toontown and Pirates of the Carribean. Still this has little to do with the engine, but rather with your understanding of networking and the server you’ll use.

I don’t quite get the question. Could you elaborate on this? Maybe this reference page also serves you some help: panda3d.org/reference/devel/ … e.Geom.php
It’s about the geom class, which you can get easily out of any loaded model.

No problem. Look through the api reference page for NodePath and search for “color”.


#3

Thank’s a lot, this greatly helps.

However, my application is really only a render-one-frame-and-go, rendering a frame and saving it to an image is really the only thing it does, so there’s no worry about networking and stuffs.

When I asked about performances, I was more specifically wondering if using Panda3D wouldn’t be overkill in my situation, where I just render 1 frame for every access to the script, but there can be maaaaanyyyy concurrent access at once. In the end, I think that the optimizations and the modularity makes Panda3D a perfect fit, as long as I load only the very specific modules I need.

Anyway, I’m still stuck at trying to do my rendering.

Your solution work (thank’s a lot!):

import direct.directbase.DirectStart

base.graphicsEngine.renderFrame()
base.screenshot(namePrefix='screenshot', defaultFilename=1, source=None, imageComment="")

But it still opens a window whenever I launch the script.

So I’ve stumbled over a few other threads on the subject, and this is what I pieced together:

from panda3d.core import loadPrcFileData
loadPrcFileData("", "window-type none" ) # Make sure we don't need a graphics engine (Will also prevent X errors / Display errors when starting on linux without X server)
loadPrcFileData("", "audio-library-name null" ) # Prevent ALSA errors

import direct.directbase.DirectStart

base.graphicsEngine.renderFrame()
base.screenshot(namePrefix='screenshot', defaultFilename=1, source=None, imageComment="")

NB: I think this tip should be in the manual for those who want to make a server-side application. This is a common application.

Anyway now the screenshot doesn’t work, as I expected, because it cannot find a window anymore!

So I’m stuck here, I’m trying to re-read the manual (I’ve spent the whole day reading it, it’s quite nice but long!) and the reference API, but I’m kinda stuck… Any help is welcome!


PNMImage to PIL?
#4

I think it should be possible to render to a buffer and make a screenshot from that. Still, I can’t figure out the code right now.


#5

Still stuck.

Here are a few other threads on the subject:
[how to render frame by frame manually)
[Panda3d server without gui)
[[SOLVED] run panda without DirectStart)
[Panda Without Render Window)
panda3d.org/manual/index.php/SPE
ru.is/kennarar/hannes/useful … ine.1.html
[makeGsg, creating offscreen buffers without a window)
[Why these codes couldn't create a hiden window)

binrand.com/post/376717-colo … mpt-3.html


#6

FOUND IT!

I used window-type none, when I should have used window-type offscreen which then create an offscreen buffer!

Here is my test code:

from panda3d.core import loadPrcFileData
loadPrcFileData("", "window-type offscreen" ) # Spawn an offscreen buffer
loadPrcFileData("", "audio-library-name null" ) # Prevent ALSA errors
from direct.showbase.ShowBase import ShowBase
#import direct.directbase.DirectStart


base = ShowBase()

base.graphicsEngine.renderFrame()
base.screenshot(namePrefix='screenshot', defaultFilename=1, source=None, imageComment="")

Just one more question: if there is no graphic card available, will Panda3D fallback to the CPU? This is important to me, because I will use my application on servers where there may not be any graphic card available, or at least access will be forbidden.


#7

For me it looks like this functionality is somewhat broken at the moment.

I tried with

win = base.openWindow(type="offscreen")

but it throws an error.
Then I tried to do by hand what ShowBase does:

from panda3d.core import loadPrcFileData
loadPrcFileData("", "window-type none" )
loadPrcFileData("", "audio-library-name null" )

from panda3d.core import GraphicsPipe, WindowProperties, FrameBufferProperties
import direct.directbase.DirectStart

flags = GraphicsPipe.BFFbPropsOptional | GraphicsPipe.BFRefuseWindow
props = WindowProperties.getDefault()
fbprops = FrameBufferProperties.getDefault()
base.makeDefaultPipe()
buff = base.graphicsEngine.makeOutput(pipe=base.pipe, name="rendering buffer", sort=0, fb_prop=fbprops, win_prop=props, flags=flags)
assert buff is not None # AssertionError

but still it seems Panda is unable to open an offscreen buffer only.


#8

Thank’s Nemesis. Indeed I also tried to it that way and it doesn’t work for the same reasons as you, the only way to do it is to define window-type offscreen instead of window-type none.

In fact when window-type is none, it seems that the engine doesn’t even bother to load a Pipe, thus base.makeDefaultPipe() just returns None, hence why the rest of the code can’t work.

So we would also need to manually create a Pipe, but I don’t know at all how to do that…

Anyway for now, it seems that the window-type offscreen trick works, so I’ll continue with that. We’ll see if it produces errors later on.


To answer my own question about CPU only rendering, it seems that tinydisplay does just that. It was renamed p3tinydisplay in Panda3D v1.8.0, see:
[tinydisplay missing in 1.8.0)

So I just added at the top of my script:

loadPrcFileData("", "load-display p3tinydisplay"); # to force CPU only (or at least make it available)

Now I guess the script should work even on servers where there’s no GPU.

Now let’s get to work! Thank’s Nemesis and to the Panda3D developpers, this is a really great open source engine!


#9

To summary for people who may have similar needs, here is how you can use Panda3D as a render engine for one frame on CPU, server-side without any visible window and which will save the rendering result as an image file:

from panda3d.core import loadPrcFileData 
loadPrcFileData("",
"""
   load-display p3tinydisplay # to force CPU only rendering (to make it available as an option if everything else fail, use aux-display p3tinydisplay)
   window-type offscreen # Spawn an offscreen buffer (use window-type none if you don't need any rendering)
   audio-library-name null # Prevent ALSA errors
   show-frame-rate-meter 0
   sync-video 0
""")
from direct.showbase.ShowBase import ShowBase 


base = ShowBase() 

base.graphicsEngine.renderFrame() 
base.screenshot(namePrefix='screenshot', defaultFilename=1, source=None, imageComment="")

Thank’s to H3LLB0Y for the multi config call, this avoids calling loadPrcFileData() multiple times.

I think this information should be added to the manual, a chapter about how to use Panda3D as a server-side only renderer.


#10

You may use the aux-display config variable to specify additional renderers to fall back onto if the load-display one doesn’t work.


#11

Thank’s rdb, but I’ve already specified it as a comment in my code above :slight_smile: For now I prefer to force p3tinydisplay to be sure that everything I want to do is rendered correctly using this API, and later I will use aux-display.


Follow-up on my question: I would now like to fetch the rendered image directly to a Python variable (a matrix containing the intensity of every pixel).

I can already do that by saving the rendered image on disk and then load it using Python, but I would prefer to economize that additional cost on drive by fetching the rendered image directly from RAM.

I’ve found the method extractTextureData(), I’ll try to use that, but I’m not sure it will work out as expected, so if anyone has got a better idea on the subject…


#12

Thank’s to ThomasEgi on IRC, here is how to move the rendered frame image from GPU to RAM, and then get it in a PNMImage object

from panda3d.core import PNMImage, PNMImageHeader
from direct.showbase.ShowBase import ShowBase

base = ShowBase()

# Rendering the frame
base.graphicsEngine.renderFrame()

# Creating a PNMImage (this is NOT a file format, it's a class provided by Panda3D for accessing and doing operations on image)
screenshot = PNMImage()
# Set the display region
dr = base.camNode.getDisplayRegion(0)
# Store the image from GPU to the RAM variable screenshot
dr.getScreenshot(screenshot)

# Example of operations: making an histogram
hist = PNMImage().Histogram() # Create an histogram object
screenshot.makeHistogram(hist) # create the histogram and store it in hist
print(hist.getNumPixels()) # print the number of unique pixels colors
print(hist.getCount(PNMImageHeader.PixelSpec(255,255,255, 255))) # print the count of all white opaque pixels

# To access a single pixel
# intensity = screenshot.getPixel(x, y)

For more infos:
panda3d.org/reference/devel/ … MImage.php

Also, be careful with Google, the manual pages referenced by the search engine are outdated, and for example you won’t find the makeHistogram() method there.

Note: this method is only useful if one frame is rendered, but if it is iterated over many frames, this will be slow. You should then better use a direct GPU operation, for example by using a shader.


#13

Update: another way to get a screenshot of a rendered image, but directly in-memory instead of writing it to a file. You need to use PNMImage:

    def renderToPNM(self):
        ### RENDER IMAGE
        # Render the frame
        self.base.graphicsEngine.renderFrame()

        ### FETCHING THE RENDERED IMAGE
        # Prepare the variable that will store the frame image (in a PNMImage class, this is NOT a file format, but a convenient image manipulation class offered by Panda3D)
        image = PNMImage()
        # Set display region to the default
        dr = base.camNode.getDisplayRegion(0)
        # Store the rendered frame into the variable screenshot
        dr.getScreenshot(image)

        return image