android opengl-es 2d opengl-es-2.0

android - Renderizar múltiples imágenes 2D en OpenGL-ES 2.0



opengl-es-2.0 (2)

Para comenzar, señalaré algunas cosas generales sobre OpenGL:

Cada textura es una imagen cuadrada grande. Cargar esa imagen en la memoria de la GPU toma tiempo, ya que no puede intercambiar imágenes activamente en la memoria de textura de la GPU y esperar un tiempo de ejecución rápido.

Q1: La razón por la que solo se ve la segunda imagen es por esta línea en tu clase de sprite:

GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0);

Llama a eso dos veces, por lo que texture0 se reemplaza por la segunda imagen, y solo se llama a esa imagen.

Para combatir esto, los desarrolladores cargan una sola imagen que contiene muchas imágenes más pequeñas, también conocido como un mapa de texturas. El tamaño de la imagen que se puede cargar depende en gran medida de la GPU. Los dispositivos Android tienen un rango aproximado de 1024 ^ 2 píxeles a 4096 ^ 2 píxeles.

Para usar una parte más pequeña de la textura de un sprite, debe definir manualmente el uvArray que está en su clase de dosificador.

Imaginemos que nuestra textura tiene 4 imágenes divididas de la siguiente manera:

(0.0, 0.0) top left _____ (1.0, 0.0) top right |__|__| middle of the square is (0.5, 0.5) middle (0.0, 1.0) bot left |__|__|(1.0, 1.0) bot right

Eso significa que los valores uv para la imagen superior izquierda son:

