Texturing issue

Hey all,

This is more of a generic OpenGL question, not specific to JOGL, but since I am using JOGL…

I am trying to recreate the Corner Pin Effect that is found in After Effects. You can see it in action here: http://livedocs.adobe.com/en_US/AfterEffects/8.0/help.html?content=WS3878526689cb91655866c1103a9d3c597-7b88.html

It is basically a perspective distortion kind of thing, but not quite. Anyways, when I tried to do it in GL, by mapping a video frame to a textured quad, what I got is a little different:

http://matadata.com/texture.jpg

Basically, if you notice, what seems to be happening is that my quad is being broken up as two triangles, which are then textured. Obviously, this is not quite what I am looking for, since (in the case of the given example) the lower left corner’s texture doesn’t get stretched if I pull the upper right corner. Does anyone have any idea how I could get that corner pin effect? or (more implausibly) force GL to render real quads?

thanks,

c.

If OpenGL rendered a quad like that it wouldn’t be a consistent polygon since the deltas wouldn’t be constant (I think), and I’m not too sure about this because you may be able to achieve the effect using two triangle polygons by altering with z. However you can fix this by creating your shape out of 4 triangle polygons with one point being in the centre.

EDIT: And I’m only up right now because I can’t sleep, so there will be no chance of me putting on my geometric hat tonight to calculate a way of doing this using Z (if it were possible)

Hi,
Did you simply try to render a GL polygon with appropriate texture coordinates.
I may be missing something but it should work no?

He’s trying to make the corner pin effect, but my suggestion should sort it.

Yes you’re right… I always forgot that polygons have to be in a single plane…
Too much work in 2D.

It didn’t quite work out, but thanks for the suggestion.

I was trying to recreate the effect from memory, but upon installing a copy of After Effects and seeing what the effect does, it is clear that it does perspective distortion, and not just some hokey texture distortion like I was trying to do. Thus, what really needs to happen (I think) is that given the 4 points of a quadrilateral I need to find a matrix transformation that fits it. I am no math person, so I have no idea how to do this, but my hunch is that I need to to exactly the opposite of what openGL does on a regular basis with vertices. That is, instead of taking a set of points and transforming them, I need to find the transformation that would give me the set of points I want. Yes? No? Maybe? Suggestions more than welcome.

c.

Hmmm yes, after closer inspection the centre point just wouldn’t work. It does use the centre point as a pivot for the quad, which is assumed to be z=0 which each corner corner intersects with its opposite. Maybe ask the gimp guys how they did it, but I suspect it’s something like the difference in the corners distance from the centre point producing the differing Z-Values.

I found this while searching in google:

http://books.google.com/books?id=q49lhAzXTFEC&pg=PA128&lpg=PA128&dq=finding+transformation+of+a+quadrilateral&source=web&ots=VssZ-kDK2E&sig=-p8zfHPgmOxy0FPIppbqsKNhuH0#PPA129,M1

which seems to do what I want. Now to dust off that math book…

Sounds about right, although (a) the code is already in the open sourced Gimp package and (b) there has got to be much more simpler docs explaining it. That is more of a definition of the maths behind the operation with proofs of its correctness.

But please please find a solution, I have this feeling that if you don’t quick enough I’ll get sidetracked into trying to solve it; in fact I think I’ll add it to my TTD.

This looks helpful: http://www.mgix.com/snippets/?CornerPin. Maybe you can adapt it and just use the created corner pin matrix as opengl projection matrix?

keldon: I looked at the Gimp code, but I couldn’t figure out how to use their matrix transformation in GL… they seem to be quite different, but then again, my math knowledge is almost null.

cylab: Thanks for that!

I turned that into Java, but I am having some issues with it. Namely, it doesn’t seem to work right. I must have done something wrong at some point. You can download the files here:

http://matadata.com/glTest.zip

In there you will find:
M4.java: a static class containing the functions of the functions from snippets
TextureTest2.java: an example, based on the TextureTest that comes with JOGL. Load a texture with the menu. It will have an additional window where you can specify the destination coordinates for the corner pinning. “dst0” is the lower left corner, “dst1” is the lower right, and so on. Press the button to update. You will notice that things don’t work as expected.

Sorry I didn’t package those into an executable, I don’t know how to make JNLP’s. Just drop them into your favorite IDE if you can.

Thanks for the help.

c.

Question: why does the centre-point solution not work?
I think if you keep your centre-point always on the intersection between the two diagonals, you’ll have what you’re looking for, no?

