Where to do OpenGL occlusion queries?

Hi all,

I'd like to do a visibility test and instead of re-doing the z-buffer test just pick the results from OpenGL. But I'm not sure if I understand it right. I tried to add in Geometry :



in preDraw(Renderer r):

GL15.glBeginQuery(GL15.GL_SAMPLES_PASSED, occquery);



in postDraw(Renderer r):

GL15.glEndQuery(GL15.GL_SAMPLES_PASSED);
GL15.glGetQueryObject(occquery, GL15.GL_QUERY_RESULT, samples);
renderedSamples = samples.get(0);



which finally gives me the number of samples per Geometry that passed the depth test. That almost works but not totally. It seems that some samples are still drawn even if a Geometry is entirely behind another one. So my questions are: is that the right place to do the queries ? is that a good idea ? what may be wrong with this ? and what could I do if this is indeed wrong ?

thanks :)

forget this

haha that's a bit abrupt ! Why do you say I should forget this ?



well I know what's wrong, it seems that the rendering order for Geometries does not respect any Z-ordering. For any viewpoint the rendering order is the same, therefore the occlusion queries provide wrong results. I may have a look on RenderQueue, this may be related to my problem.

Because messing with OpenGL while the jme scengraph also manages it is a bit dangerous and not how its supposed to be done. You should try and extend the jme renderer in a way that it does what you want.

well thanks ! it makes sense.



Btw I finally made it working :slight_smile: My problem was that the RenderQueue:

1- used an OpaqueComp sorting Spatials by their TextureState when they are Geometries instead of distanceToCam

2- did not recompute the queueDistance when computed once in distanceToCam

that's why the RenderQueue was not sorted with Z and hence my occlusion problem.



Now it works and yes you are right, I should try extending JME instead of modifying the core classes :wink:



Edit:

Well, actually I've got a problem extending the Renderer because its RenderQueue has private methods and private inner classes, so in this case I cannot do anything…

Well actually I posted without thinking first, then reread, and realized that I wrote crap XD

no problem empire phoenix :smiley:



however in fact my initial question is still not adressed. If I cannot/should not modify Geometry, what would be the place where I can do the occlusion query ? And how could I have my own RenderQueue implementation without reimplementing everything ? do you have any idea about that ?



do I have to totally let down this idea of occlusion queries ? (that would be a shame because it seems working fine ;))

Do a ray, if the ray's nearest intersection/collision is the object then you can see it

At lest this saves you from redoing Ztests

hi

yes why not a ray ? but in which direction ? you'll have to cast thousands rays to scan the whole frustum !

Hi all,

for those interested in visibility, I came up with an extension of LWJGLRenderer that performs the occlusion using the same technique than previously mentioned. It does not require any modification of Geometry. It may not be perfect but seems working :slight_smile:


package jmetests.visibility;

import java.nio.IntBuffer;
import java.util.*;

import org.lwjgl.opengl.GL15;

import com.jme.scene.*;
import com.jme.util.geom.BufferUtils;
import com.jme.renderer.lwjgl.LWJGLRenderer;

/**
 * An OcclusionLWJGLRenderer is a LWJGLRenderer that keeps track of the rendered pixels per
 * Geometry. This Renderer keeps a current render id to allow storing the rendered pixels for
 * several types of renderings (on screen, on texture, etc). For the current render id (default 0),
 * it stores the number of pixels drawn when the Geometry is rendered - currently works only for
 * TriMeshes.
 * @author Alexandre Denis
 */
public class OcclusionLWJGLRenderer extends LWJGLRenderer
{
   private int currentRenderId = 0;
   private Map<Integer, Map<Geometry, Integer>> renderedPixels; // <render id, <geom, rendered pixels>>


   /**
    * Creates a new OcclusionLWJGLRenderer with given width and height.
    * @param width
    * @param height
    */
   public OcclusionLWJGLRenderer(int width, int height)
   {
      super(width, height);
      renderedPixels = new HashMap<Integer, Map<Geometry, Integer>>();
      setCurrentRenderId(0);
   }


   @Override
   public void draw(TriMesh tris)
   {
      IntBuffer queries = BufferUtils.createIntBuffer(1);
      GL15.glGenQueries(queries);
      int occquery = queries.get(0);

      GL15.glBeginQuery(GL15.GL_SAMPLES_PASSED, occquery);

      super.draw(tris);

      GL15.glEndQuery(GL15.GL_SAMPLES_PASSED);

      IntBuffer samples = BufferUtils.createIntBuffer(1);
      GL15.glGetQueryObject(occquery, GL15.GL_QUERY_RESULT, samples);
      setRenderedPixels(tris, samples.get(0));

      GL15.glDeleteQueries(queries);
   }


   /**
    * Sets the number of rendered pixels of given Geometry.
    * @param geom the geometry
    * @param nb the number of pixels
    */
   private void setRenderedPixels(Geometry geom, int nb)
   {
      renderedPixels.get(currentRenderId).put(geom, nb);
   }


   /**
    * Returns the number of rendered pixels of given Geometry in the default render id.
    * @param geom the geometry
    * @return the number of rendered pixels or -1 if the Geometry was not rendered
    */
   public int getRenderedPixels(Geometry geom)
   {
      if (renderedPixels.containsKey(0) && renderedPixels.get(0).containsKey(geom))
         return renderedPixels.get(0).get(geom);
      else return -1;
   }


