Setting z index manually for transparent objects

I’m trying to add a number of partially transparent markers on the horizon between the main world and the sky, like this:

I can’t just make them part of the sky image because they move independently from each other every frame. They work fine as long as they aren’t near each other but when they are they z fight:

Now I thought I could fix this by explicitly setting the z order:

    viewPort.getQueue().setGeometryComparator(RenderQueue.Bucket.Transparent, new TransparentComparator(){
        @Override
        public int compare(Geometry o1, Geometry o2) {
            if(o1 instanceof ForcedOrderGeometry && o2 instanceof ForcedOrderGeometry){
                ForcedOrderGeometry fog1 = (ForcedOrderGeometry)o1;
                ForcedOrderGeometry fog2 = (ForcedOrderGeometry)o2;
                
                return fog1.getOrder() -  fog2.getOrder();
            }else{
                return super.compare(o1, o2);
            }
        }
        
    });

But that doesn’t seem to be making a difference.

I’ve tried to create a self contained example, it doesn’t quite look the same but it looks equally wrong, without a custom geometry comparator:

With a custom geometry comparator (arguably worse):

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.renderer.queue.TransparentComparator;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.shape.Quad;
import java.util.Random;

public class Main extends SimpleApplication {

    public static void main(String[] args) {
        Main app = new Main();
        app.start();
    }
    @Override
    public void simpleInitApp() {


        viewPort.getQueue().setGeometryComparator(RenderQueue.Bucket.Transparent, new TransparentComparator(){
            @Override
            public int compare(Geometry o1, Geometry o2) {
                if(o1 instanceof ForcedOrderGeometry && o2 instanceof ForcedOrderGeometry){
                    ForcedOrderGeometry fog1 = (ForcedOrderGeometry)o1;
                    ForcedOrderGeometry fog2 = (ForcedOrderGeometry)o2;

                    return fog1.getOrder() -  fog2.getOrder();
                }else{
                    return super.compare(o1, o2);
                }
            }

        });

        for(int i=0;i<10; i++){
            float angle = 0.05f * i;

            Geometry geo = createGeometry(i);

            float x = (500+i)*FastMath.sin(angle);
            float z = (500+i)*FastMath.cos(angle);

            this.rootNode.attachChild(geo);
            geo.setLocalTranslation(x, 0, z);
            geo.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);


        }
        cam.setLocation(new Vector3f(0,200,0));
        cam.lookAt(new Vector3f(0,0,500), Vector3f.UNIT_Y);

    }
    private Geometry createGeometry(int order){
        Random rnd = new Random(order);

        Mesh b = new Quad(100, 1000);

        Geometry geom = new ForcedOrderGeometry("forced", b, order);  
        Material mat = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");  
        mat.setColor("Color", new ColorRGBA(rnd.nextFloat(), rnd.nextFloat(), rnd.nextFloat(), 1));  
        mat.setTexture("ColorMap", assetManager.loadTexture("Textures/horizonImage.png"));
        mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
        geom.setQueueBucket(RenderQueue.Bucket.Transparent);
        geom.setMaterial(mat);  

        return geom;
    }

  public static class ForcedOrderGeometry extends Geometry {

    int order; 

    public  ForcedOrderGeometry(String name, Mesh mesh, int order){
        super(name,mesh);
        this.order = order;
    }

    public int getOrder() {
        return order;
    }

    }

}

I don’t really care about what the order is for these objects just as long as the order is consistent between frames and doesn’t z fight. What do I need to do to make some of the transparent items are always completely in front of others independent of the true z distance (I seem to need to make them quite far apart in real space to make them not z fight and that causes other issues.

Does

return fog2.getOrder() -  fog1.getOrder();

fix the issue?

It actually does, but that if anything makes me more confused about what setGeometryComparator does. I though I could use it to tell it any z order I liked and thats the z order it would use, which should have lead to the image having strange backwards ordering but otherwise looking fine (and as these are all conceptually at the same distance looking right) but I didn’t expect the occluding.

Why does one order seem to work and the other not? Obviously one is physically accurate and the other is not but the whole point was to ignore what was physically accurate

For correct alpha transparency, first draw the furthest object.
This way, the furthest geom is drawn, then the next one overlaps that one and so on.

If you use different order (closest first.)
The closest is drawn. The next one is drawn but no overlapping is done because the depth test fails.

1 Like

Ah, so GeometryComparator needs to be at least partially correct? GeometryComparator should just be considered a tie breaker?

(I know it’s not actually a tie breaker as it happens first but for my purposes it is, I can give it a different algoritem to “get it right” when z precision is an issue but I still need to get it right)

Yes, for correct alpha transparency, the objects got to be rendered, furthest first.

The default GeometryComparator in Transparent queue already sorts objects furthest first.

So theoretically, it should produce the correct results for you already. However this is not the case, since looking at the TransparentComparator, it sorts them furthest first based on the center of each object.

1 Like

Someone changed it then. It used to be based on the nearest point on the bounding volume or somesuch.

People waffle and change it thinking that there is a “right answer”. There is no proper Z sorting method… only some that work in some cases and not others. (It’s provably impossible to sort even triangles 100% correct using any method.)

But yes, transparent bucket needs “painter’s sort”, ie: back to front.

By the way, creating your own ForcedOrderGeometry is a super-ugly way to get the behavior that you want. What if you ever needed to extend geometry for some other reason. If you just want to tag a geometry with some behavior then just add a user data element. This is what Lemur’s layer-based geometry comparator does.

Edit: TransparentComparator does use nearest point… not sure what code you were checking:

Yes, that’s true, eg. two crossed planes can not be properly sorted in either way.

Yes, my mistake, i checked the distanceToCam2 method in TransparentComparator that is not at all used.

Thou, in his test case, neither, (closest edge and closest center) work. Since the objects are too close together and their bounds are overlapping.

Also, if you see actual “z-fighting” then it means those triangles actually have the same Z and sorting won’t really help you.

If it’s just a blending issue then sorting can help… but “z fighting” means something very specific related to the depth buffer.

Probably for these you should not be depth testing at all (or not depth writing) in which case you may find that order doesn’t even matter anymore.

I think I saw z fighting in the real game (tiny changes in camera angle/direction lead to different geometries (or parts of geometries) being the one in front?) but I couldn’t reproduce it in the toy example so possibly a different issue somewhere in my code.

“Probably for these you should not be depth testing at all (or not depth writing) in which case you may find that order doesn’t even matter anymore.”

That sounds fair, I tried mat.getAdditionalRenderState().setDepthTest(false); which made these merge beautifully but made them appear in front of everything else (which I suppose isn’t surprising). Is that what you meant?

I thought they were in the sky bucket… can they be put into the sky bucket?