I checked the files and tried to manually transform the corner points like in the C example, which resulted in the correct transformation, so the matrix seem to be right. I also found, that the code uses the matrix in row major order while opengl afaik needs column major, so I added a method to swap the matrix order to M4:


	public static void swapMajor(double[] matrix)
	{
		swap(matrix,1,4);
		swap(matrix,2,8);
		swap(matrix,3,12);
		swap(matrix,6,9);
		swap(matrix,7,13);
		swap(matrix,11,14);
	}

	private static void swap(double[] matrix, int a, int b)
	{
		double e= matrix[a]; matrix[a]=matrix[b]; matrix[b]=e;
	}

Unfortunately that didn’t help :confused: One thing from the example is, that the resulting point is devided by the W component, but afaik opengl should do the same in the transformation pipeline or am I wrong?

For the time being you could use the following workaround. Replace the glBegin(GL.GL_QUAD)…glEnd()-block of your display()-method with the following code:


	int tilesPerSide=16;
	for(int y=0; y<tilesPerSide;y++)
	{
		for(int x=0; x<tilesPerSide;x++)
		{
			gl.glBegin(GL.GL_TRIANGLE_FAN);
			{
				double[] c=  {((float)(x+0.5))/tilesPerSide,((float)(y+0.5))/tilesPerSide,0,1};
				double[] tl= {((float)x)/tilesPerSide,((float)y)/tilesPerSide,0,1};
				double[] tr= {((float)(x+1))/tilesPerSide,((float)y)/tilesPerSide,0,1};
				double[] br= {((float)(x+1))/tilesPerSide,(((float)y+1))/tilesPerSide,0,1};
				double[] bl= {((float)x)/tilesPerSide,(((float)y+1))/tilesPerSide,0,1};

				M4.trsf(c, matrix, c);   c[0]  /= c[3];  c[1]  /= c[3];
				M4.trsf(tl, matrix, tl); tl[0] /= tl[3]; tl[1] /= tl[3];
				M4.trsf(tr, matrix, tr); tr[0] /= tr[3]; tr[1] /= tr[3];
				M4.trsf(br, matrix, br); br[0] /= br[3]; br[1] /= br[3];
				M4.trsf(bl, matrix, bl); bl[0] /= bl[3]; bl[1] /= bl[3];

				// center
				gl.glTexCoord2f(((float)(x+0.5))/tilesPerSide,1f-((float)(y+0.5))/tilesPerSide);
				gl.glVertex3f((float)c[0], (float)c[1], 0f);

				// top left
				gl.glTexCoord2f(((float)x)/tilesPerSide,1f-((float)y)/tilesPerSide);
				gl.glVertex3f((float)tl[0], (float)tl[1], 0f);

				// top right
				gl.glTexCoord2f(((float)(x+1))/tilesPerSide,1f-((float)y)/tilesPerSide);
				gl.glVertex3f((float)tr[0], (float)tr[1], 0f);

				// bottom right
				gl.glTexCoord2f(((float)(x+1))/tilesPerSide,1f-((float)(y+1))/tilesPerSide);
				gl.glVertex3f((float)br[0], (float)br[1], 0f);

				// bottom left
				gl.glTexCoord2f(((float)x)/tilesPerSide,1f-((float)(y+1))/tilesPerSide);
				gl.glVertex3f((float)bl[0], (float)bl[1], 0f);

				// top left
				gl.glTexCoord2f(((float)x)/tilesPerSide,1f-((float)y)/tilesPerSide);
				gl.glVertex3f((float)tl[0], (float)tl[1], 0f);
			}
			gl.glEnd();
		}
	}

It creates a grid of 16x16 quads as triangle-fans with a center point each and transforms all vertices by the corner pin matrix. this gives a reasonable result, so it might serve your needs.

Edit: Fixes wrong texture coordinates in the code

cylab, thanks for that, but it still doesn’t do corner pinning properly.

There is no need for all of those triangles… just imaging a quad and using the resulting matrix from the M4 class gives the same result in my tests. I think that the problem is the corner pinning routine. Proper corner pinning requires the corners that are not altered are “kept in place.” The m4.pin() routine doesn’t seem to do that.

As far as row vs. column ordering of the matrix, that can be fixed in GL by using glLoadTransposeMatrixd. Using that vs. your added swapping function gives the same results.

I’m stumped. I’ll try hacking around the gimp’s code, but that is a 3x3 instead of a 4x4 matrix… so far my experiments have yielded nothing.

thanks,

c.

??? I tested it, maybe you still have the glMatrixMul in your code? Here is the complete working display-method:


public void display(GLAutoDrawable drawable) {
    GL gl = drawable.getGL();

    gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

    if (file != null) {
        try { texture = TextureIO.newTexture(file, true); }
        catch (GLException e) { e.printStackTrace(); }
        catch (IOException e) { e.printStackTrace(); }
    }

    if (texture != null) {
        texture.enable();
        texture.bind();
        gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_REPLACE);

        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();

        gl.glTranslatef(-0.5f, -0.5f, -3f);
        M4.id(matrix);
        M4.pin(matrix, dst0, dst1, dst2, dst3, src0, src1, src2, src3);

        gl.glColor3f(0, 1, 1);
        int tilesPerSide = 16;
        for (int y = 0; y < tilesPerSide; y++) {
            for (int x = 0; x < tilesPerSide; x++) {
                gl.glBegin(GL.GL_TRIANGLE_FAN);
                {
                    double[] c = {((float) (x + 0.5)) / tilesPerSide, ((float) (y + 0.5)) / tilesPerSide, 0, 1};
                    double[] tl = {((float) x) / tilesPerSide, ((float) y) / tilesPerSide, 0, 1};
                    double[] tr = {((float) (x + 1)) / tilesPerSide, ((float) y) / tilesPerSide, 0, 1};
                    double[] br = {((float) (x + 1)) / tilesPerSide, (((float) y + 1)) / tilesPerSide, 0, 1};
                    double[] bl = {((float) x) / tilesPerSide, (((float) y + 1)) / tilesPerSide, 0, 1};

                    M4.trsf(c, matrix, c);
                    c[0] /= c[3];
                    c[1] /= c[3];
                    M4.trsf(tl, matrix, tl);
                    tl[0] /= tl[3];
                    tl[1] /= tl[3];
                    M4.trsf(tr, matrix, tr);
                    tr[0] /= tr[3];
                    tr[1] /= tr[3];
                    M4.trsf(br, matrix, br);
                    br[0] /= br[3];
                    br[1] /= br[3];
                    M4.trsf(bl, matrix, bl);
                    bl[0] /= bl[3];
                    bl[1] /= bl[3];

                    // center
                    gl.glTexCoord2f(((float) (x + 0.5)) / tilesPerSide, 1f - ((float) (y + 0.5)) / tilesPerSide);
                    gl.glVertex3f((float) c[0], (float) c[1], 0f);

                    // top left
                    gl.glTexCoord2f(((float) x) / tilesPerSide, 1f - ((float) y) / tilesPerSide);
                    gl.glVertex3f((float) tl[0], (float) tl[1], 0f);

                    // top right
                    gl.glTexCoord2f(((float) (x + 1)) / tilesPerSide, 1f - ((float) y) / tilesPerSide);
                    gl.glVertex3f((float) tr[0], (float) tr[1], 0f);

                    // bottom right
                    gl.glTexCoord2f(((float) (x + 1)) / tilesPerSide, 1f - ((float) (y + 1)) / tilesPerSide);
                    gl.glVertex3f((float) br[0], (float) br[1], 0f);

                    // bottom left
                    gl.glTexCoord2f(((float) x) / tilesPerSide, 1f - ((float) (y + 1)) / tilesPerSide);
                    gl.glVertex3f((float) bl[0], (float) bl[1], 0f);

                    // top left
                    gl.glTexCoord2f(((float) x) / tilesPerSide, 1f - ((float) y) / tilesPerSide);
                    gl.glVertex3f((float) tl[0], (float) tl[1], 0f);
                }
                gl.glEnd();
            }
        }
        texture.disable();
    }
}

Since the corner-pin is a non-linear projection and this workaround does only transform the vertices, the grid is needed because the texture coordinates are interpolated linear between the vertices. You can reduce the tilesPerSide variable to inspect the behaviour.

Hey, that looks good!

I realized last night that what I was asking is not really possible as a modelview matrix transformation… but your routine makes it look just like the real thing!

I am trying to do some sort of real time After Effects-like system, mostly for video projections, and corner pinning is very important when you are dealing with projections on odd surfaces and in off-angles. My system will enable Java2D animations and Quicktime videos to be plugged as textures and then be composited and distorted in real time. I am not really a great coder, but if I get something going that I am not totally ashamed of I’ll post it somewhere.

Thanks a lot!