27

I have drawn a textured trapezoid, however the result does not appear as I had intended.

Instead of appearing as a single unbroken quadrilateral, a discontinuity occurs at the diagonal line where its two comprising triangles meet.

This illustration demonstrates the issue:
Illustration demonstrating the source texture (a checkered pattern), the expected result, and the actual (incorrect) result
(Note: the last image is not intended to be a 100% faithful representation, but it should get the point across.)

The trapezoid is being drawn using GL_TRIANGLE_STRIP in OpenGL ES 2.0 (on an iPhone). It's being drawn completely facing the screen, and is not being tilted (i.e. that's not a 3D sketch you're seeing!)

I have come to understand that I need to perform "perspective correction," presumably in my vertex and/or fragment shaders, but I am unclear how to do this.

My code includes some simple Model/View/Projection matrix math, but none of it currently influences my texture coordinate values. Update: The previous statement is incorrect, according to comment by user infact.

Furthermore, I have found this tidbit in the ES 2.0 spec, but do not understand what it means:

The PERSPECTIVE CORRECTION HINT is not supported because OpenGL ES 2.0 requires that all attributes be perspectively interpolated.

How can I make the texture draw correctly?


Edit: Added code below:

// Vertex shader
attribute vec4 position;
attribute vec2 textureCoordinate;

varying vec2 texCoord;

uniform mat4 modelViewProjectionMatrix;

void main()
{
    gl_Position = modelViewProjectionMatrix * position;
    texCoord = textureCoordinate;
}

// Fragment shader
uniform sampler2D texture;
varying mediump vec2 texCoord;

void main()
{
    gl_FragColor = texture2D(texture, texCoord);
}

// Update and Drawing code (uses GLKit helpers from iOS)

- (void)update
{
    float fov = GLKMathDegreesToRadians(65.0f);
    float aspect = fabsf(self.view.bounds.size.width / self.view.bounds.size.height);
    projectionMatrix = GLKMatrix4MakePerspective(fov, aspect, 0.1f, 50.0f);
    viewMatrix = GLKMatrix4MakeTranslation(0.0f, 0.0f, -4.0f); // zoom out
}

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glUseProgram(shaders[SHADER_DEFAULT]);

    GLKMatrix4 modelMatrix = GLKMatrix4MakeScale(0.795, 0.795, 0.795); // arbitrary scale

    GLKMatrix4 modelViewMatrix = GLKMatrix4Multiply(viewMatrix, modelMatrix);

    GLKMatrix4 modelViewProjectionMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);
    glUniformMatrix4fv(uniforms[UNIFORM_MODELVIEWPROJECTION_MATRIX], 1, GL_FALSE, modelViewProjectionMatrix.m);

    glBindTexture(GL_TEXTURE_2D, textures[TEXTURE_WALLS]);
    glUniform1i(uniforms[UNIFORM_TEXTURE], 0);

    glVertexAttribPointer(ATTRIB_VERTEX, 3, GL_FLOAT, GL_FALSE, 0, wall.vertexArray);
    glVertexAttribPointer(ATTRIB_TEXTURE_COORDINATE, 2, GL_FLOAT, GL_FALSE, 0, wall.texCoords);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, wall.vertexCount);

}
elbowpa
  • 498
  • 1
  • 5
  • 9
  • Did the shape originate as a quad which has been transformed and projected, or is this an arbitrary 2D shape? The easiest solution would be to just render a tilted quad in 3D... – JasonD Mar 06 '13 at 09:49
  • You should check UV for second triangle.Looks like texture shift – Michael IV Mar 06 '13 at 10:00
  • 1
    Show us your code. Looks somewhat _backshortened_... – Stefan Hanke Mar 06 '13 at 10:19
  • Your projection matrix could be at fault. This DOES influence interpolation. –  Mar 06 '13 at 10:32
  • @StefanHanke I have added my vertex shader, fragment shader, draw and update code snippets. Please let me know if this *still* feels backshortened. – elbowpa Mar 06 '13 at 11:12

3 Answers3

21