   /**
    * Returns the number of rendered pixels of given Geometry in the given render id.
    * @param geom the geometry
    * @param renderId the render id to retrieve the rendered pixels from
    * @return the number of rendered pixels or -1 if the Geometry was not rendered
    */
   public int getRenderedPixels(Geometry geom, int renderId)
   {
      if (renderedPixels.containsKey(renderId) && renderedPixels.get(renderId).containsKey(geom))
         return renderedPixels.get(renderId).get(geom);
      else return -1;
   }


   /**
    * Sets the current render id.
    * @param currentRenderId the current render id to set
    */
   public void setCurrentRenderId(int currentRenderId)
   {
      this.currentRenderId = currentRenderId;
      if (!renderedPixels.containsKey(currentRenderId))
         renderedPixels.put(currentRenderId, new HashMap<Geometry, Integer>());
   }


   /**
    * Returns the current render id.
    * @return the current render id
    */
   public int getCurrentRenderId()
   {
      return currentRenderId;
   }

   /**
    * Clears the color and depth buffers and resets the rendered pixels count for the current render id.
    */
   @Override
   public void clearBuffers()
   {
      super.clearBuffers();
      renderedPixels.put(currentRenderId,new HashMap<Geometry, Integer>());
   }
}



here is an example of use:

package jmetests.visibility;

import java.util.*;

import com.jme.app.SimpleGame;
import com.jme.math.Vector3f;
import com.jme.renderer.*;
import com.jme.scene.shape.Box;
import com.jme.scene.*;
import com.jme.system.JmeException;

/**
 * Tests the OcclusionLWJGLRenderer.
 * @author Alexandre Denis
 */
public class TestVisible extends SimpleGame
{
   private Set<Geometry> objects = new HashSet<Geometry>();
   private Map<Geometry, Integer> previous = new HashMap<Geometry, Integer>();
   
   /**
    * @param args
    */
   public static void main(String[] args)
   {
      TestVisible app = new TestVisible();
      // app.setConfigShowMode(ConfigShowMode.AlwaysShow);
      app.start();
   }


   @Override
   protected void initSystem() throws JmeException
   {
      super.initSystem();
      Renderer renderer = new OcclusionLWJGLRenderer(display.getWidth(), display.getHeight());
      renderer.setCamera(display.getRenderer().getCamera());
      display.setRenderer(renderer);
      display.getRenderer().setBackgroundColor(ColorRGBA.black);
   }


   @Override
   protected void simpleInitGame()
   {
      Box b1 = new Box("blue", new Vector3f(0, 0, 0), new Vector3f(10, 10, 10));
      Box b2 = new Box("red", new Vector3f(0, 0, 0), new Vector3f(20, 20, 20));
      b1.setLocalTranslation(new Vector3f(0, 0, 0));
      b2.setLocalTranslation(new Vector3f(0, 0, -30));
      b1.setSolidColor(ColorRGBA.blue);
      b2.setSolidColor(ColorRGBA.red);

      rootNode.attachChild(b1);
      rootNode.attachChild(b2);

      rootNode.setRenderQueueMode(Renderer.QUEUE_OPAQUE);

      objects.add(b1);
      objects.add(b2);
      lightState.detachAll();
   }


   /**
    * Displays the rendered pixels for each geometry if its visibility changed.
    */
   protected void simpleUpdate()
   {
      for(Geometry mesh : objects)
      {
         int pixels = ((OcclusionLWJGLRenderer) display.getRenderer()).getRenderedPixels(mesh);
         if (!previous.containsKey(mesh))
            previous.put(mesh, pixels);
         else if (previous.get(mesh) != pixels)
         {
            System.out.println(mesh + " " + pixels);
            previous.put(mesh, pixels);
         }
      }
   }
}



it works fine at me, however there is still this problem in RenderQueue. I had to modify it because it does not sort geometries according to Z which is required to do the occlusion queries. I was not able to extend it because of the private methods. Here is a patch if you want to test (I just commented two lines in distanceToCam and in OpaqueComp.compare):

### Eclipse Workspace Patch 1.0
#P jme
Index: src/com/jme/renderer/RenderQueue.java
===================================================================
--- src/com/jme/renderer/RenderQueue.java   (revision 4824)
+++ src/com/jme/renderer/RenderQueue.java   (working copy)
@@ -175,8 +175,8 @@
      * @return Distance from Spatial to camera.
      */
     private float distanceToCam(Spatial spat) {
-        if (spat.queueDistance != Float.NEGATIVE_INFINITY)
-                return spat.queueDistance;
+        /*if (spat.queueDistance != Float.NEGATIVE_INFINITY)
+                return spat.queueDistance;*/
         Camera cam = renderer.getCamera();
         spat.queueDistance = 0;
        
@@ -375,9 +375,9 @@
     private class OpaqueComp implements Comparator<Spatial> {
 
         public int compare(Spatial o1, Spatial o2) {
-            if (o1 instanceof Geometry && o2 instanceof Geometry) {
+            /*if (o1 instanceof Geometry && o2 instanceof Geometry) {
                 return compareByStates((Geometry) o1, (Geometry) o2);
-            }
+            }*/
            
             float d1 = distanceToCam(o1);
             float d2 = distanceToCam(o2);



I hope that'll work for you :D

Edit: I overrided the clearBuffers method, it seemed that with a bigger number of trimeshes this is necessary.