Offscreen Rendering Problem

Hi Everyone,



in my new game I want to render some geometries offscreen to a BufferedImage so I can later use them as icons in the GUI - but so far I was not successful. Maybe someone of you can help me? I allways get a completely black image using this class:



[java]

public class OffscreenRenderer implements SceneProcessor {



private final int width;

private final int height;



private RenderManager renderManager;



private FrameBuffer offBuffer;

private ViewPort offView;

private Camera offCamera;

private Node offNode = new Node(“offscreen”);



private final ByteBuffer cpuBuf;

private final BufferedImage rawImage, image;



private boolean initialized = false;



public OffscreenRenderer(RenderManager renderManager, int width, int height) {

this.width = width;

this.height = height;

this.renderManager = renderManager;



cpuBuf = BufferUtils.createByteBuffer(width * height * 4);

rawImage = new BufferedImage(width, height,

BufferedImage.TYPE_4BYTE_ABGR);



image = new BufferedImage(width, height,

BufferedImage.TYPE_3BYTE_BGR);



setupOffscreenView();

}



private void setupOffscreenView(){

offCamera = new Camera(width, height);



// create a pre-view. a view that is rendered before the main view

offView = renderManager.createPostView(“Offscreen View”, offCamera);

offView.setBackgroundColor(ColorRGBA.White);

offView.setEnabled(true);



// this will let us know when the scene has been rendered to the

// frame buffer

offView.addProcessor(this);



// create offscreen framebuffer

offBuffer = new FrameBuffer(width, height, 0);



//setup framebuffer’s cam

//offCamera.setFrustumPerspective(45f, 1f, 1f, 1000f);

offCamera.setFrustumPerspective(45f, width / (float) height, 0.05f, 1000f);

offCamera.setLocation(new Vector3f(0f, 0f, -5f));

offCamera.setRotation(Quaternion.DIRECTION_Z);

//offCamera.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);



//setup framebuffer to use renderbuffer

// this is faster for gpu → cpu copies

offBuffer.setDepthBuffer(Format.Depth);

offBuffer.setColorBuffer(Format.RGBA8);

// offBuffer.setColorTexture(offTex);



//set viewport to render to offscreen framebuffer

offView.setOutputFrameBuffer(offBuffer);



offView.attachScene(offNode);

}



public void attach(Spatial spatial) {

this.offNode.attachChild(spatial);

}



public void clearSpatials() {

this.offNode.detachAllChildren();

}



public BufferedImage getImage() {

return this.image;

}



@Override

public void initialize(RenderManager rm, ViewPort vp) {

this.initialized = true;

}



@Override

public void reshape(ViewPort vp, int i, int i1) {

}



@Override

public boolean isInitialized() {

return initialized;

}



@Override

public void preFrame(float f) {

offNode.updateLogicalState(f);

offNode.updateGeometricState();

}



@Override

public void postQueue(RenderQueue rq) {

}



@Override

public void postFrame(FrameBuffer fb) {

updateImageContents(fb);

}



@Override

public void cleanup() {

}



public void updateImageContents(FrameBuffer fb){

cpuBuf.clear();

renderManager.getRenderer().readFrameBuffer(offBuffer, cpuBuf);

Screenshots.convertScreenShot(cpuBuf, rawImage);



synchronized (image) {

image.getGraphics().drawImage(rawImage, 0, 0, null);

}



}



}

[/java]



which I use like this:



[java]

offscreenRenderer = new OffscreenRenderer(application.getRenderManager(), 100, 100);

Geometry geometry = new Geometry(“cube”, new Box(1.0f, 1.0f, 1.0f));

geometry.setLocalTranslation(0, 0, 0);



Material material = new Material(assetManager, “Common/MatDefs/Misc/Unshaded.j3md”);

material.setColor(“Color”, ColorRGBA.Blue);



geometry.setMaterial(material);

offscreenRenderer.attach(geometry);

[/java]



and later when everything is running:



[java]

offscreenRenderer.getImage();

[/java]



But as already stated this image is allways empty (completely black). Hopefully someone has a solution for this :slight_smile: - thanks in advance!



entrusC

PS: http://moebius.darkblue.de ← the game I’m working on

I think you need to dispose the Graphics2D object after you’re done with the image, e.g.

[java]Graphics2D g2d = image.createGraphics();

g2d.drawImage( … );

