1

I wanted to draw a square with OpenGL ES 2.0 and put a dynamic text on it. I am trying to combine the instructions in this post (which I had to port to OpenGL ES 2.0) and the four lesson of Learn OpenGL ES Tutorial.

I have an Activity just using a GLSurfaceView:

public class TexturedSquareDrawActivity extends Activity {

    private GLSurfaceView mGLView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mGLView = new MyGLSurfaceViewTexture(this);
        setContentView(mGLView);
    }
}

My GLSurfaceView just created the renderer and sets it:

public class MyGLSurfaceViewTexture extends GLSurfaceView {
    private final MyGLRendererTexture mRenderer;

    public MyGLSurfaceViewTexture(Context context){
        super(context);

        // Create an OpenGL ES 2.0 context
        setEGLContextClientVersion(2);

        mRenderer = new MyGLRendererTexture(context);

        // Set the Renderer for drawing on the GLSurfaceView
        setRenderer(mRenderer);
    }
}

Then I define a TextureSquare class like this:

public class TexturedSquare {

    private final Context mContext;
    private FloatBuffer vertexBuffer;
    private ShortBuffer drawListBuffer;

    private int mProgram;

    private final String vertexShaderCode =
            // This matrix member variable provides a hook to manipulate
            // the coordinates of the objects that use this vertex shader
            "uniform mat4 uMVPMatrix;" +
                    "attribute vec4 vPosition;" +
                    "attribute vec2 a_TexCoordinate;" +
                    "varying vec2 v_TexCoordinate;" +
                    "void main() {" +
                    // the matrix must be included as a modifier of gl_Position
                    // Note that the uMVPMatrix factor *must be first* in order
                    // for the matrix multiplication product to be correct.
                    "  gl_Position = uMVPMatrix * vPosition;" +
                    "  v_TexCoordinate = a_TexCoordinate;" +
                    "}";

    private final String fragmentShaderCode =
            "precision mediump float;" +
                    "uniform sampler2D u_Texture;" +
                    "uniform vec4 vColor;" +
                    "varying vec2 v_TexCoordinate;" +
                    "void main() {" +
//                    "  gl_FragColor = vColor;" +
                    "  gl_FragColor = vColor * texture2D(u_Texture, v_TexCoordinate);" +
                    "}";
    private int mMVPMatrixHandle;


    private int mPositionHandle;
    private int mColorHandle;


    // number of coordinates per vertex in this array
    static final int COORDS_PER_VERTEX = 3;

    private short drawOrder[] = {0, 1, 2, 0, 2, 3}; // order to draw vertices


    private final float[] mColor;

    private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex


    /**
     * Store our model data in a float buffer.
     */
    private final FloatBuffer mCubeTextureCoordinates;

    /**
     * This will be used to pass in the texture.
     */
    private int mTextureUniformHandle;

    /**
     * This will be used to pass in model texture coordinate information.
     */
    private int mTextureCoordinateHandle;

    /**
     * Size of the texture coordinate data in elements.
     */
    private final int mTextureCoordinateDataSize = 2;

    /**
     * This is a handle to our texture data.
     */
    private int mTextureDataHandle;

    // S, T (or X, Y)
    // Texture coordinate data.
    // Because images have a Y axis pointing downward (values increase as you move down the image) while
    // OpenGL has a Y axis pointing upward, we adjust for that here by flipping the Y axis.
    // What's more is that the texture coordinates are the same for every face.
    final float[] cubeTextureCoordinateData =
            {
                    // Front face
                    0.0f, 0.0f,
                    0.0f, 1.0f,
                    1.0f, 0.0f,
                    0.0f, 1.0f,
                    1.0f, 1.0f,
                    1.0f, 0.0f,
            };

