Infografía

let yourself feel

Excelente producción donde la música se fusiona a un efecto de partículas, Realizado con Particular y Adobe After Effects.

let yourself feel. por Esteban Diácono en Vimeo.

Visto en Graphics Runner

Representación volumétrica de contenido espectral

Utilizando la técnica descrita en el artículo anterior se ha representado el contenido espectral de una canción en una estructura tridimensional. Para ello se representa el espectro de cada pequeño tramo temporal de la canción en la cara frontal del cubo desplazándose en profundidad a medida que pasa el tiempo y va llegando nuevo contenido espectral.

El resultado se observa a continuación aunque la calidad del video no es demasiado buena y se observan inconsistencias en el framerate que ‘estropean’ la continuidad del efecto.

Cada plano del eje Z o de profundidad representa a un espectro. Cada espectro se representa en cada plano X-Y del volumen modificándose ligeramente de forma aleatoria el contenido del eje Y o altura con objeto de mejorar la estética del efecto. Con el mismo objetivo se aplica una pequeño efecto de escala al volumen que representa la energía promedio del espectro del intervalo de la canción que esta sonando en ese instante.

El sonido pertenece a un fragmento de uno de los episodios de la cuarta temporada de Battlestar Galactica y ha sido extraído de aquí.

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

Mapping video 3D proyectado

Nombre asignado a la proyección de video sobre una superficie de tal forma que se integre con la estructura tridimensional de la misma. El siguiente video de demonstración de la empresa easyweb muestra sus posibilidades.

Visto en Laughing Squid

Royksoop – Remind Me

Debris by Farbrausch

En estos tiempos de GigaBytes y TeraBytes resulta refrescante recordar las creaciones del grupo de la demoescena alemán Farbrausch, grupo que está detrás de algunas de las obras más importantes de la demoescena de los últimos diez años gracias a su trabajo con contenido procedural.

Es impresionante que su demo Debris esté contenida en un programa de tan solo 177 KiloBytes. El video siguiente no hace justicia a la demo por lo que si tienes un equipo relativamente potente recomiendo descargar el programa original de aquí y visualizarla a pantalla completa.

Para esta demo utilizaron una herramienta de su creación llamada Werkkzeug, que permite hacer cosas que simplemente no serían posibles utilizando herramientas convencionales. Hay que recordar que todo el contenido de la demo, tanto los modelos como las texturas son generadas en tiempo de ejecución utilizando formulas matemáticas, extrusiones, ruido Perlin, etc.

Fuente: Game Developer Magazine Octubre 2008.

Diamante - Cuadrado

Las extensiones de terreno son uno de los fenómenos naturales más comúnmente representados gráficamente por computador.

El algoritmo Diamond-Square (Diamante-Cuadrado) es un método utilizado para la generación de terreno de forma aleatoria. Fue desarrollado por Loren Carpenter, cofundador y científico jefe de Pixar, para ser usado en el modelado de una superficie planetaria.

Los resultados producidos por dicho método se pueden observar en el siguiente video, generado por un programa cuyo código fuente puede encontrar adjunto a este artículo.

Para modelar el terreno se comienza con una matriz de dimensiones mxn, donde típicamente m será igual a n ya que esto simplifica las cosas. Las dimensiones de la matriz deberán ser potencia de dos más 1, es decir, 33x33, 65x65, 129x129, 257x257, etc.

Los valores de la matriz se interpretan como la altura que tiene el terreno en ese punto, por lo que un valor en la posición 30,30 de 65 significa que en la posición 30,30 del terreno, su altura es de 65 unidades. Este tipo de matrices se conocen como ‘heightmap’ o mapa de alturas.

La siguiente figura, extraída de la publicación original, muestra el proceso de generación de las alturas para una matriz de 5x5.

El proceso comienza con las cuatro esquinas, marcadas como 0, es decir, con un ‘cuadrado’ cuyos valores de altura son inicializados a los valores de altura que deseemos, en nuestro caso serán inicializados con el valor 0.

A continuación, se utilizan dichos valores para calcular la altura del punto 1a. Las etapas del algoritmo marcadas como a (1a, 2a, 3a, etc.) forman la parte ‘diamante’ del proceso, ya que si trazamos las diagonales desde cada punto a cada una de las esquinas del ‘cuadrado’ se formara un patrón de rombos, símbolo que habitualmente se utiliza para representar a los diamantes.

Después se procede a la etapa ‘cuadrado’ por lo que se toman las esquinas de cada uno de los rombos generados para calcular un nuevo valor para su centro, puntos marcados como 1b.

Una vez realizadas las etapas diamante y cuadrado podemos observar que la matriz o rejilla ha quedado dividida en 4 cuadrados.

El algoritmo continúa entonces con una nueva fase diamante-cuadrado, marcada como 2ª y 2b, solo que esta vez partiremos de 4 cuadrados en lugar de 1 y a cuya resolución la rejilla quedará dividida en 16 cuadrados.

En este caso el proceso ha concluido porque todos los puntos de la matriz han sido asignados. Si hubiéramos comenzado con una matriz de dimensiones mayores el proceso continuaría con nuevas etapas diamante-cuadrado hasta que quedase totalmente dividida y todos sus puntos hubiesen sido asignados con su correspondiente valor de altura.

Para calcular la altura de cada punto se utiliza el promedio de los valores circundantes, es decir, los cuadrados en la etapa diamante, y las esquinas de los rombos en la etapa cuadrado. A dicho valor, posteriormente se le añade un valor aleatorio (entre –d y +d) que estará limitado por unos parámetros.

Puede observar los detalles en la implementación adjunta a este artículo. Dicha implementación está basada en el programa de Paul Martz publicada en Generating Random Fractal Terrain y utiliza XNA para la representación grafica.

DVD recopilatorio de la demoescena hispana

Leo en escena.org que han publicado un DVD recopilatorio con producciones de la 'scene' hispana.

Siempre son de agradecer este tipo de iniciativas de difusión y preservación de este tipo de material, asi que, ¡gracias a todos los involucrados!

Efecto Sepia con WPF 3.5 SP1

Una de las novedades introducidas en el service pack 1 de la versión 3.5 de Windows Presentation Foundation es la posibilidad de utilizar ‘Pixel shaders’ personalizados como efectos para cualquier componente visual. 

En los comienzos de la fotografía, se aplicaba el tonado sepia a las fotografías en blanco y negro, debido a que dicho proceso hacía más duraderas a las fotografía, y es por ello que muchas de las fotografías antiguas tienen ese característico aspecto basado en tonos marrones.

El siguiente fragmento de código define un 'pixel shader', el cual al aplicarle como efecto a cualquier elemento Visual de una aplicación WPF, le dará esa característica tonalidad sepia.

pixel shader para el efecto sepia

La variable lumaCoeffs es un vector cuyo producto escalar con el vector que define los valores RGB en una pixel de la imagen, nos da el valor de luminancia que corresponde a ese punto. El valor de luminancia se utiliza para posteriormente obtener el color en tono sepia correspondiente a ese valor utilizando la función lerp, que se utiliza para hacer una interpolación lineal entre los dos colores que definen los 'extremos' de la gama de tonalidades sepia.

Por el momento, no podemos utilizar el efecto directamente desde Visual Studio y se necesita compilarle primero utilizando la utilidad fxc del SDK de DirectX. Si hemos guardado el código de la figura anterior en un fichero con el nombre sepia.fx podemos compilarle a un archivo sepia.ps que se podrá utilizar directamente desde WPF de la siguiente manera:

  • fxc  /T ps_2_0 /E main /Fosepia.ps sepia.fx

Una vez compilado el efecto podemos utilizarlo derivando una clase de ShaderEffect llamada, por ejemplo, SepiaEffect, y asignado el archivo sepia.ps a su propiedad PixelShader. Dicho archivo, habrá tenido que ser incluido previamente en el proyecto de Visual Studio y se deberá haber establecido su ‘build action’ como resource.

Para más detalles consultar el código fuente que acompaña a este artículo, que utiliza el efecto tal y como se muestra en la siguiente figura.

Demo del efecto sepia

 Referencia:

Distribuir contenido