Let's keep doing more interesting things! We've seen how to add a color per vertex, but let's see now what do we have to change if we want to add some textures to our 3D object.
First, let's replace the color array with a texture coordinate array. We'll map texture coordinate 0 to the start of our texture, in both axes, and 1 to the end of the texture, also in both axes. Using the geometry we had in our previous example, we could then define the texture coordinates this way:
private float texCoords[] = { 1.f, 1.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, 1.f, 1.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f, };
To load these texture coordinates, we use exactly the same procedure as we did previously:
ByteBuffer tbb = ByteBuffer.allocateDirect(texCoords.length * (Float.SIZE / 8)); tbb.order(ByteOrder.nativeOrder()); texBuffer = tbb.asFloatBuffer(); texBuffer.put(texCoords); texBuffer.position(0);
Let's also create a helper method to load a resource into a texture:
private int loadTexture(int resId) { final int[] textureIds = new int[1]; GLES20.glGenTextures(1, textureIds, 0); if (textureIds[0] == 0) return -1; // do not scale the bitmap depending on screen density final BitmapFactory.Options options = new BitmapFactory.Options(); options.inScaled = false; final Bitmap textureBitmap =
BitmapFactory.decodeResource(getResources(), resId, options); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureIds[0]); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_NEAREST); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, textureBitmap, 0); textureBitmap.recycle(); return textureIds[0]; }
We have to take into account that both texture dimensions have to be to the power of 2. To preserve the original size of the image and avoid any scaling done by Android, we have to set the bitmap options inScaled flag to false. In the previous code, we generate a texture ID to hold the reference to our texture, binding it as the active texture, setting the parameters of filtering and wrapping, and finally loading the bitmap data. Once we've done so, we can recycle the temporary bitmap, as we don't need it anymore.
As we did before, we have to update our shaders as well. In our vertexShader, we have to apply almost the same changes as we did before, adding an attribute where we can set the vertex texture coordinate and a varying variable to pass to the fragmentShader:
private final String vertexShaderCode = "uniform mat4 uMVPMatrix;" + "attribute vec4 vPosition;" + "attribute vec2 aTex;" + "varying vec2 vTex;" + "void main() {" + " gl_Position = uMVPMatrix * vPosition;" + " vTex = aTex;" + "}";
Note that the vertex coordinates are a vec2 instead of a vec4, as we only have two coordinates: U and V. Our new fragmentShader is a bit more complex than the one we had before:
private final String fragmentShaderCode = "precision mediump float;" + "uniform sampler2D sTex;" + "varying vec2 vTex;" + "void main() {" + " gl_FragColor = texture2D(sTex, vTex);" + "}";
We have to create the varying texture coordinate variable and also a uniform sampler2D variable where we'll set the active texture. To get the color, we have to use the texture2D lookup function to read the color data from the texture on the specified coordinates.
Let's now add a bitmap named texture.png to our drawables res folder and modify the onSurfaceCreated() method to load it as a texture:
@Override public void onSurfaceCreated(GL10 unused, EGLConfig config) { initBuffers(); initShaders(); textureId = loadTexture(R.drawable.texture); startTime = SystemClock.elapsedRealtime(); }
Here is the image used in our example:
![](assets/52a4bc51-0f65-4acb-917f-1d025ff08a65.png)
Finally, let's update the onDrawFrame() method to set the texture coordinates:
int texCoordHandle = GLES20.glGetAttribLocation(shaderProgram, "aTex"); GLES20.glVertexAttribPointer(texCoordHandle, 2, GLES20.GL_FLOAT, false, 0, texBuffer);
Here is the texture itself:
int texHandle = GLES20.glGetUniformLocation(shaderProgram, "sTex"); GLES20.glActiveTexture(GLES20.GL_TEXTURE0); GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId); GLES20.glUniform1i(texHandle, 0);
Also, as we did before, we have to enable, and later disable, the texture coordinates vertex array.
If we run this code, we'll get the following:
![](assets/157987b0-6971-4066-ab7d-707b4325c097.png)
Check the Example25-GLDrawing on the GitHub repository for the full example source code.