viernes, 19 de noviembre de 2010

GPU-RT: Reflexiones

Una de las grandes ventajas del ray-tracing con respecto al rasterizado es la posibilidad de calcular reflexiones. La reflexion es simplemente calcular un rayo mas: hay que hacer rebotar el rayo, y seguir haciendo el ray-tracing de forma recursiva tantas veces como el nivel de detalle que se requiera.

Sin embargo, en la GPU eso plantea unos cuantos problemas, ya que no se podemos llamar al PS dentro del PS, y tenemos que ir generando la imagen en etapas sucesivas.

Para resolverlo, vamos a plantear un sistema de "retroalimentacion". Primero, como siempre se dibujan los triangulos a efectos de llamar al PS para cada pixel. Pero la salida se redirige a 3 texturas simultaneas (se requiere que la placa soporte multiples renders target, si no lo tiene igual tambien se puede hacer, pero se requieren todavia mas pasos).
El primer RenderTarget sera el color buffer pp dicho. El segundo el rayo reflejado, y el tercero el punto de interseccion.
En el segundo paso, estas 3 texturas se usan como datos de entrada. El rayo reflejado se transforma en la direccion del rayo pp dicha. Y el colorbuffer anterior sirve para hacer el blending. El punto de interseccion se usa para calculos internos.

void PS_RayTracing( float4 Diffuse:COLOR0, float2 Tex : TEXCOORD0,
out float4 Color : COLOR0 ,out float4 RayDir : COLOR1,out float4 oIp : COLOR2 )
{
// Calculo la direccion del rayo u Obtengo la direccion del rayo
float3 D = nro_R==0?
normalize(g_vViewDir + g_vDx*(2*Tex.x-1) + g_vDy*(1-2*Tex.y)).rgb:
tex2D( g_samRayDir, Tex);

......
......


Como se ve en el codigo, la primera vez (nro de recursion = nro_R = 0) se calcula el rayo, pero las siguientes se obtiene el rayo de la salida anterior.
Entonces, cuando se requiere que el algoritmo siga calculando rayos, por ejemplo el caso de un espejo, hay que generar el rayo reflejado en salida:

if(objsel!=-1)
// Calculo la direccion del rayo reflejado
D = reflect(D,N);
else
// el 2 indica que no hay mas rayos que trazar
D.x = 2;
.....
.....
// out RayDir
RayDir.xyz = D;
RayDir.w = 0;




Al final del PS se genera la direccion del rayo reflejado en salida, que sera guardado en una textura auxiliar, para usar como dato de entrada en el siguiente paso.








piso reflejando las esferas, que a su vez reflejan el piso y si mismas. Hay 2 niveles de reflexion.



misma escena con 3 niveles de recursion:


misma escena con 6 niveles de recursion:


Ahora, agregando colores a las esferas + luz especular, segun estas formulas:

float kd = saturate(dot(g_vLightDir,N));        // luz diffusa
float ks = saturate(dot(reflect(g_vLightDir,N), normalize(Ip-LF)));
ks = 0.7*pow(ks,5); // luz esspecular
color_actual = saturate((k_la + (1-k_la)*kd)*color_obj + ks);






En cada paso, los resultados guardados en las texturas del paso anterior, se combinan con los actuales con una operacion del blend. Para el blend se puede usar un factor de alfa constante, o bien, que ese valor se vaya haciendo cada vez mas pequeño en cada reflexion, motivado por el hecho que los rayos reflejados pierden energia en cada reflexion, por eso las imagenes se ven mas tenues en las sucesivas recursiones.


if(nro_R==0)
Color.rgb = color_actual;
else
{
//float alfa = pow(0.5,nro_R);
float alfa = 0.5;
Color.rgb = alfa*color_actual + (1-alfa)*tex2D( g_samColorBuf, Tex);
}

Ejemplo con alfa = 0.5


No hay comentarios:

Publicar un comentario