Texture Coordinate Problem

I am working on some polish now, trying to get things ready for a full release. I have a texture rendering problem I need some advice on.

I have a blue line running down the center of my tarmac texture. I am using a 16x16 sized texture atlas and the blue is the color of the texture right next to the tarmac one. This seems to be a floating point error? I’m not sure. Any tips on how to track down this problem? I can post the shader code if that would be helpful in analyzing whats going on.

When I examined all the texture coordinates I’m sending to the shader they are all just multiples of 0.0625 because it is a 16x16 map. Which I checked because I thought maybe it was a floating point error in the texture coordinates I was sending.

I really need to figure this out and I’m at a loss at how to approach further debugging.

Thanks, Mithrin

1 Like

See what happens if you turn off the min/mag filters.

1 Like

Yes, this is probably an approximation error. Take a look at castings on your code.

Is this rabbit hole the beginnings of what mip-mapping is all about?

It depends. Regardless of mip-mapping, filtering can blur the edges.

…which may not really be the issue but turning off filtering will tell us something either way.

For the above screenshot I’ve got the texture set like this.

texture.setMagFilter(Texture.MagFilter.Nearest);
texture.setMinFilter(Texture.MinFilter.NearestNoMipMaps);

I’m pretty sure the above settings disabled the min/mag filters. Any other thoughts?

Thanks

Yeah, that proves there must be some funky math in your code somewhere, I guess. Or maybe you hit the limits of floating point accuracy. (It’s quite likely, for example, that 0.0625 is actually 0.062499999999)

It should be pretty trivial to put together a simple test case that proves this one way or another.

Because all that being said, I’ve never hit this specific issue that it didn’t turn out to be an error somewhere on my part. I wasn’t ever dividing textures that small, though.

I have created a simple test case.

I didn’t know the best way send it so I just zipped the project folder which includes a test texture and the source file.

Let me know if you’d prefer another method like github or something.

Thanks

Can’t you just offset the texcoords inwards by the inverse of the error? Doesn’t really seem like a specific issue and something that’s bound to always happen with textures anyway.

Yes floating point errors exist, so it is and we have to live with it.

Generally, a simple test case is one class that can be posted to the forum… with maybe a link to data if necessary.

Hopefully folks will have a time to download and look at your project.

Sure thing :smiley:

just didn’t know how you’d like it.

link to texture

package mygame;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Mesh;
import com.jme3.scene.Node;
import com.jme3.scene.VertexBuffer;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Quad;
import com.jme3.system.AppSettings;
import com.jme3.texture.Texture;
import com.jme3.util.BufferUtils;
import java.util.prefs.BackingStoreException;

/**
 * This is the Main Class of your Game. You should only do initialization here.
 * Move your Logic into AppStates or Controls
 *
 * @author wobblytrout
 */
public class Main extends SimpleApplication {

    private static AppSettings settings = new AppSettings(true);

    public static void main(String[] args) throws BackingStoreException {
        Main app = new Main();

        settings.setWidth(1024);
        settings.setHeight(576);

        settings.setVSync(true);
        settings.load("Simple Test Case");
        settings.setTitle("Simple Test Case");

        settings.setMinResolution(1024, 576);
        settings.setAudioRenderer(null);

        app.setShowSettings(false);
        app.setSettings(settings);

        app.start();
    }

    @Override
    public void simpleInitApp() {

        // create a grid of quad objects and apply part of the texture from the texture atlas we are troubleshooting.
        // set the camera to be controlled by the keyboard
        getFlyByCamera().setEnabled(false);

        getCamera().setLocation(new Vector3f(0f, 0f, 250f));

        Node geomNode = new Node("geomNode");
        
        for (int x = 0; x < 64; x++) {
            for (int y = 0; y < 64; y++) {
                Geometry geom = createTestQuad();
                // quads are 16x16
                geom.setLocalTranslation(x * 16f, y * 16f, 0f);
                geomNode.attachChild(geom);
            }
        }

        geomNode.setLocalTranslation(-512,-512,0);
        
        rootNode.attachChild(geomNode);

    }

