Search code examples
opengl2dglsllwjgltexture-mapping

Projective interpolation of textures in 2D trapeziums with OpenGL


I have to map a physical cube with the image displayed by a projector from my computer. I immediately became aware of the problem of using a 3D cube and camera projections to try to fit the virtual image on the real object, so I decided to simplify the problem and use a 2D representation of a cube and to move each vertex in 2D space until it fitted the object. The software is now complete except for a little detail: textures.

I'm using LWJGL and I've based my data structures on some of the examples on their documentation.

I used 7 vertices to represent the 3 visible faces of the real cube in 2D space, this creates 3 trapeziums (or irregular quadrilaterals) that cover each face, I mapped them with the ST (or UV) coordinates you see in this image, remember this is all in 2D:

test texture showing the mapping coordinates

Here's a code block where I load my vertex data into a buffer, it's all saved into a VBO with the order XYZWRGBASTPQ (note that Z is always 0, PQ are 0,1 by default):

vertices = new VertexData[7];
vertices[0] = new VertexData().setXY(0, 0).setST(0.5f, 0.5f);
vertices[1] = new VertexData().setXY(0.5f, 1f/3).setST(0.5f, 0);
vertices[2] = new VertexData().setXY(0, 2f/3).setST(0, 0);
vertices[3] = new VertexData().setXY(-0.5f, 1f/3).setST(0, 0.5f);
vertices[4] = new VertexData().setXY(-0.5f, -1f/3).setST(0, 1);
vertices[5] = new VertexData().setXY(0, -2f/3).setST(0.5f, 1);
vertices[6] = new VertexData().setXY(0.5f, -1f/3).setST(1, 1);

0 is the center of the hexagon, from there is 1-6, counter-clockwise from the top-right corner, it is drawn as a triangle fan.

Everything is fine while the trapezoid is still a parallelogram, this is another texture test:

another texture test

But the problem is when the sides aren't equal, the texture is projected over triangles so when it tries to project the texture, it doesn't look as expected:

awful texture projection

I read a lot on the subject, some of it:

Getting to know the Q texture coordinate...

Perspective correct texturing of trapezoid in OpenGL ES 2.0

Quadrilateral Interpolation, Part 1

So far nothing has gotten me close to fixing the problem, all I know is that is has something to do with the Q coordinate of the texture.

My vertex shader is the bare minimum:

#version 150 core

in vec4 in_color;
in vec4 in_position;
in vec4 in_texture;

out vec4 pass_color;
out vec4 pass_texture;

void main(void) {
    pass_color = in_color;
    pass_texture = in_texture;
    gl_Position = in_position;
}

and my fragment shader is really simple, right now I'm using textureProj because I was trying to fiddle around with the Q coordinate.

#version 330 core

in vec4 pass_color;
in vec4 pass_texture;
out vec4 color;

uniform sampler2D texture_diffuse;

void main(void) {
    color = pass_color;
    color = textureProj(texture_diffuse, pass_texture);
}

I'm willing to post every piece of code in case you need it. I just need a push in the right direction.


Solution

  • So, after much lurking in the Internet I stumbled upon a solution, unfortunately the way I was UV mapping the triangle fan was incompatible with it, so I had to transform my triangle fan in a set of 3 quadrilaterals made from two triangles each, I made this by duplicating shared vertices between the faces:

    VertexData[] vertices = new VertexData[12];
    int i = 0;
    for(VertexData vert : oldVertices){
        vertices[i++] = new VertexData(vert);
    }
    vertices[7] = new VertexData(vertices[1]);
    vertices[7].setST(1, 0.5f);
    
    vertices[8] = new VertexData(vertices[0]);
    vertices[9] = new VertexData(vertices[3]);
    vertices[10] = new VertexData(vertices[0]);
    vertices[11] = new VertexData(vertices[5]);
    
    int[][] faces = {
        {0, 1, 2, 3},
        {4, 11, 10, 9},
        {6, 7, 8, 5}
    };
    
    if(qMapping) TextureUtils.qMapFaces(vertices, faces);
    

    With this information I can now use BitLush's algorithm, that basically calculates the Q coordinate interpolation based in the difference of each face's diagonals:

    public static void qMapFaces(VertexData[] vertices, int[][] faces){
        for(int[] face : faces){
            VertexData[] verts = new VertexData[4];
            int j = 0;
            for(int i : face){
                verts[j++] = vertices[i];
            }
    
            float ax = verts[2].getXYZ()[0] - verts[0].getXYZ()[0];
            float ay = verts[2].getXYZ()[1] - verts[0].getXYZ()[1];
            float bx = verts[3].getXYZ()[0] - verts[1].getXYZ()[0];
            float by = verts[3].getXYZ()[1] - verts[1].getXYZ()[1];
    
            float cross = ax * by - ay * bx;
    
            if(cross != 0) {
                float cx = verts[0].getXYZ()[0] - verts[1].getXYZ()[0];
                float cy = verts[0].getXYZ()[1] - verts[1].getXYZ()[1];
    
                float s = (ax * cy - ay * cx) / cross;
    
                if(s > 0 && s < 1){
                    float t = (bx * cy - by * cx) / cross;
    
                    if(t > 0 && t < 1){
                        float[] qi = new float[4];
                        qi[0] = 1 / (1 - t);
                        qi[1] = 1 / (1 - s);
                        qi[2] = 1 / t;
                        qi[3] = 1 / s;
    
                        int q = 0;
                        for(VertexData vertex : verts){
                            float[] stpq = vertex.getSTPQ();
                            vertex.setSTPQ(stpq[0] * qi[q], stpq[1] * qi[q], 0, qi[q]);
                            q++;
                        }
                    }
                }
            }
        }
    }
    

    After interpolating the Q coordinate, the projection improves greatly, but because I can't control the interpolation in two faces at once, textures don't align too well in the edges. This is not a problem for my project but if someone finds a way to solve it please contact me!!

    the before and after the Q mapping