Sombras y Reflejos
en Tiempo Real
Implementación mediante OpenGL
Uso del stencil buffer
Jose María Buades Rubio
Introducción
• Renderizar polígonos con texturas
Z-Buffer mostrará la salida correcta
• La limitación de los efectos viene dada
por el hardware
• Dos efectos que mejoran la calidad
gráfica son las sombras y los reflejos
Stencil Test
• Mejoran la calidad de las sombras y
reflejos
• Stenciling es un test extra por pixel y un
conjunto de operaciones muy parecidas
al z-buffer
• Añade planos de bits adicionales para
cada pixel, ademas de los bits de color y
profundidad
Stencil Test II
• Es una manera de “marcar” pixeles en
una renderización para controlar su
actualización en renderizaciones
siguientes
• Algunas tarjetas gráficas como RivaTNT
soporta stencil buffer de 8-bits
• Tanto DirectX y OpenGL soportan Stencil
Buffer
Secuencia de operaciones
Pixel
Ownership
Test
Scissor Test
Depth Test
Blending
Alpha Test
Stencil Test
Dithering
Logic Op
Per-pixel Stencil Testing
• Inicialización mediante GLUT & OpenGL
glutInitDisplayString(“stencil>=1 rgb depth double”)
glutCreateWindow(“Stencil Buffer Example”)
• El valor stencil de un pixel es un entero
sin signo (unsigned int)
Per-pixel Stencil Testing II
• Como borrar el stencil buffer
glClearStencil(0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
GL_STENCIL_BUFFER_BIT)
• Activar y desactivar el test
glEnable(GL_STENCIL_TEST)
glDisable(GL_STENCIL_TEST)
Per-pixel Stencil Testing III
• El stencil test compara el valor del pixel
del stencil buffer con un valor de
referencia
• Permite las siguientes comparaciones:
never, always
less than, less than or equal
greater than, greater than or equal
equal, not equal
Per-pixel Stencil Testing IV
• Es más potente que el buffer de
profundidad, antes de comparar realiza
una operación AND, tanto del valor de
referencia como del valor del buffer, con
un valor que sirve como máscara
glStencilFunc( GL_EQUAL,
0x1,
0xff);
// función de comparación
// valor de referencia
// máscara
Per-pixel Stencil Testing V
• Si el pixel no pasa el test de profundidad
es rechazado, en cambio si pasa
reemplaza el valor del z-buffer. El stencil
test es más complicado:
– NO pasa el stencil test
– Pasa stencil test y NO el test de profundidad
– Pasa stencil test y el test de profundidad
Per-pixel Stencil Testing V
• Para cada caso tenemos seis operaciones
posibles:
GL_KEEP, GL_REPLACE, GL_INVERT,
GL_INCREMENT, GL_DECREMENT, GL_ZERO
• Antes de escribir se aplica una máscara
glStencilOp(
GL_KEEP,
GL_DECR,
GL_INCR);
glStencilMask(0xff);
// stencil fail
// stencil pass, depth fail
// stencil pass, depth pass
Per-pixel Stencil Testing VI
State Description
Initial Value
State Update
Command
State Query Token
Stencilling enable
GL_FALSE
glEnable
glDisable
GL_STENCIL_TEST
Stencil function
GL_ALWAYS
glStencilFunc
GL_STENCIL_FUNC
Stencil compare mask
All 1’s
glStencilFunc
GL_STENCIL_VALUE_MASK
Stencil reference value
0
glStencilFunc
GL_STENCIL_REF
Stencil fail operation
GL_KEEP
glStencilOp
GL_STENCIL_FAIL
Stencil depth fail operation
GL_KEEP
glStencilOp
GL_STENCIL_PASS_DEPTH_PASS
Stencil depth pass operation GL_KEEP
glStencilOp
GL_STENCIL_PASS_DEPTH_PASS
Stencil write mask
All 1’s
glStencilMask
GL_STENCIL_WRITEMASK
Stencil buffer clear value
0
glClearStencil
GL_STENCIL_CLEAR_VALUE
Reflejos Planos sin Stencil
• Tiene una serie de limitaciones:
El objeto reflejado sale por fuera de la
superficie reflectante
Desde detrás del espejo se ve el objeto
reflejado
No permite reflejos infinitos, esta limitado a
un número n de reflejos
Algoritmo
1. Cargar la matriz de modelización
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glLookAt(eye[0], eye[1], eye[2],
center[0], center[1], center[2],
up[0], up[1], up[2]);
2. Introducir en la pila la matriz de modelización
glPushMatrix();
3. Aplicar la matriz de reflejo (plano z = 0)
glScalef(1.0, 1.0, -1.0);
4. Si usamos recorte de caras traseras, debemos realizar el
contrario, eliminación de caras delanteras
glCullFace(GL_FRONT);
5. Renderizar la escena, pero solo los objetos que estan en
el lado reflectante del espejo
Algoritmo (continuación)
6. Restaurar los valores antes del espejo
glCullFace(GL_BACK);
glPopMatrix();
7. Renderizar el espejo (por ejemplo si es marmol)
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE); // aditivo
rederizarEspejoConTextura();
glDisble(GL_BLEND);
Incluso si el espejo no esta renderizado como una
superficie semitransparente es importante renderizarlo
para actualizar el z-buffer y evitar que aparezcan
objetos que estan detrás del objeto
glColorMask(0, 0, 0, 0);
renderizarEspejoConPoligonos()
glColorMask(1, 1, 1, 1);
8. Finalmente renderizar la escena no reflejada
Resultados
Reflejos Planos con Stencil
• Técnica más perfeccionada
• Permite tener espejos de tamaño limitado
• Podemos situar la cámara al otro lado del
espejo
Algoritmo
Renderizar la escena, excepto los espejos, con z-buffer pero
sin stencil test
glClearStencil(0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
GL_STENCIL_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glDisable(GL_STENCIL_TEST);
DrawScene();
Para cada espejo realizar los siguientes pasos:
1. Configurar para que escriba 1 en el stencil buffer cuando
pasa el buffer de profundidad, deshabilitar escribir en
el buffer de color. Entonces renderizar el espejo
glEnable(GL_STENCIL_TEST);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glStencilFunc(GL_ALWAYS, 1, ~0);
glColorMask(0, 0, 0, 0);
RenderizarPoligonoEspejo();
Algoritmo (continuación)
2. Con el buffer de color deshabilitado poner en el z-buffer
el valor de profundidad máxima para los pixeles que
corresponden al espejo
glDepthRange(1, 1);
glDepthFunv(GL_ALWAYS);
glStencilFunc(GL_EQUAL, 1, ~0);
glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
RenderizarPoligonoEspejo();
3. Restaurar el test de profundidad, la mascara de color y
el rango de profundidad a los valores estándar
glDepthFunc(GL_LESS);
glColorMask(1, 1, 1, 1);
glDepthRange(0, 1);
Ahora tenemos marcados como 1 en el stencil buffer los
pixeles que corresponden al espejo y con la máxima
profundidad en el z-buffer
Algoritmo (continuación)
4. Para evitar renderizar polígonos ubicados detrás del
espejo definiremos un plano de clipping
GLfloat matrix[4][4];
GLdouble clipPlane[4];
glPushMatrix();
CalcularPlanoCorteEspejo(clipPlane);
glClipPlane(GL_CLIP_PLANE0, clipPlane);
CalcularMatrizEspejo(&matrix[0][0]);
glMultMatrix(&matrix[0][0]);
glCullFace(GL_FRONT);
DrawScene();
DibujarOtrosEspejosComoSuperficiesGrises();
glCullFace(GL_BACK);
glDisable(GL_CLIP_PLANE0);
glPopMatrix();
Ahora esta correctamente rederizado el reflejo del espejo
Algoritmo (continuación)
5. Ahora reseteamos el valor del stencil buffer a 0 para que
no se confundan con otros espejos, ademas actualizamos el
z-buffer para que tenga como profundidad la del espejo y
no la del objeto reflejado
glColorMask(0, 0, 0, 0);
glStencilop(GL_KEEP, GL_KEEP, GL_ZERO);
glDepthFunc(GL_ALWAYS);
RenderizarPoligonoEspejo();
glDepthFunc(GL_LESS);
glColorMask(1, 1, 1, 1);
Sombras Planas sin Stencil
• Los modelos de iluminación locales
soportados por OpenGL y Direct3D no
provocan sombras
• Proyectando un polígono sombre el plano
a sombrear es una técnica para provocar
el efecto sombra
Algoritmo
1. Cargar la matriz de modelización
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glLookAt(eye[0], eye[1], eye[2],
center[0], center[1], center[2],
up[0], up[1], up[2]);
2. Dada la luz y la ecuación del plano donde se proyecta, y
asumiendo que el objeto esta entre la luz y el plano,
habilitar la luz y renderizar el objeto y el plano
GLfloat lightPosition[4] = {LX, LY, LZ, 1.0};
GLfloat groundPlaneEquation[4] = {A, B, C, D};
glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
DibujarObjeto();
DibujarPlano();
Algoritmo (continuación)
3. Meter en la pila la matriz de modelización
glPushMatrix();
4. Construir la matriz de proyección, multiplicar la matriz
de modelización por la matriz de proyección de la sombra
GLfloat matrix[4][4];
ShadowMatrix(&matrix[0][0], lightPosition,
groundPlaneEquation);
glMultMatrix(&matrix[0][0]);
5. A menos que lo remediemos la sombra y el plano donde se
proyecta son coplanares. Esto causa problemas en el ZBuffer, a veces la sombra esta más cerca, a veces igual y
otras veces más alejado, esto es debido a imprecisiones
en el cálculo numérico
glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(1.0, 2.0);
Esto provoca que este dos unidades más cerca
Algoritmo (continuación)
6. Deshabilitar la iluminación, poner el color gris oscuro y
dibujar el objeto de nuevo
glDisable(GL_LIGHTING);
glColor3f(0.25, 0.25, 0.25);
DibujarObjeto();
7. Volver al estado anterior
glDisable(GL_POLYGON_OFFSET);
glPopMatrix();
Resultados
Sombras Planas con Stencil
• Evita problema de doble transparencia
• Impide que la sombra salga del polígono
sobre el cual se quiere calcular la sombra
Algoritmo
1. Cargar la matriz de modelización
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glLookAt(eye[0], eye[1], eye[2],
center[0], center[1], center[2],
up[0], up[1], up[2]);
2. Dada la luz y la ecuación del plano donde se proyecta, y
asumiendo que el objeto esta entre la luz y el plano,
habilitar la luz y renderizar el objeto y el plano
GLfloat lightPosition[4] = {LX, LY, LZ, 1.0};
GLfloat groundPlaneEquation[4] = {A, B, C, D};
glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
DibujarObjeto();
DibujarPlano();
Algoritmo (continuación)
3. Meter en la pila la matriz de modelización
glPushMatrix();
4. Construir la matriz de proyección, multiplicar la matriz
de modelización por la matriz de proyección de la sombra
GLfloat matrix[4][4];
ShadowMatrix(&matrix[0][0], lightPosition,
groundPlaneEquation);
glMultMatrix(&matrix[0][0]);
5. Asignar un valor distinto de cero al stencil buffer
glEnable(GL_STENCIL_BUFFER);
glStencilFunc(GL_ALWAYS, valor, ~0);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
DibujarPlanoASombrear();
glDisable(GL_STENCIL_TEST);
Algoritmo (continuación)
6. Deshabilitar la iluminación
glDisable(GL_LIGHTING);
glEnable(GL_BLEND);
glBlendFunc(GL_DST_COLOR, GL_ZERO);
glColor3f(0.5, 0.5, 0.5);
glDisable(GL_DEPTH_TEST);
Solo debemos actualizar los pixeles marcados con valor
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_EQUAL, valor, ~0);
glStencilOp(GL_KEEP, GL_KEEP, GL_ZERO);
DibujarObjeto();
7. Restaurar los valores
glDisable(GL_BLEND);
glDisable(GL_STENCIL_TEST);
glEnable(GL_DEPTH_TEST);
glPopMatrix();
Programa Demo
Dinoshade
Descargar

Sombras y Reflejos en Tiempo Real