Is there any way to check which nodes are being rendered right now?

Yeah. It’s still vsync. It can’t render at 60fps, so the next best thing is 30fps - which is half-synced. It’s the best it can do to maintain vertical sync. If you don’t want that behavior, you need to disable vsync.

60fps - 1/1 ratio with your monitor refresh rate.
30fps - half ratio.
20fps - 1/3
15fps - 1/4

… not sure what happens after that. Probably keeps going until there’s nothing left.

And just to note - just because an object isn’t in view - doesnt mean you don’t have to worry about it. It will cull them, but if you have large scenes you need to split them into chunks, and add/remove them as the camera moves.

It is not VSync. I get the same FPS with VSync turned off. Furthermore, the FPS changed when I moved the camera away from the origin as described in my previous reply. It appears to me, that as long as my camera stays at the origin, no nodes are being culled. As I move the camera away from the origin nodes start being culled.

My scene is already divided into chunks. Right now I have 1010 Geometries for terrain and 1010 InstancedNode’s for trees. Each InstancedNode contains somewhere between 1212 and 3232 many Geometries.

Can you show a screenshot with the stats enabled?

Here you go. I took 2 screenshots from the same scene. Only difference is the position of the camera. The first screenshot is with the camera zoomed in on the origin. The second screenshot is with the camera zoomed in at the opposite corner of the scene:

Bad Performance

Good Performance

Here is a schematic view of how the performance changes with the movement of the camera:

Also, sorry for not answering your initial question. There are a few methods. Best to read here.

Nearly 18 million triangles…

The whole object would need to be out of view in order for it to be culled.

It is an InstancedNode with many many trees (the green spheres for debugging purposes in the screenshot). This is, after all, a big terrain with forests and whatnot. The actual tree model will be low-poly though (perhaps changing depending on zoom level) so it will drastically cut down the actual number of triangles.

Regardless of that I would still like to know why ALL triangles need to be rendered at the lower-left corner while only 1/10th is being rendered when the camera is at the top-right corner. It is obvious that something is going wrong.

The simplest way to do as you requested is calling .checkCulling(Camera cam) on whatever it is you want to check, but it’s obvious enough that they aren’t being culled. The first port of call would be to ensure the the cullHint it set to dynamic, and secondly to ensure each object is individual, and not part of one huge mesh. If an object is not in view it will be culled, but it won’t cull any of it if even an very small portion of it is within view. Is it possible that some of your objects are actually one very large object merged together?

As I said a couple of times, I have 1010 Geometries, each with a custom mesh, for terrain. And then 1010 InstancedNode’s each one with somewhere between 1212 and 3232 Geometries (Sphere) for trees.

The culling works. If I move the camera too far off the edge everything is being culled away. If I move the camera to the top-right all the terrain and all the trees of the lower and left portions are culled away.

But for some reason the terrain and trees to the right and top of the camera are never culled. (unless I move the camera far away from any terrain).
I suspect the bounds of the nodes to be wrong somehow but I dont know JME3 enough yet to properly debug it. Which properties of a Node are responsible for culling? Where do I find how they are calculated?

It’s all open source on github. You probably want to start from here:

I did some more testing and found out that the BoundingBoxes for the InstancedNode’s are wrong. Even though all the Geometry within the node fits into a 323216 cube, the BoundingBox of the node always extends to the origin (0, 0, 0) no matter where the node is placed.

What controls the bounding box of an InstancedNode? Is there some kind of trap that I am running into when creating the node or its geometries?

After some more digging I found the following:

  1. I have an empty InstancedNode. Its BoundingVolume is ‘null’. The Translation of the node is (0.0, 64.0, 0.0), the Scale is (1.0, 1.0, 1.0)

  2. I create a new Geometry object for a tree. Its bounding volume is the following:

BoundingBox [Center: (0.50581163, 0.5, 1.9) xExtent: 0.3941884 yExtent: 0.39708355 zExtent: 0.4]

  1. I attach the Geometry to my InstancedNode, the BoundingVolume of the node changes to the BoundingVolume of the tree Geometry:

BoundingBox [Center: (0.50581163, 64.5, 1.9) xExtent: 0.3941884 yExtent: 0.39708355 zExtent: 0.4]

  1. I call the instance() method on my InstancedNode and the BoundingVolume changes to this:

BoundingBox [Center: (0.25581163, 32.25, 0.95) xExtent: 0.6441884 yExtent: 32.647087 zExtent: 1.3499999]

As you can see the BoundingVolume was extended significantly along the Y-axis for some reason. I assure you, that the InstancedNode has ONLY one child, and that child is the tree Geometry.
Can anybody please explain to me how this is happening?

1 Like

Okay, I can not figure out why the BoundingVolume is incorrect. I cant find the relevant piece of code.

Instead: Can I set the WorldBound of a Node directly? I know exactly what the bounding volume of the node is going to be since it is a very simple box. If I could just directly overwrite the WorldBounds manually, I think it would solve the problem.

Edit:
I did this very very dirty hack (just for testing purposes!) and NOW I get constant 60 FPS with VSync on:

public static class FixedBoundNode extends InstancedNode {
    public BoundingBox ownBounds = null;
    public TestNode(String name) {
        super(name);
    }
    @Override
    public BoundingVolume getWorldBound() {
        if (ownBounds == null) {
            return super.getWorldBound();
        }
        return ownBounds;
    }
}

