3D frustum culling

In a 3D world, we have a lot of objects everywhere. However, only a small number of objects will be visible in the scene. Rendering all objects, including those that are not visible, can be a waste of our processing time and resources and will affect the speed of the game. Hence, we should only render those objects that are actually visible to the camera and ignore all other objects that are outside the field of view of the camera. This is known as frustum culling and there are several ways to accomplish this.

First, let's add an array of cars. The updated scene will look like this:

3D frustum culling

The MyModelTest.java file is as follows:

public class MyModelTest extends ApplicationAdapter  {
...
   public Array<ModelInstance> instances = new Array<ModelInstance>();

   @Override
   public void create() {
          environment = new Environment();
          environment.set(new ColorAttribute(ColorAttribute.AmbientLight, 0.4f, 0.4f, 0.4f, 1f));
          environment.add(new DirectionalLight().set(0.8f, 0.8f, 0.8f, -1f, -0.8f, -0.2f));

          modelBatch = new ModelBatch();
          cam = new PerspectiveCamera(67, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
          cam.position.set(5, 20, 20);
          cam.lookAt(0, 0, 0);
          cam.near = 1f;
          cam.far = 100f;
          cam.update();

          assets = new AssetManager();
          assets.load("car.g3dj", Model.class);
          assets.finishLoading();
          model = assets.get("car.g3dj", Model.class);

          for (float x = -30; x <= 10f; x += 20) {
          for (float z = -30f; z <= 0f; z += 10f) {
                ModelInstance instance = new ModelInstance(model);
                instance.transform.setToTranslation(x, 0, z);
                instances.add(instance);
               }
          }

          camController = new CameraInputController(cam);
          Gdx.input.setInputProcessor(camController);

   }

   @Override
   public void render() {
          camController.update();

          Gdx.gl.glViewport(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
          Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT | GL20.GL_DEPTH_BUFFER_BIT);

          modelBatch.begin(cam);
          for (ModelInstance instance : instances) {
                      modelBatch.render(instance, environment);
          }
          modelBatch.end();


   }
   @Override
   public void dispose() {
          modelBatch.dispose();
          assets.dispose();
   }
...
}

The difference from our previous MyModelTest.java file is that we added an array of model instances instead of one. Note that there is only one model and 12 model instances. The position of the camera is also changed to (5, 20, 20). The updated code is highlighted. You can use the mouse or W, S, A, D keys to navigate. However, with the current code, every model instance is drawn, whether they are in the scene or not.

In order to check this, let's update the code and add some strings to the scene as follows:

   ...
   public OrthographicCamera orthoCam;
   public SpriteBatch spriteBatch;
   public BitmapFont font;
   public StringBuilder stringBuilder = new StringBuilder();

   @Override
   public void create() {
   ...
   orthoCam = new OrthographicCamera(Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
   orthoCam.position.set(Gdx.graphics.getWidth() / 2f, Gdx.graphics.getHeight() / 2f, 0);
          spriteBatch = new SpriteBatch();
          font = new BitmapFont();

   }

   @Override
   public void render() {
   ...
   modelBatch.begin(cam);
   int count = 0;
   for (ModelInstance instance : instances) {
          modelBatch.render(instance, environment);
          count++;
   }
   modelBatch.end();
   orthoCam.update();
   spriteBatch.setProjectionMatrix(orthoCam.combined);
   spriteBatch.begin();
   stringBuilder.setLength(0);
   stringBuilder.append("FPS: " + Gdx.graphics.getFramesPerSecond()).append("
") ;
   stringBuilder.append("Cars: " + count).append("
");
   stringBuilder.append("Total: " + instances.size).append("
");
   font.drawMultiLine(spriteBatch, stringBuilder, 0, Gdx.graphics.getHeight());
   spriteBatch.end();

}

Here, we add an orthographic camera to view 2D items in a 3D scene. Then, we print the FPS and total number of instances on the top-left corner of the game scene, as shown in the following screenshot:

3D frustum culling

Tip

Want some 2D in 3D?

In your 3D game, you might want to add some 2D images such as a score icon or play/pause or mute button or maybe a permanent background image in the scene. We can use the orthographic camera and sprite batch to render 2D objects in the scene, just like we rendered the text here.

Now, we can see that there are a number of cars rendering and the total cars available are 12, even after navigating the scene using the mouse or keys. Hence, regardless of where the camera is at the moment, the number of instances rendered in the scene stays 12. Now, it is time to implement frustum culling.

Note

A frustum can be seen as a shape like a pyramid in 3D space with the converging end at the camera and the body containing everything the camera can see.

Check this Wikipedia article on viewing frustum at http://en.wikipedia.org/wiki/Viewing_frustum.

Also, read this wonderful article to get a good understanding about frustum and camera at http://www.badlogicgames.com/wordpress/?p=1550.

LibGDX provides some very easy methods to check if an object is inside the frustum. Add the following code to MyModelTest.java:

private Vector3 position = new Vector3();
private boolean isVisible(final Camera cam, final ModelInstance instance) {
    instance.transform.getTranslation(position);
    return cam.frustum.pointInFrustum(position);
}

Here, we added Vector3 to hold the position. In the isVisible() method, we fetch the position of ModelInstance and next we check if that position is inside the frustum using the function pointInFrustum().

Add isVisible() to the render() function:

   @Override
   public void render() {
   ...
   modelBatch.begin(cam);
   int count = 0;
   for (ModelInstance instance : instances) {
         if (isVisible(cam, instance)) {
                modelBatch.render(instance, environment);
                count++;
         }
   }
   modelBatch.end();
   ...
   }

Now, you can run the scene and navigate. You will find that the number of cars will change according to the position of the camera.

In the preceding isVisible() function, we check whether the position of the car is inside. What if only a part of the car is within the frustum? In order to check this, LibGDX provides a function, boundsInFrustum, to check whether the bounding box is completely within the frustum. A bounding box is a box that contains the entire model/instance. The following screenshot will give you a clear picture of this:

3D frustum culling

So, we can update our isVisible() function to check the bounding box in the following way:

private boolean isVisible(PerspectiveCamera cam, ModelInstance instance) {
instance.transform.getTranslation(position);
BoundingBox box = instance.calculateBoundingBox(new BoundingBox());
return cam.frustum.boundsInFrustum(position, box.getDimensions());
}

The calculateBoundingBox method will return the bounding box of that particular instance. Note, this function is a slow operation; hence, it would be better if you cache the result.

Similarly, we can calculate the bounding sphere. Checking against a radius is a bit faster, but it might cause more false positives, as shown here:

float radius = box.getDimensions().len()/2f ;
cam.frustum.sphereInFrustum(position, radius);

Here, radius is the radius of the bounding sphere.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.190.239.166