Go to content Go to navigation Go to search

El XNA Content Pipeline

June 13th, 2010 by JuanK

Que es el Content Pipeline?

El content pipeline es una API que permite a los desarrolladores y diseñadores incorporar contenidos multimedia en los proyectos creados con XNA framework, estos contenidos multimedia son por ejemplo:

  • imágenes
  • sonidos
  • contenido 3d
  • efectos

Por defecto el content pipeline soporta una amplia gama de formatos de archivo diferentes los cuales son usualmente usados en la industria de los videojuegos, el content pipeline (en adelante CPL) facilita el acceso a estos archivos y brinda un interfaz de acceso unificada que permite acceder a dichos recursos desde cualquier objeto utilizado dentro del juego sin necesidad de hacer uso de múltiples referencias cruzadas lo cual desde luego va en favor de la independencia de cada componente lo cual en un juego generalmente es una tarea muy difícil de hacer. Sin embargo las capacidades del CPL podrían verse limitadas por la cantidad de archivos que soporta puesto que es muy común que en la industria de los videojuegos se usen formatos de archivo independientes de acuerdo a las necesidades particulares de cada proyecto, es aquí donde reside una de las mas importantes características del CPL… es extensible.

El CPL incorpora un marco de trabajo que permite fácilmente incorporar soporte a diferentes tipos de archivo e incluso extender la funcionalidad de un tipo de archivo ya soportado.

Como Funciona el Content PipeLine?

Lo primero ha tener en cuenta son los elementos con los cuales trabaja:

Namespace: Microsoft.Xna.Framework.Content.Pipeline
ContentImporter
ContentProcessor

Namespace: Microsoft.Xna.Framework.Content
ContentTypeReader

Namespace: Microsoft.Xna.Framework.Content.Pipeline.Serialization.Compile
ContentTypeWriter

El camino comienza con el ContentImporter este es el encargado de realizar lectura física del archivo a importar y colocar la información en un objeto capaz de almacenarla. Seguidamente la información colocada en el objeto es pasada a través de un ContentProcessor el cual se encarga de realizar transformaciones en la información cargada en el paso anterior. El ContentTypeWriter se encarga de escribir ese objeto en un archivo con el formato del CPL. Todos los pasos anteriores suceden en tiempo de compilación, el ultimo paso sucede en tiempo de ejecución cuando el juego ya esta ejecutándose, es allí donde el ContentTypeReader lee los archivos creados por el CPL para cargar en memoria el recurso almacenado. Debido a que se debe adicionar una referencia a estas clases en el CPL es necesario que los tres primeros objetos existan compilados en una librería dinámica, mientras que el ultimo puede hacer parte el juego directamente.



Como hacer que el Content Pipeline soporte un archivo diferente?

Normalmente con tan solo el ContentImportery el ContentProcessor es suficiente para cargar un tipo de archivo ya que basicamente lo que se hace es utilizar estos objetos para convertir los datos del archivo en uno de los tipos de dato ya soportados por el CPL, sin embargo si se requiere información adicional que no es contenida en el tipo soportado es entonces necesario que el desarrollador establezca la forma en que el CPL escribe esos datos en un archivo del CPL y la forma en que el CPL debe cargarlos una vez se solicite desde el juego, para ello son las clases ContentTypeWriter y ContentTypeReaderLa buena noticia es que para adicionar soporte a un nuevo tipo de archivo estas clases proveen los mecanismos necesarios para implementarlo de manera relativamente fácil, la mala noticia es que todas son clases abstractas así que la mayoría del trabajo lo debe hacer uno mismo. :P

A continuación un ejemplo de Implementación de un nuevo tipo de archivo para el CPL.
Supongamos existe un tipo de arhivo bmp2 dicho archivo posee la información habitual de un archivo bmp más tres datos muy importantes que requerimos (todos los campos son un ejemplo):

  • Color de Máscara
  • Número de Colores
  • Prioridad

