Download - Manual de OpenGL

Transcript
Page 1: Manual de OpenGL

1

Manual de OpenGL

Page 2: Manual de OpenGL

2

Contenido 1. ¿Qué es OpenGL? ................................................................................................................. 3

2. Código basado en OpenGL .................................................................................................. 3

2.1 Sintaxis ......................................................................................................................... 3

3. El espacio 3D .......................................................................................................................... 4

4. Primitivas geométricas .......................................................................................................... 5

5. Transformación de las primitivas ......................................................................................... 7

6. El Pipeline Gráfico ................................................................................................................. 8

6.1 Etapa de Aplicación ......................................................................................................... 9

6.2 Etapa de Geometría ........................................................................................................ 9

6.3 Etapa Rasterización ...................................................................................................... 10

7. MATERIALES E ILUMINACION ........................................................................................ 11

7.1 Especificación de normales.......................................................................................... 11

8. Color de fondo ...................................................................................................................... 12

9. Código básico ....................................................................................................................... 14

9.1 Análisis del código ......................................................................................................... 15

Analizando la función main(): ......................................................................................... 15

Analizando init(): ................................................................................................................... 16

Función display() .................................................................................................................. 17

10. Sistema de Detección de Colisiones .............................................................................. 18

10.1 Formas de Colisión ........................................................ ¡Error! Marcador no definido.

Page 3: Manual de OpenGL

3

1. ¿Qué es OpenGL?

OpenGL (Open Graphics Library) es un API (Interfaz de Programación de

Aplicaciones) libre, multiplataforma, orientado a la creación de contenido 3D. A

un nivel más simple se puede decir que es una librería que permite hacer

software con gráficos 3D sin preocuparse por el hardware en que se va a ejecutar

y que tiene un gran soporte en diferentes sistemas operativos como Windows,

OS/2, UNIX, Linux, etc.

La compañía que desarrolla esta librería es Sillicon Graphics Inc (SGI), en pro de hacer un estándar en la representación 3D gratuito y con código abierto (open source). Está basado en sus propios OS y lenguajes IRIS, de forma que es perfectamente portable a otros lenguajes. Entre ellos C, C++, etc y las librerías dinámicas permiten usarlo sin problema en Visual Basic, Visual Fortran, Java, etc.

2. Código basado en OpenGL

2.1 Sintaxis Todas las funciones de OpenGL comienzan con el prefijo “gl” y las constantes con “GL_”. Como ejemplos, la función glClearColor() y la constante GL_COLOR_BUFFER_BIT. En muchas de las funciones, aparece un sufijo compuesto por dos caracteres, una cifra y una letra, como por ejemplo glColor3f() o glVertex3i(). La cifra simboliza el número de parámetros que se le deben pasar a la función, y la letra el tipo de estos parámetros. En OpenGL existen 8 tipos distintos de datos, de una forma muy parecida a los tipos de datos de C o C++. Además, OpenGL viene con sus propias definiciones de estos datos (typedef en C). Los tipos de datos (véase la tabla 2.1):

Page 4: Manual de OpenGL

4

3. El espacio 3D

OpenGL trabaja, a grandes rasgos, en un espacio de tres dimensiones, aunque veremos que realmente, trabaja con coordenadas homogéneas (de cuatro dimensiones). Las tres dimensiones que nos interesan ahora son las especificadas por un sistema 3D ortonormal. Es decir, sus ejes son perpendiculares, y cada unidad en uno de ellos está representada por un vector de módulo 1 (si nos alejamos una unidad, nos alejamos la misma distancia del eje de coordenadas) véase figura 3.1.

La cuarta coordenada se utiliza entre otras razones, para representar la perspectiva, de este modo, el sistema de coordenadas inicial de un sistema OpenGL puede representarse con esta matriz:

Tabla 2.1 Tipos de datos en OpenGL

Page 5: Manual de OpenGL

5

4. Primitivas geométricas

En OpenGL solo se pueden dibujar primitivas muy simples, tales como puntos

líneas, cuadrados, triángulos y polígonos, a partir de estas primitivas es posible

construir primitivas más complejas como arcos y círculos aproximándolos por

polígonos.

Toda primitiva de dibujo se construye con un par: glBegin(tipo_de_primitiva);

