├── assets └── Grid.png ├── README.md └── ArbitraryQuadrilateralsActivity.java /assets/Grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitlush/android-arbitrary-quadrilaterals-in-opengl-es-2-0/HEAD/assets/Grid.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Arbitrary Quadrilaterals in OpenGL ES 2.0 for Android 2 | ===================================================== 3 | 4 | This is the Android java source code that accompanies the bitlush article: http://www.bitlush.com/posts/arbitrary-quadrilaterals-in-opengl-es-2-0 5 | 6 | Demonstrates non-affine texture mapping of sprites using a projective transformation represented in homogeneous coordinates. 7 | -------------------------------------------------------------------------------- /ArbitraryQuadrilateralsActivity.java: -------------------------------------------------------------------------------- 1 | import android.app.Activity; 2 | import android.graphics.Bitmap; 3 | import android.graphics.BitmapFactory; 4 | import android.opengl.GLES20; 5 | import android.opengl.GLUtils; 6 | import android.os.Bundle; 7 | import android.opengl.GLSurfaceView; 8 | import android.opengl.GLSurfaceView.Renderer; 9 | import javax.microedition.khronos.egl.EGLConfig; 10 | import javax.microedition.khronos.opengles.GL10; 11 | import java.nio.ByteBuffer; 12 | import java.nio.ByteOrder; 13 | import java.nio.FloatBuffer; 14 | import java.nio.ShortBuffer; 15 | 16 | public class ArbitraryQuadrilateralsActivity extends Activity implements Renderer { 17 | private GLSurfaceView view; 18 | int[] status = new int[1]; 19 | int program; 20 | FloatBuffer attributeBuffer; 21 | ShortBuffer indicesBuffer; 22 | short[] indicesData; 23 | float[] attributesData; 24 | private int[] textureIds = new int[1]; 25 | int textureId; 26 | int attributePosition; 27 | int attributeRegion; 28 | 29 | @Override 30 | public void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | 33 | view = new GLSurfaceView(this); 34 | view.setEGLContextClientVersion(2); 35 | view.setRenderer(this); 36 | 37 | setContentView(view); 38 | } 39 | 40 | @Override 41 | public void onPause() { 42 | view.onPause(); 43 | 44 | super.onPause(); 45 | } 46 | 47 | @Override 48 | public void onResume() { 49 | super.onResume(); 50 | 51 | view.onResume(); 52 | } 53 | 54 | public void onDrawFrame(GL10 gl) { 55 | GLES20.glClearColor(1f, 1f, 1f, 1f); 56 | GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); 57 | 58 | GLES20.glActiveTexture(GLES20.GL_TEXTURE0); 59 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); 60 | 61 | drawNonAffine(100, 100, 600, 100, 500, 400, 200, 600); 62 | 63 | attributeBuffer.position(0); 64 | attributeBuffer.put(attributesData); 65 | 66 | attributeBuffer.position(0); 67 | GLES20.glVertexAttribPointer(attributePosition, 2, GLES20.GL_FLOAT, false, 5 * 4, attributeBuffer); 68 | GLES20.glEnableVertexAttribArray(attributePosition); 69 | 70 | attributeBuffer.position(2); 71 | GLES20.glVertexAttribPointer(attributeRegion, 3, GLES20.GL_FLOAT, false, 5 * 4, attributeBuffer); 72 | GLES20.glEnableVertexAttribArray(attributeRegion); 73 | 74 | indicesBuffer.position(0); 75 | GLES20.glDrawElements(GLES20.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_SHORT, indicesBuffer); 76 | } 77 | 78 | public void onSurfaceCreated(GL10 gl, EGLConfig config) { 79 | String vertexShaderSource = 80 | "attribute vec2 a_Position;" + 81 | "attribute vec3 a_Region;" + 82 | "varying vec3 v_Region;" + 83 | "uniform mat3 u_World;" + 84 | "void main()" + 85 | "{" + 86 | " v_Region = a_Region;" + 87 | " vec3 xyz = u_World * vec3(a_Position, 1);" + 88 | " gl_Position = vec4(xyz.xy, 0, 1);" + 89 | "}"; 90 | 91 | String fragmentShaderSource = 92 | "precision mediump float;" + 93 | "varying vec3 v_Region;" + 94 | "uniform sampler2D u_TextureId;" + 95 | "void main()" + 96 | "{" + 97 | " gl_FragColor = texture2D(u_TextureId, v_Region.xy / v_Region.z);" + 98 | "}"; 99 | 100 | attributeBuffer = ByteBuffer.allocateDirect(5 * 4 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(); 101 | attributesData = new float[5 * 4]; 102 | 103 | indicesBuffer = ByteBuffer.allocateDirect(6 * 2).order(ByteOrder.nativeOrder()).asShortBuffer(); 104 | indicesData = new short[] { 0, 1, 2, 2, 3, 0 }; 105 | 106 | indicesBuffer.position(0); 107 | indicesBuffer.put(indicesData); 108 | 109 | program = loadProgram(vertexShaderSource, fragmentShaderSource); 110 | textureId = loadTexture("Grid.png"); 111 | 112 | GLES20.glUseProgram(program); 113 | 114 | float width = view.getWidth(); 115 | float height = view.getHeight(); 116 | 117 | float world[] = new float[] { 118 | 2f / width, 0, 0, 119 | 0, 2f / height, 0, 120 | -1f, -1f, 1 121 | }; 122 | 123 | int uniformWorld = GLES20.glGetUniformLocation(program, "u_World"); 124 | int uniformTextureId = GLES20.glGetUniformLocation(program, "u_TextureId"); 125 | 126 | GLES20.glUniformMatrix3fv(uniformWorld, 1, false, world, 0); 127 | GLES20.glUniform1i(uniformTextureId, 0); 128 | 129 | attributePosition = GLES20.glGetAttribLocation(program, "a_Position"); 130 | attributeRegion = GLES20.glGetAttribLocation(program, "a_Region"); 131 | } 132 | 133 | public void setFilters(int minFilter, int magFilter) { 134 | GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, minFilter); 135 | GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, magFilter); 136 | } 137 | 138 | public void setWrapping(int wrapS, int wrapT) { 139 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, wrapS); 140 | GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, wrapT); 141 | } 142 | 143 | public void drawNonAffine(float bottomLeftX, float bottomLeftY, float bottomRightX, float bottomRightY, float topRightX, float topRightY, float topLeftX, float topLeftY) { 144 | float ax = topRightX - bottomLeftX; 145 | float ay = topRightY - bottomLeftY; 146 | float bx = topLeftX - bottomRightX; 147 | float by = topLeftY - bottomRightY; 148 | 149 | float cross = ax * by - ay * bx; 150 | 151 | boolean rendered = false; 152 | 153 | if (cross != 0) { 154 | float cy = bottomLeftY - bottomRightY; 155 | float cx = bottomLeftX - bottomRightX; 156 | 157 | float s = (ax * cy - ay * cx) / cross; 158 | 159 | if (s > 0 && s < 1) { 160 | float t = (bx * cy - by * cx) / cross; 161 | 162 | if (t > 0 && t < 1) { 163 | //uv coordinates for texture 164 | float u0 = 0; // texture bottom left u 165 | float v0 = 0; // texture bottom left v 166 | float u2 = 1; // texture top right u 167 | float v2 = 1; // texture top right v 168 | 169 | int bufferIndex = 0; 170 | 171 | float q0 = 1 / (1 - t); 172 | float q1 = 1 / (1 - s); 173 | float q2 = 1 / t; 174 | float q3 = 1 / s; 175 | 176 | attributesData[bufferIndex++] = bottomLeftX; 177 | attributesData[bufferIndex++] = bottomLeftY; 178 | attributesData[bufferIndex++] = u0 * q0; 179 | attributesData[bufferIndex++] = v2 * q0; 180 | attributesData[bufferIndex++] = q0; 181 | 182 | attributesData[bufferIndex++] = bottomRightX; 183 | attributesData[bufferIndex++] = bottomRightY; 184 | attributesData[bufferIndex++] = u2 * q1; 185 | attributesData[bufferIndex++] = v2 * q1; 186 | attributesData[bufferIndex++] = q1; 187 | 188 | attributesData[bufferIndex++] = topRightX; 189 | attributesData[bufferIndex++] = topRightY; 190 | attributesData[bufferIndex++] = u2 * q2; 191 | attributesData[bufferIndex++] = v0 * q2; 192 | attributesData[bufferIndex++] = q2; 193 | 194 | attributesData[bufferIndex++] = topLeftX; 195 | attributesData[bufferIndex++] = topLeftY; 196 | attributesData[bufferIndex++] = u0 * q3; 197 | attributesData[bufferIndex++] = v0 * q3; 198 | attributesData[bufferIndex++] = q3; 199 | 200 | rendered = true; 201 | } 202 | } 203 | } 204 | 205 | if (!rendered) { 206 | throw new RuntimeException("Shape must be concave and vertices must be clockwise."); 207 | } 208 | } 209 | 210 | public void onSurfaceChanged(GL10 gl, int width, int height) { 211 | GLES20.glViewport(0, 0, width, height); 212 | } 213 | 214 | private int loadTexture(String assetName) { 215 | Bitmap bitmap; 216 | 217 | try { 218 | bitmap = BitmapFactory.decodeStream(getAssets().open(assetName)); 219 | } 220 | catch (Exception e) { 221 | throw new RuntimeException("Couldn't load image '" + assetName + "'.", e); 222 | } 223 | 224 | if (bitmap == null) { 225 | throw new RuntimeException("Couldn't load image '" + assetName + "'."); 226 | } 227 | 228 | GLES20.glGenTextures(1, textureIds, 0); 229 | 230 | int textureId = textureIds[0]; 231 | 232 | if (textureId == 0) { 233 | throw new RuntimeException("Could not generate texture."); 234 | } 235 | 236 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); 237 | 238 | setFilters(GLES20.GL_LINEAR, GLES20.GL_LINEAR); 239 | setWrapping(GLES20.GL_CLAMP_TO_EDGE, GLES20.GL_CLAMP_TO_EDGE); 240 | 241 | GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0); 242 | GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); 243 | 244 | bitmap.recycle(); 245 | 246 | return textureId; 247 | } 248 | 249 | private int loadProgram(String vertexShaderSource, String fragmentShaderSource) { 250 | int id = GLES20.glCreateProgram(); 251 | 252 | int vertexShaderId = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderSource); 253 | int fragmentShaderId = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderSource); 254 | 255 | GLES20.glAttachShader(id, vertexShaderId); 256 | GLES20.glAttachShader(id, fragmentShaderId); 257 | GLES20.glLinkProgram(id); 258 | GLES20.glDeleteShader(vertexShaderId); 259 | GLES20.glDeleteShader(fragmentShaderId); 260 | GLES20.glGetProgramiv(id, GLES20.GL_LINK_STATUS, status, 0); 261 | 262 | if (status[0] == 0) { 263 | String log = GLES20.glGetProgramInfoLog(id); 264 | 265 | GLES20.glDeleteProgram(id); 266 | 267 | throw new RuntimeException("Shader error:" + log); 268 | } 269 | 270 | return id; 271 | } 272 | 273 | private int loadShader(int type, String source) { 274 | int id = GLES20.glCreateShader(type); 275 | 276 | GLES20.glShaderSource(id, source); 277 | GLES20.glCompileShader(id); 278 | GLES20.glGetShaderiv(id, GLES20.GL_COMPILE_STATUS, status, 0); 279 | 280 | if (status[0] == 0) { 281 | String log = GLES20.glGetShaderInfoLog(id); 282 | 283 | GLES20.glDeleteShader(id); 284 | 285 | throw new RuntimeException("Shader error:" + log); 286 | } 287 | 288 | return id; 289 | } 290 | 291 | private void checkGlError(String op) { 292 | int error; 293 | 294 | if ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { 295 | throw new RuntimeException("GL error code: " + error + " for " + op + "."); 296 | } 297 | } 298 | } 299 | --------------------------------------------------------------------------------