domingo, 24 de abril de 2011

OpenGL-ES2.0 y Monotouch IV (Cube3D parte2)

En esta segunda parte veremos dos técnicas que mejorarán el ejemplo anterior. La primera será crear una estructura para todas las propiedades de nuestros vértices. Si nos damos cuenta de cada vértice cada vez vamos a necesitar más información, empezamos por la posición, el color, luego añadimos las coordenadas de textura y para técnicas de iluminación o normal map necesitaremos la normal, la binomial y la tangente por lo que tendríamos que tener un motón de arrays que pasar al VertexShader.

La forma más elegante de resolver esto es usar una estructura básica para nuestros vértices. Para nuestro ejemplo en principio sólo necesitamos conocer la posición y las coordenadas de textura por lo que nuestra estructura será:

struct VertexType{
public Vector3 position;
public Vector2 textureCoordinates;
}


Una vez tenemos nuestra estructura vamos a definir nuestro cubo. En la primera parte de este tutorial usamos para dibujar la función GL.DrawArrays de OpenGL lo cual nos provocaba tener que especificarle los 3 vértices de cada triángulo y teníamos muchos vértices repetidos en el array de vértices que le pasábamos a nuestro VertexShader.

Esto se soluciona usando el método de dibujado GL.DrawElements ya que nos permite especificarle los triangulos a través de un array de índices. De esta forma nosotros ahora sólo tenemos que especificar los 8 vértices que tiene nuestro cubo y luego pasar el array de índices que cuenta con combinaciones de estos vértices.



por lo que especificaremos los 8 vértices:

vertexArray = new VertexType[8];
vertexArray[0].position = new Vector3(-0.5f,-0.5f,0.0f);
vertexArray[0].textureCoordinates = new Vector2(0,1);
vertexArray[1].position = new Vector3(0.5f,-0.5f,0.0f);
vertexArray[1].textureCoordinates = new Vector2(1,1);
vertexArray[2].position = new Vector3(0.5f,0.5f,0.0f);
vertexArray[2].textureCoordinates = new Vector2(1,0);
vertexArray[3].position = new Vector3(-0.5f,0.5f,0.0f);
vertexArray[3].textureCoordinates = new Vector2(0,0);
vertexArray[4].position = new Vector3(-0.5f,-0.5f,1.0f);
vertexArray[4].textureCoordinates = new Vector2(0,1);
vertexArray[5].position = new Vector3(0.5f,-0.5f,1.0f);
vertexArray[5].textureCoordinates = new Vector2(1,1);
vertexArray[6].position = new Vector3(0.5f,0.5f,1.0f);
vertexArray[6].textureCoordinates = new Vector2(1,0);
vertexArray[7].position = new Vector3(-0.5f,0.5f,1.0f);
vertexArray[7].textureCoordinates = new Vector2(0,0);


y ahora vamos a especificar los índices o lo que es lo mismo como combinar dichos 8 vértices para construir los triángulos de nuestro cubo.

ushort[] indexes = {0,1,2,0,2,3,
4,5,1,4,1,0,
4,0,3,4,3,7,
1,5,6,1,6,2,
6,7,3,6,3,2,
5,4,7,5,7,6};


(Nota. Como podeís observar en el array de índices, cada fila sería una cara de nuestro cubo. Ej. la cara frontal estaría compuesta por 2 triángulos formados por los vértices 0-1-2 y 0-2-3)

Bien pues ahora a nuestra función de dibujado GL.DrawElements le pasamos:

GL.DrawElements(All mode, int count, All type, IntPtr indices)
- mode puede valer (All.Points/All.Lines/All.LineStrip,/All.LineLoop/ All.Triangles/ All.TriangleStrip / All.TriangleFan)
- count es el número total de índices que vamos a pasarle.
- type especifica el tipo de dato que alberga el array de índices, (All.UnsignedShort para nuestro ejemplo)
- indices que será el array de índices.

Una cosa importante en este punto es detectar que le tenemos que enviar un puntero a nuestra estructura y no nuestra propia estructura para ellos haremos uso de la clase GCHandle.

GCHandle indexesHandle = GCHandle.Alloc(indexes, GCHandleType.Pinned);


De esta forma tenemos un puntero a nuestro array de índices y para llamar al GL.DrawElements haremos:

GL.DrawElements(All.Triangles, indexes.Length, All.UnsignedShort, indexesHandle.AddrOfPinnedObject());


Bien pues una cosa similar vamos a hacer con nuestra estructura de vértices para poder pasarsela a nuestro VertexShader, lo primero creamos un puntero a nuestra estructura.

GCHandle vertexArrayHandle = GCHandle.Alloc(vertexArray, GCHandleType.Pinned);


Y para poder inyectar los vértices en el VertexShader:

int size = sizeof(float)*3+sizeof(float)*2;
GL.VertexAttribPointer(attributePosition,3,All.Float,false,size,vertexArrayHandle.AddrOfPinnedObject());
GL.VertexAttribPointer(attributeTexCoord,2,All.Float,false,size,(IntPtr)((uint)vertexArrayHandle.AddrOfPinnedObject()+(uint)(sizeof(float)*3)));


Donde size es la variable que calcula el tamaño de nuestra estructura compuesta por un Vector3 y un Vector2 de ahí el sizeof(float)*3 + sizeof(float)*2 que nos será útil pasar al VertexAttribPointer para que conozca la unidad básica o stride que es como se conoce el parámetro. También añadir que para las coordenadas de texturas hemos tenido que sumar un offset a nuestro puntero que salte el Vector3 de la posición de los vértices y empiece siempre con las coordenadas de textura. Si tuvieramos más atributos para nuestros vértices tendríamos que ir realizando diferentes offset.

Bueno pues con la creación de estructuras y la utilización del método de pintado GL.DrawElements el código final de nuestra clase EAGLView.cs nos queda:

#define OPENGLES2


using System;
using OpenTK.Platform.iPhoneOS;
using MonoTouch.CoreAnimation;
using OpenTK;

using OpenTK.Graphics.ES20;

using MonoTouch.Foundation;
using MonoTouch.ObjCRuntime;
using MonoTouch.OpenGLES;
using System.Text;
using System.Drawing;
using OpenTK.Platform;
using MonoTouch.CoreGraphics;
using MonoTouch.UIKit;
using System.Runtime.InteropServices;

