Xbox 360

Representación de campos escalares tridimensionales

La representación volumétrica es una técnica que permite la representación de campos escalares tridimensionales. Sus aplicaciones son múltiples: representación de imágenes del cuerpo humano procedentes de diversos instrumentos sensores con fines médicos, representación realista de nubes u otros fenómenos gaseosos en simulaciones visuales en entornos virtuales, o representación de diversas estructuras y constructos en la ciencia y la ingeniería.

En este artículo se describirá una variante de la técnica basada en texturas conocida como ‘volume ray casting’. Conceptualmente dicha técnica consiste en el lanzamiento de rayos con origen en la cámara de tal forma que atraviesen el volumen que contiene el campo escalar. El color resultante que se ‘pintará’ en la superficie del volumen en el punto de entrada de cada rayo será una función de los puntos que el rayo atravesó al pasar por el volumen.

Para su implementación se utilizará la plataforma XNA y se hará uso de la aceleración grafica que nos proporcionan la GPU. Será necesaria una tarjeta que soporte al menos la versión 3.0 del modelo de sombreadores (Shader Model 3.0).

El proceso comienza con la representación de un volumen que actuará como frontera del campo escalar, es decir, el campo escalar se encontrará completamente en el interior de ese volumen. Utilizaremos un cubo.

En primer lugar representaremos las posiciones de entrada del rayo y de salida en dos texturas que posteriormente utilizaremos para determinar la dirección. Al Vertex Shader en la GPU se le harán llegar las posiciones sin transformar de los vértices del cubo. Dichas coordenadas se copiaran a la variable InterpolatedPosition a la salida del Vertex Shader. Dicha variable está marcada como coordenadas de textura por lo que a la entrada del pixel shader se recibiran las coordenadas interpoladas. Lo mismo se hará para la variable InterpolatedTransformedPosition solo que en este caso almacenaremos las coordenadas transformadas por la combinación de las matrices de posicionamiento en el mundo (world), vista (view) y proyección (projection) que serán suministradas a la entrada del programa de la GPU como parámetros. Esta última variable será utilizada posteriormente para localizar la posición exacta donde se deben muestrear las texturas para obtener la dirección del rayo.

    1 struct VertexShaderInput

    2 {

    3     float4 Position : POSITION0;   

    4 };

    5 

    6 struct VertexShaderOutput

    7 {

    8     float4 Position : POSITION0;

    9     float4 InterpolatedPosition : TEXCOORD0;

   10     float4 InterpolatedTransformedPosition : TEXCOORD1;   

   11 };

   12 

   13 VertexShaderOutput VertexShaderFunction(VertexShaderInput input)

   14 {

   15     VertexShaderOutput output;

   16 

   17     float4 worldPosition = mul(input.Position, World);

   18     float4 viewPosition = mul(worldPosition, View);

   19     output.Position = mul(viewPosition, Projection);

   20     output.InterpolatedPosition = input.Position;

   21     output.InterpolatedTransformedPosition = output.Position;

   22     return output;

   23 }

   24 

   25 float4 RenderPositionsPixelShaderFunction(VertexShaderOutput input) : COLOR0

   26 {

   27     float4 color = input.InterpolatedPosition;

   28     return color;   

   29 }

Se representarán las caras frontales del cubo en una textura utilizando el estado de representación CullMode.CounterClockWise. El color asignado a la textura en realidad contiene las coordenadas tridimensionades del cubo en el espacio de coordenadas local del modelo en el formato (x,y,z,1).

A continuación se repite la operación pero esta vez representando las caras interiores del cubo.