    public TexturedSquare(Context context, final float[] squareCoords, final float[] color) {
        mContext = context;
        mColor = color;

        // initialize vertex byte buffer for shape coordinates
        ByteBuffer bb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 4 bytes per float)
                squareCoords.length * 4);
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(squareCoords);
        vertexBuffer.position(0);

        // initialize byte buffer for the draw list
        ByteBuffer dlb = ByteBuffer.allocateDirect(
                // (# of coordinate values * 2 bytes per short)
                drawOrder.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);

        mCubeTextureCoordinates = ByteBuffer.allocateDirect(cubeTextureCoordinateData.length * 4)
                .order(ByteOrder.nativeOrder()).asFloatBuffer();
        mCubeTextureCoordinates.put(cubeTextureCoordinateData).position(0);

        linkShaderCode();
    }

    private void linkShaderCode() {
        int vertexShader = MyGLRendererTexture.loadShader(GLES20.GL_VERTEX_SHADER,
                vertexShaderCode);
        int fragmentShader = MyGLRendererTexture.loadShader(GLES20.GL_FRAGMENT_SHADER,
                fragmentShaderCode);

        // create empty OpenGL ES Program
        mProgram = GLES20.glCreateProgram();

        // add the vertex shader to program
        GLES20.glAttachShader(mProgram, vertexShader);

        // add the fragment shader to program
        GLES20.glAttachShader(mProgram, fragmentShader);

        // creates OpenGL ES program executables
        GLES20.glLinkProgram(mProgram);
    }

    public void draw(float[] mvpMatrix) {
        // Add program to OpenGL ES environment
        GLES20.glUseProgram(mProgram);

        // get handle to vertex shader's vPosition member
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");

        // Enable a handle to the triangle vertices
        GLES20.glEnableVertexAttribArray(mPositionHandle);

        // Prepare the square coordinate data
        // Tell OpenGL how to handle the data in the vertexBuffer
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
                GLES20.GL_FLOAT, false,
                vertexStride, vertexBuffer);

        // get handle to fragment shader's vColor member
        mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");

        // Set color for drawing the square
        // Pass the color to the shader
        GLES20.glUniform4fv(mColorHandle, 1, mColor, 0);

        // get handle to shape's transformation matrix
        mMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

        // Pass the projection and view transformation to the shader
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);

        // Load the texture
//        mTextureDataHandle = TextureHelper.loadTexture(mContext, R.drawable.background);
        mTextureDataHandle = TextureHelper.loadText(mContext, "01234");

        mTextureUniformHandle = GLES20.glGetUniformLocation(mProgram, "u_Texture");
        mTextureCoordinateHandle = GLES20.glGetAttribLocation(mProgram, "a_TexCoordinate");


        GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);
        GLES20.glVertexAttribPointer(mTextureCoordinateHandle, mTextureCoordinateDataSize, GLES20.GL_FLOAT, false,
                0, mCubeTextureCoordinates);

        // Set the active texture unit to texture unit 0.
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

        // Bind the texture to this unit.
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle);

        // Tell the texture uniform sampler to use this texture in the shader by binding to texture unit 0.
        GLES20.glUniform1i(mTextureUniformHandle, 0);

        GLES20.glDrawElements(
                GLES20.GL_TRIANGLES, drawOrder.length,
                GLES20.GL_UNSIGNED_SHORT, drawListBuffer);

        // Disable vertex array
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
}

My renderer draws two squares. The first one shall be textured:

public class MyGLRendererTexture implements GLSurfaceView.Renderer {

    // mMVPMatrix is an abbreviation for "Model View Projection Matrix"
    private final float[] mMVPMatrix = new float[16];
    private final float[] mProjectionMatrix = new float[16];
    private final float[] mViewMatrix = new float[16];
    private final Context mContext;

    private TexturedSquare mTexturedSquare;
    private TexturedSquare mTexturedSquare2;

    static float squareCoords[] = {
            -0.5f, 0.5f, 0.0f,   // top left
            -0.5f, -0.5f, 0.0f,   // bottom left
            0.5f, -0.5f, 0.0f,   // bottom right
            0.5f, 0.5f, 0.0f}; // top right
    // Set color with red, green, blue and alpha (opacity) values
    float color[] = {0.63671875f, 0.76953125f, 0.22265625f, 1.0f};

    static float squareCoords2[] = {
            -1.0f, 0.7f, 0.0f,   // top left
            -1.0f, 0.8f, 0.0f,   // bottom left
            -0.8f, 0.8f, 0.0f,   // bottom right
            -0.8f, 0.7f, 0.0f}; // top right
    // Set color with red, green, blue and alpha (opacity) values
    float color2[] = {0.11111111f, 0.26953125f, 0.52265625f, 1.0f};

    public MyGLRendererTexture(
            Context context) {
        mContext = context;
    }


    public static int loadShader(int type, String shaderCode) {

        // create a vertex shader type (GLES20.GL_VERTEX_SHADER)
        // or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
        int shader = GLES20.glCreateShader(type);

        // add the source code to the shader and compile it
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);