(I'm taking a bit of a punt here, because your picture does not show exactly what I would expect from texturing a trapezoid, so perhaps something else is happening in your case - but the general problem is well known)

Textures will not (by default) interpolate correctly across a trapezoid. When the shape is triangulated for drawing, one of the diagonals will be chosen as an edge, and while that edge is straight through the middle of the texture, it is not through the middle of the trapezoid (picture the shape divided along a diagonal - the two triangles are very much not equal).

You need to provide more than a 2D texture coordinate to make this work - you need to provide a 3D (or rather, projective) texture coordinate, and perform the perspective divide in the fragment shader, post-interpolation (or else use a texture lookup function which will do the same).

The following shows how to provide texture coordinates for a trapezoid using old-school GL functions (which are a little easier to read for demonstration purposes). The commented-out lines are the 2d texture coordinates, which I have replaced with projective coordinates to get the correct interpolation.

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0,640,0,480,1,1000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

const float trap_wide = 600;
const float trap_narrow = 300;
const float mid = 320;

glBegin(GL_TRIANGLE_STRIP);
glColor3f(1,1,1);

//  glTexCoord4f(0,0,0,1);
glTexCoord4f(0,0,0,trap_wide);
glVertex3f(mid - trap_wide/2,10,-10);

//  glTexCoord4f(1,0,0,1);
glTexCoord4f(trap_narrow,0,0,trap_narrow);
glVertex3f(mid - trap_narrow/2,470,-10);

//  glTexCoord4f(0,1,0,1);
glTexCoord4f(0,trap_wide,0,trap_wide);
glVertex3f(mid + trap_wide/2,10,-10);

//  glTexCoord4f(1,1,0,1);
glTexCoord4f(trap_narrow,trap_narrow,0,trap_narrow);
glVertex3f(mid + trap_narrow/2,470,-10);

glEnd();

The third coordinate is unused here as we're just using a 2D texture. The fourth coordinate will divide the other two after interpolation, providing the projection. Obviously if you divide it through at the vertices, you'll see you get the original texture coordinates.

Here's what the two renderings look like:

enter image description here

If your trapezoid is actually the result of transforming a quad, it might be easier/better to just draw that quad using GL, rather than transforming it in software and feeding 2D shapes to GL...

JasonD
  • 16,464
  • 2
  • 29
  • 44
  • Unfortunately `GL_QUAD` is not available in OpenGL ES 2.0. Can this approach be adapted to work with `GL_TRIANGLE_STRIP` and ES 2.0? Specifically, I just tried to implement the above and am not sure how to use the additional two texture coordinates in my shader. – elbowpa Mar 06 '13 at 11:52
  • Quad is an irrelevance, it could be two triangles. Of the additional two texture coordinates, one is unused, the other should be used to divide the first two, as with an perspective transform. – JasonD Mar 06 '13 at 11:56
  • 2
    I'm trying to apply your example above to my test code. At this point, I've discovered that I can use `texture2DProj()` in my fragment shader in lieu of dividing by texCoord.w. I think this is the solution, and now I just need to get it to work. – elbowpa Mar 06 '13 at 13:00
  • This worked. I found my final error was leaving the texture coordinate values like (1, 0, 0, trap_narrow) instead of (trap_narrow, 0, 0, trap_narrow). Thanks for your help! – elbowpa Mar 06 '13 at 13:48
  • While this does appear to do exactly as you've described, I am introduced to a new possible problem when I have two adjacent trapezoids whose textures I wish to align: http://i.stack.imgur.com/NySBP.png It seems I'd need to shorten the upper trapezoid in that image to make it look like one continuous piece. This seems like a novice matter of determining exactly what it is I'd like to do. – elbowpa Mar 07 '13 at 00:31
  • 6
    Can anyone tell me how to translate these to a GLSL vertex shader and a fragment shader? – Timothy Groote Sep 04 '13 at 12:22
  • Are the `u` & `v` coords of the 2nd and 3rd entries swapped in the answer? If not I don't see how this correlates with the original texture coords. Shouldn't the higher `y` value correlate to `v=1`, not `u=1` (in the orig tex coords)? – Hari Honor Sep 09 '14 at 06:28
0

What you are trying here is Skewed texture. A sample fragment shader is as follows :

precision mediump float;
varying vec4 vtexCoords;
uniform sampler2D sampler;

void main()
{
   gl_FragColor = texture2DProj(sampler,vtexCoords);
}

2 things which should look different are :

1) We are using varying vec4 vtexCoords; . Texture co-ordinates are 4 dimensional. 2) texture2DProj() is used instead of texture2D()

Based on length of small and large side of your trapezium you will assign texture co-ordinates. Following URL might help : http://www.xyzw.us/~cass/qcoord/

maverick9888
  • 498
  • 6
  • 16
0

The accepted answer gives the correct solution and explanation but for those looking for a bit more help on the OpenGL (ES) 2.0 pipeline...

const GLfloat L = 2.0;
const GLfloat Z = -2.0;
const GLfloat W0 = 0.01;
const GLfloat W1 = 0.10;

/** Trapezoid shape as two triangles. */
static const GLKVector3 VERTEX_DATA[] = {
    {{-W0,    0,  Z}},
    {{+W0,    0,  Z}},
    {{-W1,    L,  Z}},

    {{+W0,    0,  Z}},
    {{+W1,    L,  Z}},
    {{-W1,    L,  Z}},
};

/** Add a 3rd coord to your texture data.  This is the perspective divisor needed in frag shader */
static const GLKVector3 TEXTURE_DATA[] = {
    {{0, 0, 0}},
    {{W0, 0, W0}},
    {{0, W1, W1}},

    {{W0, 0, W0}},
    {{W1, W1, W1}},
    {{0, W1, W1}},
};

////////////////////////////////////////////////////////////////////////////////////

// frag.glsl

varying vec3 v_texPos;
uniform sampler2D u_texture;

void main(void) 
{
    // Divide the 2D texture coords by the third projection divisor
    gl_FragColor = texture2D(u_texture, v_texPos.st / v_texPos.p);
}

Alternatively, in the shader, as per @maverick9888's answer, You can use texture2Dproj though for iOS / OpenGLES2 it still only supports a vec3 input...

void main(void) 
{
    gl_FragColor = texture2DProj(u_texture, v_texPos);
}

I haven't really benchmarked it properly but for my very simple case (a 1d texture really) the division version seems a bit snappier.

Hari Honor
  • 8,677
  • 8
  • 51
  • 54
  • I would expect the division version to be slower than the `texture2DProj` version, because it causes a dependent texture read. – Tenfour04 Feb 03 '15 at 14:07
  • 1
    http://stackoverflow.com/a/27028431/506796 On mobile GPUs, a lot of optimization is lost if a call to read from a texture uses anything but a directly-passed in vector (or a vec 4's pq components). – Tenfour04 Feb 05 '15 at 15:10
  • 1
    No optimization lost with [Apple A7 up](https://developer.apple.com/library/ios/documentation/DeviceInformation/Reference/iOSDeviceCompatibility/OpenGLESPlatforms/OpenGLESPlatforms.html): `The Apple A7, A8, and A9 GPUs do not penalize dependent-texture fetches.` – aledalgrande Oct 10 '15 at 09:00