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 - 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 - 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
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
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