0

I'm new to developing Android games using OpenGL ES 2.0.
I recently made a simple game where players must touch hidden numbers respectively that appeared within 3 seconds to win a level.
Each level contains numbers as many as two more than level amounts (For example, level 5 has 5 + 2 numbers).
The game works fine but not enough fine as I expected in two issues:
1 - The textures shown in my game, compared with the image shown in my Image Viewer application, are low in quality.
This is the screenshot of my game: image
And this is the screenshot of my Image Viewer application: image
This is my Texture Class:

package com.theNumbers.openGLES;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.ShortBuffer;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;

public class OpenGLESTexture {
    
    private final Context mActivityContext;
    private final FloatBuffer mCubeTextureCoordinates;
    private int mTextureUniformHandle;
    private int mTextureCoordinateHandle;
    private final int mTextureCoordinateDataSize = 2;
    private int mTextureDataHandle;
    private final String vertexShaderCode =
            "attribute vec2 a_TexCoordinate;" +
            "varying vec2 v_TexCoordinate;" +
            "uniform mat4 uMVPMatrix;" +
            "attribute vec4 vPosition;" +
            "void main() {" +
            "   gl_Position = uMVPMatrix * vPosition;" +
            "   v_TexCoordinate = a_TexCoordinate;" +
            "}";
    private final String fragmentShaderCode =
            "precision mediump float;" +
            "uniform sampler2D u_Texture;" +
            "varying vec2 v_TexCoordinate;" +
            "void main() {" +
            "   gl_FragColor = texture2D(u_Texture, v_TexCoordinate);" +
            "}";
    private final int shaderProgram;    
    private final FloatBuffer vertexBuffer;
    private final ShortBuffer drawListBuffer;
    private int mPositionHandle;
    private int mMVPMatrixHandle;
    static final int COORDS_PER_VERTEX = 2;
    private float[] spriteCoords = new float[8];
    private short drawOrder[] = { 0, 1, 2, 0, 2, 3 };
    private final int vertexStride = COORDS_PER_VERTEX * 4;
    private final float[] mTextureSize = new float[2];
    
    public OpenGLESTexture(final Context activityContext, final int resourceId, final short width, final short height) {
        mActivityContext = activityContext;
        mTextureSize[0] = width;
        mTextureSize[1] = height;
        spriteCoords[0] = width / 2f;
        spriteCoords[1] = height / 2f;
        spriteCoords[2] = -width / 2f;
        spriteCoords[3] = height / 2f;
        spriteCoords[4] = -width / 2f;
        spriteCoords[5] = -height / 2f;
        spriteCoords[6] = width / 2f;
        spriteCoords[7] = -height / 2f;
        ByteBuffer bb = ByteBuffer.allocateDirect(spriteCoords.length * 4); 
        bb.order(ByteOrder.nativeOrder());
        vertexBuffer = bb.asFloatBuffer();
        vertexBuffer.put(spriteCoords);
        vertexBuffer.position(0);
        final float[] cubeTextureCoordinateData =
        {        
                 0.0f,  0.0f,
                 1.0f,  0.0f,
                 1.0f,  1.0f,
                 0.0f,  1.0f
        };
        mCubeTextureCoordinates = ByteBuffer.allocateDirect(cubeTextureCoordinateData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
        mCubeTextureCoordinates.put(cubeTextureCoordinateData).position(0);
        ByteBuffer dlb = ByteBuffer.allocateDirect(spriteCoords.length * 2);
        dlb.order(ByteOrder.nativeOrder());
        drawListBuffer = dlb.asShortBuffer();
        drawListBuffer.put(drawOrder);
        drawListBuffer.position(0);
        int vertexShader = OpenGLESRenderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
        int fragmentShader = OpenGLESRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
        shaderProgram = GLES20.glCreateProgram();
        GLES20.glAttachShader(shaderProgram, vertexShader);
        GLES20.glAttachShader(shaderProgram, fragmentShader);
        GLES20.glBindAttribLocation(shaderProgram, 0, "a_TexCoordinate");
        GLES20.glLinkProgram(shaderProgram);
        mTextureDataHandle = loadTexture(mActivityContext, resourceId);
    }
    
    public void draw(float[] mvpMatrix)
    {
        GLES20.glUseProgram(shaderProgram);
        mPositionHandle = GLES20.glGetAttribLocation(shaderProgram, "vPosition");
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);
        mTextureUniformHandle = GLES20.glGetAttribLocation(shaderProgram, "u_Texture");
        mTextureCoordinateHandle = GLES20.glGetAttribLocation(shaderProgram, "a_TexCoordinate");
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureDataHandle);
        GLES20.glUniform1i(mTextureUniformHandle, 0);
        GLES20.glUniform2fv(GLES20.glGetUniformLocation(shaderProgram, "u_TextureSize"), 1, mTextureSize, 0);//
        mCubeTextureCoordinates.position(0);
        GLES20.glVertexAttribPointer(mTextureCoordinateHandle, mTextureCoordinateDataSize, GLES20.GL_FLOAT, false, 0, mCubeTextureCoordinates);
        GLES20.glEnableVertexAttribArray(mTextureCoordinateHandle);
        mMVPMatrixHandle = GLES20.glGetUniformLocation(shaderProgram, "uMVPMatrix");
        GLES20.glUniformMatrix4fv(mMVPMatrixHandle, 1, false, mvpMatrix, 0);
        GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
        GLES20.glDisableVertexAttribArray(mPositionHandle);
    }
    
    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;
            final Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), resourceId, options);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandle[0]);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR_MIPMAP_LINEAR);
            GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
            GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
            bitmap.recycle();
            GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        }
        if (textureHandle[0] == 0)
        {
            throw new RuntimeException("Error loading texture.");
        }
        return textureHandle[0];
    }

}

