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:
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:
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:
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.
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!!