HLSL Shaders: Ejemplo en XNA

Hay una parte muy cool de cuando programar gráficos se trata, esa parte
es el utilizar el GPU (Graphics Processing Unit) ósea la mismí­sima
tarjeta de video para hacer cálculos y almacenamiento de memoria.

La Content Pipeline

Actualmente existen lenguajes de alto nivel (Cg de nVidia, HLSL de
Microsoft y GLSL para OpenGL) que básicamente funcionan de la siguiente
manera:

Content Pipeline

Primero obviamente lo que necesitamos es crear un shader, este lo
podemos hacer desde Visual Studio, o utilizando un programa de autorí­a
de shaders como el Render Monkey de ATI o FX Composser de nVidia, este
archivo lo vamos a cargar en nuestra aplicación y la API gráfica se
encargará de que la tarjeta de video lo compile en tiempo real, esto se
hace para crear una optimización para cada tipo de hardware.

Una vez hecho esto nosotros generamos un conjunto de vértices
(estos pueden ser por ejemplo un modelo 3D) activamos el shader desde
nuestra aplicación y cuando el GPU realice los cálculos para la
proyección tomará en cuenta los parámetros y métodos del shader;
después nuestra los drivers convertirán esa salida generada (aún 3D) a
coordenadas de pantalla, este proceso se le conoce como rasterización,
con nuestros objetos proyectados en la pantalla podemos modificarlos
una vez más, esta vez pí­xel a pí­xel. 😀
Ejemplo

Supongamos que tenemos el siguiente código en un archivo .fx,
podemos crear estos archivos dando click derecho en nuestro explorador
de soluciones y en agregar nuevo Effect:

Código en Effect1.fx:

//Estas variables representan las matrices de posición de nuestro objeto
//la matriz de nuestra camara, y nuestra proyección
float4x4 World;
float4x4 View;
float4x4 Projection;


//Esta estructura indica que es lo que va a tomar el shader
//En este caso solo tomara la posición del vértice, podemos agregar
//las coordenadas de textura, normales, colores, etc.
struct VertexShaderInput
{
float4 Position : POSITION0;
};

//Esto es muy parecido a la estructura de entrada, pero aquí­ especificamos
//que queremos que devuelva nuestro método
struct VertexShaderOutput
{
float4 Position : POSITION0;
};

//Esta función es nuestro vertex shader
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
VertexShaderOutput output;

//Sólo aplicaremos una sencilla transormación
float4 worldPosition = mul(input.Position, World);
float4 viewPosition = mul(worldPosition, View);
output.Position = mul(viewPosition, Projection);

return output;
}

//Esta función es nuestro pixel shader
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
//Aquí­ especificamos que queremos que para todo ­xel
//que se dibuje lo pinte de color rojo :)
//podemos por ejemplo regresar el valor de una textura respecto
//a la coordenada actual
return float4( 1, 0, 0, 1);
}

//Aqui especificamos nuestras técnicas
//un archivo fx puede tener varias técnicas
technique Technique1
{
//Para cada pasada especificamos que funciones vamos a llamar
//y ademas debemos de decirle bajo que perfil se compilarán
pass Pass1
{
VertexShader = compile vs_1_1 VertexShaderFunction();
PixelShader = compile ps_1_1 PixelShaderFunction();
}
}

Para utilizarlo vamos a modificar nuestra clase Game1.cs:

Primero, vamos a declarar dos objetos en nuestra clase, uno para el modelo que utilizaremos y otro para nuestro efecto:

Model _model;
Effect _effect;

Después vamos a modificar nuestra función LoadContent() para poder cargar nuestros archivos:

        protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);

_model = Content.Load<Model>("model");
_effect = Content.Load<Effect>("Effect1");

///Esto va a quitarle el efecto actual al modelo y establecera el que
///le estamos asignando
foreach (ModelMesh mesh in _model.Meshes)
foreach (ModelMeshPart part in mesh.MeshParts)
part.Effect = _effect;

// TODO: use this.Content to load your game content here
}
protected override void Draw(GameTime gameTime)
{
graphics.GraphicsDevice.Clear(Color.CornflowerBlue);

float aspectRatio = graphics.GraphicsDevice.Viewport.Width /
graphics.GraphicsDevice.Viewport.Height;


//Definimos la tecnica
_effect.CurrentTechnique = _effect.Techniques["Technique1"];

_effect.Parameters["World"].SetValue
(Matrix.CreateWorld(Vector3.Zero, Vector3.Forward, Vector3.Up));
_effect.Parameters["View"].SetValue
(Matrix.CreateLookAt(new Vector3(0.0f, 5.0f, -10.0f), Vector3.Zero, Vector3.Up));
_effect.Parameters["Projection"].SetValue
(Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f),
aspectRatio, 1.0f, 10000.0f));

//Actualizamos el shader
_effect.CommitChanges();

foreach (ModelMesh mesh in _model.Meshes)
foreach (Effect effect in mesh.Effects)
mesh.Draw();


base.Draw(gameTime);
}

Por último vamos a dibujar nuestro modelo con el shader
correspondiente y el resultado debe de ser el modelo dibujado
completamente en rojo, y esta es la imagen de nuestro primer shader:

¿No es interesante? Bueno, es nuestro “hola, mundo!” de los
shaders, así­ que de aquí­ podremos partir a cosas más interesantes
como iluminación, a continuación les voy a presentar una imagen de un
render un poco más complejo:

Este render contiene 3 shaders, uno para los reflejos del agua,
otro para el brillo de la nave, y otro que le da un efecto de zoom
radial. 🙂

Conclusiones

Los shaders se utilizan para prácticamente todo el procesamiento
de gráficos, algunas aplicaciones lo manejan automáticamente pero si
quieres tener el control completo y agregar efectos a tus aplicaciones
gráficas la mejor manera es meterle mano.

Los shaders no solo se utilizan en videojuegos o aplicaciones 3D,
también se utilizan en edición de imágenes o ví­deo, en un futuro
tutorial hablaré de como crear shaders de post producción (como los que
se utilizan para las pelí­culas).

 

4 comentarios en “HLSL Shaders: Ejemplo en XNA”

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *