Hasta ahora hemos utilizado colores en los vértices para pintar nuestras figuras. Pero los materiales nos dan aún mucha más potencia para pintar nuestras figuras.
Ahora que tenemos dos sistemas de pintar nuestras figuras lo primero es aprender a conmutarlos, esto es decirle a OpenGL cual de los dos queremos que utilice. Esto es muy simple:
Ya solo nos falta ver las funciones para definir los materiales, y entender unas nuevas componentes del color. Además de las componentes RGBA ( Red, Green, Blue y Alpha ) ahora tenemos las siguientes:
glMaterialfv( GL_enum Caras, GL_enum Parámetros, GLfloat Color[4] ); Caras: - GL_FRONT Color Cara Frontal. - GL_BACK Color Cara Trasera. - GL_FRONT_AND_BACK Color Caras Frontal y Trasera. Parámetros: - GL_AMBIENT Color Zona no Iluminada. - GL_DIFFUSE Color Zona Iluminada. - GL_AMBIENT_AND_DIFFUSE Color Zona Iluminada y no Iluminada. - GL_SPECULAR Color de los Reflejos. - GL_SHININESS Cantidad de Reflejos [ 0.0f - 128.0f ]. - GL_EMISSION Color de la Luz Emitida.
Ni que decir tiene que para que los materiales apliquen colores diferentes a las partes iluminadas que a las oscuras y que calculen reflejos en función del ángulo de incidencia de la luz “DEBE HABER LUZ”. Aún no hemos estudiado las luces que las veremos en la siguiente práctica, ni “GL_BLEND” para aplicar transparencias según el canal Alpha. Todo a su tiempo, veamos un ejemplo práctico:
GLfloat Alpha = 1.0f; bool Blend = false; // -- R – G – B – A -- GLfloat Diffuse[4] = { 0.75f, 0.75f, 0.75f, Alpha }; GLfloat Ambient[4] = { 0.50f, 0.50f, 0.50f, Alpha }; GLfloat Specular[4] = { 1.0f, 1.0f, 1.0f, Alpha }; GLfloat Emissive[4] = { 0.0f, 0.0f, 0.0f, Alpha }; GLfloat Phongsize = 64.0f; glColor4fv( Diffuse ); glMaterialfv( GL_FRONT_AND_BACK, GL_DIFFUSE, Diffuse ); glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT, Ambient ); glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, Specular ); glMaterialfv( GL_FRONT_AND_BACK, GL_EMISSION, Emissive ); glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, Phongsize ); if ( Blend ) { glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glEnable( GL_BLEND ); // -- Activa las Transparencias -- } else glDisable( GL_BLEND ); // -- Desactiva las Transparencias --
Primero definimos las variables y vectores que especificarán en el futuro material. Segundo definimos el color de los vértices por si no queremos usar el material. Tercero definimos el material para las caras delanteras y traseras. Y Cuarto comprobamos si el material es translúcido para activar o desactivar las transparencias.
Para activar la luz número cero es necesario llamar a “glEnable( GL_LIGHTING );” y “glEnable( GL_LIGHT0 );”.
OpenGL solo soporta ocho luces y estas no producen sombras de ningún tipo. Para activar los cálculos de la luz se usa la función “glEnable( GL_LIGHTING );” y “glDisable( GL_LIGHTING );” para desactivarla.
Para usar una luz primero hay que definirla, lo veremos más abajo, y se activa con “glEnable( GL_LIGHT0 );” y se desactiva con “glDisable( GL_LIGHT0 );”. Como son ocho luces los nombres son “GL_LIGHT0, GL_LIGHT1, . . ., GL_LIGHT7”
Al igual que en los materiales las luces tienen tres componentes de color:
Hay tres tipos de luces según su forma de emitir los rayos de luz:
GLfloat light_diffuse[4] = { 1.0, 1.0, 1.0, 1.0 }; GLfloat light_ambient[4] = { 0.2, 0.2, 0.2, 1.0 }; GLfloat light_specular[4]= { 1.0, 1.0, 1.0, 1.0 }; GLfloat light_position[4]= { 1.0, 1.0, 1.0, 0.0 }; // <- w = 0 glLightfv( GL_LIGHT0, GL_AMBIENT, light_ambient ); glLightfv( GL_LIGHT0, GL_DIFFUSE, light_diffuse ); glLightfv( GL_LIGHT0, GL_SPECULAR, light_specular ); glLightfv( GL_LIGHT0, GL_POSITION, light_position );
GLfloat light_diffuse[4] = { 1.0, 1.0, 1.0, 1.0 }; GLfloat light_ambient[4] = { 0.2, 0.2, 0.2, 1.0 }; GLfloat light_specular[4]= { 1.0, 1.0, 1.0, 1.0 }; GLfloat light_position[4]= { 1.0, 1.0, 1.0, 1.0 }; // <- w = 1 glLightfv( GL_LIGHT0, GL_AMBIENT, light_ambient ); glLightfv( GL_LIGHT0, GL_DIFFUSE, light_diffuse ); glLightfv( GL_LIGHT0, GL_SPECULAR, light_specular ); glLightfv( GL_LIGHT0, GL_POSITION, light_position );
GLfloat light_diffuse[4] = { 1.0, 1.0, 1.0, 1.0 }; GLfloat light_ambient[4] = { 0.2, 0.2, 0.2, 1.0 }; GLfloat light_specular[4] = { 1.0, 1.0, 1.0, 1.0 }; GLfloat light_position[4] = { 1.0, 1.0, 1.0, 1.0 }; GLfloat spot_direction[3] = { -1.0; -1.0; -1.0 }; GLfloat spot_cutoff = 45.0; GLfloat spot_exponent = 1.0; glLightfv( GL_LIGHT0, GL_AMBIENT, light_ambient ); glLightfv( GL_LIGHT0, GL_DIFFUSE, light_diffuse ); glLightfv( GL_LIGHT0, GL_SPECULAR, light_specular ); glLightfv( GL_LIGHT0, GL_POSITION, light_position ); glLightfv( GL_LIGHT0, GL_SPOT_DIRECTION, spot_direction ); glLightf( GL_LIGHT0, GL_SPOT_CUTOFF , spot_cutoff ); glLightf( GL_LIGHT0, GL_SPOT_EXPONENT , spot_exponent );
Los rayos desde un punto crean un cono de iluminación que sigue una dirección dada con un ángulo de apertura un factor de atenuación en distancia y hacia los bordes. Lo que viene siendo un foco de toda la vida.
Estas son todas las funciones encargadas de definir las luces:
glLightfv( GLenum luz, GLenum Parámetros, GLfloat Color[4] ); Luz: de GL_LIGHT0 a GL_LIGHT7. Parámetor: - GL_AMBIENT Color residual de la luz ambiente. - GL_DIFFUSE Color de la Luz. - GL_SPECULAR Color de los Brillos. - GL_POSITION Posicion del punto de Luz. Tiene dos tipos: w = 0.0f Luz Direccional o Solar w = 1.0f Luz Puntual o Focal. - GL_SPOT_DIRECTION Dirección del Cono de Luz. Luz Focal. glLightf( GLenum luz, GLenum Parámetros, GLfloat valor ); Luz: de GL_LIGHT0 a GL_LIGHT7. Parámetros: - GL_SPOT_CUTOFF Angulo de obertura del cono [ 0 - 90 grados ] - GL_SPOT_EXPONENT Perdida intensidad al centro cono [ 0-128 ] - GL_CONSTANT_ATTENUATION Por defecto 1 Rango [ 0 - 1 ] - GL_LINEAR_ATTENUATION Por defecto 0 Rango [ 0 - 1 ] - GL_QUADRATIC_ATTENUATION Por defecto 0 Rango [ 0 - 1 ] Tenemos que la función que atenúa la iluminación con la distancia es: 1/( GL_CONSTANT_ATTENUATION + GL_LINEAR_ATTENUATION * distancia + GL_QUADRATIC_ATTENUATION * distancia^2 )
Vamos a ver un ejemplo del uso de las luces con materiales y sus componentes y estados:
// -- CREACION DE LA LUZ -- GLfloat light_diffuse[4] = { 1.0, 1.0, 1.0, 1.0 }; GLfloat light_ambient[4] = { 0.4, 0.4, 0.4, 1.0 }; GLfloat light_specular[4]= { 1.0, 1.0, 1.0, 1.0 }; GLfloat light_position[4]= { 1.0, 1.0, 1.0, 0.0 }; // <- w = 0 glLightfv( GL_LIGHT0, GL_AMBIENT, light_ambient ); glLightfv( GL_LIGHT0, GL_DIFFUSE, light_diffuse ); glLightfv( GL_LIGHT0, GL_SPECULAR, light_specular ); glLightfv( GL_LIGHT0, GL_POSITION, light_position ); // -- CREACION DEL MATERIAL -- GLfloat Alpha = 1.0f; bool Blend = false; // -- R – G – B – A -- GLfloat Diffuse[4] = { 0.0f, 0.8f, 0.0f, Alpha }; GLfloat Ambient[4] = { 0.4f, 0.4f, 0.0f, Alpha }; GLfloat Specular[4] = { 1.0f, 1.0f, 1.0f, Alpha }; GLfloat Emissive[4] = { 0.0f, 0.0f, 0.0f, Alpha }; GLfloat Phongsize = 32.0f; glColor4fv( Diffuse ); glMaterialfv( GL_FRONT_AND_BACK, GL_DIFFUSE, Diffuse ); glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT, Ambient ); glMaterialfv( GL_FRONT_AND_BACK, GL_SPECULAR, Specular ); glMaterialfv( GL_FRONT_AND_BACK, GL_EMISSION, Emissive ); glMaterialf( GL_FRONT_AND_BACK, GL_SHININESS, Phongsize );
Cuando un rayo de luz (v. amarillo) rebota en una superficie plana el ángulo de entrada es igual al de salida. Esto es debido a que la normal (v. rojo) es perpendicular al plano de la superficie (caso izq.), pero si la normal la inclinamos hacia la derecha el rebote de la luz será más inclinado a la derecha (caso der.). Si sólo nos fijamos en la normal sin atender a la superficie veremos que el rayo de luz tiene el mismo ángulo de entrada que de salida. ¿Y para qué nos sirve esto?... Pues la respuesta es muy simple, para engañarnos, para hacernos creer que vemos superficies curvas con un bajo coste de poligonización. Ya hemos visto antes como una esfera con baja poligonización nos daba la impresión de ser un modelo hecho a alta poligonización. Cuanto menos polígonos más rápido dibujamos.
OpenGL trata una normal como un vector unitario asociado a un vértice/s. Importante, un vector unitario es aquel cuyo módulo es la unidad. El módulo es la longitud del vector, ver formula inferior:
modulo = (GLfloat) sqrt( ( nx * nx ) + ( ny * ny ) + ( nz * nz ) );
Para definir las normales se utiliza la función "glNormal3f();" antes de definir un vértice/s. Varios vértices pueden compartir una misma normal como el cuadrado de la izquierda, o cada vértice tener su normal como el cuadrado de la derecha.
glNormal3f( GLfloat nx, GLfloat ny, GLfloat nz );
glBegin( GL_QUADS ); glNormal3f( 0.0, 0.0, 1.0 ); glVertex3f( -1.0, 1.0, 0.0 ); glVertex3f( -1.0, -1.0, 0.0 ); glVertex3f( 1.0, -1.0, 0.0 ); glVertex3f( 1.0, 1.0, 0.0 ); glEnd( );
glBegin( GL_QUADS ); glNormal3f( -0.577, 0.577, 0.577 ); glVertex3f( -1.0, 1.0, 0.0 ); glNormal3f( -0.577, -0.577, 0.577 ); glVertex3f( -1.0, -1.0, 0.0 ); glNormal3f( 0.577, -0.577, 0.577 ); glVertex3f( 1.0, -1.0, 0.0 ); glNormal3f( 0.577, 0.577, 0.577 ); glVertex3f( 1.0, 1.0, 0.0 ); glEnd( );
Para calcular la normal (N) de un vértice o punto (P0), al menos es necesario otros dos vértices (P1, P2). Con tres puntos hacemos un triángulo que cuyos vértices compartirán la misma normal. Nombramos a los vectores ( a, b ) y calculamos el producto en cruz para calcular la normal (c).
En la tabla inferior vemos la teoría llevada a la práctica. Si la figura a la que queremos calcular la normal es un cuadrado, un pentágono, un hexágono, etc., sólo tenemos que tomar tres puntos cualquiera y operar de la misma forma.
Se calcula la normal utilizando el producto en cruz: GLfloat cx = ( ay * bz ) - ( az * by ) GLfloat cy = ( az * bx ) - ( ax * bz ) GLfloat cz = ( ax * by ) - ( ay * bx )
Despues hay que normalizar el vector: GLfloat modulo = (GLfloat) sqrt( ( cx * cx ) + ( cy * cy ) + ( cz * cz ) ); cx /= modulo; cy /= modulo; cz /= modulo;
Por supuesto que OpenGL es capaz de normalizar o calcular automáticamente las normales de iluminación de los objetos, pero esto tiene un coste de tiempo, lo cual solo es recomendable utilizar dentro de las listas compiladas.
glEnable( GL_NORMALIZE ); glDisable( GL_NORMALIZE ); Normaliza los vectores de las normales en tiempo de ejecución. glEnable( GL_AUTO_NORMAL ); glDisable( GL_AUTO_NORMAL ); Calcula las normales en tiempo de ejecución.
Hemos visto en varios ejemplos como OpenGL permite aceptar en sus funciones punteros a vectores o arrays de tipos de datos. Esto nos permite tener por ejemplo todos los vértices de un objeto en un único array y pasárselo entero a OpenGL para que lo dibuje. En la tabla inferior están las funciones implicadas en el asunto:
glEnableClientState( GL_enum Parametros ); // Activa los Punteros a Arrays glDisableClientState( GL_enum Parametros ); // Desactiva los Punteros a Arrays Parámetros: - GL_NORMAL_ARRAY // Array de Normales. - GL_TEXTURE_COORD_ARRAY // Array de Coordenadas de Textura. - GL_COLOR_ARRAY // Array de Colores. - GL_VERTEX_ARRAY // Array de Vértices. glNormalPointer( GLenum tipo, GLsizei stride, GLvoid *puntero ); tipo: GL_BYTE, GL_SHORT, GL_INT, GL_FLOAT, or GL_DOUBLE. Normalmente GL_FLOAT. stride: Especifica la separación en bytes entre dos normales consecutivas. Normalmente 0. puntero: El puntero al array de normales. glTexCoordPointer( GLint size, GLenum tipo, GLsizei stride, GLvoid *puntero ); size: Numero de coordenadas de textura. Puede ser 1, 2, 3 o 4. Normalmente 2. tipo: GL_SHORT, GL_INT, GL_FLOAT. Normalmente GL_FLOAT. stride: Especifica la separación en bytes entre dos coordenadas consecutivas. Valor normal es 0. puntero: El puntero al array de coordenadas de textura. glColorPointer( GLint size, GLenum tipo, GLsizei stride, GLvoid * puntero ); size: Número de componentes del color 3 o 4. tipo: GL_BYTE, GL_UNSIGNED_BYTE, GL_SHORT, GL_UNSIGNED_SHORT, GL_INT, GL_UNSIGNED_INT, GL_FLOAT, o GL_DOUBLE. Normalmente GL_FLOAT. stride: Especifica la separación en bytes entre dos colores consecutivos. Valor normal es 0. puntero: El puntero al array de colores. glVertexPointer( GLint size, GLenum tipo, GLsizei stride, GLvoid *puntero ); size: Número de coordenadas por vértice 2, 3 o 4. Normalmente 3. tipo: GL_SHORT, GL_INT, GL_FLOAT, or GL_DOUBLE. Normalmente GL_FLOAT. stride: Especifica la separación en bytes entre dos vértices consecutivos. Valor normal es 0. puntero: El puntero al array de vértices. glDrawArrays( GLenum modo, GLint inico, GLsizei cuantos ); modo: GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP, GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_QUAD_STRIP, GL_QUADS y GL_POLYGON. inicio: El indice de inicio del array. Normalmente 0. cuantos: Número de indices que serán renderizados. Normalmente todos.
En la tabla inferior hay un ejemplo práctico que dibuja un cubo usando arrays, ignorar el array de coordenadas de textura puesto que aún no las hemos estudiado. En la primera parte definimos los arrays y en la segunda parte hacemos las llamadas para que dibuje los arrays. Este sistema es tan rápido como las listas compiladas pero con la ventaja de que permite variar los vértices y sus atributos en tiempo de ejecución.
Cuidado al compilar este sistema dentro de una lista puesto que podría duplicarse los Arrays en la memoria del ordenador y encima no ganaríamos velocidad alguna. Solo sería permitido si los arrays son creados en un ámbito local temporal ( entre llaves { ... } ) o dinámicamente con "new" y borrados con "delete[]" inmediatamente después de compilar la lista. Pero perderíamos la flexibilidad de poder modificar los vértices y sus atributos en tiempo de ejecución.
GLfloat bx = 1; // Ancho GLfloat by = 1; // Alto GLfloat bz = 1; // Profundo GLfloat Normal_Cubo[72] = { 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, // FONDO 0,-1, 0, 0,-1, 0, 0,-1, 0, 0,-1, 0, // ABAJO 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // DERECHA -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, // IZQUIERDA 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // ARRIBA 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1 // FRENTE }; GLfloat Texcoord_Cubo[48] = { 1, 1, 0, 1, 0, 0, 1, 0, // FONDO 1, 1, 0, 1, 0, 0, 1, 0, // ABAJO 1, 1, 0, 1, 0, 0, 1, 0, // DERECHA 1, 1, 0, 1, 0, 0, 1, 0, // IZQUIERDA 1, 1, 0, 1, 0, 0, 1, 0, // ARRIBA 1, 1, 0, 1, 0, 0, 1, 0 // FRENTE }; GLfloat Color_Cubo[72] = { 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, // FONDO 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, // ABAJO 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, // DERECHA 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, // IZQUIERDA 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // ARRIBA 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1 // FRENTE }; GLfloat Vertex_Cubo[72] = { -bx, by,-bz, bx, by,-bz, bx,-by,-bz, -bx,-by,-bz, // FONDO bx,-by, bz, -bx,-by, bz, -bx,-by,-bz, bx,-by,-bz, // ABAJO bx, by,-bz, bx, by, bz, bx,-by, bz, bx,-by,-bz, // DERECHA -bx, by, bz, -bx, by,-bz, -bx,-by,-bz, -bx,-by, bz, // IZQUIERDA bx, by,-bz, -bx, by,-bz, -bx, by, bz, bx, by, bz, // ARRIBA bx, by, bz, -bx, by, bz, -bx,-by, bz, bx,-by, bz // FRENTE }; glEnableClientState( GL_NORMAL_ARRAY ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); glEnableClientState( GL_COLOR_ARRAY ); glEnableClientState( GL_VERTEX_ARRAY ); glNormalPointer( GL_FLOAT, 0, Normal_Cubo ); glTexCoordPointer( 2, GL_FLOAT, 0, Texcoord_Cubo ); glColorPointer( 3, GL_FLOAT, 0, Color_Cubo ); glVertexPointer( 3, GL_FLOAT, 0, Vertex_Cubo ); glDrawArrays( GL_QUADS, 0, 72/3 ); // <- OJO ( 72/3=24 ) glDisableClientState( GL_NORMAL_ARRAY ); glDisableClientState( GL_COLOR_ARRAY ); glDisableClientState( GL_TEXTURE_COORD_ARRAY ); glDisableClientState( GL_VERTEX_ARRAY );
Esta forma de dibujar usando Arrays es imprescindible si queremos aplicar transformaciones a los vértices de un objeto, por ejemplo que un coche se abolle cuando choca.
Dibuja diferentes esferas usando Materiales. Activa la Iluminación y juega con activar o desactivar el color para ver que pasa.
Los materiales y la iluminación van unidos e inseparables. Hay que introducir también el concepto de Normal, que es el ángulo de refracción de la luz que incide en el material. Ahora no vamos a meternos con la iluminación que la veremos en la siguiente práctica pero si con los materiales. Estos pueden ser asignados a las caras delanteras y traseras por separado o en conjunto, varían la refracción de la luz en las zonas iluminadas, no iluminadas, reflejos y pueden emitir luz propia.
Es imprescindible tener activada al menos una luz para poder ver un material, en caso contrario se mostrarán oscurecidos o en caso de desactivar la iluminación con los colores por defecto.
También es imprescindible tener activado "glDisable( GL_COLOR_MATERIAL );" para ver los materiales y no los colores con los que hasta ahora hemos estado dibujando.
En un material con Alpha inferior a 1.0f, es muy recomendable desactivar el material después de su uso. En el resto de los casos no es necesario.
CONTROLES:
Descargar Practica nº05 (Solo usuarios registrados)
Usa todas las Iluminaciones posibles.
Las luces no proyectan sombras, solo admiten un color para la zona iluminada y otro para la que está a la sombra. Las sombras reducen en exceso la velocidad de proceso y por eso no son calculadas. En prácticas posteriores veremos como suplir esta deficiencia.
Una observación, el suelo está formado por muchos cuadrados para producir un efecto de iluminación y refracción más realista. Cuanto mayor sea un cuadrado peor iluminado puesto que la iluminación es calculada en los vertices y luego extendida al resto. Esto nos obliga a utilizar modelos con muchos polígonos como la esfera y el suelo por ejemplo para una correcta visualización. Más adelante veremos como reducir los polígonos de un objeto sin perder un buen aspecto e iluminación.
CONTROLES:
Descargar Practica nº06 (Solo usuarios registrados)
Crea una Pirámide y un Cubo usando un Array de vértices, calcula sus normales y luego suavízalas.
En esta práctica vamos ha matar dos pájaros de un tiro. Primero vamos ha aprender a dibujar usando Arrays de vértices, a calcular las normales para estos vértices, suavizarlas y dibujar todo más rápidamente que usando la función "glBegin();".
La normal por defecto de un plano es la que sale verticalmente en un ángulo de 90 grados. Las normales se representan como un vector de tres dimensiones y su módulo debe ser unitario.
Así que el cálculo se reduce con 3 puntos de un plano, obtener dos vectores, hacer el producto en cruz y normalizar el vector. Hay que hacer esto con todos los polígonos del objeto y es aquí donde usar Arrays nos facilita muchísimo la operación.
Después se puede buscar todos los puntos comunes del objeto y hacer la media de las normales de estos puntos para suavizar la iluminación en los vértices.
Descargar Practica nº07 (Solo usuarios registrados)
Vamos ha dibujar diferentes tipos de figuras geométricas básicas, calculando sus normales de iluminación y dando la opción de suavizarlas.
No prestar atención a "GL_TEXTURE_COORD_ARRAY" puesto que las coordenadas de texturas las veremos más adelante.
Descargar Practica nº08 (Solo usuarios registrados)