Learn OpenGLES 2

Use an image as texture map and render it on the screen.

Source code: https://github.com/chuzcjoe/learnopengles

2D texture is the most common texture type in OpenGLES. It is essentially a 2D image that adds details to the surface of an object. Texture coordinates range from 0 to 1. The figure below shows the texture coordinates and vertex coordinates in OpenGLES.

If we want to render a texture image, we can set the vertices and texture coordinates to be:

1
2
3
4
5
6
7
8
9
10
11
12
13
GLfloat vertices[] = {
-1.0f, 0.5f, 0.0f, // Top left
-1.0f, -0.5f, 0.0f, // Bottom left
1.0f, -0.5f, 0.0f, // Bottom right
1.0f, 0.5f, 0.0f, // Top right
};

GLfloat textureCoords[] = {
0.0f, 0.0f, // top left
0.0f, 1.0f, // bottom left
1.0f, 1.0f, // bottom right
1.0f, 0.0f // top right
};

OpenGL uses the winding order of the vertices to determine the “front” and “back” faces of a triangle. The default winding order is counter-clockwise (CCW) for front faces. So, when drawing a rectangle using two triangles, we can specify the order to be:

1
GLushort indices[] = { 0, 1, 2, 0, 2, 3 };

Generate 2D texture in OpenGL

1
2
3
4
5
6
7
8
9
10
// generate a texture
glGenTextures(1, &mTextureID);
// set the texture object as the current activate texture object
glBindTexture(GL_TEXTURE_2D, mTextureID);
// set texture wrapping
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, GL_NONE);

Load an RGBA image as texture.

On Kotlin side, we will load an jpeg as a bitmap, convert it to byteArray, and pass it down to native C++.

1
2
3
4
5
6
7
8
9
10
11
12
private fun loadBitmap(resId: Int): Bitmap? {
val fileStream = this.resources.openRawResource(resId)
val bitmap: Bitmap? = BitmapFactory.decodeStream(fileStream)
if (bitmap != null) {
val buf = ByteBuffer.allocate(bitmap.byteCount)
bitmap.copyPixelsToBuffer(buf)
val byteArray = buf.array()
mGLRender.setImageData(IMAGE_FORMAT_RGBA, bitmap.width, bitmap.height, byteArray)
Log.d(TAG, "image width: ${bitmap.width}, image height: ${bitmap.height}")
}
return bitmap
}
1
2
3
4
5
6
7
8
9
extern "C"
JNIEXPORT void JNICALL
Java_com_example_opengles_gl_MyNativeRender_native_1setImageData(JNIEnv *env, jobject thiz, jint pixel_format, jint width, jint height, jbyteArray bytes) {
jsize length = env->GetArrayLength(bytes);
uint8_t* buffer = new uint8_t[length];
env->GetByteArrayRegion(bytes, 0, length, reinterpret_cast<jbyte*>(buffer));
GLContext::getInstance()->setImageData(pixel_format, width, height, buffer);
delete[] buffer;
}

We define a NativeImage struct to hold our image data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct NativeImage {
int width;
int height;
int format;
uint8_t* planes[3];

NativeImage() {
width = 0;
height = 0;
format = 0;
planes[0] = nullptr;
planes[1] = nullptr;
planes[2] = nullptr;
}
};

Generate texture image.

1
2
3
4
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, mTextureID);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, mImage.width, mImage.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, mImage.planes[0]);
glBindTexture(GL_TEXTURE_2D, GL_NONE);

Vertex/Fragment shaders

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Texture Load Shader
const char* TextureLoadVertexShader = VERTEX_SHADER(
layout(location = 0) in vec3 aPosition;
layout(location = 1) in vec2 aTexCoord;
out vec2 vTexCoord;
void main() {
gl_Position = vec4(aPosition, 1.);
vTexCoord = aTexCoord;
}
);

const char* TextureLoadFragmentShader = FRAGMENT_SHADER(
precision mediump float;
in vec2 vTexCoord;
uniform sampler2D sTexture;
out vec4 FragColor;
void main() {
FragColor = texture(sTexture, vTexCoord);
}
);

We added a new item in the sample selection. The image will be rendered as a texture map.

References

Author

Joe Chu

Posted on

2024-07-24

Updated on

2024-07-26

Licensed under

Comments