Hello guys, I’ve been trying to tackle this issue for a couple days now. The problem is I only get around 40 FPS when drawing about 150,000 textures cubes. I have pretty beefy computer.
If anyone has any ideas, I’d really appreciate it.
[java]
package com.fortwars.map;
import java.net.URL;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import javax.swing.ImageIcon;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.TexCoords;
import com.jme.scene.TriMesh;
import com.jme.scene.geometryinstancing.GeometryBatchInstance;
import com.jme.scene.geometryinstancing.GeometryBatchInstanceAttributes;
import com.jme.scene.geometryinstancing.instance.GeometryBatchCreator;
import com.jme.scene.shape.Box;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jme.util.geom.BufferUtils;
import com.jmex.terrain.util.AbstractHeightMap;
import com.jmex.terrain.util.ImageBasedHeightMap;
public class Map extends Node {
private ArrayList<TriMesh> meshes = new ArrayList<TriMesh>();
private ArrayList<GeometryBatchCreator> geometryBatchCreators = new ArrayList<GeometryBatchCreator>();
AbstractHeightMap heightMap=null;
public Map(String heightMapLoc){
URL heightmapImg = Map.class.getClassLoader().getResource(heightMapLoc);
heightMap = new ImageBasedHeightMap(new ImageIcon(heightmapImg).getImage());
createBoxes();
}
private void createBoxes() {
// A box that will be instantiated
Box box = new Box(“Box”, new Vector3f(0, 0, 0), new Vector3f(1, 1, 1));
float x2 = 0.5f;
float y2 = 0.5f;
float z2 = 0.5f;
float[] texCoordinates = {
0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // back
0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // right
0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // front
0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // left
x2, 1, 1, 1, 1, x2, z2, x2, // top
x2, 1, 0, 1, 0, x2, z2, x2, // bottom
};
FloatBuffer buffer = box.getTextureCoords().get(0).coords;
buffer.rewind();
buffer.put(texCoordinates);
// The batch geometry creator
// Loop that creates NxN instances
for (int y = 0; y < 128; y++) {
for (int x = 0; x < 128; x++) {
for(int z = 0;z<(heightMap.getTrueHeightAtPoint(x, y)*0.3f)-8; z++) {
if(geometryBatchCreators.size()-1<z)
geometryBatchCreators.add(z,new GeometryBatchCreator());
// Box instance attributes
GeometryBatchInstanceAttributes attributes =
new GeometryBatchInstanceAttributes(
new Vector3f(x,z,y),
// Translation
new Vector3f(1f,1f,1f),
// Scale
box.getLocalRotation(),
// Rotation
new ColorRGBA(1,1,1,0));
// Color
// Box instance (batch and attributes)
GeometryBatchInstance instance =
new GeometryBatchInstance(box, attributes);
// Add the instance
geometryBatchCreators.get(z).addInstance(instance);
}
}
}
// Create a TriMesh
for(int n = 0;n<geometryBatchCreators.size();n++){
System.out.println(“batch:” +n);
TriMesh mesh = new TriMesh();
mesh.setModelBound(new BoundingBox());
// Create the batch’s buffers
mesh.setIndexBuffer(BufferUtils.createIntBuffer(
geometryBatchCreators.get(n).getNumIndices()));
mesh.setVertexBuffer(BufferUtils.createVector3Buffer(
geometryBatchCreators.get(n).getNumVertices()));
mesh.setNormalBuffer(BufferUtils.createVector3Buffer(
geometryBatchCreators.get(n).getNumVertices()));
mesh.setTextureCoords(new TexCoords(BufferUtils.createVector2Buffer(
geometryBatchCreators.get(n).getNumVertices())), 0);
mesh.setColorBuffer(BufferUtils.createFloatBuffer(
geometryBatchCreators.get(n).getNumVertices() * 4));
// Commit the instances to the mesh batch
geometryBatchCreators.get(n).commit(mesh);
// Add a texture
TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
TextureManager.COMPRESS_BY_DEFAULT = false;
Texture t0 = TextureManager.loadTexture(
Map.class.getClassLoader().getResource(
“res/grass.png”),
Texture.MinificationFilter.NearestNeighborLinearMipMap,
Texture.MagnificationFilter.NearestNeighbor, 2.0f, false);
t0.setWrap(Texture.WrapMode.Clamp);
ts.setTexture(t0, 0);
// Material
MaterialState ms = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
ms.setColorMaterial(MaterialState.ColorMaterial.AmbientAndDiffuse);
// Render states
mesh.setRenderState(ms);
mesh.setRenderState(ts);
mesh.updateRenderState();
// Return the mesh
mesh.updateModelBound();
mesh.lock();
meshes.add(mesh);
this.attachChild(mesh);
}
}
public boolean isTileAt(int x3, int y3, int z3){
for(TriMesh t: meshes){
Vector3f v = t.getWorldTranslation();
if((int)v.x == x3 && (int)v.y==y3 && (int)v.z == z3){
return true;
}
}
return false;
}
}
[/java]
I could be misstaken, but isn’t 150k cubes a lot? What is your aim?
What kind of computer do you have anyway? Also, could you add code to it’s testable?
Very interesting sofar
You should join some boxes to geometries. Many objects are more of a strain to the GPU than many faces.
Normen: I use geometrybatchcreator and do indeed join boxes.
150,000 cubes is only 1.5 million triangles, a GTX 260 should easily be about to handle this.
Here is my updated code, I changed the map to be grouped into 16x16 chunks:
HelloWorld.java
[java]
import com.fortwars.entity.Player;
import com.fortwars.map.Map;
import com.jme.app.SimpleGame;
import com.jme.input.ChaseCamera;
import com.jme.math.Vector3f;
/**
- Started Date: Jul 20, 2004
-
- Simple HelloWorld program for jME
*
-
@author Jack Lindamood
/
public class HelloWorld extends SimpleGame {
public static void main(String[] args) {
HelloWorld app = new HelloWorld();
app.setConfigShowMode(ConfigShowMode.AlwaysShow);
app.start();
}
protected void simpleInitGame() {
Map map = new Map(“res/heightmap.png”);
map.lock();
rootNode.attachChild(map);
cam.setLocation(new Vector3f(10,8,15));
}
}
[/java]
Map.java
[java]
package com.fortwars.map;
import java.net.URL;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import javax.swing.ImageIcon;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Node;
import com.jme.scene.SharedMesh;
import com.jme.scene.TexCoords;
import com.jme.scene.TriMesh;
import com.jme.scene.geometryinstancing.GeometryBatchInstance;
import com.jme.scene.geometryinstancing.GeometryBatchInstanceAttributes;
import com.jme.scene.geometryinstancing.instance.GeometryBatchCreator;
import com.jme.scene.shape.Box;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jme.util.geom.BufferUtils;
import com.jmex.terrain.util.AbstractHeightMap;
import com.jmex.terrain.util.ImageBasedHeightMap;
public class Map extends Node {
ArrayList batchCreators = new ArrayList ();
private ArrayList geomBatchInstances = new ArrayList();
Box box = new Box(“Box”, new Vector3f(0, 0, 0), new Vector3f(1, 1, 1));
AbstractHeightMap heightMap=null;
int[][][] mapData = new int[128][128][16];
float x2 = 0.5f;
float y2 = 0.5f;
float z2 = 0.5f;
float[] texCoordinates = {
0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // back
0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // right
0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // front
0, 0.5f, 0.5f, 0.5f, 0.5f, 0.0f, 0f, 0f, // left
x2, 1, 1, 1, 1, x2, z2, x2, // top
x2, 1, 0, 1, 0, x2, z2, x2, // bottom
};
public Map(String heightMapLoc){
FloatBuffer buffer = box.getTextureCoords().get(0).coords;
buffer.rewind();
buffer.put(texCoordinates);
URL heightmapImg = Map.class.getClassLoader().getResource(heightMapLoc);
heightMap = new ImageBasedHeightMap(new ImageIcon(heightmapImg).getImage());
//createBoxes();
loadMapData();
buildMap();
}
public void buildMap(){
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 8; y++) {
int[][][] tempChunk = new int[16][16][16];
for(int k = 0;k<16;k++){
for(int l = 0;l<16;l++){
for(int z = 0;z<16; z++) {
tempChunk[k][l][z]=mapData[((x16)+k)][((y16)+l)][z];
}
}
}
TriMesh mesh = createChunk(tempChunk);
mesh.setLocalTranslation(x16,0,y*16);
attachChild(mesh);
}
}
}
private void loadMapData(){
for (int x = 0; x < 128; x++) {
for (int y = 0; y < 128; y++) {
for(int z = 0;z<(heightMap.getTrueHeightAtPoint(x, y)*0.3f)-9; z++) {
mapData[x][y][z]=1;
}
}
}
}
//
private TriMesh createChunk(int[][][] chunkData){
GeometryBatchCreator batchCreator = new GeometryBatchCreator();
System.out.println(“chunk created”);
//Adds instance data together for all the boxes in the chunk
for (int x = 0; x < 16; x++) {
for (int y = 0; y < 16; y++) {
for(int z = 0;z<16; z++) {
// Box instance attributes
if(chunkData[x][y][z]==1){
GeometryBatchInstanceAttributes attributes =
new GeometryBatchInstanceAttributes(
new Vector3f(x,z,y),
// Translation
new Vector3f(1f,1f,1f),
// Scale
box.getLocalRotation(),
// Rotation
new ColorRGBA(1,1,1,0));
// Color
// Box instance (batch and attributes)
GeometryBatchInstance instance =
new GeometryBatchInstance(box, attributes);
geomBatchInstances.add(instance);
// Add the instance
batchCreator.addInstance(instance);
}
}
}
}
// Create a TriMesh
TriMesh mesh = new TriMesh();
mesh.setModelBound(new BoundingBox());
// Create the batch’s buffers
System.out.println(batchCreator.getNumIndices());
mesh.setIndexBuffer(BufferUtils.createIntBuffer(
batchCreator.getNumIndices()));
mesh.setVertexBuffer(BufferUtils.createVector3Buffer(
batchCreator.getNumVertices()));
mesh.setNormalBuffer(BufferUtils.createVector3Buffer(
batchCreator.getNumVertices()));
mesh.setTextureCoords(new TexCoords(BufferUtils.createVector2Buffer(
batchCreator.getNumVertices())), 0);
mesh.setColorBuffer(BufferUtils.createFloatBuffer(
batchCreator.getNumVertices() * 4));
// Commit the instances to the mesh batch
batchCreator.commit(mesh);
batchCreators.add(batchCreator);
// Add a texture
TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
TextureManager.COMPRESS_BY_DEFAULT = false;
Texture t0 = TextureManager.loadTexture(
Map.class.getClassLoader().getResource(
“res/grass.png”),
Texture.MinificationFilter.NearestNeighborLinearMipMap,
Texture.MagnificationFilter.NearestNeighbor, 2.0f, false);
t0.setWrap(Texture.WrapMode.Clamp);
ts.setTexture(t0, 0);
// Material
MaterialState ms = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
ms.setColorMaterial(MaterialState.ColorMaterial.AmbientAndDiffuse);
// Render states
mesh.setRenderState(ms);
mesh.setRenderState(ts);
mesh.updateRenderState();
// Return the mesh
mesh.updateModelBound();
//mesh.lock();
return mesh;
}
/public boolean isTileAt(int x3, int y3, int z3){
for(TriMesh t: meshes){
Vector3f v = t.getWorldTranslation();
if((int)v.x == x3 && (int)v.y==y3 && (int)v.z == z3){
return true;
}
}
return false;
}/
}
[/java]
I am at 23fps for 285152 boxes on screen (looking top down), on a Radeon 4800 using JME3 and a Lighting shader.
I found that for my combo the optimum batch size consists of 140 boxes.
Being ‘in the landscape’ looking around, the generic fps is around 120.
Strange thing is, that CPU load is always at or slightly over 25% (1 full core). I’m sure that’s the current bottleneck when using a sufficiently sized batch. I wonder if multiple cores can be used.
Hope that helps getting some perspective.
Don’t load the same texture (TextureState ts, MaterialState ms) several times, but outside createChunk()
I assume an instance of the same texture is loaded per each mesh, so it can explain slow rendering. (and a huge memory allocation !)
Damien