La tarea nro 1 crear un ContentImporter. Hay que recordar que ContentImporter es una clase abstracta asi que se debe crear una clase que herede de ContentImporter, adicionalmente esta clase es genérica por lo cual debemos pasar el parámetro del tipo de dato que se soportara para importar.

public class BMP2Importer : ContentImporter 
{ }

pero un momento… de donde salio BMP2Content ? bueno dado que se esta incorporando un nuevo tipo de archivo es necesario que crear un objeto contenedor de los datos que se cargan desde ese archivo, teniendo en cuenta que el formato bmp2 lo usaremos para crear texturas 2D lo mejor que se puede hacer es crear una clase que herede de un contenedor existente, para este caso Texture2DContent :

using Microsoft.Xna.Framework; 
using Microsoft.Xna.Framework.Graphics; 
using Microsoft.Xna.Framework.Content.Pipeline; 
using Microsoft.Xna.Framework.Content.Pipeline.Graphics; 
 
public class BMP2Content : Texture2DContent 
{
    public BMP2Content(): base() 
    {} 
    
    private Color maskColor; 
    public Color MaskColor 
    { 
        get { return maskColor; } 
        set { maskColor = value; } 
    } 
    
    private int numColores; 
    public int NumColores 
    { 
        get { return numColores; } 
        set { numColores = value; } 
    } 
    
    private byte prioridad; 
    public byte Prioridad 
    { 
        get { return prioridad; } 
        set { prioridad = value; } 
    } 
}

Este contenedor permite manipular los datos desde su cargue hasta su transformacion y escritura en un archivo del content pipeline, sin embargo se requiere de una estructura adicional para manipularlo, porque? bueno el contenedor puede ser realmente cualquier estructura pero se requiere que sea liviana puesto que la cantidad de recursos que tengamos puede incrementar dramaticamente el tiempo que se invcierta en la compilacion del proyecto ya que cada uno de los recursos pasara por un importer, un processor y un writer, ya cuando se va a utilizar esa informacion en un objeto util para el juego si debemos disponer de toda la infromacion necesaria.

Para este ejemplo, debido a que el archivo de tipo bmp2 va a ser usado como textura se pueden usar dos opciones, una es crear una clase de textura nueva (poco recomendable) y la otra es crear una clase que herede de un tipo de textura ya existente, para el caso lo que mas nos conviene es crear una clase que herede de Texture2D.

public class BMP2Texture2D : Texture2D 
{ 
    public BMP2Texture2D(GraphicsDevice device, int height, 
                         int with, int numberLevels, 
                         ResourceUsage usage, SurfaceFormat format) : 
                            base(device, with, height, 
                                 numberLevels, usage, format) 
    {} 
    
    public BMP2Texture2D(GraphicsDevice device, int height, int with, 
                         int numberLevels, ResourceUsage usage, 
                         SurfaceFormat format, ResourceManagementMode resourceManagementMode) : 
                            base(device, with, height, 
                                 numberLevels, usage, 
                                 format,resourceManagementMode) 
    { } 
    
    private Color maskColor; 
    public Color MaskColor 
    { 
        get { return maskColor; }
        set { maskColor = value; } 
    } 
    
    private int numColores; 
    public int NumColores 
    { 
        get { return numColores; } 
        set { numColores = value; } 
    } 
    
    private byte prioridad; 
    public byte Prioridad 
    { 
        get { return prioridad; } 
        set { prioridad = value; } 
    } 
}

Esta clase ya trae todo lo que necesita una textura 2d más las cosas propias del formato bmp2 de el ejemplo.
Ahora si se puede crear el ContentImporter indicando el tipo de contenido a almacenar.