static float[] uvArray = new float[]{ 0.0f, 0.0f, //top left 0.0f, 0.5f, //bot left 0.5f, 0.5f, //bot right 0.5f, 0.0f //top right };

De esta forma, has cuadruplicado la cantidad de sprites que puedes tener en una textura.

Debido a esto, deberá pasar no solo la textura en la que está el sprite, sino también los uvs personalizados que el dosificador debe usar.

Soy nuevo en OpenGL e intento aprender ES 2.0.

Para empezar, estoy trabajando en un juego de cartas, donde necesito renderizar múltiples imágenes de cartas. Seguí esto http://www.learnopengles.com/android-lesson-four-introducing-basic-texturing/

He creado algunas clases para manejar los datos y las acciones.

  • MySprite contiene la información de textura, incluida la ubicación y los factores de escala.
  • Batcher dibuja todos los sprites de una vez. Es una implementación aproximada.
  • ShaderHelper administra la creación de sombreadores y los vincula a un programa.
  • GLRenderer es donde se maneja la representación (implementa `Renderer`).

Q1

Mi programa representa una imagen correctamente. El problema es que cuando represento 2 imágenes, la primera es reemplazada por la última en su lugar, por lo tanto, la segunda se representa dos veces.

Sospecho que es algo relacionado con la forma en que creo texturas en la clase MySprite . Pero no estoy seguro por qué. ¿Puede usted ayudar?

Q2

Leí que si tengo que renderizar 2 imágenes, necesito usar GL_TEXTURE0 y GL_TEXTURE1 , en lugar de simplemente usar GL_TEXTURE0 .

_GLES20.glActiveTexture(GLES20.GL_TEXTURE0);

Pero dado que estas constantes son limitadas (de 0 a 31), ¿existe una mejor manera de procesar más de 32 imágenes pequeñas sin perder la singularidad de las imágenes?

Por favor, apúntame en la dirección correcta.

El código

GLRenderer:

public class GLRenderer implements Renderer { ArrayList<MySprite> images = new ArrayList<MySprite>(); Batcher batch; int x = 0; ... @Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { batch = new Batcher(); MySprite s = MySprite.createGLSprite(mContext.getAssets(), "menu/back.png"); images.add(s); s.XScale = 2; s.YScale = 3; images.add(MySprite.createGLSprite(mContext.getAssets(), "menu/play.png")); // Set the clear color to black GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1); ShaderHelper.initGlProgram(); } @Override public void onSurfaceChanged(GL10 gl, int width, int height) { mScreenWidth = width; mScreenHeight = height; // Redo the Viewport, making it fullscreen. GLES20.glViewport(0, 0, mScreenWidth, mScreenHeight); batch.setScreenDimension(width, height); // Set our shader programm GLES20.glUseProgram(ShaderHelper.programTexture); } @Override public void onDrawFrame(GL10 unused) { // clear Screen and Depth Buffer, we have set the clear color as black. GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); batch.begin(); int y = 0; for (MySprite s : images) { s.X = x; s.Y = y; batch.draw(s); y += 200; } batch.end(); x += 1; } }

Batcher:

public class Batcher { // Store the model matrix. This matrix is used to move models from object space (where each model can be thought // of being located at the center of the universe) to world space. private final float[] mtrxModel = new float[16]; // Store the projection matrix. This is used to project the scene onto a 2D viewport. private static final float[] mtrxProjection = new float[16]; // Allocate storage for the final combined matrix. This will be passed into the shader program. private final float[] mtrxMVP = new float[16]; // Create our UV coordinates. static float[] uvArray = new float[]{ 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f }; static FloatBuffer uvBuffer; static FloatBuffer vertexBuffer; static boolean staticInitialized = false; static short[] indices = new short[]{0, 1, 2, 0, 2, 3}; // The order of vertexrendering. static ShortBuffer indicesBuffer; ArrayList<MySprite> sprites = new ArrayList<MySprite>(); public Batcher() { if (!staticInitialized) { // The texture buffer uvBuffer = ByteBuffer.allocateDirect(uvArray.length * 4) .order(ByteOrder.nativeOrder()) .asFloatBuffer(); uvBuffer.put(uvArray) .position(0); // initialize byte buffer for the draw list indicesBuffer = ByteBuffer.allocateDirect(indices.length * 2) .order(ByteOrder.nativeOrder()) .asShortBuffer(); indicesBuffer.put(indices) .position(0); float[] vertices = new float[] { 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0 }; // The vertex buffer. vertexBuffer = ByteBuffer.allocateDirect(vertices.length * 4) .order(ByteOrder.nativeOrder()) .asFloatBuffer(); vertexBuffer.put(vertices) .position(0); staticInitialized = true; } } public void setScreenDimension(int screenWidth, int screenHeight) { Matrix.setIdentityM(mtrxProjection, 0); // (0,0)---> // | // v //I want it to be more natural like desktop screen Matrix.orthoM(mtrxProjection, 0, -1f, screenWidth, screenHeight, -1f, -1f, 1f); } public void begin() { sprites.clear(); } public void draw(MySprite sprite) { sprites.add(sprite); } public void end() { // Get handle to shape''s transformation matrix int u_MVPMatrix = GLES20.glGetUniformLocation(ShaderHelper.programTexture, "u_MVPMatrix"); int a_Position = GLES20.glGetAttribLocation(ShaderHelper.programTexture, "a_Position"); int a_texCoord = GLES20.glGetAttribLocation(ShaderHelper.programTexture, "a_texCoord"); int u_texture = GLES20.glGetUniformLocation(ShaderHelper.programTexture, "u_texture"); GLES20.glEnableVertexAttribArray(a_Position); GLES20.glEnableVertexAttribArray(a_texCoord); //loop all sprites for (int i = 0; i < sprites.size(); i++) { MySprite ms = sprites.get(i); // Matrix op - start Matrix.setIdentityM(mtrxMVP, 0); Matrix.setIdentityM(mtrxModel, 0); Matrix.translateM(mtrxModel, 0, ms.X, ms.Y, 0f); Matrix.scaleM(mtrxModel, 0, ms.getWidth() * ms.XScale, ms.getHeight() * ms.YScale, 0f); Matrix.multiplyMM(mtrxMVP, 0, mtrxModel, 0, mtrxMVP, 0); Matrix.multiplyMM(mtrxMVP, 0, mtrxProjection, 0, mtrxMVP, 0); // Matrix op - end // Pass the data to shaders - start // Prepare the triangle coordinate data GLES20.glVertexAttribPointer(a_Position, 3, GLES20.GL_FLOAT, false, 0, vertexBuffer); // Prepare the texturecoordinates GLES20.glVertexAttribPointer(a_texCoord, 2, GLES20.GL_FLOAT, false, 0, uvBuffer); GLES20.glUniformMatrix4fv(u_MVPMatrix, 1, false, mtrxMVP, 0); // Set the sampler texture unit to where we have saved the texture. GLES20.glUniform1i(u_texture, ms.getTextureId()); // Pass the data to shaders - end // Draw the triangles GLES20.glDrawElements(GLES20.GL_TRIANGLES, indices.length, GLES20.GL_UNSIGNED_SHORT, indicesBuffer); } } }

ShaderHelper

public class ShaderHelper { static final String vs_Image = "uniform mat4 u_MVPMatrix;" + "attribute vec4 a_Position;" + "attribute vec2 a_texCoord;" + "varying vec2 v_texCoord;" + "void main() {" + " gl_Position = u_MVPMatrix * a_Position;" + " v_texCoord = a_texCoord;" + "}"; static final String fs_Image = "precision mediump float;" + "uniform sampler2D u_texture;" + "varying vec2 v_texCoord;" + "void main() {" + " gl_FragColor = texture2D(u_texture, v_texCoord);" + "}"; // Program variables public static int programTexture; public static int vertexShaderImage, fragmentShaderImage; 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 the shader return shader; } public static void initGlProgram() { // Create the shaders, images vertexShaderImage = ShaderHelper.loadShader(GLES20.GL_VERTEX_SHADER, ShaderHelper.vs_Image); fragmentShaderImage = ShaderHelper.loadShader(GLES20.GL_FRAGMENT_SHADER, ShaderHelper.fs_Image); ShaderHelper.programTexture = GLES20.glCreateProgram(); // create empty OpenGL ES Program GLES20.glAttachShader(ShaderHelper.programTexture, vertexShaderImage); // add the vertex shader to program GLES20.glAttachShader(ShaderHelper.programTexture, fragmentShaderImage); // add the fragment shader to program GLES20.glLinkProgram(ShaderHelper.programTexture); // creates OpenGL ES program executables } public static void dispose() { GLES20.glDetachShader(ShaderHelper.programTexture, ShaderHelper.vertexShaderImage); GLES20.glDetachShader(ShaderHelper.programTexture, ShaderHelper.fragmentShaderImage); GLES20.glDeleteShader(ShaderHelper.fragmentShaderImage); GLES20.glDeleteShader(ShaderHelper.vertexShaderImage); GLES20.glDeleteProgram(ShaderHelper.programTexture); } }

MySprite

public class MySprite { public int X, Y; public float XScale, YScale; private int w, h; int textureId = -1; private MySprite(Bitmap bmp, int textureId) { this.w = bmp.getWidth(); this.h = bmp.getHeight(); this.textureId = textureId; this.XScale = this.YScale = 1f; } public static MySprite createGLSprite(final AssetManager assets, final String assetImagePath) { Bitmap bmp = TextureHelper.getBitmapFromAsset(assets, assetImagePath); if (bmp == null) return null; MySprite ms = new MySprite(bmp, createGlTexture()); Log.d("G1", "image id = " + ms.getTextureId()); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR); GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); // Load the bitmap into the bound texture. GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0); bmp.recycle(); return ms; } private static int createGlTexture() { // Generate Textures, if more needed, alter these numbers. final int[] textureHandles = new int[1]; GLES20.glGenTextures(1, textureHandles, 0); if (textureHandles[0] != 0) { GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureHandles[0]); return textureHandles[0]; } else { throw new RuntimeException("Error loading texture."); } } ... }


Su código mezcla dos conceptos: identificaciones de textura (o, como se llaman en la documentación oficial de OpenGL, nombres de textura) y unidades de textura:

  • Una ID de textura es una ID única para cada objeto de textura, donde un objeto de textura posee los datos reales, así como los parámetros de muestreo. Puede tener una cantidad virtualmente ilimitada de objetos de textura, con el límite práctico típicamente siendo la cantidad de memoria en su máquina.
  • Una unidad de textura es una entrada en una tabla de texturas que están actualmente encuadernadas y disponibles para ser muestreadas por un sombreador. El tamaño máximo de esta tabla es un límite dependiente de la implementación, que se puede consultar con glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, ...) . El mínimo garantizado para las implementaciones de ES 2.0 compatibles es 8.

Está utilizando identificadores de textura correctamente al crear sus texturas, generando una identificación con glGenTextures() , vinculándola con glBindTexture() y luego configurando la textura.

El problema es donde configuras las texturas para dibujar:

GLES20.glUniform1i(u_texture, ms.getTextureId());

El valor del uniforme de muestra no es una ID de textura, es el índice de una unidad de textura. Luego debes unir la textura que deseas usar a la unidad de textura que especifiques.

Usando la unidad de textura 0, el código correcto se ve así:

GLES20.glUniform1i(u_texture, 0); GLES20.glActiveTexture(GL_TEXTURE0); GLES20.glBindTexture(ms.getTextureId());

Algunas observaciones sobre esta secuencia de código:

  • Tenga en cuenta que el valor uniforme es el índice de la unidad de textura ( 0 ), mientras que el argumento de glActiveTexture() es la enumeración correspondiente ( GL_TEXTURE0 ). Eso es porque ... fue definido de esa manera. Desafortunado diseño de API, en mi humilde opinión, pero solo debe tenerlo en cuenta.
  • glBindTexture() vincula la textura a la unidad de textura actualmente activa, por lo que debe venir después de glActiveTexture() .
  • La llamada glActiveTexture() no es realmente necesaria si solo usa una textura. GL_TEXTURE0 es el valor predeterminado. Lo puse allí para ilustrar cómo se establece la conexión entre la unidad de textura y el ID de textura.

Se utilizan varias unidades de textura si desea muestrear varias texturas en el mismo sombreado.