Rendering at 100k+ Meters Many Issues

I am hoping to get some help or insight on dealing with this issue.
Recently for the voxel engine I introduced infinite world gen so I decided to go exploring and found that about 100k+ meters out rendering breaks down.

I think this may be related to the WebGL being stuck with 32 bit floats instead of 64 bit floats.

I however could not find anything online with anyone dealing with just wanting to rendering something very far away from 0,0.


And at 30 million+ meters the whole scene breaks down:

Here is a basic playground showing off the problem:

So, one of the first things I tried was this:

 const engine = new BABYLON.Engine(canvas, antialias, {
  useHighPrecisionMatrix: true,
  useHighPrecisionFloats: true,
 });

I also tried making it so that all the meshes vertex positions are actually relative to origin and then its actual position is set. So, the positions themselves are within the 32bit float range but the transform is 64 bits. This fixed some issues but caused other ones.

So, if you anyone had any advice or articles/documentation I could read to figure out this issue that would be awesome.

Hey there! I don’t have much first-hand experience with huge distances but @imerso has an amazing post about it: Floating Origin Template for BJS 5.x - Demos and projects - Babylon.js (babylonjs.com) :smiley:

2 Likes

Thank you!
All right awesome I will look into this a bit more and see what I can intergrade from it.

2 Likes

And in the doc:

4 Likes

All right awesome!
Give me a day or two and I will see if I can get this working in DVE.
I will post back here when I get it figured out.
Does not appear that it would be too hard would just require some changes under the hood.
Thanks again everyone!

1 Like

Okay I got it working:

The largest distance I was able to go was around x: 2,000,000,000 z: 2,000,000,000
I would say that is big enough lol.

I only did one thing different then the example and that was to parent all chunk meshes to one transform node since the chunk’s themselves should never be that far away from the player.

The only thing that kinda sucks is that I have to now keep all the worldMatrix for every mesh unfrozen.
Though currently if I stay within a chunk the FPS is at 60.
I think I may just be sending to many messages to the render thread from workers which is causing some lag when moving to one chunk to other.

So, any idea to reduce overhead for this would be welcomed but I am going to at least make sure I optimize the infinite generation first.

Thank you again everyone!

2 Likes

oh yup definitely needs smthg a bit different I guess @Evgeni_Popov can you think of anything ?

I was considering just doing the matrix math in the shader.
So, it would be something like this:

gl_Position = worldViewProjection * vec4(position, 1.0) * worldOrigin; 

Where worldOrigin would be the transform nodes point.

Though I am not sure what the exact math would be to produce the same result and not sure if this would affect culling.

Okay using this worked:

gl_Position =  worldViewProjection * vec4(position + worldOrigin, 1.0) ;

But it totally screwed up culling so idk.

What I think I would have to do is create my own mesh culling strategy.
Since I know the max size of a chunk mesh I could do it kind of easily.
However, it may take up more CPU cycles on the render thread then DVE could afford.
I have no idea. I will play around with it.

Though I have no idea how I would straight up disable mesh culling in Babylon.

2 Likes

Okay last thing I am going to say about this.

You can disable built in culling with this:

  mesh.alwaysSelectAsActiveMesh = true;

And then you can cull the mesh itself with this:

  mesh.setEnabled(false);

So, in theory I could write my own culling strategy. Since I know the “true” position of the chunk mesh and how big it is.

2 Likes

Hi, I actually had to do something like this, and it does work. I ended up using the onBeforeActiveMeshesEvaluationObservable observable to trigger my logic.

Background on my culling reasoning (very different to yours) is here: Custom method to evaluate active meshes

4 Likes

All right cool thank you!
I will look into it.
I think it would not be that hard since the chunk mesh is literally an axis aligned box and all other entitles/meshes in the scene should have some sort of AABB as well.
It would just have to be calculated in terms of “voxel world space” instead of the “view space” .

2 Likes

Okay I just wanted to update this question one more time showing what I did.
This way if someone has my same problem they will have some ideas.

First thing is that I added a vec3 uniform to the shader called worldOrigin.
I pass the Floating Origin Central Transform Node position to the shader. In the docs it is referred to as an Entity.

material.setVector3( "worldOrigin",floatingOriginNode.position);

Then in the shader I add the origin to the mesh’s vertex position:

gl_Position =  worldViewProjection * vec4(position + worldOrigin, 1.0) ;

Then finally for custom culling I did this.
1:
Set alwaysSelectAsActiveMesh to true:

  mesh.alwaysSelectAsActiveMesh = true;

2:
Then for mesh culling I use one bounding box for all meshes and update it based on the floating origin position:

  const fallbackNode = new BABYLON.TransformNode("", scene);
  const min = new BABYLON.Vector3();
  const max = new BABYLON.Vector3();
  scene.onBeforeActiveMeshesEvaluationObservable.add(() => {
   const cam = scene.activeCamera;
   if (!cam) return;
   const node = FOManager.activeNode ? FOManager.activeNode : fallbackNode;
   const meshesLength = scene.meshes.length;
   for (let i = 0; i < meshesLength; i++) {
    const mesh = scene.meshes[i];
    if ((mesh as any).type == "chunk") {
     const position = mesh.position;
     min.set(
      node.position.x + position.x,
      node.position.y + position.y,
      node.position.z + position.z
     );
     max.set(
      node.position.x + position.x + 16,
      node.position.y + position.y + 16,
      node.position.z + position.z + 16
     );
     box.reConstruct(min, max);
     if (cam.isInFrustum(box)) {
      mesh.isVisible = true;
      continue;
     }
     mesh.isVisible = false;
    }
   }
  });

So, now the mesh’s worldMatrix never has to be updated. All I have to do is pass the origin to the shader.

This works perfectly and everything actually runs pretty fast.
I may in the future work on an even faster culling strategy but right now this works just fine.

2 Likes