Huge FPS drop when pixel shader is used on android - android

In my app, there is no frustum culling, objects not seen by camera are drawn anyway. When the camera is not looking at the objects, my fps is near 60 on my android device. When it looks at the objects, my fps drops to about 15.
Therefore I think this is an issue with my pixel shader. Because I use monogame, the shader is actually written with directx shader model 3.0. Below is my texture sampler declaration and my pixel shader. (this is the only pixel shader used)
texture2D tex1;
sampler2D tex1Sampler = sampler_state
{
Texture = <tex1>;
MinFilter = Point;
MagFilter = Point;
MipFilter = Point;
AddressU = Clamp;
AddressV = Clamp;
};
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
float4 color = tex2D(tex1Sampler,input.Texture);
clip(color.a - .12f);
color *= input.Tint;
return color;
}
What could be causing this FPS drop? Some other info: the texture used is size 250 x 250, and it's the only texture used in my tests.
it's not something absurd either, like a billion triangles all lined up behind each other and no back face culling enabled. It's only like 1000 small triangles in the scene, which should not be unreasonable.

removed Clip() from pixel shader and got 30 fps increase.
Now I can't really do alpha blending without sorting everything.....

Related

OpenGL - use different polygons one drawcall - instancing

I am using OpenGL ES version 3 for my android game and have implemented instancing. It works well IF I use a polygon of the same size/dimension that is identical vertices. I can jump to different UV-coordinates of the texture atlas if I want to create change the sprites state for every frame.
v_TexCoordinate = a_TexCoordinate + uvCoordsOffset[gl_InstanceID];
that is .. I just change the texture coordinates with a uniform-vec which consists of offset coordinates.
BUT - here comes the issue.
What if I want to do the same but with sprites that have different dimensions?
in the drawcall ...
GLES30.glDrawElementsInstanced(GLES30.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_SHORT, indexOffset, nFallingObj);
I can only send one polygon, that is the green dinosaur vertices in the screenshot. I have this as base polygon and I want to get to the RED dinosaur of the atlas. I can do this easily with texture offset as described above BUT you see how it becomes? the polygon of the green dinosaur is used but I want to be changed as of the red dinosaurs.
Is there any easy solution to this issue?
thanks in advance!!!
some source-code
the draw-method
public void drawFallingObjects() {
GLES30.glUseProgram(mProgramHandle);
GLES30.glEnableVertexAttribArray(mPositionHandle);
GLES30.glVertexAttribPointer(mPositionHandle, CreateGLContext.POSITION_DATA_SIZE, GLES20.GL_FLOAT, false, CreateGLContext.STRIDE, 0);
GLES30.glEnableVertexAttribArray(mTextureCoordinateHandle);
GLES30.glVertexAttribPointer(mTextureCoordinateHandle, CreateGLContext.TEXTURE_COORDINATE_DATA_SIZE, GLES20.GL_FLOAT, false,
CreateGLContext.STRIDE, CreateGLContext.POSITION_DATA_SIZE * CreateGLContext.BYTES_PER_FLOAT);
GLES30.glUniform2fv(uvCoordsOffsetLoc, nFallingObj, uvOffsetVec, 0);
GLES30.glUniformMatrix4fv(mMVPMatrixHandle, nFallingObj, false, mMVPMatrixMajor, 0);
GLES30.glDrawElementsInstanced(GLES30.GL_TRIANGLES, 6, GLES20.GL_UNSIGNED_SHORT, indexOffset, nFallingObj);
}
vertex-shader code
void main()
{
v_Color = a_Color;
v_TexCoordinate = a_TexCoordinate + uvCoordsOffset[gl_InstanceID];
gl_Position = u_MVPMatrix[gl_InstanceID] * a_Position;
}
What if I want to do the same but with sprites that have different
dimensions?
Your MVP matrix can encode scale and skew, which is all you need to adjust the on-screen sprite size.

OpenGL ES write depth data to color