g2d.dispose();[/java]

PS: http://moebius.darkblue.de <- the game I'm working on

wow. Another minecraft of life :). Great work ;).

have you tried the camera code inside a simple application to verify your camera settings and the positioning of the cube is correct? I would guess the cam is not looking at the cube.

Momoko_Fan said:
I think you need to dispose the Graphics2D object after you're done with the image, e.g.
[java]Graphics2D g2d = image.createGraphics();
g2d.drawImage( .... );
g2d.dispose();[/java]

I will try that later when I'm home ... thanks for the hint :)

glaucomardano said:
wow. Another minecraft of life :). Great work ;).

Thanks :)

ghoust said:
have you tried the camera code inside a simple application to verify your camera settings and the positioning of the cube is correct? I would guess the cam is not looking at the cube.

I'm quite sure the camera settings should be correct - but I'll try this as well :)

Nothing worked :frowning: - camera is set up correctly and g2d.dispose() did not resolve the issue …



I put everything in a simple test app - you can download it here:



http://files.darkblue.de/BasicGame.zip

Anyone has another idea why this is not working? Please guys - some of you wrote this engine - I don’t see why this simple example should not work :frowning:

I guess you simply miss a repaint() call to awt, note that repaint() can be called from any thread and does not actually paint the image but only sets it to be painted in the next awt update loop call.

entrusc said:
Please guys - some of you wrote this engine - I don't see why this simple example should not work :(


It's not a jME issue, it's a Java2D issue xD.
normen said:
I guess you simply miss a repaint() call to awt, note that repaint() can be called from any thread and does not actually paint the image but only sets it to be painted in the next awt update loop call.