namespace Cube3D2
{
public partial class EAGLView : iPhoneOSGameView
{
int viewportWidth, viewportHeight;
int program;

struct VertexType{
public Vector3 position;
public Vector2 textureCoordinates;
}

VertexType[] vertexArray;
GCHandle vertexArrayHandle;

ushort[] indexes = {0,1,2,0,2,3,
4,5,1,4,1,0,
4,0,3,4,3,7,
1,5,6,1,6,2,
6,7,3,6,3,2,
5,4,7,5,7,6};
GCHandle indexesHandle;

//Texture
int textureId;

IntPtr data;

Matrix4 matWorldViewProjection, matProjection, matView, matWorld;
int uniformMat, uniformSampler;
int attributePosition = 0;
int attributeTexCoord = 1;

[Export("layerClass")]
static Class LayerClass ()
{
return iPhoneOSGameView.GetLayerClass ();
}

[Export("initWithCoder:")]
public EAGLView (NSCoder coder) : base(coder)
{
LayerRetainsBacking = false;
LayerColorFormat = EAGLColorFormat.RGBA8;
}

protected override void CreateFrameBuffer ()
{
ContextRenderingApi = EAGLRenderingAPI.OpenGLES2;
base.CreateFrameBuffer();
Initialize();
}

private bool Initialize()
{
viewportHeight = Size.Height;
viewportWidth = Size.Width;

//Structure
vertexArray = new VertexType[8];
vertexArray[0].position = new Vector3(-0.5f,-0.5f,0.0f);
vertexArray[0].textureCoordinates = new Vector2(0,1);
vertexArray[1].position = new Vector3(0.5f,-0.5f,0.0f);
vertexArray[1].textureCoordinates = new Vector2(1,1);
vertexArray[2].position = new Vector3(0.5f,0.5f,0.0f);
vertexArray[2].textureCoordinates = new Vector2(1,0);
vertexArray[3].position = new Vector3(-0.5f,0.5f,0.0f);
vertexArray[3].textureCoordinates = new Vector2(0,0);
vertexArray[4].position = new Vector3(-0.5f,-0.5f,1.0f);
vertexArray[4].textureCoordinates = new Vector2(0,1);
vertexArray[5].position = new Vector3(0.5f,-0.5f,1.0f);
vertexArray[5].textureCoordinates = new Vector2(1,1);
vertexArray[6].position = new Vector3(0.5f,0.5f,1.0f);
vertexArray[6].textureCoordinates = new Vector2(1,0);
vertexArray[7].position = new Vector3(-0.5f,0.5f,1.0f);
vertexArray[7].textureCoordinates = new Vector2(0,0);
vertexArrayHandle = GCHandle.Alloc(vertexArray, GCHandleType.Pinned);

indexesHandle = GCHandle.Alloc(indexes, GCHandleType.Pinned);

// Vertex and fragment shaders
string vertexShaderSrc = @"uniform mat4 uMVPMatrix;
attribute vec4 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
void main()
{
vTexCoord = aTexCoord;
gl_Position = uMVPMatrix * aPosition;
}";

string fragmentShaderSrc = @"precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D sTexture;
void main()
{
//gl_FragColor = vec4(1.0,0.0,0.0,1.0);
gl_FragColor = texture2D(sTexture, vTexCoord);
}";

int vertexShader = LoadShader (All.VertexShader, vertexShaderSrc );
int fragmentShader = LoadShader (All.FragmentShader, fragmentShaderSrc );
program = GL.CreateProgram();
if (program == 0)
throw new InvalidOperationException ("Unable to create program");

GL.AttachShader (program, vertexShader);
GL.AttachShader (program, fragmentShader);

//Set position
GL.BindAttribLocation (program, attributePosition, "aPosition");
GL.BindAttribLocation (program, attributeTexCoord, "aTexCoord");


GL.LinkProgram (program);

int linked = 0;
GL.GetProgram (program, All.LinkStatus, ref linked);
if (linked == 0) {
// link failed
int length = 0;
GL.GetProgram (program, All.InfoLogLength, ref length);
if (length > 0) {
var log = new StringBuilder (length);
GL.GetProgramInfoLog (program, length, ref length, log);
Console.WriteLine ("GL2", "Couldn't link program: " + log.ToString ());
return false;
}

GL.DeleteProgram (program);
throw new InvalidOperationException ("Unable to link program");
}

//View
matWorld = Matrix4.Identity;
float aspectRatio = (float) (Size.Width) / (float)(Size.Height);
matProjection = Matrix4.CreatePerspectiveFieldOfView(((float)(Math.PI) / 180.0f)*45.0f, aspectRatio, 1.0f,20.0f);
matView = Matrix4.CreateRotationX(0.5f)*Matrix4.CreateTranslation(0,0,-8);
matWorldViewProjection = matWorld * matView * matProjection;

GetUniformVariables();

LoadTexture();

return true;
}

private int LoadShader ( All type, string source )
{
int shader = GL.CreateShader(type);

if ( shader == 0 )
throw new InvalidOperationException("Unable to create shader");

// Load the shader source
int length = 0;
GL.ShaderSource(shader, 1, new string[] {source}, (int[])null);

// Compile the shader
GL.CompileShader( shader );

int compiled = 0;
GL.GetShader (shader, All.CompileStatus, ref compiled);
if (compiled == 0) {
length = 0;
GL.GetShader (shader, All.InfoLogLength, ref length);
if (length > 0) {
var log = new StringBuilder (length);
GL.GetShaderInfoLog (shader, length, ref length, log);
Console.WriteLine("GL2", "Couldn't compile shader: " + log.ToString ());
}

GL.DeleteShader (shader);
throw new InvalidOperationException ("Unable to compile shader of type : " + type.ToString ());
}

return shader;

}

private void LoadTexture()
{

UIImage ui = UIImage.FromFile("mario3.jpg");
CGImage image = ui.CGImage;

CGColorSpace colorspace = CGColorSpace.CreateDeviceRGB();
data = Marshal.AllocHGlobal(image.Height*image.Width*4);
CGContext context = new CGBitmapContext(data,image.Width,image.Height,8,4*image.Width, colorspace,CGImageAlphaInfo.NoneSkipLast);
colorspace.Dispose();
context.ClearRect(new RectangleF(0,0,image.Width,image.Height));
context.DrawImage(new RectangleF(0,0,image.Width,image.Height),image);

//Generate a texture object
GL.GenTextures(1,ref textureId);

//Bind the texture object
GL.ActiveTexture(All.Texture0);
GL.BindTexture(All.Texture2D, textureId);
GL.Uniform1(textureId,uniformSampler);

//Load the texture
GL.TexImage2D(All.Texture2D, 0, (int)All.Rgba, image.Width, image.Height, 0 ,All.Rgba, All.UnsignedByte, data);

//Set the filtering mode
GL.TexParameter(All.Texture2D, All.TextureMinFilter, (float)All.Nearest);
GL.TexParameter(All.Texture2D, All.TextureMagFilter, (float)All.Nearest);

//Wrap setting
GL.TexParameter(All.Texture2D, All.TextureWrapS,(int)All.Repeat);
GL.TexParameter(All.Texture2D, All.TextureWrapT,(int)All.Repeat);

context.Dispose();
Marshal.FreeHGlobal(data);
}

protected override void ConfigureLayer (CAEAGLLayer eaglLayer)
{
eaglLayer.Opaque = true;
}

private void GetUniformVariables()
{
uniformMat = GL.GetUniformLocation(program, "uMVPMatrix");
uniformSampler = GL.GetUniformLocation(program, "sTexture");
}

private void SetUniformMatrix4(int location, bool transpose, ref Matrix4 matrix)
{
unsafe
{
fixed (float* matrix_ptr = &matrix.Row0.X)
{
GL.UniformMatrix4(location,1,transpose,matrix_ptr);
}
}
}

float rotateY = 0;

protected override void OnRenderFrame (FrameEventArgs e)
{
base.OnRenderFrame (e);

MakeCurrent();

GL.ClearColor (0.7f, 0.7f, 0.7f, 1);
GL.Clear ((int)All.ColorBufferBit);

GL.FrontFace(All.Ccw);
GL.CullFace(All.Front);
GL.Enable(All.CullFace);


GL.Viewport (0, 0, viewportWidth, viewportHeight);
GL.UseProgram (program);

matWorld = Matrix4.CreateRotationY((float)e.Time + rotateY) * Matrix4.CreateTranslation(Vector3.Zero);
matWorldViewProjection = matWorld * matView * matProjection;

SetUniformMatrix4(uniformMat,false,ref matWorldViewProjection);

//Active params
GL.EnableVertexAttribArray (attributePosition);
GL.EnableVertexAttribArray (attributeTexCoord);

//Set params
int size = sizeof(float)*3+sizeof(float)*2;
GL.VertexAttribPointer(attributePosition,3,All.Float,false,size,vertexArrayHandle.AddrOfPinnedObject());
GL.VertexAttribPointer(attributeTexCoord,2,All.Float,false,size,(IntPtr)((uint)vertexArrayHandle.AddrOfPinnedObject()+(uint)(sizeof(float)*3)));

//GL.DrawArrays (All.Triangles, 0, vertexArray);
GL.DrawElements(All.Triangles, indexes.Length, All.UnsignedShort, indexesHandle.AddrOfPinnedObject());

rotateY += (float)Math.PI / 270;

//Swapped buffer (Front and Back buffer)
SwapBuffers ();
}

protected override void OnDisposed (EventArgs e)
{
base.OnDisposed (e);
Marshal.FreeHGlobal (data);
if (textureId != 0)
GL.DeleteTextures(1, ref textureId);
}

}
}

