En este primer post vamos a aprender a manejar los conceptos básicos de una de las librerías graficas más utilizadas, OpenGL.
Luego iremos haciendo cosas un poco más complejas, hasta llegar a gráficos en 3D y sus respectivas operaciones, pero primero lo primero.
Lo primero que debes conocer antes de empezar a graficar con OpenGL, son las clases a utilizar, y son:
- GLSurfaceView, la clase que te permite escribir las aplicaciones con OpenGL
- GLSurfaceView.Renderer, es una interfaz de renderizado genérica donde escribirás el código de lo que quieras que se dibuje.
PARA ENTENDER MÁS RÁPIDO…
Para que puedas asimilar la forma de graficar en OpenGL rápidamente, puedes hacer una pequeña analogía con un pintor y un lienzo, en este caso el pintor sería la interfazGLSurfaceView.Renderer y el lienzo sería GLSurfaceView. Por lo dicho anteriormente, elGLSurfaceView sería el parámetro que pasaríamos en el método setContentView(), que usualmente se coloca al sobrescribir el método onCreate().
Como ya sabrás al implementar un interfaz, tenemos que implementar los métodos que esta trae, en este caso son:
- onSurfaceCreated(), este método es llamado cuando la superficie (a.k.a. surface) es creada o es re-creada, como este método es llamado al inicio del renderizado, es un buen lugar para colocar lo que no variara en el ciclo del renderizado. Ejm: el color de fondo, la activación del índice z, etc, etc.
- onDrawFrame(), este método en particular es el encargado de dibujar sobre la superficie (a.k.a. surface)
- onSurfaceChanged(), este método es llamado cuando la superficie cambia de alguna manera, por ejemplo al girar el móvil y colocarlo en posición de paisaje.
MANOS A LA OBRA (O MÁS BIEN AL CÓDIGO) – LIENZO
Explicada y entendida la teoría anterior, vamos a echar mano al código, primero crearemos una actividad de manera regular, el único cambio que haremos en este caso, será al definir la Vista de contenidos.
MANOS A LA OBRA (O MÁS BIEN AL CÓDIGO) – PINTOR
Siguiendo con la analogía de la primera parte, el lienzo está puesto, ahora vamos a codificar al pintor, en este caso es una clase extra que debe implementar la interfazGLSurfaceView.Renderer.
Lo que conseguimos con este código es algo simple, cada vez que gires tu móvil del modo retrato al modo paisaje el fondo cambiará de color.
El método gl.glClearColor() establece el color que se utilizará para limpiar la pantalla, para motivos prácticos dejamos que se elija colores al azar, y es llamado a través del métodogl.glClear(), dado que el parámetro que le pasamos indica que limpie solo el buffer de color, que en este caso, esta en el método onDrawFrame().
Al momento de llamar al método onSurfaceChanged() se destruye la Vista de Contenido por lo que vuelve a llamar al método onCreate() y en este método se asigna el color de limpieza, por lo que cada vez que se gira el móvil, se vuelve a asignar un color diferente y es el que se muestra en pantalla.
Sin excepción alguna, todos los renderizados en 3D están formados por vértices, bordes y caras, resumiendo: polígonos. Es por ello que entender como se generan es una parte importante para entender como trabaja esta librería.
Para empezar con este tutorial no se necesitará de nuevas librerías.
LA TEORÍA POR DELANTE…
Antes de meter mano al código, deberás recordar tu teoría básica de geometría (no te asustes, cuando digo lo básico, es lo verdaderamente básico):
- Vértice(s): Un vértice es la base de todo, a partir de esto se generan los demás partes que permitan graficar polígonos o modelos en 3D. Un vértice es el punto donde 2 o más bordes se encuentran.
- Borde (o Línea): Un borde (o línea) es la unión de 2 vértices. En un modelo 3D, el borde puede ser compartido por 2 caras o polígonos.
- Cara: Es uno (de los tantos) lados que pueda contar el polígono o el modelo en 3D. La modificación de una cara afecta a sus vértices y a sus bordes.
Todas estas definiciones anteriores son conocidas como las primitivas de dibujo.
MANOS AL CÓDIGO
Ahora que tenemos en cuenta la teoría básica sobre la que trabaja OpenGL, vamos a identificar como podemos generar cada uno de los puntos explicados en el segmento anterior:
- Vértice: Para definir un vértice en OpenGL, se necesita establecer un array del tipo float con los puntos definidos en (x,y,z).
Ejm:
- Para un punto sería:
- Para un cuadrado sería:
- Para una pirámide de base triangular sería:
- Borde: En OpenGL no se definen los bordes, estos quedan automáticamente establecidos al definir las caras (esto lo veremos en el sgte. punto). Para modificar un borde solo tienes que modificar los vértices que lo componen.
- Cara: La definición de caras usando OpenGL es a través de triángulos, por ejemplo para definir un cuadro de vértices v0, v1, v2, v3, seria de la siguiente manera:
SE HORNEA DESPUÉS DE PREPARAR LA MASA
Con esto quiero decir, que el orden si importa al momento de generar las caras, porque es aquí cuando se especifica la dirección con la que se dibujaran (en la de las agujas del reloj o en contra de estas). Si quieres mejorar el rendimiento del renderizado, deberás graficar todos tus dibujos en la misma dirección, asi mediante código podremos esconder la parte trasera de tu dibujo cuando no sea necesario mostrarla.
La dirección del renderizado, mediante código, se realiza de la sgte. manera:
Para poder poder usar las caracteristicas de OpenGL, como las de esconder las caras que están del mirando a la pantalla, se deben activar primero y una vez aplicadas se deben desactivar.
De la siguiente manera:
y después indicar que lado debe esconder:
y al final lo deshabilitamos:
MOMENTO DE MOSTRAR ALGO EN PANTALLA
Para poder mostrar el grafico cuyos vértices y caras hayamos definido, podemos utilizar 2 metodos:
- public abstract void glDrawArrays(int mode, int first, int count)
- public abstract void glDrawElements(int mode, int count, int type, Buffer indices)
El primer método dibuja la grafica según el orden como se haya definido en la construcción de los vértices. El segundo método necesita unos parámetros extras para graficar, necesita el orden en el que se graficaran los vértices.
Lo único común a ambas funciones es que ambos necesitan saber que primitivas deben renderizar. Hay varias formas de renderizar las primitivas:
- GL_POINTS: Renderizar solo los vértices.
- GL_LINE_STRIP: Renderizar segmentos de línea conectados entre si por un vértice.
- GL_LINE_LOOP: Renderiza igual que el anterior, pero une el punto final con el punto inicial.
- GL_LINES: Renderiza segmentos de línea entre 2 vertices independientes de otros vértices.
- GL_TRIANGLES: Renderiza superficies mediante triángulos usando 3 puntos de referencia.
- GL_TRIANGLE_STRIP: Renderiza triángulos continuos usando 3 puntos de referencia
- GL_TRIANGULE_FAN: Renderiza triángulos usando un punto en común, a modo de abanico
CODIGO PURO Y DURO
Para este pequeño ejemplo necesitaremos crear 3 archivos:
- La actividad principal (puede ser la utilizada en el post anterior)
- El renderizador (el archivo que implementara la interfaz GLSurfaceView.Renderer)
- y una clase que crearemos indicando numero de vértices, caras y demás características del dibujo que queramos ver en pantalla.
La actividad principal:
Con esto terminamos las importaciones que necesitamos para que no haya problemas en el código.
En el método onCreate() instanciamos una superficie sobre la que dibujaremos y nuestro renderizado, que será el encargado de dibujar.
El renderizador:
Importaciones necesarias para que el codigo compile sin problemas.
Como lo habias indicado en el primer tutorial, en el método onSurfaceCreated() se establecen los valores cuya variación será poca o nula durante la ejecución del programa, en este caso establecemos:
- el color de fondo mediante gl.glClearColor()
- el sombreado suave en el renderizado mediante gl.glShadeModel()
- el buffer de profundidad mediante gl.glClearDepth()
- un parámetro de renderizado,el testeo de profundidad, mediante gl.glEnable()
- el tipo de testeo de profundidad mediante gl.glDepthFunc()
- la calidad del color y la interpolacion de las coordenadas de la textura mediante gl.glHint() y lo ponemos en la máxima calidad posible.
En el método onDrawFrame(), insertamos el código que queremos que se ejecute durante el renderizado, por lo que hacemos lo sgte:
- Limpiamos la pantalla y el buffer
- Cargamos la matriz identidad
- Trasladamos el dibujo 4 unidades en el eje z
- Llamamos al metodo draw de nuestro objeto Piramide, instanciado anteriormente.
En el método onSurfaceChanged() colocamos los ajustes que queramos hacer cuando la superficie sobre la que dibujamos sufra alguna modificación. En este caso:
- Utilizamos gl.glViewPort() para afinar la transformación de los ejes x,y de las coordenadas de un dispositivo a las de una ventana
- gl.glMatrixMode() para especificar a que matriz se le van a aplicar los cambios posteriores
- Gl.glLoadIdentity() para reiniciar la matriz indicada en el método anterior
- GLU.gluPerspective() para calcular el ratio de la ventana
- Gl.glMatrixMode, de nuevo, para seleccionar la matriz de vista de modelo
- Gl.glLoadIdentity para reiniciar la matriz de la vista del modelo
Nuestra clase:
Para este tutorial graficaremos una pirámide triangular, por lo que nuestra clase se llamara (a que no adivinas) Piramide.
En el constructor de nuestra clase inicializaremos las variables, de vértices, caras y colores, de manera que OpenGL pueda interpretarlas correctamente
En este ultimo método de nuestra clase habilitamos todas las caracterisiticas explicadas en el tutorial para que el dibujo se pueda hacer de manera correcta.
El resultado final de todo este codigo es el siguiente:
Si te gusto el tutorial, compártelo, cualquier duda o pregunta pueden dejarla en los comentarios.
Este comentario ha sido eliminado por el autor.
ResponderEliminarse puede desarrollar en netbeans y cuales son las libreras k hay k descargar o como le hago si esta interesante el tema me gustaria desarrollar algo de este estilo pero si pudieras ayudarme te lo agradeciera
ResponderEliminarEste comentario ha sido eliminado por el autor.
ResponderEliminarEste comentario ha sido eliminado por el autor.
ResponderEliminarNo funciona
ResponderEliminarclaro que funciona men!
EliminarMuchas gracias por el aporte.
ResponderEliminarSólo tengo una duda, al girar la piramide del modo gl.glRotatef(mAngle, 0.0f, 1.0f, 1.0f); (con mAngle previamente definido en una funcion aparte) note que no todas las caras de la piramide se encuentran unidas, porque sucede eso? y como puedo solucionarlo
Gracias