Okay, now main issue with transparent png exporting is fixed on master branch.
https://github.com/jMonkeyEngine/jmonkeyengine/pull/1054
I have yet one small issue, see below generated png icon:
As you can see behind the leaves (marked in red) is transparent.
Any way to fix it ?
And here is my code:
public class IconGeneratorState extends BaseAppState {
static Logger log = LoggerFactory.getLogger(IconGeneratorState.class);
private Node root;
private FrameBuffer offBuffer;
private ViewPort offView;
private Geometry wireBounds;
private boolean debugBounds = false;
private boolean debugTexture = false;
private int width;
private int height;
private File path;
public IconGeneratorState() {
this(256, 256);
}
public IconGeneratorState(int width, int height) {
this.width = width;
this.height = height;
}
@Override
protected void initialize(Application app) {
root = new Node("RootNode:");
Camera offCamera = new Camera(width, height);
offView = getApplication().getRenderManager().createPreView("Offscreen View", offCamera);
offView.setClearFlags(true, true, true);
offView.setBackgroundColor(ColorRGBA.BlackNoAlpha);
// create offscreen framebuffer
offBuffer = new FrameBuffer(width, height, 1);
//setup framebuffer's cam
offCamera.setFrustumPerspective(45f, 1f, 1f, 1000f);
offCamera.setLocation(new Vector3f(0f, 0f, 10f));
offCamera.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);
//setup framebuffer's texture
Texture2D offTex = new Texture2D(width, height, Image.Format.RGBA8);
//offTex.setMinFilter(Texture.MinFilter.Trilinear);
//offTex.setMagFilter(Texture.MagFilter.Bilinear);
//setup framebuffer to use texture
offBuffer.setDepthBuffer(Image.Format.Depth);
offBuffer.setColorTexture(offTex);
//set viewport to render to offscreen framebuffer
offView.setOutputFrameBuffer(offBuffer);
// attach the scene to the viewport to be rendered
offView.attachScene(root);
if (debugTexture) {
//setup main scene
Quad mesh = new Quad(2, 2);
Geometry quad = new Geometry("box", mesh);
Material mat = GuiGlobals.getInstance().createMaterial(false).getMaterial();
mat.setTexture("ColorMap", offTex);
//mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
quad.setMaterial(mat);
((SimpleApplication) getApplication()).getRootNode().attachChild(quad);
}
//initFilters();
}
private void initFilters() {
FilterPostProcessor fpp = new FilterPostProcessor(getApplication().getAssetManager());
fpp.addFilter(new FXAAFilter());
fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(11.0f)));//4.0f
fpp.addFilter(new SSAOFilter(0.5f, 3, 0.2f, 0.2f));
offView.addProcessor(fpp);
}
@Override
protected void cleanup(Application app) {
getApplication().getRenderManager().removePreView(offView);
}
@Override
protected void onEnable() {
offView.setEnabled(true);
}
@Override
protected void onDisable() {
offView.setEnabled(false);
}
float time;
@Override
public void update(float tpf) {
root.updateLogicalState(tpf);
root.updateGeometricState();
updateCamera();
time += tpf;
}
@Override
public void render(RenderManager rm) {
if (path != null && time > 0.2) {
Renderer renderer = rm.getRenderer();
Image image = createFrameBufferImage(offBuffer);
//Texture2D texture = new Texture2D(image);
renderer.readFrameBuffer(offBuffer, image.getData(0));
//image.setUpdateNeeded();
try {
savePng(path, image);
} catch (IOException ex) {
log.error("Error generating icon!", ex);
}
path = null;
}
}
public void generateIcon(Spatial model, File path) {
// Vector3f bound = ((BoundingBox) model.getWorldBound()).getExtent(new Vector3f()).multLocal(2);
// float max = Math.max(Math.max(bound.x, bound.y), bound.z);
// float resize = baseSize / max;
// model.scale(resize);
// setup framebuffer's scene
root.detachAllChildren();
root.attachChild(model);
this.path = path;
this.time = 0;
}
protected void updateCamera() {
BoundingBox bb = (BoundingBox) root.getWorldBound();
Vector3f min = bb.getMin(null);
Vector3f max = bb.getMax(null);
float xSize = Math.max(Math.abs(min.x), Math.abs(max.x));
float ySize = max.y - min.y;
float zSize = Math.max(Math.abs(min.z), Math.abs(max.z));
float size = ySize * 0.5f;
size = Math.max(size, xSize);
size = Math.max(size, zSize);
// In the projection matrix, [1][1] should be:
// (2 * Zn) / camHeight
// where Zn is distance to near plane.
float m11 = offView.getCamera().getViewProjectionMatrix().m11;
// We want our position to be such that
// 'size' is otherwise = cameraHeight when rendered.
float z = m11 * size;
// Add the z extents so that we adjust for the near plane
// of the bounding box... well we will be rotating so
// let's just be sure and take the max of x and z
//float offset = Math.max(bb.getXExtent(), bb.getZExtent());
//z += offset;
// This creates problems because it makes way too much
// space around the tree. A proper solution would require
// a bunch of math and in the end would also have to be duplicated
// on the quad generation side or somehow stored with the atlas.
Vector3f center = bb.getCenter();
float sizeOffset = 0;
Vector3f camLoc = new Vector3f(0, center.y + sizeOffset, z);
offView.getCamera().setLocation(camLoc);
if (debugBounds) {
WireBox box;
if (wireBounds == null) {
box = new WireBox();
wireBounds = new Geometry("wire box", box);
Material mat = GuiGlobals.getInstance().createMaterial(ColorRGBA.Yellow, false).getMaterial();
wireBounds.setMaterial(mat);
root.attachChild(wireBounds);
} else {
box = (WireBox) wireBounds.getMesh();
}
box.updatePositions(bb.getXExtent(), bb.getYExtent(), bb.getZExtent());
box.setBound(new BoundingBox(new Vector3f(0, 0, 0), 0, 0, 0));
wireBounds.setLocalTranslation(bb.getCenter());
//wireBounds.setLocalRotation(leafGeom.getLocalRotation());
}
}
protected void savePng(File f, Image img) throws IOException {
try (OutputStream out = new FileOutputStream(f)) {
JmeSystem.writeImageFile(out, "png", img.getData(0), img.getWidth(), img.getHeight());
log.debug("Icon saved at {}", f.getPath());
}
}
protected Image createFrameBufferImage(FrameBuffer fb) {
int width = fb.getWidth();
int height = fb.getHeight();
int size = width * height * 4;
ByteBuffer buffer = BufferUtils.createByteBuffer(size);
Image.Format format = fb.getColorBuffer().getFormat();
// I guess readFrameBuffer always writes in the same
// format regardless of the frame buffer format
format = Image.Format.BGRA8;
return new Image(format, width, height, buffer);
}
}