How to render to an off-screen image?

Hi,

I upgraded the version of jogl from three years ago and discovered that my code no longer works. For example, where did GLCanvas.createOffscreenDrawable go?

More importantly, can someone point out a howto on rendering to an off-screen buffer for the purposes of creating an image?

Many thank in advance!

Pahidla

Here is code I wrote to create a FBO and have the ability to save the buffered image to a file. If you find problems please let me know.

Hi,

Thanks a lot for your help. I really appreciate it.

I think I tried to use your code in too naive a way (shown below). I have a program that’s not a game but simply displays a mesh in a GLCanvas and responds to mouse clicks and drags. I’m looking to add code that will take a snapshot of the scene to specified resolution. So here’s my code:

public BufferedImage takeAPicture(int width, int height) {
if (myFBO == null) {
myFBO = new FrameBufferObject(width, height);
}

GL gl = myCanvas3D.getGL();

myFBO.init(myCanvas3D.getGL());
myFBO.activate(gl);
myFBO.readFrame(gl);

BufferedImage bi = myFBO.getBufferedImage();
myFBO.deactivate(gl);
 
return bi;

}

I think I have to tie this code into a GLEventListener, but I’m not doing that. Can you suggest a fix.

Once again, I very much appreciate your help.

Pahidla

with the code I gave you and with FBO’s in general you have to always ‘activate’ them first, draw the scene, get the buffer data, and then ‘deactivate’ them. Looking at the code you have for the ‘takeAPicture’ you don’t have the scene being drawn anywhere…

Basically I would have a ‘takeAPicture()’ method be simply a method that would enable a boolean flag that you would check in your display method…for example I would do this.


public void takeAPicture() {
   takepic = true;
}

public void init( ... ) {
   myFBO = init(gl);
}

public void display( ... ) {
  if (takepic) {
      myFBO.activate(gl);
  }

  // Draw the scene
  
  // Draw more of the scene

  if (takepic) {
      myFBO.readFrame(gl);
      BufferedImage bi = myFBO.getBufferedImage();

      // Use the BufferedImage bi

      myFBO.deactivate(gl);
      takepic = false;  // clear screencapture flag
  }
}

Does that make sense? … I would also use my call saveBufferedImage(“name”); to see if it was saving the image…note change the name each time you call saveBufferedImage() because if that filename already exists it won’t create a replacement one, ie it won’t overwrite it.

Thanks - it makes sense, but not 100%.

When you say “public void init( … )” you mean the init of the GLEventListener?

It’s not 100 percent clear to me, then, how to set the size at the time of takingAPicture…

Thanks, this is really helpful.

Pahidla

yes, the init() of the GLEventListener. likewise the display() of the GLEventListener…read more below why I suggest calling the myFBO.init() in the init() method and the others in the display() method.

as for the width/height there are two possible solutions.

The default way it works right now is that you have to define the buffer width/height at creation of the myFBO, and then myFBO.init() allocated the actual buffer memory. This myFBO.init() is called once in the init() method of the GLEventListener. So when you save the image in the display() method, that sized image is produced. If you wanted to have this width/height change everytime you take a screenshot then you could simply have to reset the width/height using setWidth()/setHeight() and reinitialize the myFBO.init() each time you do the screenshot…I think this becomes very costly to do, but it would work - or at least it should.

The second option is to have a large width/height initially as the buffer and then when you read the BufferedImage you could scale the BufferedImage in code using some scaling code…I could probably find some code to do that too if you need it.

Thanks for the help.

Before, I ask more questions, I’d like to overcome the fact that I’m getting “null” for the buffered image.

I have

public void init(…) {

 GL gl = drawable.getGL();

  gl.glClearColor(1f, 1f, 1f, 1f);
  gl.glEnable(GL.GL_DEPTH_TEST);
  
  //etc

 myFBO = new FrameBufferObject(drawable.getWidth(), drawable.getHeight());
  myFBO.init(gl);

}

and in display():

public void display(GLAutoDrawable drawable) {
  GL gl = drawable.getGL();

  if (takeAPictureFlag) {
    myFBO.activate(gl);
  }


  gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

  setView(drawable, false); //my function

  // do the drawing, call some lists
  gl.glFlush();

  if (takeAPictureFlag) {
    myFBO.readFrame(gl);
    BufferedImage bi = myFBO.getBufferedImage(); // This is null!
     myFBO.deactivate(gl);
    takeAPictureFlag = false;  // clear screencapture flag
  }

}