[ContentImporter(".bmp2", DisplayName = "Imagen BMP2", DefaultProcessor = "Procesador para imagenes BMP2")] 
public class BMP2Importer : ContentImporter<bmp2content> 
{ 
    public override BMP2Content Import(string filename, ContentImporterContext context) 
    { 
        BPM2 miBmp = new BMP2(filename); 
        //Este objeto es de ayuda para recuperar la informacion de color y llevarla facilmente al 
        //MipmapChain del objeto textureContent PixelBitmapContent<rgb24> pixelHelper; 
        //Array de textura a retornar BMP2Content textureContent; 
        //Array de bytes para capturar la informacion del archivo JKI byte[] byteArray; 
        //Inicializa un nuevo contenedor de datos de pixeles 
        pixelHelper = new PixelBitmapContent</rgb24><rgb24>(miBmp.Width,miBmp.Height); 
        //Cargar la informacion de bytes para el archivo 
        byteArray = miBmp.GetBytes(); 
        //Envia los datos de bytes al pixel helper 
        pixelHelper.SetPixelData(byteArray); 
        //Inicializa un nuevo contenedor para la textura 
        textureContent = new BMP2Content(); 
        //Carga la informacion de la imagen en la textura 
        textureContent.Mipmaps = new MipmapChain(pixelHelper); 
        //Guarda el color de mascara de la imagen 
        textureContent.MaskColor = new Color(miBmp.ColorMascara.R,miBmp.ColorMascara.G, miBmp.ColorMascara.B); 
        //Establecer el numero de colores 
        textureContent.NumColores = miBmp.Palette.Count; 
        textureContent.Prioridad = 0; 
        return textureContent; 
    } 
} 
</rgb24></bmp2content>

La primera linea es un atributo de la clase, la cual le permitira al Content Pipeline identificar que esta es un ContentImporter y le proporcionara informacion adicional acerca de que informacion debe mostrar desde el IDE de Visual Studio respecto a los archivos que este ContentImporter es capaz de utilizar asi como un nombre descriptivo de la funcionalidad. Seguidamente lo que se hace cargar desde archvo un objeto BMP2 el cual desde luego es un objeto que ya es capaz de cargar un archivo bmp2, el objeto pixelHelper es utilizado como una ‘Helper Class’ ya que si bien no se necesita de manera directa, si es un excelente atajo para poder crear un MipmapChain el cual es requerido para cargar en un objeto Texture2DContent con la informacion de la imagen que se requiere, finalmente en la parte inferior del método se asignan la informacion adicional que se requiere.

Una vez se ha importado la informacion desde el archivo esta debe pasar por el processor, el procesor se debe hacer para que funcione igual que funciona un procesor para una imagen BMP normal asi que se puede reutilizar un processor que ya este creado en XNA y eso es todo ya que sobre el resto de la informacion no se requiere hacer ninguna modificacion. El processor en este caso es bastante sencillo:

[ContentProcessor(DisplayName = "Procesador para imagenes BMP2")] 
class BMP2Processor : ContentProcessor<bmp2content , BMP2Content> 
{ 
    public override BMP2Content Process(BMP2Content input, ContentProcessorContext context) 
    { 
        input[i] = (BMP2Content)context.Convert<texturecontent ,TextureContent>( 
                         (TextureContent)input[i], "SpriteTextureProcessor"); 
        return input; 
    } 
}
</texturecontent></bmp2content>

El atributo inicial tiene la misma funcionalidad que en el importer y es proveer una pequeña descripcion del ContentProcessor para que aparezca en el IDE de Visual Studio. Se llama al metodo context.Convert indicandole que debe usar el builting processor SpriteTextureProcessor, esto hara que la informacion inherente a una textura 2d sea procesada de la manera habitual . Finalmente se procede a crear el ContentTypeWriter.

[ContentTypeWriter] 
public class BMP2ContentWriter : ContentTypeWriter<bmp2content> 
{
    protected override void Write(ContentWriter output, BMP2Content value) 
    { 
        //Buffer para la informacion de la imagen 
        byte[] pixelData; 
        //Escribe alto y ancho 
        output.Write(value.Mipmaps[0].Height); 
        output.Write(value.Mipmaps[0].Width); 
        //Obtiene el contenido de la imagen 
        pixelData = value.Mipmaps[0].GetPixelData(); 
        //Escribe el tamaño de datos de la imagen 
        output.Write(pixelData.Length); 
        //Escribe la informacion de la imagen 
        output.Write(pixelData); 
        //Escribe el color de mascara de la imagen 
        output.WriteObject<color>(value.MaskColor); 
    } 
    