sábado, 23 de abril de 2011

OpenGL-ES2.0 y Monotouch IV (Cube3D parte1)

Siguiendo con el aprendizaje de OpenGLES2.0 hoy voy a escribir un tutorial de como crear un cubo en 3D y texturizarlo. Esta tutorial es muy facil de entender si habéis leido los anteriores.

Lo primero que necesitamos es definir nuestro cubo 3D, para ellos tenemos que entender como OpenGLES2.0 pinta objetos o mejor dicho triangulos en pantalla. OpenGLES2.0 tiene dos métodos para pintar:

GL.DrawArrays
GL.DrawElements

En los tutoriales anteriores hemos usado siempre DrawArrays pero a la hora de pintar un objeto más complejo que un quad es mucho más interesante usar DrawElements. De todas formas en esta primera parte vamos a usar el método DrawArrays y ya en la segunda parte veremos las ventajas que nos plantea usar DrawElements.

Bueno como ya sabéis el método DrawArrays tiene la siguiente declaración:

GL.DrawArrays(All mode, int first, int count);

- mode -> (All.Points/All.Lines/All.LineStrip,/All.LineLoop/ All.Triangles/ All.TriangleStrip / All.TriangleFan)
- first -> Indica el índice del primer vertices a pintar
- count -> Indica el número total de vertices a pintar


Por lo tanto para pintar nuestro cubo necesitaremos descomponerlo en triangulos. Un cubo tiene 6 caras y cada cara se puede descomponer en 2 triangulos que son 3 vértices, por lo que tendremos un total de 36 vertices.




float[] squareVertices = { //Front
   -0.5f,-0.5f,0.0f, //0
   0.5f,-0.5f,0.0f, //1
   0.5f,0.5f,0.0f, //2
   -0.5f,-0.5f,0.0f, //0
   0.5f,0.5f,0.0f, //2
   -0.5f,0.5f,0.0f, //3
  //Botton
-0.5f,-0.5f,1.0f, //4  
           0.5f,-0.5f,1.0f, //5
       0.5f,-0.5f,0.0f, //1
   -0.5f,-0.5f,1.0f, //4
   0.5f,-0.5f,0.0f, //1
-0.5f,-0.5f,0.0f, //0
//Left
   -0.5f,-0.5f,1.0f, //4
       -0.5f,-0.5f,0.0f, //0
   -0.5f,0.5f,0.0f, //3
   -0.5f,-0.5f,1.0f, //4
   -0.5f,0.5f,0.0f, //3
   -0.5f,0.5f,1.0f, //7

//Right
   0.5f,-0.5f,0.0f, //1
   0.5f,-0.5f,1.0f, //5
   0.5f,0.5f,1.0f, //6
   0.5f,-0.5f,0.0f, //1
   0.5f,0.5f,1.0f, //6
   0.5f,0.5f,0.0f, //2
   //Top
0.5f,0.5f,1.0f, //6
-0.5f,0.5f,1.0f, //7
-0.5f,0.5f,0.0f, //3
0.5f,0.5f,1.0f, //6
-0.5f,0.5f,0.0f, //3
   0.5f,0.5f,0.0f, //2

//Back
0.5f,-0.5f,1.0f, //5
-0.5f,-0.5f,1.0f, //4
-0.5f,0.5f,1.0f, //7
0.5f,-0.5f,1.0f, //5
-0.5f,0.5f,1.0f, //7
0.5f,0.5f,1.0f //6   
};


Es importante el orden en el que especificáis los vértices ya que indican hacia donde apunta la normal del triangulo o lo que es lo mismo desde donde se va a ver el triangulo. Yo uso el sentido antihorario que es el estandar también llamado en física "la regla de la mano derecha" donde siguiendo el sentido en el que los dedos de la mano derecha rodearía en triangulo el pulgar sería el vector normal del triangulo.

De la misma forma necesitamos también especificar las coordenadas de textura para cada vértice por lo tanto tendremos que construir el array de coordenadas de texturas:


flfloat[] texCoords = new float[] { 0.0f,1.0f,
  1.0f,1.0f,
  1.0f,0.0f,
  0.0f,1.0f,
  1.0f,0.0f,
  0.0f,0.0f,

  0.0f,1.0f,
  1.0f,1.0f,
  1.0f,0.0f,
  0.0f,1.0f,
  1.0f,0.0f,
  0.0f,0.0f,

  0.0f,1.0f,
  1.0f,1.0f,
  1.0f,0.0f,
  0.0f,1.0f,
  1.0f,0.0f,
  0.0f,0.0f,

  0.0f,1.0f,
  1.0f,1.0f,
  1.0f,0.0f,
  0.0f,1.0f,
  1.0f,0.0f,
  0.0f,0.0f,

  0.0f,1.0f,
  1.0f,1.0f,
  1.0f,0.0f,
  0.0f,1.0f,
  1.0f,0.0f,
  0.0f,0.0f,

  0.0f,1.0f,
  1.0f,1.0f,
  1.0f,0.0f,
  0.0f,1.0f,
  1.0f,0.0f,
  0.0f,0.0f
};


(Nota. Seguro que ya empezáis a notar que debe haber una forma más cómoda y menos repetitiva de definir un cubo, bien lo veremos en la segunda parte)

Bien ahora una de las cosas que vamos a añadirle a este tutorial es que el cubo este girando, el motivo es que si no, no podríamos verlo desde todas sus caras y no podríamos comprobar si está correctamente construido y todas sus caras están correctamente texturizadas.

Para ello vamos a definir las típicas 3 matrices (World / View / Projection):


matWorld = Matrix4.Identity;
float aspectRatio = (float) (Size.Width) / (float)(Size.Height);
matProjection = Matrix4.CreatePerspectiveFieldOfView(((float)(Math.PI) / 180.0f)*45.0f, aspectRatio, 1.0f,20.0f);
matView = Matrix4.CreateRotationX(0.5f)*Matrix4.CreateTranslation(0,0,-8);
matWorldViewProjection = matWorld * matView * matProjection;


Bien pues modificando la matrix del mundo podremos hacer que nuestro cubo 3d esté rotando.


float rotateY = 0;