        return shader;
    }

    @Override
    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        // Set the background frame color
        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        // initialize a triangle
        // initialize a square
        mTexturedSquare = new TexturedSquare(mContext, squareCoords, color);
        mTexturedSquare2 = new TexturedSquare(mContext, squareCoords2, color2);
    }


    @Override
    public void onSurfaceChanged(GL10 unused, int width, int height) {
        GLES20.glViewport(0, 0, width, height);

        float ratio = (float) width / height;

        // this projection matrix is applied to object coordinates
        // in the onDrawFrame() method
        Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
    }

    @Override
    public void onDrawFrame(GL10 unused) {
        // Redraw background color
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

        // Set the camera position (View matrix)
        Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

        // Calculate the projection and view transformation
        Matrix.multiplyMM(mMVPMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

        mTexturedSquare.draw(mMVPMatrix);
        mTexturedSquare2.draw(mMVPMatrix);
    }
}

And finally I have a helper class defining helper methods I am using in the upper code.

public class TextureHelper {
    public static int loadTexture(final Context context, final int resourceId) {
        final int[] textureHandle = new int[1];

        GLES20.glGenTextures(1, textureHandle, 0);

        if (textureHandle[0] != 0) {
            final BitmapFactory.Options options = new BitmapFactory.Options();
            options.inScaled = false;    // No pre-scaling

            // Read in the resource
            final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);

            // Bind to the texture in OpenGL
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);

            // Set filtering
            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);

            // Load the bitmap into the bound texture.
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

            // Recycle the bitmap, since its data has been loaded into OpenGL.
            bitmap.recycle();
        }

        if (textureHandle[0] == 0) {
            throw new RuntimeException("Error loading texture.");
        }

        return textureHandle[0];
    }

    public static int loadText(final Context context, String text) {
        final int[] textureHandle = new int[1];

        GLES20.glGenTextures(1, textureHandle, 0);

        if (textureHandle[0] != 0) {

            // Create an empty, mutable bitmap
            Bitmap bitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_4444);
                // get a canvas to paint over the bitmap
            Canvas canvas = new Canvas(bitmap);
            bitmap.eraseColor(0);

            // get a background image from resources
            // note the image format must match the bitmap format
            Drawable background = context.getResources().getDrawable(R.drawable.background);
            background.setBounds(0, 0, 256, 256);
            background.draw(canvas); // draw the background to our bitmap

            // Draw the text
            Paint textPaint = new Paint();
            textPaint.setTextSize(32);
            textPaint.setAntiAlias(true);
            textPaint.setARGB(0xff, 0x00, 0x00, 0x00);
            // draw the text centered
            canvas.drawText(text, 16,112, textPaint);

            // Bind to the texture in OpenGL
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);

            // Set filtering
            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);

            // Load the bitmap into the bound texture.
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

            // Recycle the bitmap, since its data has been loaded into OpenGL.
            bitmap.recycle();
        }

        if (textureHandle[0] == 0) {
            throw new RuntimeException("Error loading texture.");
        }

        return textureHandle[0];
    }
}

But the texture is drawn for both triangles the square consists of. How can I just draw the texture once, placed horizontally within in the square?

I understand that the square is drawn by drawing two triangles. And I understand, that the texture is placed the same way. But I don't know how to tell OpenGL to place this texture only once within the square.

EDIT:

I have now edited the texture coordinates to:

final float[] cubeTextureCoordinateData =
{
    -0.5f, 0.5f,
    -0.5f, -0.5f,
    0.5f, -0.5f,
    0.5f, 0.5f
}

resulting in this:

First

These coordinates:

-1.0f, 1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f

result in this:

Second

These coordinates:

0.5f, -0.5f,
0.5f, 0.5f,
-0.5f, 0.5f,
-0.5f, -0.5f

result in this:

Third

And these coordinates:

1.0f, -1.0f,
1.0f, 1.0f,
-1.0f, 1.0f,
-1.0f, -1.0f

result in this:

Fourth

So the 4th approach seems to be "the most right one". There the text is drawn at bottom right. It even seems that my square is divided into 4 smaller squares. Because as a texture I use this picture:

texture

Why is this divided into four parts?

Community
  • 1
  • 1