    public override string GetRuntimeReader(TargetPlatform targetPlatform) 
    { 
        return typeof(BMP2ContentReader).AssemblyQualifiedName; 
    } 
} 
</color></bmp2content>

El atributo inicial tiene la misma funcionalidad que en el importer. El método write lo que hace es escribir en el archivo del CPL la informacion relacionada con el archivo ya previamente cargado en el BMP2Content, el objeto output recibido como parametro posee metodos para escribir a nivel de tipos nativos y soporta una amplia gama de tipos incluidos en el xna framework, el método GetRuntimeReader Almacena informacion que le indica el CPL en tiempo dejecucion que clase debe instanciar para poder convertir un archivo del CPL en el objeto que se desea, es decir indica cual implementacion de ContentTypeReader debe utilizarce para tal fin. Esta es la implementacion de ContentTypeReader para leer en tiempo de ejecucion lo que el ContentTypeWriter ha guardado en tiempo de compilacion, el objeto input recibido como parametro posee metodos para leer a nivel de tipos nativos y soporta una amplia gama de tipos incluidos en el xna framework.

public class BMP2ContentReader : ContentTypeReader<bmp2texture2d> 
{ 
    protected override BMP2Texture2D Read(ContentReader input, BMP2Texture2D existingInstance) 
    { 
        //Se obtiene el dispositivo grafico 
        GraphicsDevice graphics = ((IGraphicsDeviceService) 
            input.ContentManager.ServiceProvider.GetService(typeof(IGraphicsDeviceService))).GraphicsDevice; 
        //Inicializa la instancia actual, lee el ancho y alto de la imagen 
        existingInstance = new BMP2Texture2D(graphics, input.ReadInt32(), input.ReadInt32(), 
                               1, ResourceUsage.AutoGenerateMipMap, SurfaceFormat.Color); 
        //Establece el contenido de la imagen 
        //lee el tamaño de la informacion en bytes a leer 
        //y lee la informacion en bytes 
        existingInstance[i].SetData<byte>(input.ReadBytes(input.ReadInt32())); 
        //Establece el color de mascara 
        existingInstance[i].maskColor = input.ReadObject<color>(); 
        return existingInstance; 
    }
} 
</color></byte></bmp2texture2d>

Eso es todo, para hacerlo funcionar, se requiere compilar estas clases en una dll la cual se debe adicionar al content pipeline, para hacer eso se debe ir a las propiedades del proyecto y luego a la ultima pestaña la cual desde luego se llama Content Pipeline, desde alli presionar el boton Add y seleccionar la dll creada. Para que desde el juego se peuda cargar la informacion se debe incluir referencia por lo menos al ensamblado donde se compilo el ContentTypeReader. En adelante tan solo se accede al recurso a travez de un content manager como se hace de manera habitual:

ContentManager content; 
… 
… 
… 
BMP2Texture myTexture = content.Load<bmp2texture>(@"myImage.bmp2"); 
</bmp2texture>

Espero les sea de utilidad. Saludos.

Juan Carlos Ruiz Pacheco
Ingeniero de Sistemas

Bookmark and Share

C# – XNA- Como Convertir Una Imagen a Escala de Grises

April 18th, 2010 by JuanK

Hola, este artículo abordara un problema relativamente sencillo, aplicar a una imagen un efecto para verla en escala de grises.

 

Este tema, aunque sencillo, es muy necesario para adentrarse un poco más en el procesamiento digital de imágenes ,lo cual desde luego estaré trabajando dentro de poco en el blog.

 

Para este artículo he decidido utilizar XNA Framework, pero bien habría podido utilizar un PictureBox o un objeto Bitmap o cualquier otro conjunto de objetos existentes en el .Net Framework, incluso podría haber escogido simplemente leer un archivo analizarlo, convertirlo y luego escribirlo de nuevo. He escogido XNA simplemente porque es la tecnología más adecuada para exponer este tipo de temas y es donde sin duda muchos de ustedes los querrán aplicar.

 