Please download the Project and see for yourselves - it is not working - the file that is created (I'm not displaying it on screen) is completely black. When I switch to the main rendering viewport (in OffscreenRenderer) everything works fine - so I don't think it's an awt problem at all ;) Just that the preview viewport is not rendered ...

I can give it a try tomorrow, if nobody finds an answere inbetween.

My dear friend, rendering previews works just splendidly, you can verify that in TestRenderToMemory, TestRenderToTexture, the whole SDK and at least 10 game projects. Maybe you forgot to tell us you don’t actually start the application? The previews won’t be rendered if the main context is not being rendered somewhere.

normen said:
My dear friend, rendering previews works just splendidly, you can verify that in TestRenderToMemory, TestRenderToTexture, the whole SDK and at least 10 game projects. Maybe you forgot to tell us you don't actually start the application? The previews won't be rendered if the main context is not being rendered somewhere.


Ok - then I made an error somewhere. It's good to know that it should work and that there is no engine related problem here (because I found an old offscreen rendering thread somewhere around here with no solution). I will look at the examples you named when I get home :)

My dear friend normen, after studying the examples carefully I found that this



[java]offView.setClearEnabled(true);[/java]



was missing in my class - but I didn’t even find what it is used for in the JavaDocs



http://hub.jmonkeyengine.org/javadoc/com/jme3/renderer/ViewPort.html



but I guess this tells the renderer to clear the framebuffer each time before rendering. So for all others that search for a way to render offscreen to a Bufferedimage here is the complete working class:



[java]

package mygame;



import com.jme3.math.ColorRGBA;

import com.jme3.math.Quaternion;

import com.jme3.math.Vector3f;

import com.jme3.post.SceneProcessor;

import com.jme3.renderer.Camera;

import com.jme3.renderer.RenderManager;

import com.jme3.renderer.ViewPort;

import com.jme3.renderer.queue.RenderQueue;

import com.jme3.scene.Node;

import com.jme3.scene.Spatial;

import com.jme3.texture.FrameBuffer;

import com.jme3.texture.Image.Format;

import com.jme3.util.BufferUtils;

import com.jme3.util.Screenshots;

import java.awt.Graphics2D;

import java.awt.image.BufferedImage;

import java.io.File;

import java.io.IOException;

import java.nio.ByteBuffer;

import java.util.logging.Level;

import java.util.logging.Logger;

import javax.imageio.ImageIO;



/**

  • Offscreen render helper class

    */

    public class OffscreenRenderer implements SceneProcessor {



    private final int width;

    private final int height;



    private RenderManager renderManager;



    private FrameBuffer offBuffer;

    private ViewPort offView;

    private Camera offCamera;

    private Node offNode = new Node(“offscreen”);



    private boolean hasImage = false;



    private final ByteBuffer cpuBuf;

    private final BufferedImage rawImage, image;



    private boolean initialized = false;



    public OffscreenRenderer(RenderManager renderManager, int width, int height) {

    this.width = width;

    this.height = height;

    this.renderManager = renderManager;



    cpuBuf = BufferUtils.createByteBuffer(width * height * 4);

    rawImage = new BufferedImage(width, height,

    BufferedImage.TYPE_4BYTE_ABGR);



    image = new BufferedImage(width, height,

    BufferedImage.TYPE_3BYTE_BGR);



    setupOffscreenView();

    }



    private void setupOffscreenView(){

    offCamera = new Camera(width, height);



    // create a pre-view. a view that is rendered before the main view

    offView = renderManager.createPreView(“Offscreen View”, offCamera);

    offView.setClearEnabled(true);

    offView.setBackgroundColor(ColorRGBA.White);

    offView.setEnabled(true);



    // this will let us know when the scene has been rendered to the

    // frame buffer

    offView.addProcessor(this);



    // create offscreen framebuffer

    offBuffer = new FrameBuffer(width, height, 0);



    //setup framebuffer’s cam

    //offCamera.setFrustumPerspective(45f, 1f, 1f, 1000f);

    offCamera.setFrustumPerspective(45f, width / (float) height, 0.05f, 1000f);

    offCamera.setLocation(new Vector3f(0f, 0f, -5f));

    offCamera.setRotation(Quaternion.DIRECTION_Z);

    //offCamera.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);

    // this.offNode.setCullHint(Spatial.CullHint.Never);



    //setup framebuffer to use renderbuffer

    // this is faster for gpu -> cpu copies

    offBuffer.setDepthBuffer(Format.Depth);

    offBuffer.setColorBuffer(Format.RGBA8);

    // offBuffer.setColorTexture(offTex);



    //set viewport to render to offscreen framebuffer

    offView.setOutputFrameBuffer(offBuffer);



    offNode.setCullHint(Spatial.CullHint.Never);

    offView.attachScene(offNode);

    }



    public void attach(Spatial spatial) {

    this.offNode.attachChild(spatial);

    }



    public void clearSpatials() {

    this.offNode.detachAllChildren();

    }



    public BufferedImage getImage() {

    synchronized (image) {

    BufferedImage newImage = new BufferedImage(width, height,

    BufferedImage.TYPE_3BYTE_BGR);



    Graphics2D g2d = (Graphics2D) newImage.getGraphics();

    g2d.drawImage(image, 0, 0, null);

    g2d.dispose();



    return newImage;

    }

    }



    @Override

    public void initialize(RenderManager rm, ViewPort vp) {

    this.initialized = true;

    }



    @Override

    public void reshape(ViewPort vp, int i, int i1) {

    }



    @Override

    public boolean isInitialized() {

    return initialized;

    }



    @Override

    public void preFrame(float f) {

    offNode.updateLogicalState(f);

    offNode.updateGeometricState();

    }



    @Override

    public void postQueue(RenderQueue rq) {

    }



    @Override

    public void postFrame(FrameBuffer fb) {

    updateImageContents(fb);

    }



    @Override

    public void cleanup() {

    }



    public void updateImageContents(FrameBuffer fb){

    cpuBuf.clear();

    renderManager.getRenderer().readFrameBuffer(offBuffer, cpuBuf);

    Screenshots.convertScreenShot(cpuBuf, rawImage);



    synchronized (image) {

    Graphics2D g2d = image.createGraphics();

    g2d.drawImage(rawImage, 0, 0, null);

    g2d.dispose();



    hasImage = true;

    }



    }



    boolean hasImage() {

    return hasImage;

    }



    }

    [/java]

@all, please use the normal TestRenderToMemory and TestAwtPanels, they work fine and do exactly this.

yes - not that easily reusable but yes it does basically the same :). By the way I noticed that setClearEnabled() is marked deprecated in newer versions of JME - can I replace it with setClearFlags()?

You will have to, theres a reason I suggest using the premade classes and examples :wink:

I see - and which one is the normal “clear” flag? Color, depth or stencil?

Uh… “normal”… ^^ Color in most cases.

ok - so thanks again for your help - I’m happy now this issue is resolved as well - now I can continue coding soon :slight_smile: