Reputation: 3803
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:
These coordinates:
-1.0f, 1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f
result in this:
These coordinates:
0.5f, -0.5f,
0.5f, 0.5f,
-0.5f, 0.5f,
-0.5f, -0.5f
result in this:
And these coordinates:
1.0f, -1.0f,
1.0f, 1.0f,
-1.0f, 1.0f,
-1.0f, -1.0f
result in this:
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:
Why is this divided into four parts?
Upvotes: 1
Views: 4797
Reputation: 39
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;
}
Upvotes: 0
Reputation: 1295
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/
Upvotes: 1