Cómo Convertir una Imagen a Escala de Grises

 

Básicamente para que una imagen sea vea en tonos de gris se requiere que los tres componentes básicos del color (  en el computador: rojo, verde, azul – RGB por sus siglas en ingles  ) tengan más o menos la misma intensidad, podemos decir que si queremos convertir un pixel a su equivalente en escala de grises bastaría con hacer algo como esto:

 

  1. Sumar los valores de los componentes de color del pixel, es decir sumar R + G + B
  2. Sacar el promedio de esa suma
  3. El valor hallado se debe asignar a R, G y B

Con estos tres pasos ya logramos que el pixel sea de color gris ya que cada uno de sus componentes tiene el mismo valor.

 

Hay muchas otras formas de hacerlo, incluso alguien que haya trabajado previamente con imágenes puede tener su propia versión de como implementarlo de acuerdo a lo que necesite o al tiempo que tenga. Pero existe una manera ampliamente conocida y aceptada en el gremio de las personas que trabajan con imágenes y visión por computador esa manera es la que aprenderemos a efectuar.

 

El ojo humano y su sensibilidad

Bien, resulta que el ojo humano es mucho más sensible a los colores verdes y rojos que al azul, por lo que en cuanto a precepción de iluminación se trata nuestro ojo reconoce los patrones de iluminación en color en las siguientes proporciones para cada componente:

 

  • Rojo:30%
  • Verde:59%
  • Azul:11%

 

Así que lo más adecuado es calcular el valor de cada componente de color con base a esta proporción y de este modo se obtiene el pixel de color gris con la iluminación adecuada para que nuestro ojo lo perciba como un mejor equivalente a su versión en color.

 

Manos A La Obra, Tiempo de Programar

 

Bueno, la idea de este artículo no es mostrar como poner imágenes en pantalla con XNA pero un super repaso no sienta nada mal.

 

Para utilizar la técnica que he planteado lo primero que se debe hacer es cargar la imagen, en XNA esto se hace con un Objeto Texture2D, al cual le asignaremos una imagen traída desde el Content:

protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
 
            // TODO: use this.Content to load your game content here
            conejo = Content.Load<Texture2D>("Imagenes/conejo");
        }

Y para dibujar esta imagen basta con hacer esto:

protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
 
            // TODO: Add your drawing code here
            spriteBatch.Begin(SpriteBlendMode.AlphaBlend);
 
            spriteBatch.Draw(imagenEfectiva, Vector2.Zero, Color.White);
 
            spriteBatch.End();
 
            base.Draw(gameTime);
        }

 

Este es el resultado en pantalla:

image

 

Ahora comienza el verdadero trabajo. Implementaré un método que nos cree una copia de la imagen, pero en escala de grises, este método recibe como parámetro nuestra textura original (es decir la imagen).

 

Lo primero que se debe hacer es crear una nueva Texture2D de las mismas dimensiones que la Texture2D original, para utilizarla como lienzo, donde se dibujara la nueva imagen en escala de grises.

public Texture2D GrayScale(Texture2D source)
        {
            Texture2D target = new Texture2D(source.GraphicsDevice, source.Width, source.Height);
            
            return target;
        }

 

Para poder dibujar en esta textura es necesario acceder a su información (sus pixeles) esto se hace con el método genérico GetData el cual se puede utilizar para obtener un array de bytes con la información de cada punto color.

El formato de color que utiliza Texture2D es por defecto color de 32 bit RGBA (la A es de Alpha para las transparencias… no nos importa ahora) así que GetData nos devuelve un array de bytes utilizando 4 bytes para cada punto de color de la imagen, por lo cual la capacidad de el array de bytes debe ser 4 * ancho * alto de la imagen.