protected override void OnRenderFrame (FrameEventArgs e)
{
                        ...

matWorld = Matrix4.CreateRotationY((float)e.Time + rotateY) * Matrix4.CreateTranslation(Vector3.Zero);
matWorldViewProjection = matWorld * matView * matProjection;


(Nota. Por motivos de eficiencia vamos a pasarle al VertexShader la multiplicación de las 3 matrices ya hecha de manera que
no sea necesario que el la haga por cada vertice. Por lo que no se puede olvidar modificar la matriz matWorldViewProjection cada vez que modifiquemos la matriz del mundo.)


Nuestro VertexShader tendrá pues una variable de tipo uniform que albergará la matriz resultado de la multiplicación de las 3 matrices. Por lo que nuestro VertexShader será:


string vertexShaderSrc =  @"uniform mat4 uMVPMatrix;
attribute vec4 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
    void main()                  
    {                        
   vTexCoord = aTexCoord;
       gl_Position = uMVPMatrix * aPosition;
    }";


Bien ahora el problema siguiente es setear el valor de la variable uniform del VertexShader. Para ello existen unas funciones dependiendo del tipo de estructura que quieras pasar llamadas GL.UniformX y concretamente para setear una variable de tipo matrix4x4 tenemos GL.UniformMatrix4. El problema es que dicha función no admite un objeto de tipo Matrix4 que es lo que tenemos sino que se le tenemos que pasar un puntero a nuestra matriz.

Para ello yo me he creado el siguiente método:


private void SetUniformMatrix4(int location, bool transpose, ref Matrix4 matrix)
{
unsafe
{
fixed (float* matrix_ptr = &matrix.Row0.X)
{
GL.UniformMatrix4(location,1,transpose,matrix_ptr);
}
}
}


(Nota. Como podéis observar el método usa código no seguro "unsafe" por lo que para que esto es funcione deberéis activar la posibilidad de ejecutar código no seguro en vuestro IDE).

Para terminar en nuestro método de dibujado deberemos especificar el sentido en el que vamos a dibujar los triángulos, a favor de las agujas del reloj o de forma antihoraria esto es conocido como el cullmode. En OpenGLES2.0 existen 3 funciones necesarias para especificar el cullmode:

GL.FrontFace(All type) -> (Donde type puede ser All.Cw (horario) o All.Ccw (antihorario)



Nosotros como explicamos anteriormente hemos especificado los triángulos de forma antihoraria por lo que usaremos All.Ccw.

GL.CullFace(All mode) -> (Donde mode puede ser All.Front / All.Back / All.FrontAndBack)

Donde indicamos cual de las caras del triangulo vamos a dibujar, la delantera, la trasera o las dos. El valor por defecto es las caras traseras por lo que nosotros setearemos esta a All.Front.

GL.Enable(All.CullFace)

Y por último y para que todo esto tenga efecto activaremos el cullmode de la misma forma que la mayoría de parámetros en OpenGL, llamando al método GL.Enable.

Como siempre os pongo el código completo de la clase EAGLView.cs de la plantilla de monotouch.

Paste your text here.#define OPENGLES2


using System;
using OpenTK.Platform.iPhoneOS;
using MonoTouch.CoreAnimation;
using OpenTK;

using OpenTK.Graphics.ES20;

using MonoTouch.Foundation;
using MonoTouch.ObjCRuntime;
using MonoTouch.OpenGLES;
using System.Text;
using System.Drawing;
using OpenTK.Platform;
using MonoTouch.CoreGraphics;
using MonoTouch.UIKit;
using System.Runtime.InteropServices;

namespace Cube3D
{
public partial class EAGLView : iPhoneOSGameView
{
int viewportWidth, viewportHeight;
int program;

float[] squareVertices = { //Front
-0.5f,-0.5f,0.0f, //0
0.5f,-0.5f,0.0f, //1
0.5f,0.5f,0.0f, //2
-0.5f,-0.5f,0.0f, //0
0.5f,0.5f,0.0f, //2
-0.5f,0.5f,0.0f, //3
//Botton
-0.5f,-0.5f,1.0f, //4
0.5f,-0.5f,1.0f, //5
0.5f,-0.5f,0.0f, //1
-0.5f,-0.5f,1.0f, //4
0.5f,-0.5f,0.0f, //1
-0.5f,-0.5f,0.0f, //0
//Left
-0.5f,-0.5f,1.0f, //4
-0.5f,-0.5f,0.0f, //0
-0.5f,0.5f,0.0f, //3
-0.5f,-0.5f,1.0f, //4
-0.5f,0.5f,0.0f, //3
-0.5f,0.5f,1.0f, //7

//Right
0.5f,-0.5f,0.0f, //1
0.5f,-0.5f,1.0f, //5
0.5f,0.5f,1.0f, //6
0.5f,-0.5f,0.0f, //1
0.5f,0.5f,1.0f, //6
0.5f,0.5f,0.0f, //2
//Top
0.5f,0.5f,1.0f, //6
-0.5f,0.5f,1.0f, //7
-0.5f,0.5f,0.0f, //3
0.5f,0.5f,1.0f, //6
-0.5f,0.5f,0.0f, //3
0.5f,0.5f,0.0f, //2

//Back
0.5f,-0.5f,1.0f, //5
-0.5f,-0.5f,1.0f, //4
-0.5f,0.5f,1.0f, //7
0.5f,-0.5f,1.0f, //5
-0.5f,0.5f,1.0f, //7
0.5f,0.5f,1.0f //6
};
// int[] indices = { 0,1,2,0,2,3,
// 0,3,4,0,4,5,
// 0,5,6,0,6,1,
// 7,6,1,7,1,2,
// 7,4,5,7,5,6,
// 7,2,3,7,3,4
// };

//Texture
int textureId;
float[] texCoords = new float[] { 0.0f,1.0f,
1.0f,1.0f,
1.0f,0.0f,
0.0f,1.0f,
1.0f,0.0f,
0.0f,0.0f,

0.0f,1.0f,
1.0f,1.0f,
1.0f,0.0f,
0.0f,1.0f,
1.0f,0.0f,
0.0f,0.0f,

0.0f,1.0f,
1.0f,1.0f,
1.0f,0.0f,
0.0f,1.0f,
1.0f,0.0f,
0.0f,0.0f,

0.0f,1.0f,
1.0f,1.0f,
1.0f,0.0f,
0.0f,1.0f,
1.0f,0.0f,
0.0f,0.0f,

0.0f,1.0f,
1.0f,1.0f,
1.0f,0.0f,
0.0f,1.0f,
1.0f,0.0f,
0.0f,0.0f,

0.0f,1.0f,
1.0f,1.0f,
1.0f,0.0f,
0.0f,1.0f,
1.0f,0.0f,
0.0f,0.0f
};

IntPtr data;

Matrix4 matWorldViewProjection, matProjection, matView, matWorld;
int uniformMat, uniformSampler;
int attributePosition = 0;
int attributeTexCoord = 1;

[Export("layerClass")]
static Class LayerClass ()
{
return iPhoneOSGameView.GetLayerClass ();
}

[Export("initWithCoder:")]
public EAGLView (NSCoder coder) : base(coder)
{
LayerRetainsBacking = false;
LayerColorFormat = EAGLColorFormat.RGBA8;
}

protected override void CreateFrameBuffer ()
{
ContextRenderingApi = EAGLRenderingAPI.OpenGLES2;
base.CreateFrameBuffer();
Initialize();
}

private bool Initialize()
{
viewportHeight = Size.Height;
viewportWidth = Size.Width;

// Vertex and fragment shaders
string vertexShaderSrc = @"uniform mat4 uMVPMatrix;
attribute vec4 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
void main()
{
vTexCoord = aTexCoord;
gl_Position = uMVPMatrix * aPosition;
}";

string fragmentShaderSrc = @"precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D sTexture;
void main()
{
//gl_FragColor = vec4(1.0,0.0,0.0,1.0);
gl_FragColor = texture2D(sTexture, vTexCoord);
}";

int vertexShader = LoadShader (All.VertexShader, vertexShaderSrc );
int fragmentShader = LoadShader (All.FragmentShader, fragmentShaderSrc );
program = GL.CreateProgram();
if (program == 0)
throw new InvalidOperationException ("Unable to create program");

GL.AttachShader (program, vertexShader);
GL.AttachShader (program, fragmentShader);

//Set position
GL.BindAttribLocation (program, attributePosition, "aPosition");
GL.BindAttribLocation (program, attributeTexCoord, "aTexCoord");


GL.LinkProgram (program);

int linked = 0;
GL.GetProgram (program, All.LinkStatus, ref linked);
if (linked == 0) {
// link failed
int length = 0;
GL.GetProgram (program, All.InfoLogLength, ref length);
if (length > 0) {
var log = new StringBuilder (length);
GL.GetProgramInfoLog (program, length, ref length, log);
Console.WriteLine ("GL2", "Couldn't link program: " + log.ToString ());
return false;
}

GL.DeleteProgram (program);
throw new InvalidOperationException ("Unable to link program");
}

//View
matWorld = Matrix4.Identity;
float aspectRatio = (float) (Size.Width) / (float)(Size.Height);
matProjection = Matrix4.CreatePerspectiveFieldOfView(((float)(Math.PI) / 180.0f)*45.0f, aspectRatio, 1.0f,20.0f);
matView = Matrix4.CreateRotationX(0.5f)*Matrix4.CreateTranslation(0,0,-8);
matWorldViewProjection = matWorld * matView * matProjection;

GetUniformVariables();

LoadTexture();

return true;
}

private int LoadShader ( All type, string source )
{
int shader = GL.CreateShader(type);

if ( shader == 0 )
throw new InvalidOperationException("Unable to create shader");

// Load the shader source
int length = 0;
GL.ShaderSource(shader, 1, new string[] {source}, (int[])null);

// Compile the shader
GL.CompileShader( shader );

int compiled = 0;
GL.GetShader (shader, All.CompileStatus, ref compiled);
if (compiled == 0) {
length = 0;
GL.GetShader (shader, All.InfoLogLength, ref length);
if (length > 0) {
var log = new StringBuilder (length);
GL.GetShaderInfoLog (shader, length, ref length, log);
Console.WriteLine("GL2", "Couldn't compile shader: " + log.ToString ());
}

GL.DeleteShader (shader);
throw new InvalidOperationException ("Unable to compile shader of type : " + type.ToString ());
}

return shader;

}

private void LoadTexture()
{

UIImage ui = UIImage.FromFile("mario3.jpg");
CGImage image = ui.CGImage;

CGColorSpace colorspace = CGColorSpace.CreateDeviceRGB();
data = Marshal.AllocHGlobal(image.Height*image.Width*4);
CGContext context = new CGBitmapContext(data,image.Width,image.Height,8,4*image.Width, colorspace,CGImageAlphaInfo.NoneSkipLast);
colorspace.Dispose();
context.ClearRect(new RectangleF(0,0,image.Width,image.Height));
context.DrawImage(new RectangleF(0,0,image.Width,image.Height),image);

//Generate a texture object
GL.GenTextures(1,ref textureId);

//Bind the texture object
GL.ActiveTexture(All.Texture0);
GL.BindTexture(All.Texture2D, textureId);
GL.Uniform1(textureId,uniformSampler);

//Load the texture
GL.TexImage2D(All.Texture2D, 0, (int)All.Rgba, image.Width, image.Height, 0 ,All.Rgba, All.UnsignedByte, data);

//Set the filtering mode
GL.TexParameter(All.Texture2D, All.TextureMinFilter, (float)All.Nearest);
GL.TexParameter(All.Texture2D, All.TextureMagFilter, (float)All.Nearest);

//Wrap setting
GL.TexParameter(All.Texture2D, All.TextureWrapS,(int)All.Repeat);
GL.TexParameter(All.Texture2D, All.TextureWrapT,(int)All.Repeat);

context.Dispose();
Marshal.FreeHGlobal(data);
}

protected override void ConfigureLayer (CAEAGLLayer eaglLayer)
{
eaglLayer.Opaque = true;
}

private void GetUniformVariables()
{
uniformMat = GL.GetUniformLocation(program, "uMVPMatrix");
uniformSampler = GL.GetUniformLocation(program, "sTexture");
}

private void SetUniformMatrix4(int location, bool transpose, ref Matrix4 matrix)
{
unsafe
{
fixed (float* matrix_ptr = &matrix.Row0.X)
{
GL.UniformMatrix4(location,1,transpose,matrix_ptr);
}
}
}

float rotateY = 0;

protected override void OnRenderFrame (FrameEventArgs e)
{
base.OnRenderFrame (e);

MakeCurrent();

GL.ClearColor (0.7f, 0.7f, 0.7f, 1);
GL.Clear ((int)All.ColorBufferBit);

GL.FrontFace(All.Ccw);
GL.CullFace(All.Front);
GL.Enable(All.CullFace);


GL.Viewport (0, 0, viewportWidth, viewportHeight);
GL.UseProgram (program);

matWorld = Matrix4.CreateRotationY((float)e.Time + rotateY) * Matrix4.CreateTranslation(Vector3.Zero);
matWorldViewProjection = matWorld * matView * matProjection;

SetUniformMatrix4(uniformMat,false,ref matWorldViewProjection);

//Active params
GL.EnableVertexAttribArray (attributePosition);
GL.EnableVertexAttribArray (attributeTexCoord);

//Set params
GL.VertexAttribPointer(attributePosition,3,All.Float,false,3*sizeof(float),squareVertices);
GL.VertexAttribPointer(attributeTexCoord,2,All.Float,false,2*sizeof(float),texCoords);

GL.DrawArrays (All.Triangles, 0, 36);
//GL.DrawElements(All.Triangles, indices.Length, All.Int, indices);

rotateY += (float)Math.PI / 270;

//Swapped buffer (Front and Back buffer)
SwapBuffers ();
}

protected override void OnDisposed (EventArgs e)
{
base.OnDisposed (e);
Marshal.FreeHGlobal (data);
if (textureId != 0)
GL.DeleteTextures(1, ref textureId);
}

}
}



Y el resultado en pantalla sería algo así:

viernes, 22 de abril de 2011

OpenGL-ES2.0 y Monotouch III (AlphaTest y Scissor)

En este tercer tutotrial muestro como realizar AlphaTest y Scissor.

AlphaTest

En la mayoría de motores 3D como sabéis existen dos técnicas a la hora de trabajar con el canal Alpha. Estas son AlphaTest o AlphaBlending.

AlphaTest es la técnica más eficiente y consiste en marcar un umbral para el canal Alpha, si este lo supero el pixel se mostrará y si no lo supera el pixel no se pintará y por lo tanto la imagen será transparente en ese pixel.

AlphaBlending esta técnica es mucho más sofisticada y al mismo tiempo menos eficiente, pero consigue implementar diferentes niveles de alpha de forma que un pixel con alpha 0.7 será más opaco que uno con alpha 0.2.

En este tutorial explico como realizar AlphaTest a través de Fragment shader de OpenGLES2.0.

string fragmentShaderSrc = @"precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D sTexture;
void main()
{
vec4 baseColor = texture2D(sTexture, vTexCoord);

if (baseColor.a < 0.5)
{
discard;
}
else
{
gl_FragColor = vec4(baseColor.xyz,1.0);
}
}";


Lo que hacemos es coger el valor alpha de la textura (que tendrá que ser una textura que albergue canal alpha como PNG) y le hacemos un alphaTest diciendo que si supera nuestro umbral fijado en 0.5f (el canal alpha esta comprendido entre 0 y 1) se le manda al glFragColor y si no se llama a la función discard que descarta el pixel y no lo envia para pintar.

El resultado sería:




Scissor

Scissor es una técnica por la cual le podemos decir al render que no pinte toda la pantalla sino un rectángulo de ella. Un ejemplo sería, imaginaros los tipicos juegos donde tenemos nuestra escena 3d pero esta está rodeada de controles o botones que nos permiten cambiar cosas de la escena. Bien pues la escena 3d no tiene que ocupar toda la pantalla sólo el rectangulo que queda libre dentro del marco de botones.

Para esta técnica sólo necesitamos añadir dos funciones al método de dibujado que nos proporciona la clase iPhoneOSGameView llamado OnRenderFrame.

Primero llamaremos al método GL.Scissor de OpenglES para especificar el rectangulo que limitará el pintado. Y lo segundo que debemos hacer es decirle a OpenglES que active la fase de ScissorTest en el pipeline para que tenga efecto.


...
GL.Scissor(0,0,viewportWidth/2,viewportHeight);
GL.Enable(All.ScissorTest);
...


(Nota. Tener en cuenta que la coordenada (0,0) sería la esquina inferior izquierda de nuestra pantalla)

Resultado sería:

domingo, 17 de abril de 2011

OpenGL-ES2.0 y Monotouch II (textures)

Hoy publico un nuevo ejemplo de cómo cargar y renderizar texturas con Monotouch y OpenGL ES2.0. En el anterior artículo publiqué como crear un poligono en pantalla el ejemplo de hoy es un poco más complejo aunque sólo amplía el código del artículo anterior, por lo que si no lo has leido te recomiendo que lo hagas "OpenGLES2.0 y Monotouch I".

Lo siguiente a cuando ya sabemos crear polígonos es poder aplicarles texturas, para ello es muy importante tener algunos conceptos claros.

Lo primero vamos a crear un square en pantalla esto sería:


public float[] squareVertices = { -0.5f, -0.5f,
   0.5f, -0.5f,
   -0.5f, 0.5f,
   0.5f, 0.5f };


Con esto definimos los cuatro vertices que tendrá nuestro cuadrado.
A la hora de texturizarlo es importante conocer lo que son las coordenadas de texturas. Estas permiten a los polígonos posicionar la textura sobre ellos. Estas suelen llamarse (u,v) o (s,t) y es un vector de 2 float en el que sus valores estan entre [0-1] por cada vertice de nuestro poligono.

Para que la textura cubra totalmente nuestro square debemos definir un array de 4 vectores (u,v) que son las coordenadas de textura de cada vertice.



Las definimos como:

float[] texCoords = new float[] { 0.0f,1.0f,
  1.0f,1.0f,
  0.0f,0.0f,
  1.0f,0.0f
};


Ahora Vamos a definir el Vertex shader y el Fragment shader:

Vertex Shader



string vertexShaderSrc =  @"attribute vec4 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
    void main()                  
    {                        
   vTexCoord = aTexCoord;
       gl_Position = aPosition;
    }";    


Definimos el atributo aTexCoord que es dónde cargaremos las coordenadas de textura del vertice que en ese momento estemos procesando. Pero como la única salida de un Vertex Shader es a través de variables de tipo varying definimos también vTexCoord al que se le copiaran los valores.

Y ahora el Fragment Shader


string fragmentShaderSrc = @"precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D sTexture;
            void main()                                
            {                                        
              gl_FragColor = texture2D(sTexture,vTexCoord);
            }";


En el fragment shader tenemos como variable de entrada vTexCoord que fue pasada por el vertex shader y se define un variable de tipo Uniform que es la que albergará los pixeles de la textura. Finalmente para aplicar la textura llamamos a la función texture2D y el pasamos la textura y las coordenadas de textura que le corresponden y esta pintará correctamente la textura sobre el polígono.

Ahora necesitamos cargar la textura. Para ello desde código OpenGLES2 puro escribimos:


//Generate a texture object
GL.GenTextures(1,ref textureId);

//Bind the texture object
GL.ActiveTexture(All.Texture0);
GL.BindTexture(All.Texture2D, textureId);
GL.Uniform1(textureId,0);

Creamos un objeto textura con la función GenTextures

Y luego muy importante activamos el canal de textura0 que tiene OpenGL-ES, básicamente le estamos diciendo que vamos a trabajar sobre el primer canal le decimos también que es una textura 2D y no una textura cúbica con la función BindTexture y le decimos que la textura cargada será enlazada contra la primera variable de tipo uniform con Uniform1.

Para cargar la textura usaremos las clases que wrapea monotouch sobre IPhone, que es la clase UIImage.


UIImage ui = UIImage.FromFile("mario3.jpg");
CGImage image = ui.CGImage;

CGColorSpace colorspace = CGColorSpace.CreateDeviceRGB();
data = Marshal.AllocHGlobal(image.Height*image.Width*4);
CGContext context = new CGBitmapContext(data,image.Width,image.Height,8,4*image.Width, colorspace,CGImageAlphaInfo.NoneSkipLast);
colorspace.Dispose();
context.ClearRect(new RectangleF(0,0,image.Width,image.Height));
context.DrawImage(new RectangleF(0,0,image.Width,image.Height),image);


(Nota. La imagen debe ser multiplo de 2 para que la cargue opengl-es, es decir, 2-4-8-16-32-64... En otro tutorial explicaré como adaptar nuestra imagen dada la resolución que sea).

Tener en cuenta que la imagen que estoy cargando es un retrato de Mario Bros en formato PNG que como sabeis tiene formato RGBA. de hay que el espacio reservado sea 4(bytes) por image.width para cada fila de nuestra variable data y tantas columnas como image.height.

Una vez la imagen cargada para enlazarla a nuestro objeto textura escribiremos:


GL.TexImage2D(All.Texture2D, 0, (int)All.Rgba, image.Width, image.Height, 0 ,All.Rgba, All.UnsignedByte, data);


Con esto le decimos que es una imagen de tipo RGBA (con canal alpha) y que el contenido son bytes sin signos.

Una vez cargada le aplicamos los filtros y el wrap a la textura:


//Set the filtering mode
GL.TexParameter(All.Texture2D, All.TextureMinFilter, (float)All.Nearest);
GL.TexParameter(All.Texture2D, All.TextureMagFilter, (float)All.Nearest);

//Wrap setting
GL.TexParameter(All.Texture2D, All.TextureWrapS,(int)All.Repeat);
GL.TexParameter(All.Texture2D, All.TextureWrapT,(int)All.Repeat);


En otro tutorial explicaré los filtros posibles, así como los 3 modos de wrap que soporta OpenGL-ES2.0.

Y por último y no menos importante liberar el espacio reservado:

context.Dispose();
Marshal.FreeHGlobal(data);


En el método draw aplicaremos al vertex shader los vértices y las coordenadas de textura de la siguiente forma:

//Active params
GL.EnableVertexAttribArray (0);
GL.EnableVertexAttribArray (1);


//Set params
GL.VertexAttribPointer(0,2,All.Float,false,2*sizeof(float),squareVertices);
GL.VertexAttribPointer(1,2,All.Float,false,2*sizeof(float),texCoords);


Y listo, si quereis probarlo sólo necesitais tener monodevelop con Monotouch instalado en vuestro MacOSX. Crear un proyecto de tipo Monotouch OpenGL y modificar el código de la clase llamada EAGLView.cs por:




#define OPENGLES2


using System;
using OpenTK.Platform.iPhoneOS;
using MonoTouch.CoreAnimation;
using OpenTK;

#if OPENGLES2
using OpenTK.Graphics.ES20;
#else
using OpenTK.Graphics.ES11;
#endif
using MonoTouch.Foundation;
using MonoTouch.ObjCRuntime;
using MonoTouch.OpenGLES;
using System.Text;
using System.Drawing;
using OpenTK.Platform;
using MonoTouch.CoreGraphics;
using MonoTouch.UIKit;
using System.Runtime.InteropServices;

namespace Texturing
{
public partial class EAGLView : iPhoneOSGameView
{
int viewportWidth, viewportHeight;
int program;

float[] squareVertices = { -0.5f,-0.5f,
   0.5f,-0.5f,
   -0.5f,0.5f,
   0.5f,0.5f
};
// byte[] pixels = {255,0,0, //Red
// 0,255,0, //Green
//          0,0,255, //Blue
// 255,255,0 //Yellow
// };

//Texture
int textureId;
float[] texCoords = new float[] { 0.0f,1.0f,
  1.0f,1.0f,
  0.0f,0.0f,
  1.0f,0.0f
};

IntPtr data;


[Export("layerClass")]
static Class LayerClass ()
{
return iPhoneOSGameView.GetLayerClass ();
}

[Export("initWithCoder:")]
public EAGLView (NSCoder coder) : base(coder)
{
LayerRetainsBacking = false;
LayerColorFormat = EAGLColorFormat.RGBA8;
}

protected override void CreateFrameBuffer ()
{
#if OPENGLES2
ContextRenderingApi = EAGLRenderingAPI.OpenGLES2;
base.CreateFrameBuffer();
Initialize();
#else
ContextRenderingApi = EAGLRenderingAPI.OpenGLES1;
base.CreateFrameBuffer();
#endif
}

                                

#if OPENGLES2

private bool Initialize()
{
viewportHeight = Size.Height;
viewportWidth = Size.Width;

// Vertex and fragment shaders
string vertexShaderSrc =  @"attribute vec4 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
    void main()                  
    {                        
   vTexCoord = aTexCoord;
       gl_Position = aPosition;
    }";                          

string fragmentShaderSrc = @"precision mediump float;
varying vec2 vTexCoord;
uniform sampler2D sTexture;
            void main()                                
            {                                        
              gl_FragColor = texture2D(sTexture,vTexCoord);
            }";

int vertexShader = LoadShader (All.VertexShader, vertexShaderSrc );
int fragmentShader = LoadShader (All.FragmentShader, fragmentShaderSrc );
program = GL.CreateProgram();
if (program == 0)
throw new InvalidOperationException ("Unable to create program");

GL.AttachShader (program, vertexShader);
GL.AttachShader (program, fragmentShader);

//Set position
GL.BindAttribLocation (program, 0, "aPosition");
GL.BindAttribLocation (program, 1, "aTexCoord");

GL.LinkProgram (program);

int linked = 0;
GL.GetProgram (program, All.LinkStatus, ref linked);
if (linked == 0) {
// link failed
int length = 0;
GL.GetProgram (program, All.InfoLogLength, ref length);
if (length > 0) {
var log = new StringBuilder (length);
GL.GetProgramInfoLog (program, length, ref length, log);
Console.WriteLine ("GL2", "Couldn't link program: " + log.ToString ());
return false;
}

GL.DeleteProgram (program);
throw new InvalidOperationException ("Unable to link program");
}

LoadTexture();

return true;
}

private int LoadShader ( All type, string source )
{
   int shader = GL.CreateShader(type);

   if ( shader == 0 )
   throw new InvalidOperationException("Unable to create shader");     

   // Load the shader source
   int length = 0;
GL.ShaderSource(shader, 1, new string[] {source}, (int[])null);
  
   // Compile the shader
   GL.CompileShader( shader );

   int compiled = 0;
GL.GetShader (shader, All.CompileStatus, ref compiled);
if (compiled == 0) {
length = 0;
GL.GetShader (shader, All.InfoLogLength, ref length);
if (length > 0) {
var log = new StringBuilder (length);
GL.GetShaderInfoLog (shader, length, ref length, log);
Console.WriteLine("GL2", "Couldn't compile shader: " + log.ToString ());
}

GL.DeleteShader (shader);
throw new InvalidOperationException ("Unable to compile shader of type : " + type.ToString ());
}

return shader;

}

private void LoadTexture()
{

UIImage ui = UIImage.FromFile("mario3.jpg");
CGImage image = ui.CGImage;

CGColorSpace colorspace = CGColorSpace.CreateDeviceRGB();
data = Marshal.AllocHGlobal(image.Height*image.Width*4);
CGContext context = new CGBitmapContext(data,image.Width,image.Height,8,4*image.Width, colorspace,CGImageAlphaInfo.NoneSkipLast);
colorspace.Dispose();
context.ClearRect(new RectangleF(0,0,image.Width,image.Height));
context.DrawImage(new RectangleF(0,0,image.Width,image.Height),image);


//Use tightly packed data
//GL.PixelStore(All.UnpackAlignment, 1);

//Generate a texture object
GL.GenTextures(1,ref textureId);

//Bind the texture object
GL.ActiveTexture(All.Texture0);
GL.BindTexture(All.Texture2D, textureId);
GL.Uniform1(textureId,0);

//Load the texture
GL.TexImage2D(All.Texture2D, 0, (int)All.Rgba, image.Width, image.Height, 0 ,All.Rgba, All.UnsignedByte, data);
//GL.TexImage2D(All.Texture2D,0, (int)All.Rgb,2,2,0, All.Rgb,All.UnsignedByte,pixels);

//Set the filtering mode
GL.TexParameter(All.Texture2D, All.TextureMinFilter, (float)All.Nearest);
GL.TexParameter(All.Texture2D, All.TextureMagFilter, (float)All.Nearest);

//Wrap setting
GL.TexParameter(All.Texture2D, All.TextureWrapS,(int)All.Repeat);
GL.TexParameter(All.Texture2D, All.TextureWrapT,(int)All.Repeat);

context.Dispose();
Marshal.FreeHGlobal(data);
}


#endif

protected override void ConfigureLayer (CAEAGLLayer eaglLayer)
{
eaglLayer.Opaque = true;
}


#if OPENGLES2
protected override void OnRenderFrame (FrameEventArgs e)
{
base.OnRenderFrame (e);

MakeCurrent();

GL.ClearColor (0.7f, 0.7f, 0.7f, 1);
GL.Clear ((int)All.ColorBufferBit);

GL.Viewport (0, 0, viewportWidth, viewportHeight);
GL.UseProgram (program);

//Active params
GL.EnableVertexAttribArray (0);
GL.EnableVertexAttribArray (1);

//Set params
GL.VertexAttribPointer(0,2,All.Float,false,2*sizeof(float),squareVertices);
GL.VertexAttribPointer(1,2,All.Float,false,2*sizeof(float),texCoords);

GL.DrawArrays (All.TriangleStrip, 0, 4);

//Swapped buffer (Front and Back buffer)
SwapBuffers ();
}

protected override void OnDisposed (EventArgs e)
{
base.OnDisposed (e);
Marshal.FreeHGlobal (data);
if (textureId != 0)
GL.DeleteTextures(1, ref textureId);
}

#else
protected override void OnRenderFrame (FrameEventArgs e)
{
base.OnRenderFrame(e);

float[] squareVertices = { -0.5f, -0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f };
byte[] squareColors = { 255, 255, 0, 255, 0, 255, 255, 255, 0, 0,
0, 0, 255, 0, 255, 255 };

MakeCurrent ();
GL.Viewport (0, 0, Size.Width, Size.Height);

GL.MatrixMode (All.Projection);
GL.LoadIdentity ();
GL.Ortho (-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
GL.MatrixMode (All.Modelview);
GL.Rotate (3.0f, 0.0f, 0.0f, 1.0f);

GL.ClearColor (0.5f, 0.5f, 0.5f, 1.0f);
GL.Clear ((uint)All.ColorBufferBit);

GL.VertexPointer (2, All.Float, 0, squareVertices);
GL.EnableClientState (All.VertexArray);
GL.ColorPointer (4, All.UnsignedByte, 0, squareColors);
GL.EnableClientState (All.ColorArray);

GL.DrawArrays (All.TriangleStrip, 0, 4);

SwapBuffers ();
}
#endif
}
}


martes, 12 de abril de 2011

OpenGLES2.0 y Monotouch I (Poligon)

Actualmente me encuentro trabajando con OpenGLES2.0 en el proyecto MonoGame y una de las cosas que más trabajo me ha costado encontrar
es un ejemplo de monotouch usando openGL-ES2.0. Por ello he decido escribir uno para muchos como yo que estuvierán perdidos.

Bien pues si teneís instalado Monotouch en vuestro MacOSX (actualmente monotouch sólo funciona sobre MacOSX) y usáis Monodevelop podréis ver una plantilla de Monotouch y OpenGL.

Creamos un proyecto de este tipo y veremos que tenemos dos ficheros (Main.cs y EAGLView.cs) además del componente ventana (fichero xib).

Main.cs: Es la clase que contiene el método main y que llama a UIApplication.Main que es el método principal al trabajar en IOS.

EAGLView.cs: Es la clase de ejemplo que nos crea Monotouch usando OpenGL-ES1.1 con fixed pipeline. Que basicamente significa que no podemos modificar el pipeline de dibujado y por lo tanto no podemos usar shader. Los shader nos permiten tener un control más pontente del pipeline de dibujado y por lo tanto mayor flexibilidad. Otra de las mejoras al usar OpenGL-ES2.0 es que en vez de limitarnos a texturas de 1024x1024 en la versión ES1.1 en la segunda versión ya puedes usar texturas de 2048x2048.

Bueno en resumen por estos motivos y muchos más es muy interesante migrarnos a OpenGL-ES2.0 pero con la salvedad de que en la actualidad aún todos los terminales no soportan ES2.0, así que si el dispositivo no detecta esto deberemos volver a la versión 1.1

Para usar OpenGLES2.0 sobre este ejemplo sólo vamos a tocar la clase EAGLView.cs por lo que es la única que os muestro:



EAGLView.cs


#define OPENGLES2


using System;
using OpenTK.Platform.iPhoneOS;
using MonoTouch.CoreAnimation;
using OpenTK;

#if OPENGLES2
using OpenTK.Graphics.ES20;
#else
using OpenTK.Graphics.ES11;
#endif
using MonoTouch.Foundation;
using MonoTouch.ObjCRuntime;
using MonoTouch.OpenGLES;
using System.Text;
using System.Drawing;
using OpenTK.Platform;

namespace OpenGLES
{
public partial class EAGLView : iPhoneOSGameView
{
int viewportWidth, viewportHeight;
int program;
float [] vertices = new float [] {0.0f, 0.5f, 0.0f,
  -0.5f, -0.5f, 0.0f,
  0.5f, -0.5f, 0.0f
     };

[Export("layerClass")]
static Class LayerClass ()
{
return iPhoneOSGameView.GetLayerClass ();
}

[Export("initWithCoder:")]
public EAGLView (NSCoder coder) : base(coder)
{
LayerRetainsBacking = false;
LayerColorFormat = EAGLColorFormat.RGBA8;
}

protected override void CreateFrameBuffer ()
{
#if OPENGLES2
ContextRenderingApi = EAGLRenderingAPI.OpenGLES2;
base.CreateFrameBuffer();
Initialize();
#else
ContextRenderingApi = EAGLRenderingAPI.OpenGLES1;
base.CreateFrameBuffer();
#endif
}

                                

#if OPENGLES2
// protected override void OnLoad(EventArgs e)
// {
// Initialize();
// }

private bool Initialize()
{
viewportHeight = Size.Height;
viewportWidth = Size.Width;

// Vertex and fragment shaders
string vertexShaderSrc =  @"attribute vec4 aPosition;  
    void main()                  
    {                        
       gl_Position = aPosition;
    }";                          

string fragmentShaderSrc = @"precision mediump float;
varying vec4 vcolor;
            void main()                                
            {                                        
              gl_FragColor = vec4(1.0,0.0,0.0,1.0);
            }";

int vertexShader = LoadShader (All.VertexShader, vertexShaderSrc );
int fragmentShader = LoadShader (All.FragmentShader, fragmentShaderSrc );
program = GL.CreateProgram();
if (program == 0)
throw new InvalidOperationException ("Unable to create program");

GL.AttachShader (program, vertexShader);
GL.AttachShader (program, fragmentShader);

//Set position
GL.BindAttribLocation (program, 0, "aPosition");

GL.LinkProgram (program);

int linked = 0;
GL.GetProgram (program, All.LinkStatus, ref linked);
if (linked == 0) {
// link failed
int length = 0;
GL.GetProgram (program, All.InfoLogLength, ref length);
if (length > 0) {
var log = new StringBuilder (length);
GL.GetProgramInfoLog (program, length, ref length, log);
Console.WriteLine ("GL2", "Couldn't link program: " + log.ToString ());
return false;
}

GL.DeleteProgram (program);
throw new InvalidOperationException ("Unable to link program");
}

return true;
}

private int LoadShader ( All type, string source )
{
   int shader = GL.CreateShader(type);

   if ( shader == 0 )
   throw new InvalidOperationException("Unable to create shader");     

   // Load the shader source
   int length = 0;
GL.ShaderSource(shader, 1, new string[] {source}, (int[])null);
  
   // Compile the shader
   GL.CompileShader( shader );

   int compiled = 0;
GL.GetShader (shader, All.CompileStatus, ref compiled);
if (compiled == 0) {
length = 0;
GL.GetShader (shader, All.InfoLogLength, ref length);
if (length > 0) {
var log = new StringBuilder (length);
GL.GetShaderInfoLog (shader, length, ref length, log);
Console.WriteLine("GL2", "Couldn't compile shader: " + log.ToString ());
}

GL.DeleteShader (shader);
throw new InvalidOperationException ("Unable to compile shader of type : " + type.ToString ());
}

return shader;

}
#endif

protected override void ConfigureLayer (CAEAGLLayer eaglLayer)
{
eaglLayer.Opaque = true;
}


#if OPENGLES2
protected override void OnRenderFrame (FrameEventArgs e)
{
base.OnRenderFrame (e);

MakeCurrent();

GL.ClearColor (0.7f, 0.7f, 0.7f, 1);
GL.Clear ((int)All.ColorBufferBit);

GL.Viewport (0, 0, viewportWidth, viewportHeight);
GL.UseProgram (program);

GL.EnableVertexAttribArray (0);

GL.VertexAttribPointer (0, 3, All.Float, false, 0, vertices);

GL.DrawArrays (All.Triangles, 0, 3);

SwapBuffers ();
}

#else
protected override void OnRenderFrame (FrameEventArgs e)
{
base.OnRenderFrame(e);

float[] squareVertices = { -0.5f, -0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f };
byte[] squareColors = { 255, 255, 0, 255, 0, 255, 255, 255, 0, 0,
0, 0, 255, 0, 255, 255 };

MakeCurrent ();
GL.Viewport (0, 0, Size.Width, Size.Height);

GL.MatrixMode (All.Projection);
GL.LoadIdentity ();
GL.Ortho (-1.0f, 1.0f, -1.5f, 1.5f, -1.0f, 1.0f);
GL.MatrixMode (All.Modelview);
GL.Rotate (3.0f, 0.0f, 0.0f, 1.0f);

GL.ClearColor (0.5f, 0.5f, 0.5f, 1.0f);
GL.Clear ((uint)All.ColorBufferBit);

GL.VertexPointer (2, All.Float, 0, squareVertices);
GL.EnableClientState (All.VertexArray);
GL.ColorPointer (4, All.UnsignedByte, 0, squareColors);
GL.EnableClientState (All.ColorArray);

GL.DrawArrays (All.TriangleStrip, 0, 4);

SwapBuffers ();
}
#endif
}
}




Nota. Para terminar si comentas la primera linea es decir "#define OPENGLES2" se renderizará usando ES1.1 y si no
usará ES2.0 con shaders.

Abrir dos instancias de la misma aplicación en MacOSX

Actualmente estoy trabajando con Monodevelop en MacOSX y uno de los primeros incovenientes que te encuentras es el no poder abrir varias instancias de Monodevelop con lo necesario que esto es para un programador. En principio MacOSX no te deja hacerlo desde su interfaz gráfico pero esto no quiere decir que no se pueda. Así que hoy os cuento otro tip sobre MacOSX que a mi me facilitó mucho la tarea al trabajar con Monodevelop.


Abrimos un terminal y ponemos

/Applications/MonoDevelop.app/Contents/MacOS/monodevelop &

Dentro del directorio Applications se encuentran todas las aplicaciones que tenemos instaladas por lo que supongo que esto seguro que os sirve para otros programas.

Mostrar ficheros ocultos en MacOSX

Hoy escribo un pequeño truco que me reportó Javier fernandez de Syderis para poder ver en una ventana del finder en MacOSX los ficheros y directorios ocultos. Esto es un poco más rebuscado que en sistemas como Windows y no tenemos una propiedad fácilmente modificable. Necesitamos modificar la variable AppleShowAllFiles que es un bool y ponerla a true ya que por defecto viene a false, para ello:

Abrimos un terminal y ponemos:

Mostrar ficheros ocultos:
defaults write com.apple.finder AppleShowAllFiles TRUE

killall Finder


Ocultar ficheros ocultos:
defaults write com.apple.finder AppleShowAllFiles FALSE

killall Finder