Back in this post I discussed ways of making dynamically equipped 2D sprites. One way was to render out a 3D object to 2D textures dynamically. So far we have looked at working in 3D in LibGDX, then exporting and rendering an animated model from Blender, the next step would be a dynamic 3D object to texture. At first glance I thought this would be difficult, but reality is, it was stupidly simple. In fact, the very first bit of code I wrote simply worked! Well, except the results being upside down I suppose… details details…
Anyways, that is what this post discusses. Taking a 3D scene in LibGDX and rendering in a 2D texture. The code is just a modification of the code from the Blender to GDX post from a couple days back.
package com.gamefromscratch;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Files.FileType;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.PerspectiveCamera;
import com.badlogic.gdx.graphics.Pixmap.Format;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.graphics.g3d.Environment;
import com.badlogic.gdx.graphics.g3d.Model;
import com.badlogic.gdx.graphics.g3d.ModelBatch;
import com.badlogic.gdx.graphics.g3d.ModelInstance;
import com.badlogic.gdx.graphics.g3d.attributes.ColorAttribute;
import com.badlogic.gdx.graphics.g3d.loader.G3dModelLoader;
import com.badlogic.gdx.utils.UBJsonReader;
import com.badlogic.gdx.graphics.g3d.utils.AnimationController;
import com.badlogic.gdx.graphics.g3d.utils.AnimationController.AnimationDesc;
import com.badlogic.gdx.graphics.g3d.utils.AnimationController.AnimationListener;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
public class ModelTest implements ApplicationListener, InputProcessor {
private PerspectiveCamera camera;
private ModelBatch modelBatch;
private Model model;
private ModelInstance modelInstance;
private Environment environment;
private AnimationController controller;
private boolean screenShot = false;
private FrameBuffer frameBuffer;
private Texture texture = null;
private TextureRegion textureRegion;
private SpriteBatch spriteBatch;
@Override
public void create() {
// Create camera sized to screens width/height with Field of View of 75 degrees
camera = new PerspectiveCamera(
75,
Gdx.graphics.getWidth(),
Gdx.graphics.getHeight());
// Move the camera 5 units back along the z-axis and look at the origin
camera.position.set(0f,0f,7f);
camera.lookAt(0f,0f,0f);
// Near and Far (plane) represent the minimum and maximum ranges of the camera in, um, units
camera.near = 0.1f;
camera.far = 300.0f;
// A ModelBatch is like a SpriteBatch, just for models. Use it to batch up geometry for OpenGL
modelBatch = new ModelBatch();
// Model loader needs a binary json reader to decode
UBJsonReader jsonReader = new UBJsonReader();
// Create a model loader passing in our json reader
G3dModelLoader modelLoader = new G3dModelLoader(jsonReader);
// Now load the model by name
// Note, the model (g3db file ) and textures need to be added to the assets folder of the Android proj
model = modelLoader.loadModel(Gdx.files.getFileHandle("data/benddemo.g3db", FileType.Internal));
// Now create an instance. Instance holds the positioning data, etc of an instance of your model
modelInstance = new ModelInstance(model);
//move the model down a bit on the screen ( in a z-up world, down is -z ).
modelInstance.transform.translate(0, 0, -2);
// Finally we want some light, or we wont see our color. The environment gets passed in during
// the rendering process. Create one, then create an Ambient ( non-positioned, non-directional ) light.
environment = new Environment();
environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.8f, 0.8f, 0.8f, 1.0f));
// You use an AnimationController to um, control animations. Each control is tied to the model instance
controller = new AnimationController(modelInstance);
// Pick the current animation by name
controller.setAnimation("Bend",1, new AnimationListener(){
@Override
public void onEnd(AnimationDesc animation) {
// this will be called when the current animation is done.
// queue up another animation called "balloon".
// Passing a negative to loop count loops forever. 1f for speed is normal speed.
controller.queue("Balloon",-1,1f,null,0f);
}
@Override
public void onLoop(AnimationDesc animation) {
// TODO Auto-generated method stub
}
});
frameBuffer = new FrameBuffer(Format.RGB888,Gdx.graphics.getWidth(),Gdx.graphics.getHeight(),false);
Gdx.input.setInputProcessor(this);
spriteBatch = new SpriteBatch();
}
@Override
public void dispose() {
modelBatch.dispose();
model.dispose();
}
@Override
public void render() {
// You've seen all this before, just be sure to clear the GL_DEPTH_BUFFER_BIT when working in 3D
Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
Gdx.gl.glClearColor(1, 1, 1, 1);
Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
// When you change the camera details, you need to call update();
// Also note, you need to call update() at least once.
camera.update();
// You need to call update on the animation controller so it will advance the animation. Pass in frame delta
controller.update(Gdx.graphics.getDeltaTime());
// If the user requested a screenshot, we need to call begin on our framebuffer
// This redirects output to the framebuffer instead of the screen.
if(screenShot)
frameBuffer.begin();
// Like spriteBatch, just with models! pass in the box Instance and the environment
modelBatch.begin(camera);
modelBatch.render(modelInstance, environment);
modelBatch.end();
// Now tell OpenGL that we are done sending graphics to the framebuffer
if(screenShot)
{
frameBuffer.end();
// get the graphics rendered to the framebuffer as a texture
texture = frameBuffer.getColorBufferTexture();
// welcome to the wonderful world of different coordinate systems!
// simply put, the framebuffer is upside down to normal textures, so we have to flip it
// Use a TextureRegion to do so
textureRegion = new TextureRegion(texture);
// and.... FLIP! V (vertical) only
textureRegion.flip(false, true);
}
// In the case that we have a texture object to actually draw, we do so
// using the old familiar SpriteBatch to do so.
if(texture != null)
{
spriteBatch.begin();
spriteBatch.draw(textureRegion,0,0);
spriteBatch.end();
screenShot = false;
}
}
@Override
public void resize(int width, int height) {
}
@Override
public void pause() {
}
@Override
public void resume() {
}
@Override
public boolean keyDown(int keycode) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean keyUp(int keycode) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean keyTyped(char character) {
// If the user hits a key, take a screen shot.
this.screenShot = true;
return false;
}
@Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean touchUp(int screenX, int screenY, int pointer, int button) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean touchDragged(int screenX, int screenY, int pointer) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean mouseMoved(int screenX, int screenY) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean scrolled(int amount) {
// TODO Auto-generated method stub
return false;
}
}
And… that’s it.
Run the code and you get the exact same results as the last example:

However, if you press any key, the current frame is saved to a texture, and that is instead displayed on screen. Press a key again and it will update to the current frame:

Of course, the background colour is different, because we didn’t implicitly set one. The above a is LibGDX Texture object, which can now be treated as a 2D sprite, used as a texture map, whatever.
The code is ultra simple. We have a toggle variable screenShot, that gets set if a user hits a key. The actual process of rendering to texture is done with the magic of FrameBuffersl Think of a framebuffer as a place for OpenGL to render other than your video card. So instead of drawing the graphics to the screen, it instead draws the graphics to a memory buffer. We then get this memory buffer as a texture using getColorBufferTexture(). The only complication is the frame buffer is rendered upside down. This is easily fixed by wrapping the Texture in a TextureRegion and flipping the V coordinate. Finally we display our newly generated texture using our old friend, the SpriteBatch.
Gotta love it when something you expect to be difficult ends up being ultra easy. Next I have to measure the performance, so see if this is something that can be done in a realtime situation, or do we need to do this onload/change?
Programming
LibGDX Java 3D 2D