Everything still gets rendered nicely btw. All terrain and trees within the cameras view are rendered.

This should prove, that it is indeed the BoundingVolume which is at fault here. Now the real question is: How can I debug why the BoundingVolume is wrong?

It will not solve your problem at all… as jayfella said 18millions triangles is just to much …instanced or not…

The fps are better when you move to the “top right” because your instance node is culled (thanks to the bad bounding box which stay at 0, 0, 0), and then as soon as you will increase the bounding this node will never be culled anymore :s

You have to split your trees into smaller chunks that you load/unload on the fly when you move the cam (search for paging system). You can always use instancing but one instance by chunk and not one for the whole world :s

I DID split the trees into smaller chunks. I said that numerous times. Look at these screenshot after the above hack:

Screen1

Screen2

Screen3

Screen4

As you can tell from the numbers, culling works now; triangle counts reflect the number of trees visible on screen. The FPS are good as long as I dont zoom out too far. The scene from these screenshots is still the same scene as the one from the screenshots above with the bad performance. (except for the fact trees and terrain are randomly generated)

The hack with the BoundingBox is the only change I made in code.

So except the fact instanced node bounding box is wrong computed, everything seems pretty normal then.

With 18millions polygons (when your cam is zoomed out) you can’t run at 60 fps and the vsync do its job by lowering fps to 30.

Maybe @nehon (i believe he did instance nodes) can tell if it’s a bug or a limitation of instance nodes?

I am not sure if its a bug. Maybe I am just using it wrong. But I am 100% certain the BoundingVolume being wrong is at fault here.

By the way: The number of triangles are not the problem. I can render 100 million triangles at 50 FPS as long as the triangles are only about 1 pixel (or less) in size and all batched into a single draw call. Perhaps not with JMonkeyEngine, but when using raw OpenGL calls directly it works quite well.
However, for this particular application the numbers will not be that high. In these test images I am showing I am inflating the numbers on purposes to test the performance and to find the reason for the bugged BoundingVolume. After all, if I always got a nice frame rate I wouldnt have noticed the problem.

Could you please provide a test case to eliminate any outside interference. The person that fixes it will need it to repair it if it is indeed a bug.

A simple single class with the minimal set models to expose the bug.

Sure,

here is an example as simple as it gets:

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.instancing.InstancedNode;
import com.jme3.scene.shape.Sphere;

public class Main extends SimpleApplication  {
    
    public static void main(String[] args){
        Main app = new Main();
        app.start();
    }
    
    @Override
    public void simpleInitApp() {
        Mesh mesh = new Sphere(13, 13, 0.4f, true, false);
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setBoolean("UseInstancing", true);
        mat.setColor("Color", ColorRGBA.Red);
        
        InstancedNode instancedNode = new InstancedNode("Node");
        instancedNode.setLocalTranslation(128, 128, 0);
        
        Geometry geom = new Geometry("Geometry", mesh);
        geom.setMaterial(mat);
        geom.setLocalTranslation(0, 0, 0);
        
        System.out.println("Geometry.WorldBound="+geom.getWorldBound());
        System.out.println("@BEFORE_ATTACH Node:WorldBound="+instancedNode.getWorldBound());
        instancedNode.attachChild(geom);
        System.out.println("@AFTER_ATTACH Node:WorldBound="+instancedNode.getWorldBound());
        instancedNode.instance();
        System.out.println("@AFTER_INSTANCE Node:WorldBound="+instancedNode.getWorldBound());
        
        
        rootNode.attachChild(instancedNode);
    }
}

When I run the above code I get the following output:

Geometry.WorldBound=BoundingBox [Center: (0.005811617, 0.0, 0.0) xExtent: 0.3941884 yExtent: 0.39708355 zExtent: 0.4]
@BEFORE_ATTACH Node:WorldBound=null
@AFTER_ATTACH Node:WorldBound=BoundingBox [Center: (128.00581, 128.0, 0.0) xExtent: 0.3941884 yExtent: 0.39708355 zExtent: 0.4]
@AFTER_INSTANCE Node:WorldBound=BoundingBox [Center: (64.00581, 63.999996, 0.0) xExtent: 64.394196 yExtent: 64.39708 zExtent: 0.4]

The important part is the difference between @AFTER_ATTACH and @AFTER_INSTANCE. You can see the extent of the BoundingBox suddenly increase. The extent of the bounding box is dependent on the translation of the InstancedNode.

If I change the translation from (128.0, 128.0, 0.0) to (256.0, 256.0, 0.0) I get the following output:

Geometry.WorldBound=BoundingBox [Center: (0.005811617, 0.0, 0.0) xExtent: 0.3941884 yExtent: 0.39708355 zExtent: 0.4]
@BEFORE_ATTACH Node:WorldBound=null
@AFTER_ATTACH Node:WorldBound=BoundingBox [Center: (256.0058, 256.0, 0.0) xExtent: 0.3941884 yExtent: 0.39708355 zExtent: 0.4]
@AFTER_INSTANCE Node:WorldBound=BoundingBox [Center: (128.00581, 128.0, 0.0) xExtent: 128.39418 yExtent: 128.3971 zExtent: 0.4]

This seems obviously wrong to me.