    private Geometry createTestQuad() {
        // my game uses 16x16 sized tiles.
        Quad quad = new Quad(16f, 16f);
        Geometry geom = new Geometry("Quad", quad);
        Mesh mesh = geom.getMesh();
        Vector2f[] textureCoordinatesArray = new Vector2f[4];
        // 1, 4 is the position in the texture we wish to test...        
        textureCoordinatesArray[0] = new Vector2f(0.1875f, 0.0625f);
        textureCoordinatesArray[1] = new Vector2f(0.250f, 0.0625f);
        textureCoordinatesArray[2] = new Vector2f(0.250f, 0.125f);
        textureCoordinatesArray[3] = new Vector2f(0.1875f, 0.125f);
        mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(textureCoordinatesArray));
        Material mat = setupQuadMaterial();
        geom.setMaterial(mat);
        return geom;
    }

    private Material setupQuadMaterial() {
        Material meshMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        Texture texture = assetManager.loadTexture("Textures/terrain.png");
        texture.setMagFilter(Texture.MagFilter.Nearest);
        texture.setMinFilter(Texture.MinFilter.NearestNoMipMaps);
        meshMaterial.setTexture("ColorMap", texture);
        return meshMaterial;
    }

    @Override
    public void simpleUpdate(float tpf) {
        //TODO: add update code
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }
}

And the results of running the test case

private Geometry createTestQuad() {
	
	float unit = 0.1875f;
	
	float x = 1*unit+0.001f;
	float y = 0*unit+0.001f;
	
    // my game uses 16x16 sized tiles.
    Quad quad = new Quad(16f, 16f);
    Geometry geom = new Geometry("Quad", quad);
    Mesh mesh = geom.getMesh();
    Vector2f[] textureCoordinatesArray = new Vector2f[4];
    // 1, 4 is the position in the texture we wish to test...        
    textureCoordinatesArray[0] = new Vector2f(x,y);
    textureCoordinatesArray[1] = new Vector2f(x,y + unit*0.99f);
    textureCoordinatesArray[2] = new Vector2f(x + unit*0.99f,y + unit*0.99f);
    textureCoordinatesArray[3] = new Vector2f(x + unit*0.99f,y);
    
    
    mesh.setBuffer(VertexBuffer.Type.TexCoord, 2, BufferUtils.createFloatBuffer(textureCoordinatesArray));
    Material mat = setupQuadMaterial();
    geom.setMaterial(mat);
    return geom;
}

Also your unit is kinda weird, 384x384? That doesn’t divide 2048 very well.

Ok, so I ended up discussing this at length on the discord with @MoffKalast and a few others.

Seems my options to fix this are this.

  • add padding between texture altas elements. In this case a 1 pixel repeat of the texture itself would work. So minimum 2 pixels of padding.

  • do some weird half pixel round stuff in the frag shader?

  • implement texture arrays and use that instead.

My question now is texture atlas vs texture arrays? Which is a better idea moving forward? Is there any performance reason to stick with a texture atlas instead of a texture array?

@pspeed thank you for your help man, I really appreciate it. Your libraries are awesome. I have learned so much from your examples and libraries I really appreciate what you’ve done.

Thanks,

Mithrin

Your welcome. Glad you are getting use out of them.

…makes it all worthwhile to see cool games using a little of my stuff.

Talking about texture array performance: When i changed my pbr terrain shader from a lot of diffuse samplers to texture array, i recognized a little drain of performance, i lost around 5-10fps in average (i was measuring in ms but that was not much).

I am glad that i changed to it - it makes some thing much easier( global snow /noise texture in the array). But here some things you will have to keep in mind:

  • You will duplicate the “data”, if you use the base textures in other places (They aren’t shared anymore, because you have to assign the data to the texture array). This will lead to the point where you could do the same as i’ve done, by having all textures in one huge texture array.
  • All elements in the texture array have the same format and dimensions (e.g. RGBA8 2048x2048). This can introduce the need of converting all textures.
  • You will stick with OpenGL Version >=3.0

It’s kind of funny how opengl v1.0 lacks this.

Like was there somebody on the dev team like “Hey we should probably support arrays of all data types that we have, like every programming language in existence you know?” and some other guy with more authority was like “Nah, who’ll ever need arrays of textures, that’s just mad!”

Perhaps they just didn’t anticipate Apple being such dicks.

2 Likes

I don’t think anyone anticipated that. :stuck_out_tongue:

2 Likes