unlimited101
  • 3,653
  • 4
  • 22
  • 41
  • 1
    Not sure if it's the problem but your cube texture coordinates have opposite rotation. You typically want to follow the right hand rule. Your first set rotates left instead of right. – Ben Sep 28 '16 at 13:17
  • Yes that might be. Please have a look at my edit. Maybe you can tell me exactly which texture coordiantes I have to use to rotate right? – unlimited101 Sep 28 '16 at 13:58
  • 1
    Why -1 to 1. I would think you want to use 0 to 1. So {1, 0}, {1,1}, {0, 1}, {0, 0}. It's important to remember that in opengl texture coordinates go from 0 to 1. – Ben Sep 28 '16 at 14:08
  • Also see my updated answer for the repeat question you added – Ben Sep 28 '16 at 14:30
  • Sure, you're right. Texture coordinates in OpenGL go from 0 to 1. As even described in this tutorial: http://www.learnopengles.com/android-lesson-four-introducing-basic-texturing/. I found out that these ones: {1f, 0f, 1f, 1f, 0f, 1f, 0f, 0f} are the right coordinates. But still I don't understand why. These ones go from anti-clockwise from (1,0), so beginning from bottom right. But drawing vertices starts from top left. Why? – unlimited101 Sep 28 '16 at 14:50
  • I've got to be honest I'm not sure, I'll have to look at your code particularly your vertices and indices some more. Something along the way is getting flipped. – Ben Sep 29 '16 at 00:33
  • how to make the texture transparent? i am trying but it is ending up with black background every time :( Any help please? – Partho Bhowmick Apr 05 '20 at 05:51

2 Answers2

1

GLES sets textures to repeat by default so you need to change the parameter.

GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);

Also the tutorials you are using are pretty good here is the opengl es documentation which is pretty helpful. https://www.khronos.org/opengles/sdk/docs/man/

Ben
  • 1,285
  • 1
  • 9
  • 15
  • Thanks but with me new coordinates I don't see any difference between implementing and not implementing this code. But maybe the texture rendering is repeated though I don't see the repeated drawing? So shall I keep these parameters for performance issues? – unlimited101 Sep 28 '16 at 15:00
  • I'm not positive on the performance difference between the two, but I would recommend using these if you intend for the texture to not wrap. It could be that your device repeat is not the default even though the docs say it should be. I find with opengl on mobile it's always best to define the behavior you want to have and not rely on defaults. – Ben Sep 29 '16 at 00:28
0

To draw any UI component in openGL you need to create canvas and use that into openGL.

Bitmap textedBitmap = drawTextToBitmap();
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, textedBitmap, 0);



private Bitmap drawTextToBitmap() {
    // TODO Auto-generated method stub

    Bitmap bitmap = Bitmap.createBitmap(256, 256, Bitmap.Config.ARGB_4444);
    // get a canvas to paint over the bitmap
    Canvas canvas = new Canvas(bitmap);
    bitmap.eraseColor(android.graphics.Color.TRANSPARENT);

    canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);



    TextPaint textPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG);
    Paint textPaint = new Paint();
    textPaint.setStyle(Paint.Style.FILL);
    textPaint.setAntiAlias(true);
    textPaint.setColor(Color.BLACK);
    textPaint.setTextSize(10);

    TextView tv = new TextView(context);
    tv.setTextColor(Color.BLACK);
    tv.setTextSize(10);

    String text = "DEMO TEXT";

    tv.setText(text);
    tv.setEllipsize(TextUtils.TruncateAt.END);
    tv.setMaxLines(4);
    tv.setGravity(Gravity.BOTTOM);
    tv.setPadding(8, 8, 8, 50);
    tv.setDrawingCacheEnabled(true);
    tv.measure(MeasureSpec.makeMeasureSpec(canvas.getWidth(),
            MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
            canvas.getHeight(), MeasureSpec.EXACTLY));
    tv.layout(0, 0, tv.getMeasuredWidth(), tv.getMeasuredHeight());



    LinearLayout parent = null;
    if (bitmap != null && !bitmap.isRecycled()) {
        parent = new LinearLayout(context);

        parent.setDrawingCacheEnabled(true);
        parent.measure(MeasureSpec.makeMeasureSpec(canvas.getWidth(),
                MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(
                canvas.getHeight(), MeasureSpec.EXACTLY));
        parent.layout(0, 0, parent.getMeasuredWidth(),
                parent.getMeasuredHeight());

        parent.setLayoutParams(new LinearLayout.LayoutParams(
                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        parent.setOrientation(LinearLayout.VERTICAL);

        parent.setBackgroundColor(context.getResources().getColor(R.color.transpernt));


        parent.addView(tv);

    } else {
        // write code to recreate bitmap from source
        // Write code to show bitmap to canvas
    }

    canvas.drawBitmap(parent.getDrawingCache(), 0, 0, textPaint);

    tv.setDrawingCacheEnabled(false);
    iv.setDrawingCacheEnabled(false);
    parent.setDrawingCacheEnabled(false);

    return bitmap;

}
Amit Soni
  • 39
  • 6