El siguiente y ultimo paso es la representación del cubo, solo que en esta ocasión el color resultante será función del campo escalar, muestreado en base a las direcciones grabadas en las texturas anteriores. Al muestrear cada una de las texturas anteriores podemos obtener para cada punto (x,y) de la textura, las coordenadas (x,y,z) del cubo por las cuales entró el rayo y por las cuales salió. Con estas dos coordenadas se puede calcular la dirección del rayo y al conocer el punto inicial y la dirección del rayo que atraviesa el campo escalar se puede recorrer cada uno de los puntos de ese campo para calcular su contribución al color de la superficie del cubo. Este proceso se realiza en el Pixel Shader que representará el resultado final en pantalla.

    1 float4 RenderVolumePixelShaderFunction(VertexShaderOutput input) : COLOR0

    2 {

    3     //Calculamos que puntos de las texturas debemos muestrear

    4     float2 texC = input.InterpolatedTransformedPosition.xy /= input.InterpolatedTransformedPosition.w;

    5     //lo llevamos al rango [0,1] espacio de coordenadas de textura desde espacio de proyeccion 2D

    6     texC.x =  0.5f*texC.x + 0.5f;

    7     texC.y = -0.5f*texC.y + 0.5f; 

    8     //Muestreamos las texturas de posiciones iniciales y finales

    9     float3 frontPos = tex2D(FrontTextureSampler, texC);

   10     float3 backPos = tex2D(BackTextureSampler, texC);

   11     //Calculamos el punto inicial y la direccion

   12     float4 currentPosition = float4(frontPos,0);   

   13     float3 direction = normalize(backPos - frontPos);

   14     //Inicializamos las variables del color

   15     float4 color = float4(0, 0, 0, 0);

   16     float4 src = 0;   

   17     float value = 0;   

   18     //Recorremos el campo escalar en la dirección calculada

   19     //acumulando opacidad en función del campo

   20     float3 Step = direction * (1.0f/256.0f);   

   21     for(int i = 0; i < 256; i++)

   22     {

   23         //muestreamos la textura       

   24         value = tex3Dlod(VolumeTextureSampler, currentPosition).r;               

   25         src = (float4)value;

   26         //Front to back blending

   27         src.rgb *= src.a;

   28         color = (1.0f - color.a)*src + color;       

   29         //advance the current position

   30         currentPosition.xyz += Step;

   31     }   

   32     return color;

   33 }

El rendimiento de esta técnica al ser completamente acelerada por GPU es muy bueno como se puede constatar en los siguientes videos obtenidos con una tarjeta gráfica de gama media/baja con un consumo de CPU mínimo.

Basado en un artículo original de Graphic Runner.
Campos escalares obtenidos de vorbis.
Bibliografía: GPU - Based Interactive Visualization Techniques - D. Weiskopf

El juego de la vida

Resulta interesante observar como partiendo de un estado inicial y de unas reglas sencillas, el juego de la vida evoluciona exhibiendo complejos patrones.

Ejemplo de autómata celular, concebido por John Horton Conway y popularizado por Martin Gardner en un artículo de Scientific American aparecido en 1970, el juego de la vida, desde su aparición, ha sido plasmado por los programadores en multitud de sistemas.

Utilizando la plataforma XNA y tomando como base uno de los ejemplos que se proporcionan consistente en un sistema de partículas 2D, se realizó una implementación del juego de la vida. La implementación funciona tanto en sistemas Windows como en Xbox 360 suponiendo que contemos con una licencia para ejecutar código en dicha consola. Para compilar el código es necesario disponer de XNA Game Studio Express 1.0 Refresh, el cual se puede descargar gratuitamente.

En el siguiente vídeo se puede observar la evolución del autómata celular para un estado inicial concreto establecido al azar. 

La implementación consta de una rejilla de 50x50 celdas o 'células'. Cada célula puede estar en uno de dos posibles estados: viva o muerta.

Para cada iteración se calcula el siguiente estado utilizando las reglas descritas a continuación:

  1. una célula 'nace', es decir, pasa al estado de viva si y solo si tres células vecinas suyas están vivas.
  2. Si una célula viva tiene como vecinas a dos o tres células vivas, seguirá viva. En caso contrario la célula morirá.
¿Desea saber más?
  1. Completo artículo en Wikipedia
Distribuir contenido