domingo, 31 de octubre de 2010

Shadow-Texture Map

Este post esta relacionado con el de Shadows Map anterior. La idea surgió haciendo modificaciones al shadow map para que en lugar de proyectar una sombra, proyecte una luz o una textura. (ojo que no es un light map), por ejemplo una persiana que proyecta la luz (y la sombra) sobre el piso.
Esto mismo se podría hacer generando la geometría de la persiana y ubicando una luz atras de la misma, y usando el shadow map standard, pero eso es muy costoso, en el sentido que la geometría suele ser bastante complicada. (En verdad ni lo probé).
Lo que si probé, es modificar la textura del shadow map a mano, simulando que existe una geometría, y eso si funciona relativamente rápido. Por ejemplo, si escribo un texto en el shadow map (una vez que lo tengo en memoria ya calculado, usando el shadow map en 2d), el efecto es que se proyecta el texto como sombra en la escena. Sin embargo después de analizarlo más a fondo, encontré que ni hace falta generar el shadow map, y la cosa es mucho más sencilla.
Vamos de cero:
En el SM standard, el problema central radica en obtener las coordenadas del shadowmap a partir de las coordenadas del pixel actual. En otras palabras, lo que quiero saber es que coordenadas tendría el pixel actual desde el punto de vista de luz, y no de la camara.

Si partimos de la posición iPos de un vértice en object space y en lugar de aplicar la transformación de la camara, aplicamos la de la luz:

vPosLight = mul( iPos, g_mLightWorldViewProj );

g_mLightWorldViewProj es lo mismo que el g_mWorldViewProj pero haciendo de cuenta que la camara esta posicionada en la luz, y mirando hacia la direccion arbitraria a la que apunta el spot de la luz pp dicha.


Ahora, en el shadowmap standard, habria que usar las coordenadas vPosLight, para buscar en el shadowmap (previamente calculado) y verificar si el punto esta iluminado o no. Sin embargo lo que vamos a hacer es mucho mas simple:
1- No hay ningun shadow map si no una textura previamente cargada, que es la que queremos proyectar
2- En el VertexShader generamos vPosLigth como siempre: vPosLight = mul( iPos, g_mLightWorldViewProj );
3- En el PixelShader, usamos vPosLight para calcular la pos. en la textura a proyectar, y si esta dentro de los limites de la misma (x,y entre 0 y 1), tomamos el color de la textura.

Ejemplo:

float4 PixScene2( float2 Tex : TEXCOORD0,
float4 vPos : TEXCOORD1,
float3 vNormal : TEXCOORD2,
float4 vPosLight : TEXCOORD3 ) : COLOR
{
float4 color1 = 0;
float4 color2 = tex2D( g_samScene, Tex );
if(vPosLight.w>=0)
{
float2 pos = 0.5 * vPosLight.xy / vPosLight.w + float2( 0.5, 0.5 );
pos.y = 1.0f - pos.y;
if(pos.x>=0 && pos.x<=1 && pos.y>=0 && pos.y<=1)
color1 = tex2D( g_samProjTex, pos);
}

return lerp(color1,color2,0.5);
}










Esta linea sirve para limitar la proyeccion a la parte que esta delante del nearplane
if(pos.x>=0 && pos.x<=1 && pos.y>=0 && pos.y<=1)
De lo contrario saldria asi:





Por otra parte, la matematica involucrada en la proyeccion genera 2 imagenes, una inversa de la otra, cuando las coordenadas homogeneas son ambas negativas, es decir
tanto z, como w son negativas, luego Zp = z/w >0, pero estamos del otro lado de la "camara", ejemplo:




Para eliminar ese artificio, simplemente se puede poner:
if(vPosLight.w>=0)

Por ultimo, habría que combinarlo con el ShadowMap estandard, para evitar proyectar la textura en los lugares donde no podria verse, porque esta oculta por otro objeto.