Anything obvious catching your eye?

Thanks again,

Pahidla

hmm…nothing jumps out at me. It is possible that your video card may not support the FBOs and unfortunately I hid the warning message that would normally display.

Do a couple of tests.

  1. Call this method myFBO.saveBufferedImage(“name.png”) instead of getBufferedImage() for the time being and see if that file gets created…it should create at least an ORANGE colored image ( more on this below)

  2. Remove the if (_debug) flags in the init() method of the class I gave you, and see if you get the warning messages on whether the FBO is supported or not.

If part 1 still doesn’t work, but part 2 gave a “supported” message then can you attach your code here (create some simple example…I don’t want your entire code, just this part) and I can take a look at it in the morning? I’m guessing you have a simple example that I could just try to run myself. I know this code works because I used it as late as last week and I’ve made zero changes…but I need to double check my actual code where I use it to verify I’m not missing something.

One more quick note on the “ORANGE” thing I mentioned above…The code I gave you was from something I’m working on and in that code I explicitly force the background image color to be ORANGE…I forgot to take that out of the code so that it wouldn’t affect you…so at minimum you should see a completely orange background…the color is set in the activate() method so you will eventually have to strip that part out of the code if you don’t want it…or you’d have to call the glClearColor() method with whatever color you want the background to be before the gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

Hi,

I think I was missing myFBO.copyFrame(gl); the null exception no longer happens.

So now I have to resolve several problems.

  1. What’s the right thing to do to have my function takeAPicture return the BufferedImage? I don’t have a animator or such and everything happens only in response to keyboard and mouse events. So display() won’t get tirggered until something happens.

  2. If I still want to custom size, what do I do to make “init” happen again?

  3. Taking a picture turns the background in my Canvas to black? Why does that happen?

Many many thanks,

Pahidla

And I have one more problem. In the following picture, you see the rendering on the left and the “picture” on the right.

http://freeboundaries.com/screenshot.png

So something went wrong there…

Thank you again,

Pahidla

So which picture is bad? I’m guessing the right side…are you saying the ORANGE background is bad or the fact that you don’t have the lines being drawn?

The ORANGE background is based on the fact that I force an orange background…you’ll have to remove those glClearColor() and glClear() lines in the ‘activate()’ method of the FrameBufferObject class I gave you.

If you are talking about the lines being missing then I don’t know the cause…are you sure you are rendering them between the ‘activate()’ and ‘deactivate()’ calls? if you are, then it might be a setting in the creation of the FBO and that will be something you’ll have to investigate.

No, the ORANGE is beautiful. It’s the lack of lines and the general not-sameness (what’s that dark triangle on the shiny part of the sphere?)

Here’s my entire display(…) method. Maybe you’ll notice something.

public void display(GLAutoDrawable drawable) {
  GL gl = drawable.getGL();
  if (takeAPictureFlag) {
    myFBO.activate(gl);
  }

  gl.glClearColor(1f, 1f, 1f, 1f);


  gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

  pSetView(drawable, false);

  myLight0.pGenerate3D(drawable);

  for (int i = 0; i < my3DObjects.size(); i++) {
    gl.glCallList(my3DObjects.get(i)); // Reveals implementation
  }
  gl.glFlush();



  if (takeAPictureFlag) {
    myFBO.readFrame(gl);
    myFBO.copyFrame(gl);

// BufferedImage bi = myFBO.getBufferedImage();
// myFBO.saveBufferedImage(“C:/pg/name.png”);
// System.out.println("Buffered Image: " + bi);

    myFBO.deactivate(gl);
    takeAPictureFlag = false;  // clear screencapture flag
  }


}

Thank you!

I can’t see anything, but I’m not an expert in FBOs…I would hope someone else can comment here.

The FrameBufferObject class I provided uses a color RenderBuffer as the buffer that the data is written to, and I don’t know if that is the limitation causing the problem or not. The other way to use FBO is to render to textures and that may be an alternative that works…unfortunately I don’t know how to do that specifically but it should require some minor modification to the code I provided. The tutorials mentioned should cover this but that’s all I can help with. Unfortunately, I created this code for a specific purpose and not a fully general FBO purpose.

Good luck.

Could you maybe point out a tutorial on offscreen rendering or demo that includes it. I haven’t been able to find one.

Thanks a lot for all the help.

I have some links to tutorials in the file itself…the javadoc portion. I didn’t find anything beyond that because I was concentrating on the color renderbuffer case and all of the demos, etc were about writing to textures.