Hey guys,
I am currently working on grass for my game. I use mutlitple Quads with transparent billboard textures and add a large amount of them. That looks good, but then the framerate is too low, so I attach them to a BatchNode and batch them together. This somehow resets the Transparent BUcket. Once I set Bucket.Transparent again on the children of the BatchNode, you can only see the terrain through the transparent parts of the model, not the other grass models. This video demonstrates it:
If I reattach the children to the BatchNode, everything is working fine, but they are not batched and therefore the framerate is low again. Can I somehow fix this?
That’s my code:
package mygame;
import com.jme3.app.SimpleApplication;
import com.jme3.collision.CollisionResults;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.BatchNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Quad;
import com.jme3.terrain.geomipmap.TerrainQuad;
import java.util.ArrayList;
import jme3tools.optimize.GeometryBatchFactory;
/**
* test
*
* @author normenhansen
*/
public class Main extends SimpleApplication implements ActionListener {
private Spatial spatialToAdd;
private Spatial flower;
private Spatial terrain;
private DirectionalLight sun;
private Node modelRootNode = new Node("Models");
private BatchNode grassRootNode = new BatchNode("Grass");
private float radius = 5f;
private float density = .5f;
private float flowerDensity = 0f;
private float height = .1f;
private TerrainQuad tq;
private Material grassMat;
private AmbientLight grassLight;
private boolean trans = true;
public static void main(String[] args) {
Main app = new Main();
// app.setShowSettings(false);
app.start();
}
private boolean paint;
@Override
public void simpleInitApp() {
/**
* A white ambient light source.
*/
grassMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); //Ein Model durschauen mit mehreren texturen
grassMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
grassMat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
grassMat.setBoolean("UseAlpha", true);
grassMat.getAdditionalRenderState().setDepthTest(true);
grassMat.setTexture("DiffuseMap", assetManager.loadTexture("Textures/billboardGrass.png"));
grassLight = new AmbientLight();
grassLight.setColor(ColorRGBA.White.mult(2.5f));
AmbientLight ambient = new AmbientLight();
ambient.setColor(ColorRGBA.White);
flyCam.setMoveSpeed(100);
inputManager.setCursorVisible(true);
flyCam.setDragToRotate(true);
cam.setLocation(new Vector3f(0, 30, 0));
modelRootNode.addLight(ambient);
terrain = assetManager.loadModel("Scenes/newScene.j3o");
modelRootNode.attachChild(terrain);
spatialToAdd = getGrassNode();
spatialToAdd.scale(height);
flower = getFlowerNode();
inputManager.addMapping("AddModel", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
inputManager.addListener(this, "AddModel");
inputManager.addMapping("opt", new KeyTrigger(KeyInput.KEY_O));
inputManager.addListener(this, "opt");
inputManager.addMapping("trans", new KeyTrigger(KeyInput.KEY_T));
inputManager.addListener(this, "trans");
/**
* A white, directional light source
*/
sun = new DirectionalLight();
sun.setDirection(new Vector3f(-.5f, -.5f, -.5f));
sun.setColor(ColorRGBA.White.mult(1.5f));
modelRootNode.addLight(sun);
rootNode.attachChild(modelRootNode);
grassRootNode.addLight(grassLight);
rootNode.attachChild(grassRootNode);
tq = (TerrainQuad) ((Node) terrain).getChild("terrain-newScene");
}
@Override
public void simpleUpdate(float tpf) {
if (paint) {
Vector2f click2d = inputManager.getCursorPosition();
Vector3f click3d = cam.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f).clone();
Vector3f dir = cam.getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f).subtractLocal(click3d).normalizeLocal();
CollisionResults results = new CollisionResults();
Ray ray = new Ray(click3d, dir);
terrain.collideWith(ray, results);
if (results.size() > 0) {
Vector3f contact = results.getClosestCollision().getContactPoint();
ArrayList<Vector3f> points = new ArrayList<>();
for (float x = -radius; x < radius; x++) {
for (float z = -radius; z < radius; z++) {
float rand = (float) Math.random();
if (rand < 1 - density) {
continue;
}
points.add(new Vector3f(contact.x + x, 0, contact.z + z));
}
}
for (Vector3f point : points) {
float yangle = (float) Math.random() * 360;
spatialToAdd.rotate(0, yangle * FastMath.DEG_TO_RAD, 0);
spatialToAdd.setLocalTranslation(point.x, tq.getHeight(new Vector2f(point.x, point.z)), point.z);
Spatial clone = spatialToAdd.clone();
clone.setMaterial(grassMat);
grassRootNode.attachChild(clone);
}
}
}
}
@Override
public void simpleRender(RenderManager rm) {
//TODO: add render code
}
@Override
public void onAction(String name, boolean isPressed, float tpf) {
if (name.equals("opt")) {
if (isPressed) {
grassRootNode.batch();
trans = false;
}
}
if (name.equals("trans") && isPressed) {
for (int i = 0; i < grassRootNode.getQuantity(); i++) { //da wo schwarz war, wird es transparent, aber man sieht nur dass terrain durch, nicht die anderen mdoels
Spatial child = grassRootNode.getChild(i);
child.setQueueBucket(Bucket.Transparent);
}
}
if (name.equals("AddModel")) {
if (isPressed) {
paint = true;
} else {
paint = false;
}
}
}
private Node getGrassNode() {
Quad quad = new Quad(5, 5);
Node grassNode = new Node();
Geometry geom = new Geometry("grass", quad);
geom.setMaterial(grassMat);
geom.setQueueBucket(Bucket.Transparent);
geom.setLocalTranslation(0, 0, 1.1f);
grassNode.attachChild(geom);
Geometry clone = geom.clone();
clone.rotate(0, 130 * FastMath.DEG_TO_RAD, 0);
clone.setLocalTranslation(5, 0, 1);
grassNode.attachChild(clone);
Geometry clone2 = geom.clone();
clone2.rotate(0, 60 * FastMath.DEG_TO_RAD, 0);
clone2.setLocalTranslation(0, 0, 1);
grassNode.attachChild(clone2);
return grassNode;
}
private Node getFlowerNode() {
Quad quad = new Quad(5, 5);
Node grassNode = new Node();
Geometry geom = new Geometry("grass", quad);
Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
mat.setTexture("DiffuseMap", assetManager.loadTexture("Textures/billboardGrassFlower.png"));
geom.setMaterial(mat);
geom.setQueueBucket(Bucket.Transparent);
grassNode.attachChild(geom);
Geometry clone = geom.clone();
clone.rotate(0, 130 * FastMath.DEG_TO_RAD, 0);
clone.setLocalTranslation(5, 0, 1);
grassNode.attachChild(clone);
Geometry clone2 = geom.clone();
clone2.rotate(0, 60 * FastMath.DEG_TO_RAD, 0);
clone2.setLocalTranslation(0, 0, 1);
grassNode.attachChild(clone2);
AmbientLight ambient = new AmbientLight();
ambient.setColor(ColorRGBA.White.mult(2.5f));
grassNode.addLight(ambient);
return grassNode;
}
}