Departamento de Tecnologías de la Información Tema 3 Color, material Ciencias de la Computación e Inteligencia Artificial
Índice 3.1 El color en OpenGL 3.2 Tipos de luz y de materiales 3.4 Sombras 3.5 Blending y Antialiasing 3.6 Niebla 2
Índice 3.1 El color en OpenGL 3.2 Tipos de luz y de materiales 3.4 Sombras 3.5 Blending y Antialiasing 3.6 Niebla 3
3.1 El color en OpenGL OpenGL utiliza el formato RGBA (red, green, blue, alpha) para definir los colores. En la mayoría de los ordenadores actuales, el color se representa en 32 bits (8 bits para cada componente, es decir, de 0 a 255). En OpenGL, los componentes se pueden expresar también en números reales entre 0.0 y 1.0. El color es una propiedad asociada a los vértices. Para asignar un determinado color se utiliza la función glcolor (). Por ejemplo: glcolor3ui(0,255,255); glcolor4f(1.0f, 1.0f, 1.0f, 1.0f) ; Los tipos de datos aceptados son b, i, s, ub, ui, us, f y d. El número de parámetros pueden ser 3 (RGB con alpha = 255) o 4 (RGBA). 4
3.1 El color en OpenGL Si dibujamos puntos sueltos (GL_POINTS), el color de cada punto será el asignado a su vértice. Si dibujamos líneas o polígonos con vértices de distinto color, de que color se dibuja la línea o la superficie? OpenGL admite dos tipos de coloreado: GL_SMOOTH y GL_FLAT. GL_FLAT indica que las líneas y los polígonos se dibujan con color uniforme. Generalmente corresponde al color del último vértice de la primitiva, salvo en GL_POLYGON que se toma el color del primer vértice. GL_SMOOTH realiza una interpolación entre los colores de los vértices, creando un dibujo con colores degradados. Para asignar el modo de coloreado se utiliza la función glshademodel(gl_smooth) o glshademodel(gl_flat). 5
Índice 3.1 El color en OpenGL 3.2 Tipos de luz y de materiales 3.4 Sombras 3.5 Blending y Antialiasing 3.6 Niebla 6
3.2 Tipos de luz y de materiales El color con el que vemos los objetos se debe a que la luz choca contra ellos y es en parte absorbida y en parte dispersada. Por ejemplo, un objeto es azul si absorbe todos los colores salvo el azul. Para conseguir un color realista en vez de aplicar un color a cada superficie hay que pensar en la forma en la que la luz va a iluminar el modelo. OpenGL considera que los objetos están iluminados por tres tipos de luces: luz ambiental, luz difusa y luz especular. 7
3.2 Tipos de luz y de materiales La luz ambiental es una luz que procede de todas direcciones y que al chocar con los cuerpos se dispersa en todas las direcciones. La luz ambiental provoca un color que no cambia al modificar la posición de los cuerpos o su orientación. La luz difusa es una luz que proviene de una determinada dirección y que al chocar con los objetos se dispersa en todas direcciones. La intensidad con la que la luz difusa se dispersa es proporcional al ángulo con el que incide sobre la superficie de manera si la luz incide de forma vertical su brillo es mayor que si incide de forma oblicua. La luz especular es una luz que proviene de una determinada dirección y al chocar con un objeto se refleja con el mismo ángulo de incidencia. 8
3.2 Tipos de luz y de materiales Para calcular el color de cada punto de un objeto es necesario conocer tanto el tipo de luz que incide sobre el objeto como las propiedades de absorción de la luz de dicho objeto. Las propiedades de un material frente a la luz ambiental se describen mediante las componentes RGBA. Por ejemplo, un objeto azul se definiría como (0.0, 0.0, 1.0, 1.0). Si la luz ambiental fuera un blanco puro (1.0, 1.0, 1.0, 1.0) el color del objeto sería azul puro (0.0, 0.0, 1.0, 1.0), pero si la luz ambiental fuera rojo puro (1.0, 0.0, 0.0, 1.0) el objeto absorbería toda esa luz y se vería negro (0.0, 0.0, 0.0, 1.0). De la misma manera se puede definir el efecto de la luz difusa y de la luz especular sobre el objeto. 9
Índice 3.1 El color en OpenGL 3.2 Tipos de luz y de materiales 3.4 Sombras 3.5 Blending y Antialiasing 3.6 Niebla 10
El primer paso para trabajar con efectos de luz en OpenGL es activar el modo GL_LIGHTING. De esta forma se indica a OpenGL que debe utilizar las propiedades de material y las fuentes de luz para calcular el color de cada superficie. glenable(gl_lighting); 11
Para activar un cierto modelo d se utiliza la función gllightmodel () Por defecto, la luz ambiental tiene el valor (0.2, 0.2, 0.2, 1.0), lo que resulta bastante tenue. Para utilizar una luz ambiental más intensa hay que especificarlo al seleccionar el modelo d: Glfloat ambient[] = { 1.0, 1.0, 1.0, 1.0 }; glenable(gl_lighting); gllightmodelfv(gl_light_model_ambient, ambient); 12
Una vez activado un tipo d, hay que especificar las propiedades de los materiales a utilizar. Para eso se utiliza la función glmaterial (). El primer argumento de la función glmaterial () indica la cara sobre la que se aplica la definición de material, que puede ser GL_FRONT, GL_BACK y GL_FRONT_AND_BACK. El segundo argumento indica el tipo de propiedad que se quiere describir del material, que puede ser GL_AMBIENT, GL_DIFFUSE, GL_AMBIENT_AND_DIFFUSE, GL_SPECULAR, GL_EMISSION, GL_SHININESS, y GL_COLOR_INDEXES. El tercer argumento es el valor de la propiedad. 13
Por ejemplo, para indicar que un triángulo es de color gris se programaría de la siguiente forma: Glfloat grey[] = { 0.75f, 0.75f, 0.75f, 1.0f }; glmaterialfv(gl_front, GL_AMBIENT_AND_DIFFUSE, grey); glbegin(gl_triangles); glvertex3f(-1.0f, -1.0f, 0.0f); glvertex3f( 1.0f, -1.0f, 0.0f); glvertex3f( 0.0f, 1.0f, 0.0f); glend(); 14
Generalmente las propiedades de los materiales para la luz ambiental y difusa son las mismas, por lo que se suele utilizar el modo GL_AMBIENT_AND_DIFFUSE. El código anterior exige asignar el tipo material cada vez que queramos modificar el color usado para definir polígonos. Normalmente la descripción del material es equivalente en la mayoría de polígonos salvo por el color por lo que resulta más cómodo indicar el color en vez de las propiedades del material. Esto se conoce como color tracking. Para utilizar la opción de color tracking en primer lugar hay que activarla y, a continuación, hay que asignar el tipo de material con glcolormaterial(). 15
Utilizando color tracking el código quedaría así: glenable(gl_color_material); glcolormaterial(gl_front, GL_AMBIENT_AND_DIFFUSE); glcolor3f(0.75f, 0.75f, 0.75f); glbegin(gl_triangles); glvertex3f(-1.0f, -1.0f, 0.0f); glvertex3f( 1.0f, -1.0f, 0.0f); glvertex3f( 0.0f, 1.0f, 0.0f); glend(); 16
La propiedad GL_SPECULAR permite describir el comportamiento del material ante la luz especular. Por defecto el valor es (0.0, 0.0, 0.0, 1.0) indicando que el material no refleja nada de luz. La propiedad GL_EMISSION indica si el material es emisor de luz. Por defecto es (0.0, 0.0, 0.0, 1.0) indicando que el material no emite. La propiedad GL_SHININESS indica el exponente especular del material. Se trata de un valor entre 0 y 128. Por defecto es 0 lo que indica que el material es muy brillante, es decir, que refleja la luz con toda la intensidad en todas las direcciones. El valor 128 hace que el reflejo sea muy direccional y que las fuentes de luz se van reflejadas en el material como puntos. 17
Para utilizar luz difusa o especular es necesario crear fuentes de luz direccionales e indicar el vector perpendicular (normal) a cada polígono para poder calcular el ángulo con el que incide la luz en cada superficie. Los vectores perpendiculares a los polígonos se asignan con la función glnormal (). En realidad, se trata de una propiedad asignada al vértice (como el color). Si indicamos glshademodel(gl_flat), todos los puntos del polígono tienen la misma normal. Si indicamos glshademodel(gl_smooth), la normal en cada punto del polígono se calcula como una interpolación entre las normales de cada vértice. Esto permite simular superficies curvas. 18
Para que el cálculo de la iluminación sea correcto, los vectores normales deben ser vectores unitarios (de módulo 1). Si queremos que OpenGL se encargue de normalizar (escalar a 1) los vectores normales hay que activar la opción glenable(gl_normalize); Esta opción supone que OpenGL va a normalizar todos los vectores normales antes de generar la imagen lo que suele suponer un coste computacional muy alto. Si las llamadas a glnormal..() se realizan siempre con vectores unitarios la única forma de perder esta condición es realizando transformaciones de escalado con glscalef(). Se puede indicar a OpenGL que sólo normalice los vectores al aplicar escalado con la opción glenable(gl_rescale_normals); 19
Ejemplo: glenable(gl_lighting); glenable(gl_color_material); glcolormaterial(gl_front, GL_AMBIENT_AND_DIFFUSE); glenable(gl_rescale_normals); glcolor3f(0.75f, 0.75f, 0.75f); glbegin(gl_triangles); // Triángulo perpendicular al eje Z glnormal3f(0.0f,0.0f,1.0f); glvertex3f(-1.0f, -1.0f, 0.0f); glvertex3f( 1.0f, -1.0f, 0.0f); glvertex3f( 0.0f, 1.0f, 0.0f); glend(); 20
En realidad, el código anterior sólo utiliza luz ambiental. Para utilizar luz difusa es necesario crear una fuente de luz utilizando la función gllight (). El primer argumento de esta función es el identificador de la fuente de luz, que puede ser GL_LIGHT0, GL_LIGHT1, hasta el índice GL_MAX_LIGHTS -1. El valor de GL_MAX_LIGHTS depende de cada plataforma y se puede obtener con la llamada glgetintegerv(gl_max_ligths, &num); 21
El segundo argumento de gllight () es la propiedad que se pretende asignar a la fuente de luz. Se pueden asignar varias propiedades a la misma fuente de luz con varias llamadas a gllight..(). Las propiedades que se pueden asignar son: GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, GL_POSITION, GL_SPOT_CUTOFF, GL_SPOT_DIRECTION, GL_SPOT_EXPONENT, GL_CONSTANT_ATTENUATION, GL_LINEAR_ATTENUATION y GL_QUADRATIC_ATTENUATION. El tercer argumento de gllight () es el valor de la propiedad que estamos asignando a la fuente de luz. Una vez descrita la fuente de luz, hay que activarla con glenable(gl_light0); 22
La propiedad GL_AMBIENT describe el componente de luz ambiental, que por defecto es (0.0, 0.0, 0.0, 1.0). La propiedad GL_DIFFUSE describe la componente de luz difusa. Por defecto el valor de GL_LIGHT0 es (1.0,1.0,1.0,1.0) y el de las demás es (0.0, 0.0, 0.0, 1.0). La propiedad GL_SPECULAR describe la componente de luz especular. Por defecto es (0.0, 0.0, 0.0, 1.0). La propiedad GL_POSITION describe la posición de la fuente de luz. El valor de la componente w puede ser 0 indicando que la fuente se en cuentra a una distancia infinita. Por defecto es (0.0, 0.0, 1.0, 0.0) indicando una luz situada sobre el eje Z a una distancia infinita. La posición se transforma con el valor de la matriz modelview en el momento de la llamada a gllight(). 23
La propiedad GL_SPOT_DIRECTION indica la dirección principal de la luz. Se utiliza con luz difusa o especular situada en un punto concreto. La propiedad GL_SPOT_CUTOFF indica el ángulo de corte de la luz medido en grados. Esto permite definir focos con una determinada amplitud. Se admiten valores entre 0 y 90, así como el valor especial 180. Por defecto el valor es 180 indicando que la fuente emite en todas direcciones. La propiedad GL_SPOT_EXPONENT indica la distribución de intensidad de la luz direccional. La intensidad en una dirección se calcula como el coseno del ángulo sobre la dirección principal, elevado a este exponente. Por defecto es 0, por lo que la distribución es constante. 24
Las propiedades GL_CONSTANT_ATTENUATION, GL_LINEAR _ATTENUATION y GL_QUADRATIC_ATTENUATION describen la forma en que la luz se atenua con la distancia a la fuente de luz. Para una distancia x, el factor de atenuación es 1/(C+L x+q x 2 ). Por defecto los valores son (1,0,0) por lo que la luz no se atenua con la distancia. 25
Ejemplo: GLfloat lightpos[] = { 0.0f, 0.0f, 75.0f, 1.0f }; GLfloat specular[] = { 1.0f, 1.0f, 1.0f, 1.0f }; GLfloat specref[] = { 1.0f, 1.0f, 1.0f, 1.0f }; GLfloat ambient[] = { 0.5f, 0.5f, 0.5f, 1.0f }; GLfloat spotdir[] = { 0.0f, 0.0f, -1.0f }; glenable(gl_lighting); gllightmodelfv(gl_light_model_ambient, ambient); gllightfv(gl_light0, GL_DIFFUSE, ambient); gllightfv(gl_light0, GL_SPECULAR, specular); gllightfv(gl_light0, GL_POSITION, lightpos); gllightf(gl_light0, GL_SPOT_CUTOFF, 60.0f); glenable(gl_light0); 26
Ejemplo (continuación): glenable(gl_color_material); glcolormaterial(gl_front, GL_AMBIENT_AND_DIFFUSE); glenable(gl_rescale_normals); glmaterialfv(gl_front, GL_SPECULAR, specref); glmateriali(gl_front, GL_SHININESS, 128); glcolor3f(0.75f, 0.75f, 0.75f); glbegin(gl_triangles); // Triángulo perpendicular al eje Z glnormal3f(0.0f,0.0f,1.0f); glvertex3f(-1.0f, -1.0f, 0.0f); glvertex3f( 1.0f, -1.0f, 0.0f); glvertex3f( 0.0f, 1.0f, 0.0f); glend(); 27
Índice 3.1 El color en OpenGL 3.2 Tipos de luz y de materiales 3.4 Sombras 3.5 Blending y Antialiasing 3.6 Niebla 28
3.4 Sombras Las sombras permiten mejorar el realismo de los gráficos. Un objeto sólido debería tapar la luz que proviene de un foco, de manera que los objetos que estén colocados detrás no sean iluminados por ese foco. OpenGL no permite generar sombras de manera automática. La generación de sombras debe programarse de forma manual. Generar la sombra que produce un objeto sobre una superficie plana es relativamente sencillo. Consiste en proyectar el objeto sobre dicha superficie. Para ello hay que calcular la matriz de proyección, aplicarla al modelo y dibujar el objeto (de color sombreado). 29
3.4 Sombras Ejemplo: // point0, point1 y point2 definen el plano // planeeq es un vector de 4 componentes que contienen los // coeficientes de la ecuación del plano MakePlaneEquation(planeEq, point0, point1, point2); // calcula la matriz de proyección sobre el plano // (planeeq) en la dirección de la luz (lightpos) MakePlanarShadowMatrix(&shadowMatrix, lightpos, planeeq); 30
3.4 Sombras Ejemplo: // Dibuja la sombra de un objeto void DrawShadow() { gldisable(gl_depth_test); gldisable(gl_lighting); glpushmatrix(); glmultmatrixf(shadowmatrix); glcolor3f(0.2f, 0.2f, 0.2f); // color de la sombra DrawObject(); glpopmatrix(); glenable(gl_lighting); glenable(gl_depth_test); } 31
3.4 Sombras Para generar la sombra de esta forma hay que deshabilitar la iluminación y el test de profundidad. De esta forma la sombra se dibuja encima del plano y no es iluminada por el foco. El orden debe ser el siguiente. Primero dibujar el plano (el suelo, por ejemplo). A continuación dibujar la sombra (encima del plano y sin efectos de luz). Por último se dibuja el objeto. Este truco sirve para generar sombras sobre el suelo o, en general, sobre superficies planas en las que quepa la superficie completa. No es válido para dibujar sombras de un objeto sobre otro. 32
3.4 Sombras Otra técnica para generar sombras de forma general (incluidas sombras de un objeto sobre otro) consiste en estudiar la imagen que se generaría si el observador estuviera colocado en el foco de luz. Esta imagen corresponde a la zona que quedaría iluminada, de manera que lo que no aparezca en esa imagen debe encontrarse en zona de sombra. La información del buffer de profundidad se utiliza como textura para generar la imagen desde la posición del observador. 33
Índice 3.1 El color en OpenGL 3.2 Tipos de luz y de materiales 3.4 Sombras 3.5 Blending y Antialiasing 3.6 Niebla 34
3.5 Blending y Antialiasing Hasta ahora, todos los colores que hemos considerado han sido opacos (es decir, la componente alpha era 1.0). Para tener en cuenta efectos de transparencia es necesario utilizar valores diferentes para esta componente alpha. Por defecto, OpenGL no tiene en cuenta las transparencias sino que utiliza el buffer de profundidad para detectar cual es la superficie más cercana y utiliza el color de esa superficie para calcular el color final a mostrar en la imagen. La transparencia provoca una mezcla (blending) de colores entre el color del material traslúcido y el color de la superficie que se encuentre detrás. Para activar los efectos de transparencia hay que ejecutar glenable(gl_blend); 35
3.5 Blending y Antialiasing La imagen generada en el proceso de rendering se almacena en el COLOR_BUFFER. Si la opción de profundidad (GL_DEPTH_TEST) no está activada, los polígonos se dibujan en el orden en el que se generan. Si la opción de profundidad está activada, al recibir la información de un polígono se calcula la profundidad de cada punto y se compara con la profundidad de lo pintado anteriormente. Si el nuevo polígono se encuentra más cerca se sobreescribe el COLOR_BUFFER. Si está más lejos no se modifica el COLOR_BUFFER. Si la opción de mezcla está activada, el color final se calcula combinando el color que se encuentra en el buffer (destination color) con el del nuevo polígono (source color). 36
3.5 Blending y Antialiasing Por defecto, la función de mezcla para cada componente RGBA es la siguiente: C f = (C s * S) + (C d * D) Para asignar los valores de S y D se utiliza la función glblendfunc(glenum S, GLenum D); Los valores de S y D pueden ser los indicados en la siguiente tabla: 37
3.5 Blending y Antialiasing Función Componentes RGB Componente A GL_ZERO (0,0,0) 0 GL_ONE (1,1,1) 1 GL_SRC_COLOR (Rs, Gs, Bs) As GL_ONE_MINUS_SRC_COLOR (1-Rs, 1-Gs, 1-Bs) 1-As GL_DST_COLOR (Rd, Gd, Bd) Ad GL_ONE_MINUS_DST_COLOR (1-Rd, 1-Gd, 1-Bd) 1-Ad GL_SRC_ALPHA (As, As, As) As GL_ONE_MINUS_SRC_ALPHA (1-As, 1-As, 1-As) 1-As GL_DST_ALPHA (Ad, Ad, Ad) Ad GL_ONE_MINUS_DST_ALPHA (1-Ad, 1-Ad, 1-Ad) 1-Ad GL_CONSTANT_COLOR (Rc, Gc, Bc) Ac GL_ONE_MINUS_CONSTANT_COLOR (1-Rc, 1-Gc, 1-Bc) 1-Ac GL_CONSTANT_ALPHA (Ac, Ac, Ac) Ac GL_ONE_MINUS_CONSTANT_ALPHA (1-Ac, 1-Ac, 1-Ac) 1-Ac GL_SRC_ALPHA_SATURATE Min(As, 1-As) 1 38
3.5 Blending y Antialiasing Los valores de las opciones que usan constantes se fijan con la función glblendcolor( r, g, b, a). Por ejemplo: glblendfunc(gl_src_alpha, GL_ONE_MINUS_SRC_ALPHA); Para un destination color (1.0f, 0.0f, 0.0f, 1.0f) y un source color (0.0f,0.0f, 1.0f, 0.6f) la mezcla queda RGBA f = (0.0f,0.0f,1.0f,0.6f)*0.6 + (1.0f,0.0f,0.0f,1.0f)*0.4 = (0.4f, 0.0f, 0.6f, 0.76f) 39
3.5 Blending y Antialiasing También es posible modificar la función de mezcla: glblendequation(glenum mode); Donde el modo puede ser Modo Función GL_FUNC_ADD C f = (C s * S) + (C d * D) GL_FUNC_SUBTRACT C f = (C s * S) - (C d * D) GL_FUNC_REVERSE_SUBTRACT C f = (C d * D) - (C s * S) GL_MIN C f = min(c s, C d ) GL_MAX C f = max(c s, C d ) 40
3.5 Blending y Antialiasing Una de las aplicaciones de la mezcla de colores es el antialiasing. El aliasing consiste en que se aprecie el pixelado de la imagen. Para activar la opción de antialiasing es necesario activar el blending con la especificación que hemos visto: glenable(gl_blend); glblendfunc(gl_src_alpha, GL_ONE_MINUS_SRC_ALPHA); A continuación se pueden activar las opciones de antialiasing sobre el dibujo de puntos, líneas o superficies: glenable(gl_point_smooth); glenable(gl_line_smooth); glenable(gl_polygon_smooth); 41
Índice 3.1 El color en OpenGL 3.2 Tipos de luz y de materiales 3.4 Sombras 3.5 Blending y Antialiasing 3.6 Niebla 42
3.6 Niebla OpenGL permite añadir un efecto de niebla que se aplica después de realizar el resto de los cálculos de color. El efecto de la niebla consiste en mezclar una cantidad de color que varía en función de la distancia a la que se encuentra cada superficie. Para utilizar el efecto de la niebla se usan las funciones: glenable(gl_fog); // Activa la opción glfogfv(gl_fog_color, color); // Asigna el color de la niebla glfogf(gl_fog_start, dist); // Distancia a la que comienza la niebla glfogf(gl_fog_end, dist); // Distancia a la que se satura la niebla glfogi(gl_fog_mode, GL_LINEAR); // Ecuación utilizada 43
3.6 Niebla OpenGL admite tres ecuaciones diferentes para calcular el efecto de la niebla: Modo Ecuación GL_LINEAR (end c) / (end start) GL_EXP exp(-d * c) GL_EXP2 exp( - (d*c) 2 ) Donde c es la distancia a la que se encuentra la superficie y d es la densidad de la niebla, que puede asignarse con: glfogf(gl_fog_density, d); 44