glVertex2f(); ... glEnd(); donde tipo_de_primitiva puede ser cualquiera de las

siguientes (véase tabla 4.1 y figura 4.2):

Tabla 4.1 Tipos de primitivas en OpenGL.

Figura 4.2 Tipos de primitivas

Page 6: Manual de OpenGL

6

La librería GLUT amplia este conjunto de primitivas geométricas con 9 nuevas

primitivas más complejas y muy habituales en la representación de escenas

tridimensionales como, por ejemplo, la esfera, el cubo, el toro o el cono.

Se han definido nueve primitivas graficas en dos modos:

Modo alámbrico. Cada primitiva se visualiza utilizando únicamente líneas.

Todas las órdenes de dibujo comienzan por glutWire.

Modo sólido. La superficie de la primitiva se visualiza mediante un

conjunto de polígonos. Todas las órdenes de dibujo comienzan por

glutSolid.

Los comandos se muestran a continuación:

Esfera, void glutWireSphere(GLdouble radio, GLint slices, GLint

stacks);

Cubo, void glutWireTorus (GLdouble radio_interior, GLdouble

radio_exterior, GLint nsides, GLint rings);

Icosaedro, void glutWirecosahedron (void);

Octaedro, void glutWireOctahedron (void);

Tetraedro, void glutWireTetrahedrom (void);

Dodecaedro, void glutWireDodecahedron (void);

Cono, void glutWireCone (GLdouble radio, GLdouble altura, GLint

slices, GLint stacks);

Tetera, void glutWireTeapot (GLdouble tamaño);

Los parámetros que poseen algunas de las primitivas siempre especifican

valores sobre su dimensión, nunca de su posición u orientación. Las

primitivas se dibujaran centradas en el origen de coordenadas a no ser que

previamente se haya definido alguna operación de transformación.

Algunos parámetros cuyo significado puede no resultar obvio son:

Slices, nsides: representan las secciones longitudinales de la primitiva.

Stacks, rings: las secciones transversales de la primitiva.

Los comandos anteriores visualizan las primitivas en modo alámbrico. Para

visualizarlas en modo sólido hay que cambiar …Wire… por …Solid.

A continuación se muestra un ejemplo de uso de las primitivas de la librería

GLUT:

glutSolidTorus(1.0,5.0,10.0,30.0);

Page 7: Manual de OpenGL

7

glutSolidSphere(5.0,30.0,30.0);

glutSolidTorus(5.0,8.0,10.0,10.0);

5. Transformación de las primitivas

En OpenGL existen funciones disponibles para especificar transformaciones

en 3D: traslación, giro y escalado. Estas funciones serán de utilidad tanto

para aplicar transformaciones a los distintos objetos de la escena, como para

definir las transformaciones asociadas al posicionamiento de dicha escena

en el volumen de la vista.

OpenGL utiliza distintas matrices durante el proceso de visualización. Una de

ellas es la matriz de transformación del modelo. Así, en primer lugar, se debe

especificar que se desea trabajar con dicha matriz:

void glMatrixMode (GL_MODELVIEW);

Para inicializarla a la matriz de identidad se usa la orden:

void glLoadIdentify (void);

Las transformaciones realizadas, irán multiplicando esta matriz de forma

acumulativa. Existen tres rutinas en OpenGL para realizar transformaciones:

Rutina de traslación, void glTranslate{f d} (TIPO x, TIPO y, TIPO z);

multiplica la matriz actual por una matriz con desplazamientos en los ejes

indicados en los parámetros x,y,z.

Rutina de rotación, void glRotate{f d} (TIPO angulo, TIPO x, TIPO y,

TIPO z); la matriz actual es multiplicada por otra que realiza un giro en el

sentido contrario a las agujas del reloj, del valor indicado por el parámetro

angulo. El ángulo se define en grados. El giro se realiza alrededor del eje

que pasa por los puntos (0,0,0) y (x,y,z).

Rutina de escalado, void glScale{f d} (TIPO x, TIPO y, TIPO z); multiplica

la matriz por otra que amplía o reduce el objeto con respecto al objeto

indicado. Valores mayores de 2.0 lo amplían, y entre 0 y 1 lo reducen.

Las transformaciones siempre se aplican en orden inverso al que han sido