And this is my Renderer Class:

package com.theNumbers.openGLES;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import com.theNumbers.game.AssetsManager;

import android.content.Context;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.Matrix;

public class OpenGLESRenderer implements GLSurfaceView.Renderer {
    
    private final Context mActivityContext;
    private final float[] mMVPMatrix = new float[16];
    private final float[] mProjMatrix = new float[16];
    private final float[] mVMatrix = new float[16];
    private float[] mRotationMatrix = new float[16];
    private boolean onCreate = true;
    public static float[] mStaticMVPMatrix = new float[16];
    public volatile float mAngle;
    
    public OpenGLESRenderer(final Context context) {
        mActivityContext = context;
    }

    public void onSurfaceCreated(GL10 unused, EGLConfig config) {
        GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        GLES20.glEnable(GLES20.GL_BLEND);
        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
        AssetsManager.loadAssets(mActivityContext);
    }

    public void onDrawFrame(GL10 unused) {
        GLES20.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
        Matrix.setLookAtM(mVMatrix, 0, 0, 0, -3, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
        Matrix.multiplyMM(mMVPMatrix, 0, mProjMatrix, 0, mVMatrix, 0);
        Matrix.setRotateM(mRotationMatrix, 0, mAngle, 0, 0, -1.0f);
        Matrix.multiplyMM(mMVPMatrix, 0, mRotationMatrix, 0, mMVPMatrix, 0);
        AssetsManager.equalizeMatrices(mMVPMatrix);
        if (onCreate) {
            onCreate = false;
            AssetsManager.load(mActivityContext);
            AssetsManager.save(mActivityContext);
            AssetsManager.createGUI();
            AssetsManager.updateLines();
            AssetsManager.mDoesFileExist = false;
        }
        AssetsManager.updateTime();
        AssetsManager.drawGUI();
        AssetsManager.drawRooms(mActivityContext);
        AssetsManager.mIsTouched = false;
    }

    public void onSurfaceChanged(GL10 unused, int width, int height) {
        float aspectRatio = (float) width / height;
        GLES20.glViewport(0, 0, width, height);
        Matrix.frustumM(mProjMatrix, 0, -aspectRatio * 960, aspectRatio * 960, -960, 960, 3, 7);
        AssetsManager.updateLines();
    }

    public static int loadShader(int type, String shaderCode) {
        int shader = GLES20.glCreateShader(type);
        GLES20.glShaderSource(shader, shaderCode);
        GLES20.glCompileShader(shader);
        return shader;
    }
    
}

You can get the complete source code and the APK file here.
2 - Others can easily copy the textures I used in my game.
Just change the APK suffix to the ZIP suffix and open it to see all of my textures.

How can I solve these two issues that I explained above?

Nima
  • 1
  • 1
  • Did you try to remove mip-mapping? It looks like you do minification with mips. It can lead to blurry image. `GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); ` and remove `GLES20.glGenerateMipmap(GLES20.GL_TEXTURE_2D);` Additionally, to achieving pixel-perfect rendering, be sure that you don't significantly scale images in rendering. – rokuz Dec 06 '22 at 19:57
  • @rokuz Yes, I did. The result of using `GL_LINEAR` without mipmapping is [here](http://i.imgur.com/aZ7qyXd.png), and the result of using `GL_NEAREST` without mipmapping is [here](http://i.imgur.com/B9y8nk6.png), but these are low in quality compared with the original image. Your meaning of "Don't significantly scale images" is: Do I have to use multiple of the same image with different sizes? (If yes, how do the other game engines like Unity do this with one image?) Or should I write my own library to scale images? (If yes, how?) – Nima Dec 08 '22 at 15:23
  • When you render 2D sprites you have geometry to which you map textures. To achieve pixel-perfect rendering, you need to be sure that combination of geometry, projection and other transformations gives you finally 1:1 scale (or close to 1:1 scale) to real image size. Please refer to this https://stackoverflow.com/questions/27985367/rendering-texture-1-to-1-in-opengl or https://gamedev.stackexchange.com/questions/103334/how-can-i-set-up-a-pixel-perfect-camera-using-opengl-and-orthographic-projection – rokuz Dec 08 '22 at 16:19
  • @rokuz That's means, should I write my own method to scale up or scale down sprites? Or I cannot scale any sprites? – Nima Dec 09 '22 at 11:48
  • You can scale any sprites, but quality of output depends on scale factor. Large scale factors will lead to descreasing quality. Some systems (e.g. Android) when you add raster images allow you to upload several scales (1x, 2x, 3x etc). It's exactly the case when you minimize scale factors on different screens. wrt your case, if you have the same sprites that you reuse with different scaling, I'd recommend to have several resolutions for the same sprite and write the algorithm that selects one the most closest to pixel-perfect results. – rokuz Dec 09 '22 at 12:03
  • @rokuz Thanks for replying, but it takes too many different-sized sprites for just one unique sprite if I need many factor scales. I think I should implement my own method to scale sprites—the method which I don't know how to do. – Nima Dec 09 '22 at 14:00

0 Answers0