Arbitrary Quadrilaterals in OpenGL ES 2.0

This article presents an approach to drawing arbitrary quadrilaterals using OpenGL ES 2.0 with a full working example for Android. Android java source code for drawing arbitrary quadrilaterals in OpenGL ES 2.0.

If you are familiar with game development and drawing 2D sprites in OpenGL ES 2.0 on a mobile platform like Android or iOS, you will know it's easy to scale and rotate images using affine transformations.

However, if you take the following texture

and then try and map it to the following coordinates representing a non-affine transformation

you will end up with an image that has a visible seam as below

but what you really want is something that looks like this

which is only possible by ensuring your vertex coordinates are an affine transformation of your texture coordinates in 2D to get the GPU to render the texture mapping correctly without a seam.

Fixing OpenGL ES 2.0 Using a Projective Transformation

To render the image without distortion we have to modified the fragment shader and vertex shader to accept three coordinates rather than two coordinates for a 2D sprite.

Instead of passing each vertex as a \((u, v)\) coordinate in 2D space, the trick is to pass in homogeneous coordinates \((qu, qv, q)\) to the vertex shader and normalize them back to \((u, v)\) in the fragment shader. The \(q\) values are chosen so that \(qu\) and \(qv\) can be interpolated by the GPU correctly. It just so happens that there are \(q\) values which effectively map the 2D non-affine transformation to a 3D affine transformation in homogeneous coordinates.

The following approach happens to be equivalent to a projective transformation where \(q\) represents a projective divide.

Calculating q Values

Let \(a = p_2 - p_0\), \(b = p_3 - p_1\), \(c = p_0 - p_1\)

Let \(s = \frac{b \times c}{b \times a}\), \(t = \frac{a \times c}{b \times a}\)

Then \(q_0 = \frac{1}{1 - s}\), \(q_1 = \frac{1}{1 - t}\), \(q_2 = \frac{1}{s}\), \(q_3 = \frac{1}{t}\)

This gives us two ways to express \((u, v)\) for a vertex \(p\). In 2D \(p_n = (u_n, v_n)\) and in homogeneous coordinates \(p_n = (q_nu_n, q_nv_n, q_n)\) where \(n = 0, 1, 2, 3\).

Homogeneous Coordinate Calculation in Java

The Java code to calculate \(q_n\) is:

float ax = p2x - p0x;
float ay = p2y - p0y;
float bx = p3x - p1x;
float by = p3y - p1y;

float cross = ax * by - ay * bx;

if (cross != 0) {
  float cy = p0y - p1y;
  float cx = p0x - p1x;

  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 q0 = 1 / (1 - t);
      float q1 = 1 / (1 - s);
      float q2 = 1 / t;
      float q3 = 1 / s;

      // you can now pass (u * q, v * q, q) to OpenGL
    }
  }
}

The Projective Mapping Vertex Shader

Here is the vertex shader for OpenGL ES 2.0:

attribute vec2 a_Position;
attribute vec3 a_Region;
varying vec3 v_Region;
uniform mat3 u_World;

void main()
{
  v_Region = a_Region;
  vec3 xyz = u_World * vec3(a_Position, 1);
  gl_Position = vec4(xyz.xy, 0, 1);
}

The Projective Mapping Fragment Shader

Here is the fragment shader for OpenGL ES 2.0 where \((x, y, z)\) on the vector \(\textit{v_Region}\) contains \((qu, qv, q)\).

precision mediump float;
varying vec3 v_Region;
uniform sampler2D u_TextureId;

void main()
{
  gl_FragColor = texture2D(u_TextureId, v_Region.xy / v_Region.z);
}

If you are a 2D game developer targeting mobile platforms, this will allow you to draw 2D sprites mapped to any arbitrary coordinates.