especificadas en el programa. Por ejemplo, si se desea trasladar y rotar un

objeto, la codificación es la siguiente:

glRotatef (…);

glTranslatef (…);

dibuja_objeto();

Page 8: Manual de OpenGL

8

Como las transformaciones se almacenan como matrices, OpenGL utiliza una

pila de matrices para recordar situaciones anteriores a las transformaciones

aplicadas. La matriz actual es siempre la que está en la posición más elevada de

la pila. Existen dos rutinas para gestionar esta pila:

Void glPushMatrix (void); esta rutina almacena en la pila una copia de

la matriz actual.

void glPopMatrix (void); elimina de la pila la matriz que está en la

posición más elevada. La matriz actual será la que se encontraba en

segundo lugar desde arriba antes de la operación. En el caso de que antes

de ejecutar esta rutina solo existiera una matriz en la pila, se produciría

un error.

A continuación se muestra un pequeño ejemplo, donde se dibujar un cono y un

toro separado en 22.0 unidades a lo largo del eje x.

glMatrixMode (GL_MODEVIEW) ;

glLoadIdentify ();

glutSolidCone (9.0,20.0,16,16);

glTranslatef(22.0,0.0,0.0);

glutSolidTorus(3.0,7.0,16,16);

Se añade una rotación, -90 grados alrededor del eje X, que produce el giro de

ambas primitivas.

6. El Pipeline Gráfico

Para obtener una imagen de una escena 3D definida en el Sistema de Referencia Universal, necesitamos definir un sistema de referencia de coordenadas para los

parámetros de visualización (también denominados parámetros de cámara).

Este sistema de referencia nos definirá el plano de proyección, que sería el

equivalente de la zona de la cámara sobre la que se registrará la imagen. De

este modo se transfieren los objetos al sistema de coordenadas de visualización

y finalmente se proyectan sobre el plano de visualización Para obtener una

imagen de una escena 3D definida en el Sistema de Referencia Universal,

necesitamos definir un sistema de referencia de coordenadas para los

parámetros de visualización (también denominados parámetros de cámara).

Page 9: Manual de OpenGL

9

Este sistema de referencia nos definirá el plano de proyección, que sería el equivalente de la zona de la cámara sobre la que se registrará la imagen. De este modo se transfieren los objetos al sistema de coordenadas de visualización y finalmente se proyectan sobre el plano de visualización. El Pipeline está dividido en etapas funcionales. Algunas de estas etapas se realizan en paralelo y otras secuencialmente.

Como señala Akenine-Möler , el pipeline interactivo se divide en tres etapas conceptuales de Aplicación, Geometría y Rasterización (ver Figura 6.1). A continuación estudiaremos estas etapas.

6.1 Etapa de Aplicación

La etapa de aplicación se ejecuta en la CPU. Actualmente la mayoría de las CPUs son multinúcleo, por lo que el diseño de esta aplicación se realiza mediante diferentes hilos de ejecución en paralelo. Habitualmente en esta etapa se ejecutan tareas asociadas al cálculo de la posición de los modelos 3D mediante simulaciones físicas, detección de colisiones, gestión de la entrada del usuario (teclado, ratón, joystick...). De igual modo, el uso de estructuras de datos de alto nivel para la aceleración del despliegue (reduciendo el número de polígonos que se envían a la GPU) se implementa en la etapa de aplicación.

6.2 Etapa de Geometría

A los vértices de cada modelo se le aplican la denominada Transformación de Modelado para posicionarlo y orientarlo respecto del Sistema de Coordenadas Universal, obteniendo así las denominadas Coordenadas Universales o Coordenadas del Mundo. La posición y orientación de la cámara nos determinará qué objetos aparecerán en la imagen final. Esta cámara tendrá igualmente unas coordenadas universales. El propósito de la Transformación de Visualización es posicionar la cámara en el origen del SRU, apuntando en la dirección negativa del eje Z y el eje Y hacia arriba. Obtenemos de este modo las Coordenadas de Visualización o Coordenadas en Espacio Cámara (ver Figura 6.2).

Figura 6.1 Etapas del pipeline

Page 10: Manual de OpenGL

10

