Red channel of the rendered image is swapping with the blue channel

I want to render a scene in a loop. About 90 out of 100 times I get the image I want, but the remaining 10 times the red channel of the image is swapped with the blue channel. How can I prevent this strange behavior? By the way if I get the texture data with “getScreenshot()” instead of “getTexture()” everythings works good but it is much slower. I cannot use this method because my work is time critical.

I am rendering on a headless server. I built panda3d without x11 support with “python3 makepanda/makepanda.py --everything --threads 8 --wheel --no-x11”.

My Code:

    loadPrcFileData("", "win-size 960 540")     
    loadPrcFileData("", "threading-model Cull/Draw")
    loadPrcFileData("", "load-display pandagl")
    loadPrcFileData("", "aux-display pandagl")
    loadPrcFileData("", "audio-library-name null")
    loadPrcFileData("", "window-type none")

    self.base = ShowBase(windowType='offscreen')
    self.base.setBackgroundColor(0. ,0. ,0. ,0.)
    self.altBuffer = self.base.win.makeTextureBuffer("flag", 960, 540, to_ram=True)
    self.altBuffer.setSort(-100)
    self.altBuffer.setClearColorActive(True)
    self.altBuffer.setClearColor((0., 0., 0., 0.))
    self.altBuffer.setClearDepthActive(True)
    self.altBuffer.setClearDepth(1.0)

    self.altRender = NodePath("render_flag")
    self.altRender.reparentTo(self.base.render)

    self.altCam = self.base.makeCamera(self.altBuffer)
    self.altCam.reparentTo(self.altRender)

    self.flag = self.base.loader.loadModel(flag_path)
    self.flag.reparentTo(self.altRender)

    for i in range(self.batch_size):

        ## OTHER STUFF
        
        self.altBuffer.setActive(True)
        self.altBuffer.engine.renderFrame()
        tex = self.altBuffer.getTexture()
        # tex = self.altBuffer.getScreenshot()
        tex_anim = tex.makeCopy()
        self.altBuffer.setActive(False)

        data_anim = tex_anim.getRamImage()
        image_anim = np.frombuffer(data_anim, np.uint8)
        image_anim.shape = (tex_anim.getYSize(), tex_anim.getXSize(), tex_anim.getNumComponents())
        image_anim = np.flipud(image_anim)
        # cv2.imwrite("/home/ubuntu/live/debugImages/rendered" + str(i) + ".png", image_anim)


Perhaps this is the result of using a multithreaded render. Try turning it off.

It is quite odd. Normally it should be reversed, and you can use getRamImage("RGBA") to get it in the order you expect.

Could it be dependent on the specific framebuffer properties you are requesting?

Yes, when I tried to turn it off, it works as expected but it gets slower which exceeds my time limits.
By the way, it would be very helpful if there are tips on how to speed up the render time regardless of the color issue.

When I use getRamImageAs(“RGBA”) it is still same issue, nothing changed.

I didn’t change any framebuffer property. It is the default one.

It is not entirely clear what goals you are pursuing. However, at the moment you are accessing the disk every frame to write a file, which slows down rendering accordingly, if it is possible to save all files to RAM. Then, using another thread, extract them to disk in parallel.

Also, for such simple tasks, there is no need to use the ShowBase. Something like this template is enough:

from panda3d.core import GraphicsEngine, GraphicsPipeSelection, FrameBufferProperties, WindowProperties, GraphicsPipe, GraphicsOutput
from panda3d.core import LColor, NodePath, Texture, Camera, Loader, Filename, PNMImage, PerspectiveLens

engine = GraphicsEngine.get_global_ptr()
pipe = GraphicsPipeSelection.get_global_ptr().make_module_pipe("pandagl")
loader = Loader.get_global_ptr()

fb_prop = FrameBufferProperties()
fb_prop.rgb_color = 1
fb_prop.color_bits = 24
fb_prop.depth_bits = 24

win_prop = WindowProperties()
win_prop.size = (960, 540)

render = NodePath("render")

lens = PerspectiveLens()
lens.set_fov(30)
lens.set_near_far(0.5, 100000)

cam = Camera("camera")
cam.set_lens(lens)
camera = NodePath(cam)
camera.set_pos(0, -30, 5)
camera.set_sx(win_prop.size[0]/win_prop.size[1])
camera.reparent_to(render)

texture = Texture("texture")

buffer = engine.make_output(pipe, name = "buffer", sort = 0, fb_prop = fb_prop, win_prop = win_prop, flags = GraphicsPipe.BFRefuseWindow)
buffer.set_clear_color_active(True)
buffer.set_clear_color(LColor(0, 0, 0, 0))
buffer.set_clear_depth_active(True)
buffer.set_clear_depth(1.0)
buffer.add_render_texture(texture, GraphicsOutput.RTM_copy_ram)

display_region = buffer.make_display_region()
display_region.camera = camera

model = NodePath(loader.load_sync("panda"))
model.reparent_to(render)

for i in range(2):
    engine.render_frame()

    frame = PNMImage()
    texture.store(frame)
    frame.write(Filename(f'rendered{i}.png'))
2 Likes

Perhaps the problem is here, if I understood correctly, this method is NumPy. I think this is not a thread-safe method, that is, it is not atomic. These are just assumptions, it needs to be checked.

When I used the template you provided, the color problem was not resolved, but the time was considerably reduced and I removed “loadPrcFileData(”", “threading-model Cull/Draw”)". So indirectly my problem was solved. Thanks a lot

I am glad that this is the case, but, nevertheless, the problem remained at the engine level. Maybe @rdb has thoughts on how to detect it…

However, I ran this code and did not find a color replacement for the channel.

from panda3d.core import GraphicsEngine, GraphicsPipeSelection, FrameBufferProperties, WindowProperties, GraphicsPipe, GraphicsOutput
from panda3d.core import LColor, NodePath, Texture, Camera, Loader, Filename, PNMImage, PerspectiveLens, loadPrcFileData

loadPrcFileData("", "threading-model Cull/Draw")

engine = GraphicsEngine.get_global_ptr()
pipe = GraphicsPipeSelection.get_global_ptr().make_module_pipe("pandagl")
loader = Loader.get_global_ptr()

fb_prop = FrameBufferProperties()
fb_prop.rgb_color = 1
fb_prop.color_bits = 24
fb_prop.depth_bits = 24

win_prop = WindowProperties()
win_prop.size = (960, 540)

render = NodePath("render")

lens = PerspectiveLens()
lens.set_fov(30)
lens.set_near_far(0.5, 100000)

cam = Camera("camera")
cam.set_lens(lens)
camera = NodePath(cam)
camera.set_pos(0, -30, 5)
camera.set_sx(win_prop.size[0]/win_prop.size[1])
camera.reparent_to(render)

texture = Texture("texture")

buffer = engine.make_output(pipe, name = "buffer", sort = 0, fb_prop = fb_prop, win_prop = win_prop, flags = GraphicsPipe.BFRefuseWindow)
buffer.set_clear_color_active(True)
buffer.set_clear_color(LColor(0, 0, 0, 0))
buffer.set_clear_depth_active(True)
buffer.set_clear_depth(1.0)
buffer.add_render_texture(texture, GraphicsOutput.RTM_copy_ram)

display_region = buffer.make_display_region()
display_region.camera = camera

model = NodePath(loader.load_sync("panda"))
model.reparent_to(render)

for i in range(1000):
    engine.render_frame()

    frame = PNMImage()
    texture.store(frame)
    frame.write(Filename(f'out/rendered{i}.png'))