public Texture2D GrayScale(Texture2D source)
        {
            Texture2D target = new Texture2D(source.GraphicsDevice, source.Width, source.Height);
            
            byte[] data = new byte[source.Width * source.Height * 4];
 
            source.GetData<byte>(data);
 
 
            return target;
        }

 

Ahora se debe recorrer al array de bytes de pixel en pixel ( es decir en saltos de a 4 bytes ) sabiendo que los 3 primeros bytes de cada grupo de 4 son respectivamente R, G y B, de tal forma que multiplicamos el valor de cada byte por el valor de la proporción de iluminación, este es el valor de la intensidad asi que asignamos a cada uno de los tres bytes el valor de iluminación que hemos hallado

public Texture2D GrayScale(Texture2D source)
        {
            byte y = 0;
            Texture2D target = new Texture2D(source.GraphicsDevice, source.Width, source.Height);
            
            byte[] data = new byte[source.Width * source.Height * XNA_COLOR_DEPTH];
 
            source.GetData<byte>(data);
 
            for (int i = 0; i < data.Length; i += 4)
            {
                // Y = R * 0.3 + G * 0.59 + B * 0.11
                y = (byte)(data[i] * 0.3f + data[i + 1] * 0.59f + data[i + 2] * 0.11f);
                data[i + 2] = data[i + 1] = data[i] = y;
            }
 
            return target;
        }

 

Finalmente se utiliza el método genérico SetData de la Texture2D para asignar a la textura el array de bytes modificado.

public Texture2D GrayScale(Texture2D source)
        {
            byte y = 0;
            Texture2D target = new Texture2D(source.GraphicsDevice, source.Width, source.Height);
            
            byte[] data = new byte[source.Width * source.Height * XNA_COLOR_DEPTH];
 
            source.GetData<byte>(data);
 
            for (int i = 0; i < data.Length; i += 4)
            {
                // Y = R * 0.3 + G * 0.59 + B * 0.11
                y = (byte)(data[i] * 0.11f + data[i + 1] * 0.59f + data[i + 2] * 0.3f);
                data[i + 2] = data[i + 1] = data[i] = y;
            }
            target.SetData<byte>(data);
            return target;
        }

 

La implementación definitiva puede quedar así:

public Texture2D GrayScale(Texture2D source)
        {
            Texture2D target = new Texture2D(source.GraphicsDevice, source.Width, source.Height);
            
            byte[] data = new byte[source.Width * source.Height * XNA_COLOR_DEPTH];
 
            source.GetData<byte>(data);
 
            for (int i = 0; i < data.Length; i += 4)
                data[i + 2] = data[i + 1] = data[i] = (byte)(data[i] * 0.3f + data[i + 1] * 0.59f + data[i + 2] * 0.11f);
 
            target.SetData<byte>(data);
 
            return target;
        }

 

Utilizando este método podemos entonces crear una textura que sea la representación en escala de grises de otra, lo cual al ponerlo en pantalla sería así:

image

Eso es todo!!

En estos otros artículos muestro como hacer lo mismo pero con Bitmap y PictureBox, y como hacerlo utilizando XNA y Shaders:

Con Bitmap y Picturebox= http://juank.black-byte.com/c-bitmap-convertir-imagen-escala-grises/
Con XNA y Shaders= http://juank.black-byte.com/xna-shaders-convertir-imagen-grises/






Bookmark and Share

C# – XNA – Shaders – Como Convertir Una Imagen a Escala de Grises

December 23rd, 2009 by JuanK

Hola, continuando con el tema del procesamiento de imágenes, este es el tercer artículo relacionado con la conversión de una imagen a escala de grises.

 

En los dos artículos anteriores:

http://juank.black-byte.com/xna-convertir-imagen-escala-grises/

http://juank.black-byte.com/c-bitmap-convertir-imagen-escala-grises/

 

Se reviso como convertir una imagen a escala de grises utilizando XNA/Texture2D y también como hacerlo utilizando un objeto Bitmap del .net Framework.

 

Sin embargo hubo un tema que no toqué y honestamente no pretendía tocar, al menos no por ahora, pero uno de mis lectores dejo sembrada en mi esa inquietud, y aquí lo tengo este artículo que muestra como convertir una imagen en escala de grises utilizando el método que es, de lejos, el más eficiente de todos y también el más sencillo una vez se sabe acerca de los Shaders.

 

Como trabajar los Shaders en XNA?

 

Para programar el shader he preparado un código de ejemplo inicial, el cual sencillamente crea un rectángulo y lo pone en un entorno 3D y le aplica una textura, la cual será a la que le modificaremos la información por medio del shader. Acá esta el código inicial sin Shaders.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
 
namespace BlogHLSL
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        VertexPositionTexture[] verts;
        BasicEffect effect;
        Texture2D textura;
 
        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }
 
        protected override void Initialize()
        {
            
            GraphicsDevice.RenderState.CullMode = CullMode.None;
            GraphicsDevice.VertexDeclaration = new VertexDeclaration(GraphicsDevice, VertexPositionTexture.VertexElements);
 
            
            effect = new BasicEffect(GraphicsDevice, null);
            effect.View = Matrix.CreateLookAt(new Vector3(0,0,-10), new Vector3(0,0,1), Vector3.Up);
            effect.Projection = Matrix.CreatePerspectiveFieldOfView(0.5f, 
                                               Window.ClientBounds.Width/ Window.ClientBounds.Height, 
                                               1, 10);
            effect.World = Matrix.Identity * Matrix.CreateTranslation(1.7f,1.7f,0);
            effect.TextureEnabled = true;
 
            base.Initialize();
        }
 
        protected override void LoadContent()
        {
            textura = Content.Load<texture2d>("rabbid");
            effect.Texture = textura;
 
            verts = new VertexPositionTexture[4];
            verts[0] = new VertexPositionTexture(new Vector3(-1,1,0),      new Vector2(0,0));
            verts[1] = new VertexPositionTexture(new Vector3(1, 1, 0),     new Vector2(1, 0));
            verts[2] = new VertexPositionTexture(new Vector3(-1, -2.7f, 0),new Vector2(0, 1));
            verts[3] = new VertexPositionTexture(new Vector3(1, -2.7f, 0), new Vector2(1, 1));            
        }
 
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
 
            effect.Begin();
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                GraphicsDevice.DrawUserPrimitives<vertexpositiontexture>(PrimitiveType.TriangleStrip, verts, 0, 2);
                pass.End();
            }
            effect.End();
            base.Draw(gameTime);
        }
    }
}

 

Ahora a programar con Shaders!!!

 

Tomando el código anterior como base lo que haré será dibujar la textura en otra posición, simplemente cambiando la posicion del world, la diferencia será que el efecto que usaré para dibujar la textura no será un BasicEffect sino un Effect.

Al utilizar Effect se tiene todo el poder de los Shaders en las manos, pero se debe asumir un pequeño costo y es que dejar la aplicación haciendo exactamente lo que hacia es un poco más complicado de hacer de lo que era con un BasicEffect , si bien la funcionalidad del BasicEffect se puede considerar muy limitada.

Shader para dibujar una textura

BasicEffect es un shader pre implementado que viene con XNA el cual sencillamente nos permite comenzar a trabajar sin preocuparnos por saber o no de Shaders. En su lugar vamos a trabajar con Effect. Para inicializar Effect necesitamos un archivo .fx que es el que contendrá el código del shader, así que en el Content se debe agregar un nuevo archivo de tipo Effect y lo llamaré Efecto.fx.

Este es el shader con el que iniciaré a trabajar, desde luego esto va en el archivo .fx, es decir este es el Shader mínimo que permite hacer exactamente lo mismo que con BasicEffect:

float4x4 WorldViewProjection;
Texture textura;
sampler muestreador = sampler_state
{
  Texture = <textura>;    
  magfilter = LINEAR; 
  minfilter = LINEAR; 
  mipfilter = LINEAR;
  AddressU = mirror; 
  AddressV = mirror;
};
 
struct VertexShaderInput
{
    float4 Position : POSITION0;
    float2 Tex : TEXCOORD0;
};
 
struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float2 Tex : TEXCOORD0;
};
 
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{    
    VertexShaderOutput output = (VertexShaderOutput)0;
    output.Position = mul(input.Position , WorldViewProjection);
    output.Tex= input.Tex;
    return output;
}
 
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    return tex2D(muestreador, input.Tex.xy);
}
 
technique Technique1
{
    pass Pass1
    {
        VertexShader = compile vs_1_1 VertexShaderFunction();
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

 

No es complicado entender el código pero no entrare en mayores detalles porque recuerden que mi intención no es hacer un curso de Shaders, ya habrá tiempo para ello, por ahora el objetivo es aprender a aplicar el efecto de escala de grises y brindaré la información suficiente para que el artículo sea suficientemente entendible para todos.

Una vez hecho el shader se debe preparar el código para utilizarlo, así que se debe declarar una referencia a un objeto Effect el cual he llamado shaderEffect, y en el método LoadContent adiciono este código de inicialización justo después de cargar la textura:

Matrix tmpWorld = Matrix.Identity * Matrix.CreateTranslation(-0.5f, 1.7f, 0);
            Matrix tmpView = Matrix.CreateLookAt(new Vector3(0,0,-10), new Vector3(0,0,1), Vector3.Up);
            Matrix tmpProjection = Matrix.CreatePerspectiveFieldOfView(0.5f, 
                                               Window.ClientBounds.Width/ Window.ClientBounds.Height, 
                                               1, 10);
 
            shaderEffect = Content.Load<effect>("Efecto");
            shaderEffect.CurrentTechnique = shaderEffect.Techniques["Technique1"];
            shaderEffect.Parameters["WorldViewProjection"].SetValue(tmpWorld * tmpView * tmpProjection);
            shaderEffect.Parameters["textura"].SetValue(textura);

 

Prácticamente es el mismo código que para BasicEffect pero con algunas diferencias menores, es importante recordar que el world ha sido inicializado con una traslación diferente para que la imagen de la textura quede justo al frente de la original. Otra cosa importante de notar es que a diferencia de los dos artículos anteriores donde lo que hice fue crear una copia de la textura, en este la copia solo se realiza a nivel de la memoria de video por lo cual no crearemos un nuevo Texture2D. (Esto en realidad es lo que lo hace increíblemente más rápido ya que todo el procesamiento se hace en la GPU)

El código para dibujar en el método draw es básicamente replicar el anterior, adiciono esto justo debajo de effect.End():

shaderEffect.Begin();
            foreach (EffectPass pass in shaderEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                GraphicsDevice.DrawUserPrimitives<vertexpositiontexture>(PrimitiveType.TriangleStrip, verts, 0, 2);
                pass.End();
            }
            shaderEffect.End();

 

Ahora tengo esto:

image

Shader para aplicar el efecto de escala grises

Tal como lo mostré en los artículos anteriores, para convertir una imagen en escala de grises basta con sumar los componentes de color de cada pixel y distribuir la intensidad de color de manera proporcional a como nuestro ojo percibe los diferentes componentes de color, esto es 0.3R 0.59G 0.11B. Para hacer esto desde el shader vasta con modificar el PixelShader (en este caso PixelShaderFunction)de la siguiente forma:

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    float4 color = tex2D(muestreador, input.Tex.xy);
    color.rgb = color.r * 0.3 + color.g * 0.59 + color.b *.11;
    return color;
}

 

Y Listo! se ejecuta el programa y…:

image

Hasta pronto!

Eso es todo!!

En estos otros artículos muestro como hacer lo mismo pero con XNA y Texture 2D, y como hacerlo utilizando Bitmap y PictureBox:

Con XNA y Texture 2D= http://juank.black-byte.com/xna-convertir-imagen-escala-grises/
Con XNA y Texture 2D= http://juank.black-byte.com/xna-convertir-imagen-escala-grises/






Bookmark and Share

« Previous Entries