Habitualmente el pipeline contiene una etapa adicional intermedia que se denomina Vertex Shader Sombreado de Vértice que consiste en obtener la representación del material del objeto modelando las transformaciones en las fuentes de luz, utilizando los vectores normales a los puntos de la superficie, información de color, etc. Es conveniente en muchas ocasiones transformar las posiciones de estos elementos (fuentes de luz, cámara) a otro espacio (como Coordenadas de Modelo) para realizar los cálculos. La Transformación de Proyección convierte el volumen de visualización en un cubo unitario. Este volumen de visualización se define mediante planos de recorte 3D y define todos los elementos que serán visualizados.

6.3 Etapa Rasterización

A partir de los vértices proyectados (en Coordenadas de Pantalla) y la información asociada a su sombreado obtenidas de la etapa anterior, la etapa de rasterización se encarga de calcular los colores finales que se asignarán a los píxeles de los objetos. Esta etapa de rasterización se divide normalmente en las siguientes etapas funciones para lograr mayor paralelismo. En la primera etapa del pipeline llamada Configuración de Triángulos (Triangle Setup), se calculan las coordenadas 2D que definen el contorno de cada triángulo (el primer y último punto de cada vértice). Esta información es utilizada en la siguiente etapa (y en la interpolación), y normalmente se implementa directamente en hardware dedicado. A continuación, en la etapa del Recorrido de Triángulo (Triangle Traversal) se generan fragmentos para la parte de cada píxel que pertenece al triángulo. El recorrido del triángulo se basa por tanto en encontrar los píxeles que forman parte del triángulo, y se denomina Triangle Traversal (o Scan Conversion). El

Figura 6.2 Espacio de trabajo

Page 11: Manual de OpenGL

11

fragmento se calcula interpolando la información de los tres vértices definidos en la etapa de Configuración de Triángulos y contiene información calculada sobre la profundidad desde la cámara y el sombreado (obtenida en la etapa de geometría a nivel de todo el triángulo). Finalmente en la etapa de Fusión (Merging) se almacena la información del color de cada píxel en un array de colores denominado Color Buffer. Para ello, se combina el resultado de los fragmentos que son visibles de la etapa de Sombreado de Píxel. La visibilidad se suele resolver en la mayoría de los casos mediante un buffer de profundidad Z-Buffer, empleando la información que almacenan los fragmentos.

7. MATERIALES E ILUMINACION

7.1 Especificación de normales

Las normales en OpenGL son utilizadas para la iluminación, indicando la

orientación relativa de un objeto respecto a las fuentes de iluminación.

Las rutinas que se especifican la normal en OpenGL son:

void glNormal3 {bsidf} (TIPO nx, TIPO ny, TIPO nz);

void glNormal3{bsidf}v (const (TIPO *v);

Una vez establecida la normal, todos los vértices que se dibujen a continuación

asumirán la misma, a no ser que se especifiqué de forma explícita una normal

para cada uno de los vértices a dibujar.

Los vectores normales permanecen normalizados siempre que las

transformaciones realizadas sean rotaciones y traslaciones. Si bien se realizan

otras transformaciones o bien se especifican vectores normales no unitarios, se

deben normalizar de forma automática las normales de los objetos con la rutina:

glEnable (GL_NORMALIZE);

Las primitivas básicas creadas directamente con las rutinas de la GLUT

(esfera, cono, toro, etc) tienen definidas ya las normales.

Page 12: Manual de OpenGL

12

Ejemplo

En la siguiente figura se muestra el código que define la misma normal para

todos los vértices de un polígono. En consecuencia, el polígono se pintará de

color uniforme tal y como se muestra en la imagen.

En la siguiente figura se muestra el código que define una normal diferente para

cada vértice. Esto permite que el color varíe a lo largo de la superficie del

polígono pudiendo obtener un resultado como el que se muestra en la imagen.

8. Color de fondo

Antes de modificar el color de fondo de la ventana, debemos tener en mente como almacena la información de pantalla los dispositivos gráficos, normalmente la intensidad de un pixel corresponde a la suma de diferentes buffers, cada uno de ellos almacena información referente al pixel en cuestión, OpenGL utiliza estos buffers para determinar el color y alguna información adicional de cada pixel.

Page 13: Manual de OpenGL

13

Los buffers que utiliza son:

Para modificar el color de fondo de la ventana podemos utilizar los siguientes comandos OpenGL:

glClearColor (Rojo, Verde, Azul, Alpha); glClearDepth( 1.0 ); Esta función define el color de fondo que se aplicará, utiliza los valores de RGBA, por lo tanto cada argumento corresponde a la intensidad del color, Rojo, Verde y Azul, el rango de estos valores debe ir comprendido entre el valor 0.0, menor intensidad, 1.0 máxima intensidad. El cuarto parámetro que utiliza la función glClearColor, corresponde al valor alpha del color que se utiliza para determinar información referente a la transparencia del color, de igual forma que los tres argumentos anteriores, esté también toma valores comprendidos entre 0.0 opaco, y 1.0 totalmente transparente. La función glClearDepth, se utiliza para especificar información sobre la profundidad. Una vez que sé a definido el color de fondo debemos utilizar la función glClear para borrar la pantalla con el color que habíamos definido, el argumento de esta función podrá ser simple o compuesto, ya que podremos combinar la información de varios buffers tal como: glClearColor() //borra el buffer de color. glClearDepth() //Buffer de profundidad glClearAccum() //Buffer acumulativo glClearStencil() //Buffer de plantilla

Page 14: Manual de OpenGL

14

En este caso el color de fondo depende de la suma de los valores de los buffers de color y de profundidad. Generalmente, el glColor3f(rojo, verde, azul) especifica el primer plano del color del dibujo, que es el color aplicado a los objetos que se está elaborando. El valor de cada componente de color, que debe ser un número entre 0.0 y 1.0, determina su intensidad. Por ejemplo, glColor3f (1.0, 1.0, 0.0) es el amarillo brillante mientras el glColor3f(0.5,0.5,0.0) es un débil amarillo. Nota: Los valores de color se sujetan cada uno a la gama [0:1]. Esta significa que, si un valor pasa a ser mayor que 1, entonces se toma a ser 1; si es inferior a 0, se toma como 0.

9. Código básico

Lo que podría considerarse la aplicación más sencilla utilizando OpenGL se presenta en esta sección. El siguiente código abre una ventana y dibuja un triángulo de color blanco.

Page 15: Manual de OpenGL

15

9.1 Análisis del código

La estructura de un clásico programa sobre GLUT es la que se aprecia en el código anterior.

Analizando la función main():

glutInit(&argc, argv);

Esta función es la que inicializa a OpenGL, y negocia con el sistema de ventanas para abrir una. Los parámetros deben ser los mismos argc y argv sin modificar el main().

glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);

Define el modo en el que debe dibujar en la ventana. GLUT_SINGLE indica que se debe usar un solo buffer y GLUT_RGB el tipo de modelo de color con el que se dibujará. glutInitWindowPosition(50, 50);

Posición x e y de la esquina superior izquierda de la nueva ventana, con respecto al escritorio en el que se trabaje. glutInitWindowSize(500, 500);

El ancho y alto de la nueva ventana.

glutCreateWindow("Hello OpenGL");

Page 16: Manual de OpenGL

16

Esta función es la que propiamente crea la ventana, y el parámetro es el nombre de la misma. init();

Es una función que hemos definido nosotros, activamos y definimos una serie de estados de OpenGL. glutDisplayFunc(display);

Aquí se define la llamada a la función display. La función pasada como parámetro será llamada cada vez que GLUT determine oportuno que la ventana debe ser redibujada, como al maximizarse, poner otras ventanas por encima o sacarlas, etc. glutReshapeFunc(reshape);

En este caso para saber que hace cuando la ventana es explícitamente

reescalada. Esta acción afecta en principio directamente al render, puesto que

se está cambiando el tamaño del plano de proyección. Por eso en esta función

(en este caso reshape) se suele corregir esto de alguna forma.

glutMainLoop();

Esta función cede el control del flujo del programa a GLUT, que a partir de estos "eventos", ira llamando a las funciones que han sido pasadas como parametros.

Analizando init():

glClearColor(0,0,0,0);