I'm trying to implement DepthBuffer-like functionality using OpenGL ES on Android.
In other words I'm trying to get the 3D point on surface that is rendered on point [x, y] on the user device. In order to make that I need to be able to read the distance of the fragment at that given point.
Answer in different circumstances:
When using normal OpenGL you could achieve this by creating FrameBuffer and then attach either RenderBuffer or Texture with depth component to it.
Both of those approaches use glReadPixels, with internal format of GL_DEPTH_COMPONENT to retrieve the data from the buffer/texture. Unfortunately OpenGL ES only supports GL_ALPHA, GL_RGB, and GL_RGBA as the readback formats, so there's really no way to reach the framebuffer's depth data directly.
The only viable approach that I can think of (and that I have found suggested on the internet) is to create different shaders just for depth buffering. The shader, that is used only for depth rendering, should write gl_FragCoord.z value (=the distance value that we want to read.) on the gl_FragColor. However:
The actual Question:
When I write gl_FragCoord.z value on the gl_FragColor = new Vec4(vec3(gl_FragCoord.z), 1.0); and later use glReadPixels to read back the rgb values, those read values don't match up with the input.
What I have tried:
I realize that there's only 24 bits (r, g, b * 8 bits each) representing the depth data so I tried shifting the returned value by 8 - to get 32 bits, but it didn't seem to work. I also tried to shift distance when applying it to red, green and blue, but that didn't seem to work as expected. I have been trying to figure out what's wrong by observing the bits, results at the bottom.
fragmentShader.glsl(candidate #3):
void main() {
highp float distance = 1.0; //currently just 1.0 to test the results with different values.
lowp float red = distance / exp2(16.0);
lowp float green = distance / exp2(8.0);
lowp float blue = distance / exp2(0.0);
gl_FragColor = vec4(red, green, blue, 1.0);
}
Method to read the values (=glReadPixels)
private float getDepth(int x, int y){
FloatBuffer buffer = GeneralSettings.getFloatBuffer(1); //just creates FloatBuffer with capacity of 1 float value.
terrainDepthBuffer.bindFrameBuffer(); //bind the framebuffer before read back.
GLES20.glReadPixels(x, y, 1, 1, GLES20.GL_RGB, GLES20.GL_UNSIGNED_BYTE, buffer); //read the values from previously bind framebuffer.
GeneralSettings.checkGlError("glReadPixels"); //Make sure there is no gl related errors.
terrainDepthBuffer.unbindCurrentFrameBuffer(); //Remember to unbind the buffer after reading/writing.
System.out.println(buffer.get(0)); //Print the value.
}
Observations in bits using the shader & method above:
Value | Shader input | ReadPixels output
1.0f | 111111100000000000000000000000 | 111111110000000100000000
0.0f | 0 | 0
0.5f | 111111000000000000000000000000 | 100000000000000100000000

Opencv increasing accuracy of threshold

I am working on an app that is expected to remove image backgrounds using opencv, at first I tried using grabcut but it was too slow and the results were not always accurate, then I tried using threshold, although the results are not yet close th grabcut, its very fast and looks like a better, So my code is first looking at the image hue and analying which portion of it appears more, that portion is taken in as the background, the issue is at times its getting the foreground as background below is my code:
private Bitmap backGrndErase()
{
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.skirt);
Log.d(TAG, "bitmap: " + bitmap.getWidth() + "x" + bitmap.getHeight());
bitmap = ResizeImage.getResizedBitmap(bitmap, calculatePercentage(40, bitmap.getWidth()), calculatePercentage(40, bitmap.getHeight()));
Mat frame = new Mat();
Utils.bitmapToMat(bitmap, frame);
Mat hsvImg = new Mat();
List<Mat> hsvPlanes = new ArrayList<>();
Mat thresholdImg = new Mat();
// int thresh_type = Imgproc.THRESH_BINARY_INV;
//if (this.inverse.isSelected())
int thresh_type = Imgproc.THRESH_BINARY;
// threshold the image with the average hue value
hsvImg.create(frame.size(), CvType.CV_8U);
Imgproc.cvtColor(frame, hsvImg, Imgproc.COLOR_BGR2HSV);
Core.split(hsvImg, hsvPlanes);
// get the average hue value of the image
double threshValue = this.getHistAverage(hsvImg, hsvPlanes.get(0));
Imgproc.threshold(hsvPlanes.get(0), thresholdImg, threshValue, mThresholdValue, thresh_type);
// Imgproc.adaptiveThreshold(hsvPlanes.get(0), thresholdImg, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 11, 2);
Imgproc.blur(thresholdImg, thresholdImg, new Size(5, 5));
// dilate to fill gaps, erode to smooth edges
Imgproc.dilate(thresholdImg, thresholdImg, new Mat(), new Point(-1, -1), 1);
Imgproc.erode(thresholdImg, thresholdImg, new Mat(), new Point(-1, -1), 3);
Imgproc.threshold(thresholdImg, thresholdImg, threshValue, mThresholdValue, Imgproc.THRESH_BINARY);
//Imgproc.adaptiveThreshold(thresholdImg, thresholdImg, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, 11, 2);
// create the new image
Mat foreground = new Mat(frame.size(), CvType.CV_8UC3, new Scalar(255, 255, 255));
frame.copyTo(foreground, thresholdImg);
Utils.matToBitmap(foreground,bitmap);
//return foreground;
alreadyRun = true;
return bitmap;
}
the method responsible for Hue:
private double getHistAverage(Mat hsvImg, Mat hueValues)
{
// init
double average = 0.0;
Mat hist_hue = new Mat();
// 0-180: range of Hue values
MatOfInt histSize = new MatOfInt(180);
List<Mat> hue = new ArrayList<>();
hue.add(hueValues);
// compute the histogram
Imgproc.calcHist(hue, new MatOfInt(0), new Mat(), hist_hue, histSize, new MatOfFloat(0, 179));
// get the average Hue value of the image
// (sum(bin(h)*h))/(image-height*image-width)
// -----------------
// equivalent to get the hue of each pixel in the image, add them, and
// divide for the image size (height and width)
for (int h = 0; h < 180; h++)
{
// for each bin, get its value and multiply it for the corresponding
// hue
average += (hist_hue.get(h, 0)[0] * h);
}
// return the average hue of the image
average = average / hsvImg.size().height / hsvImg.size().width;
return average;
}
A sample of the input and output:[
Input Image 2 and Output:
Input Image 3 and Output:
Indeed, as others have said you are unlikely to get good results just with a threshold on hue. You can use something similar to GrabCut, but faster.
Under the hood, GrabCut calculates foreground and background histograms, then calculates the probability of each pixel being FG/BG based on these histograms, and then optimizes the resulting probability map using graph cut to obtain a segmentation.
Last step is most expensive, and it may be ignored depending on the application. Instead, you may apply the threshold to the probability map to obtain a segmentation. It may (and will) be worse than GrabCut, but will be better than your current approach.
There are some points to consider for this approach. The choice of histogram model would be very important here. You can either consider 2 channels in some space like YUV or HSV, consider 3 channels of RGB, or consider 2 channels of normalized RGB. You also have to select an appropriate bin size for those histograms. Too small bins would lead to 'overtraining', while too large will reduce the precision. The tradeoffs between those are a topic for a separate discussion, in brief - I would advice using RGB with 64 bins per channel for start and then see what changes are better for your data.
Also, you can get better results for coarse binning if you use interpolation to get values between bins. In past I have used trilinear interpolation and it was kind of good, compared to no interpolation at all.
But remember that there are no guarantees that your segmentation will be correct without prior knowledge on object shape, either with GrabCut, thresholding or this approach.
I would try again Grabcut, it is one of the best segmentation methods available. This is the result I get
cv::Mat bgModel,fgModel; // the models (internally used)
cv::grabCut(image,// input image
object_mask,// segmentation result
rectang,// rectangle containing foreground
bgModel,fgModel, // models
5,// number of iterations
cv::GC_INIT_WITH_RECT); // use rectangle
// Get the pixels marked as likely foreground
cv::compare(object_mask,cv::GC_PR_FGD,object_mask,cv::CMP_EQ);
cv::threshold(object_mask, object_mask, 0,255, CV_THRESH_BINARY); //ensure the mask is binary
The only problem of Grabcut is that you have to give as an input a rectangle containing the object you want to extract. Apart from that it works pretty well.
Your method of finding average hue is WRONG! As you most probably know, hue is expressed as angle and takes value in [0,360] range. Therefore, a pixel with hue 360 essentially has same colour as a pixel with hue 0 (both are pure red). In the same way, a pixel with hue 350 is actually closer to a pixel with hue 10 than a pixel with hue, say for example, 300.
As for opencv, cvtColor function actually divides calculated hue value by 2 to fit it in 8 bit integer. Thus, in opencv, hue values wrap after 180. Now, consider we have two red(ish) pixels with hues 10 and 170. If we take their average, we will get 90 — hue of pure cyan, the exact opposite of red — which is not our desired value.
Therefore, to correctly find the average hue, you need to first find average pixel value in RGB colour space, then calculate the hue from this RGB value. You can create 1x1 matrix with average RGB pixel and convert it to HSV/HSL.
Following the same reasoning, applying threshold to hue image doesn't work flawlessly. It does not consider wrapping of hue values.
If I understand correctly, you want to find pixels with similar hue as the background. Assuming we know the colour of background, I would do this segmentation in RGB space. I would introduce some tolerance variable. I would use the background pixel value as centre and this tolerance as radius and thus define a sphere in RGB colour space. Now, rest is inspecting each pixel value, if it falls inside this sphere, then classify as background; otherwise, regard it as foreground pixel.

Translate between 'Touch Plane' and 'Game Plane'

I am trying to create a 2D game. Because I am using OpenGL ES I have to plot everything in 3D, but I just fix the z coordinate, which is fine. Now what I want to do is calculate the angle between two vectors (C = player center, P = point just above player, T = touch point) CP and CT so that I can make the player face that direction. I know how to get the angle between 2 vectors, but my problem is getting all the points to exist on the same plane (by translating the T).
I know that T exists on a plane where (0,0) is upper left and UP is actually DOWN (visually). I also know that C and P's UP is actually UP and that any their X and Y is on a completely 3 dimensional different plane to T. I need to get either C and P onto T's plane (which I have tried below) or get T onto C and P's plane. Can anyone help me? I am using the standard OpenGL projection model and I am 0,0,-4 zoomed out of the frustrum (I am looking directly at (0,0,0)). My 2D objects all sit on the plane (0,0,1);
private float getRotation(float touch_x, float touch_y)
{
//center_x = this.getWidth() / 2;
//center_y = this.getHeight() / 2;
float cx, cy, tx, ty, ux, uy;
cx = (player.x * _renderer.centerx);
cy = (player.y * -_renderer.centery);
ux = cx;
uy = cy+1.0f;
tx = (touch_x - _renderer.centerx);
ty = (touch_y - _renderer.centery);
Log.d(TAG, "center x: "+cx+"y:"+cy);
Log.d(TAG, "up x: "+ux+"y:"+uy);
Log.d(TAG, "touched x: "+tx+"y:"+ty);
float P12 = length(cx,cy,tx,ty);
float P13 = length(cx,cy,ux,uy);
float P23 = length(tx,ty,ux,uy);
return (float)Math.toDegrees(Math.acos((P12*P12 + P13*P13 - P23*P23)/2.0 * P12 * P13));
}
Basically I want to know if there is a way I can translate (tx, ty, -4) to (x, y, 1) using the standard view frustum.
I have tried some other things now. In my touch event I am trying to do this:
float[] coords = new float[4];
GLU.gluUnProject(touch_x, touch_y, -4.0f, renderer.model, 0, renderer.project, 0, renderer.view, 0, coords, 0);
Which is throwing an exception I am setting up the model, projection and view in the OnSurfaceChanged of the Renderer object:
GL11 gl11 = (GL11)gl;
model = new float[16];
project = new float[16];
int[] view = new int[4];
gl11.glGetFloatv(GL10.GL_MODELVIEW, model, 0);
gl11.glGetFloatv(GL10.GL_PROJECTION, project, 0);
gl11.glGetIntegerv(GL11.GL_VIEWPORT, view, 0);
I have several textbooks on openGL and after dusting one off I found that the term for what I want to do is called picking. Once I knew what I was asking, I found a lot of good web sites and references:
http://www.lighthouse3d.com/opengl/picking/
OpenGL ES (iPhone) Touch Picking
Coordinate Picking with OpenGL ES 2.0
Android OpenGL 3D picking
converting 2D mouse coordinates to 3D space in OpenGL ES
Coordinate Picking with OpenGL ES 2.0
Ray-picking in OpenGL ES 2.0
Android: GLES20: Called unimplemented OpenGL ES API
...
The list is almost innumerable. There are 700 ways to do this, and none of them worked for me. Ultimately I have decided to go back to basics and do a thorough OpenGL|ES learning stint, to which effect I have bought the book here: http://www.amazon.com/Graphics-Programming-Android-Programmer-ebook/dp/B0070D83W2/ref=sr_1_2?s=digital-text&ie=UTF8&qid=1362250733&sr=1-2&keywords=opengl+es+2.0+android
One thing I have already learnt is that I was most definitely using the wrong type of projection. I should not use full 3D for a 2D game. In order to do picking in a full 3D environment I would have to cast a ray from the screen point onto the surface of the 3D plane where the game was taking place. In addition to being a horrendous waste of resources (raycasting per click), there were other tell-tales. I would render my player with a circle encompassing her, and as I moved her, the circle would go off center of the player. This is due to the full 3D environment rendered on a 2D plane. It just will not produce a professional result. I need to use an orthographic projection.
I think you're trying to do too much all at once. I can understand each sentence of your question separately; but strung all together, it's very confusing.
For the exceptions, you probably need to pass identity matrices instead of zero matrices to get a basic 1-to-1 projection.
Then I'd suggest that you scale the y dimension by -1 so all the UPs and DOWNs match at least.
I hope this helps, because I'm not 100% sure what you're trying to do. Particularly, " translate (tx, ty, -4) to (x, y, 1) using the standard view frustum" doesn't make sense to me. You can translate with a translation matrix. You can clip to a view frustum, or project an object from the frustum to a plane (usually the view plane). But if all your Zs are constant, you can just discard them right? So, assuming x=tx and y=ty, then tz += 5?

Draw text in OpenGL ES

I'm currently developing a small OpenGL game for the Android platform and I wonder if there's an easy way to render text on top of the rendered frame (like a HUD with the player´s score etc). The text would need to use a custom font also.
I've seen an example using a View as an overlay, but I don't know if I want to do that since I might want to port the game to other platforms later.
Any ideas?
Rendering text to a texture is simpler than what the Sprite Text demo make it looks like, the basic idea is to use the Canvas class to render to a Bitmap and then pass the Bitmap to an OpenGL texture:
// 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("Hello World", 16,112, textPaint);
//Generate one texture pointer...
gl.glGenTextures(1, textures, 0);
//...and bind it to our array
gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
//Create Nearest Filtered Texture
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
//Different possible texture parameters, e.g. GL10.GL_CLAMP_TO_EDGE
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_REPEAT);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_REPEAT);
//Use the Android GLUtils to specify a two-dimensional texture image from our bitmap
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, bitmap, 0);
//Clean up
bitmap.recycle();
The Android SDK doesn't come with any easy way to draw text on OpenGL views. Leaving you with the following options.
Place a TextView over your SurfaceView. This is slow and bad, but the most direct approach.
Render common strings to textures, and simply draw those textures. This is by far the simplest and fastest, but the least flexible.
Roll-your-own text rendering code based on a sprite. Probably second best choice if 2 isn't an option. A good way to get your feet wet but note that while it seems simple (and basic features are), it get's harder and more challenging as you add more features (texture-alignment, dealing with line-breaks, variable-width fonts etc.) - if you take this route, make it as simple as you can get away with!
Use an off-the-shelf/open-source library. There are a few around if you hunt on Google, the tricky bit is getting them integrated and running. But at least, once you do that, you'll have all the flexibility and maturity they provide.
I've written a tutorial that expands on the answer posted by JVitela. Basically, it uses the same idea, but instead of rendering each string to a texture, it renders all characters from a font file to a texture and uses that to allow for full dynamic text rendering with no further slowdowns (once the initialization is complete).
The main advantage of my method, compared to the various font atlas generators, is that you can ship small font files (.ttf .otf) with your project instead of having to ship large bitmaps for every font variation and size. It can generate perfect quality fonts at any resolution using only a font file :)
The tutorial includes full code that can be used in any project :)
According to this link:
http://code.neenbedankt.com/how-to-render-an-android-view-to-a-bitmap
You can render any View to a bitmap. It's probably worth assuming that you can layout a view as you require (including text, images etc.) and then render it to a Bitmap.
Using JVitela's code above you should be able to use that Bitmap as an OpenGL texture.
Take a look at CBFG and the Android port of the loading/rendering code. You should be able to drop the code into your project and use it straight away.
CBFG - http://www.codehead.co.uk/cbfg
Android loader - http://www.codehead.co.uk/cbfg/TexFont.java
I looked at the sprite text example and it looks awfully complicated for such a task, I considered rendering to a texture too, but I'm worried about the performance hit that might cause.
I might just have to go with a view instead and worry about porting when it's time to cross that bridge :)
IMHO there are three reasons to use OpenGL ES in a game:
Avoid differences between mobile platforms by using an open standard;
To have more control of the render process;
To benefit from GPU parallel processing;
Drawing text is always a problem in game design, because you are drawing things, so you cannot have the look and feel of a common activity, with widgets and so on.
You can use a framework to generate Bitmap fonts from TrueType fonts and render them. All the frameworks I've seen operate the same way: generate the vertex and texture coordinates for the text in draw time. This is not the most efficient use of OpenGL.
The best way is to allocate remote buffers (vertex buffer objects - VBOs) for the vertices and textures early in code, avoiding the lazy memory transfer operations in draw time.
Keep in mind that game players don't like to read text, so you won't write a long dynamically generated text. For labels, you can use static textures, leaving dynamic text for time and score, and both are numeric with a few characters long.
So, my solution is simple:
Create texture for common labels and warnings;
Create texture for numbers 0-9, ":", "+", and "-". One texture for each character;
Generate remote VBOs for all positions in the screen. I can render static or dynamic text in that positions, but the VBOs are static;
Generate just one Texture VBO, as text is always rendered one way;
In draw time, I render the static text;
For dynamic text, I can peek at the position VBO, get the character texture and draw it, a character at a time.
Draw operations are fast, if you use remote static buffers.
I create an XML file with screen positions (based on screen's diagonal percentage) and textures (static and characters), and then I load this XML before rendering.
To get a high FPS rate, you should avoid generating VBOs at draw time.
Look at the "Sprite Text" sample in the GLSurfaceView samples.
If you insist on using GL, you could render the text on to textures. Assuming that most of the HUD is relatively static, you shouldn't have to load the textures to texture memory too often.
Take a look at CBFG and the Android port of the loading/rendering
code. You should be able to drop the code into your project and use it
straight away.
CBFG
Android loader
I have problems with this implementation. It displays only one character, when I try do change size of the font's bitmap (I need special letters) whole draw fails :(
I have been looking for this for a few hours, this was the first article i came accross and although it has the best answer, the most popular answers i think are off the mark. Certainly for what i needed.
weichsel's and shakazed's answers were right on the button but a bit obscured in the articles.
To put you right to the project. Here:
Just create a new Android project based on existing sample. Choose ApiDemos:
Look under the source folder
ApiDemos/src/com/example/android/apis/graphics/spritetext
And you will find everything you need.
For static text:
Generate an image with all words used on your PC (For example with GIMP).
Load this as a texture and use it as material for a plane.
For long text that needs to be updated once in a while:
Let android draw on a bitmap canvas (JVitela's solution).
Load this as material for a plane.
Use different texture coordinates for each word.
For a number (formatted 00.0):
Generate an image with all numbers and a dot.
Load this as material for a plane.
Use below shader.
In your onDraw event only update the value variable sent to the shader.
precision highp float;
precision highp sampler2D;
uniform float uTime;
uniform float uValue;
uniform vec3 iResolution;
varying vec4 v_Color;
varying vec2 vTextureCoord;
uniform sampler2D s_texture;
void main() {
vec4 fragColor = vec4(1.0, 0.5, 0.2, 0.5);
vec2 uv = vTextureCoord;
float devisor = 10.75;
float digit;
float i;
float uCol;
float uRow;
if (uv.y < 0.45) {
if (uv.x > 0.75) {
digit = floor(uValue*10.0);
digit = digit - floor(digit/10.0)*10.0;
i = 48.0 - 32.0 + digit;
uRow = floor(i / 10.0);
uCol = i - 10.0 * uRow;
fragColor = texture2D( s_texture, uv / devisor * 2.0 + vec2((uCol-1.5) / devisor, uRow / devisor) );
} else if (uv.x > 0.5) {
uCol = 4.0;
uRow = 1.0;
fragColor = texture2D( s_texture, uv / devisor * 2.0 + vec2((uCol-1.0) / devisor, uRow / devisor) );
} else if (uv.x > 0.25) {
digit = floor(uValue);
digit = digit - floor(digit/10.0)*10.0;
i = 48.0 - 32.0 + digit;
uRow = floor(i / 10.0);
uCol = i - 10.0 * uRow;
fragColor = texture2D( s_texture, uv / devisor * 2.0 + vec2((uCol-0.5) / devisor, uRow / devisor) );
} else if (uValue >= 10.0) {
digit = floor(uValue/10.0);
digit = digit - floor(digit/10.0)*10.0;
i = 48.0 - 32.0 + digit;
uRow = floor(i / 10.0);
uCol = i - 10.0 * uRow;
fragColor = texture2D( s_texture, uv / devisor * 2.0 + vec2((uCol-0.0) / devisor, uRow / devisor) );
} else {
fragColor = vec4(0.0, 0.0, 0.0, 0.0);
}
} else {
fragColor = vec4(0.0, 0.0, 0.0, 0.0);
}
gl_FragColor = fragColor;
}
Above code works for a texture atlas where numbers start from 0 at the 7th column of the 2nd row of the font atlas (texture).
Refer to https://www.shadertoy.com/view/Xl23Dw for demonstration (with wrong texture though)
In the OpenGL ES 2.0/3.0 you can also combining OGL View and Android's UI-elements:
public class GameActivity extends AppCompatActivity {
private SurfaceView surfaceView;
#Override
protected void onCreate(Bundle state) {
setContentView(R.layout.activity_gl);
surfaceView = findViewById(R.id.oglView);
surfaceView.init(this.getApplicationContext());
...
}
}
public class SurfaceView extends GLSurfaceView {
private SceneRenderer renderer;
public SurfaceView(Context context) {
super(context);
}
public SurfaceView(Context context, AttributeSet attributes) {
super(context, attributes);
}
public void init(Context context) {
renderer = new SceneRenderer(context);
setRenderer(renderer);
...
}
}
Create layout activity_gl.xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
tools:context=".activities.GameActivity">
<com.app.SurfaceView
android:id="#+id/oglView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<TextView ... />
<TextView ... />
<TextView ... />
</androidx.constraintlayout.widget.ConstraintLayout>
To update elements from the render thread, can use Handler/Looper.

Categories

Resources