Con esto se define el color con el que se borrara el buffer al hacer un glClear(). Los 3 primeros parámetros son las componentes R, G y B, siguiendo un rango de [0..1]. La última es el valor alpha. reshape(). Esta función, al ser pasada a glutReshapeFunc, será llamada cada vez que se reescale a ventana. La función siempre debe ser definida con el siguiente esqueleto: void reshape(int, int) { ... } El primer parámetro será el ancho y el segundo el alto, después del reescalado. Con estos dos valores trabajara la función cuando, en tiempo de ejecución, el usuario reescale la ventana. glViewport(0, 0, width, height); Esta función define la porción de ventana donde puede dibujar OpenGL. Los parámetros son x e y, esquina superior izquierda del "cuadro" donde puede dibujar (con referencia la ventana), y ancho y alto. En este caso toma el width y

Page 17: Manual de OpenGL

17

height, que son los parámetros de reshape(), es decir, los datos que acaba de recibir para el reescalado de la ventana. glMatrixMode(GL_PROJECTION); Especifica la matriz actual. En OpenGL las operaciones de rotación, translación, escalado, etc. se realizan a través de matrices de transformación. Dependiendo de lo que estemos tratando, hay tres tipos de matriz (que son los tres posibles flags que puede llevar de parámetro la función): matriz de proyección (GL_PROJECTION), matriz de modelo (GL_MODELVIEW) y matriz de textura (GL_TEXTURE). Con esta función indicamos a cual de estas tres se deben afectar las operaciones. Concretamente, GL_PROJECTION afecta a las vistas o perspectivas o proyecciones. glLoadIdentity(); Con esto cargamos en el "tipo" de matriz actual la matriz identidad (es como resetear la matriz). glOrtho(-1, 1, -1, 1, -1, 1); glOrtho() define una perspectiva ortonormal. Esto quiere decir que lo que veremos será una proyección en uno de los planos definidos por los ejes. Es como plasmar los objetos en un plano, y luego observar el plano. Los parámetros son para delimitar la zona de trabajo, y son x_minima, x_maxima, y_minima, y_maxima, z_minima, z_maxima. Con estos seis puntos, definimos una caja que será lo que se proyecte. glMatrixMode(GL_MODELVIEW); Se vuelve a este tipo de matrices, que afecta a las primitivas geométricas.

Función display(). Como se ha dicho, al ser pasada a glutDisplayFunc(), será llamada cada vez que haya que redibujar la ventana. La función debe ser definida con el siguiente esqueleto: void display(void) { ... } Analicemos el contenido de la función. glClear(GL_COLOR_BUFFER_BIT); Borra un buffer, en este caso, el buffer de los colores lo borra (en realidad, cada componente R G y B tienen un buffer distinto, pero aquí los trata como el mismo). Para borrarlos utiliza el color que ha sido previamente definido en init() mediante glClearColor(), en este caso, el (0,0,0,0) es decir, negro.

Page 18: Manual de OpenGL

18

glColor3f(1,1,1); Selecciona el color actual con el que dibujar. Parámetros R G y B, rango [0..1], así que estamos ante el color blanco. glLoadIdentity(); Carga la matriz identidad. glBegin(GL_TRIANGLES);

glVertex3f(-1,-1,0); glVertex3f(1,-1,0); glVertex3f(0,1,0);

glEnd(); Analizamos toda esta parte entera, por formar una estructura. glBegin() comienza una secuencia de vértices con los que se construirán primitivas. El tipo de primitivas viene dado por el parámetro de glBegin(), en este caso GL_TRIANGLES. Al haber tres vértices dentro de la estructura, está definiendo un triángulo. glEnd() simplemente cierra la estructura. Los posibles parámetros de glBegin. glFlush(); El comando glFlush causa la ejecución de cualquier comando en espera.

10. Sistema de Detección de Colisiones

La responsabilidad principal del Sistema de Detección de Colisiones (SDC) es calcular cuándo colisionan los objetos de la escena. Para calcular esta colisión, los objetos se representan internamente por una forma geométrica sencilla (como esferas, cajas, cilindros...). Además de comprobar si hubo colisión entre los objetos, el SDC se encarga de proporcionar información relevante al resto de módulos del simulador físico sobre las propiedades de la colisión. Esta información se utiliza para evitar efectos indeseables, como la penetración de un objeto en otro, y conseguir la estabilidad en la simulación cuando el objeto llega a la posición de equilibrio.