cargador

33
n el proyecto anterior aprendimos a cargar nuestro primer formato de modelo en 3D, Primer cargador de modelo parte 1 (OFF) en OpenTK . En esta nueva entrega seguiremos con esa onda, sin embargo, pasaremos a manipular un formato un poco más complejo que el anterior (.obj). En esta primera parte para este cargador nos concentraremos exclusivamente en lograr cargar y desplegar el modelo. Por ahora nos olvidaremos de los materiales y las posibles texturas que el modelo pueda incluir. Antes de comenzar Organicemos un poco nuestro proyecto. Para mantener el orden, y en vista de que ya manipulamos textura, archivos .off y ahora .obj lo más recomendable es tener una carpeta de recursos, donde coloquemos todos esos archivos. En mi caso he creado una carpeta en la raíz de mi proyecto y le llame “Files”. ¡Ahora si estamos listos! Descarga el código fuente → ARRIBA Formato .obj El formato .obj en mi opinión es uno de los formatos más fácil de manipular y más versátiles, teniendo en cuenta que soporta materiales, texturas, mapas de normales y lo mejor de todo es que esta en texto plano. La estructura básica es muy simple. Recuerda que nos concentraremos en esta primera parte en el modelo en sí. Vértices y normales Cada vértice viene precedido por una letra (v) y al menos un espacio antes de los datos Cada normal viene precedido de las letras (vn) y al menos un espacio antes de los datos

Transcript of cargador

Page 1: cargador

n el proyecto anterior aprendimos a cargar nuestro primer formato de modelo en 3D, Primer cargador de

modelo parte 1 (OFF) en OpenTK. En esta nueva entrega seguiremos con esa onda, sin embargo, pasaremos

a manipular un formato un poco más complejo que el anterior (.obj).

En esta primera parte para este cargador nos concentraremos exclusivamente en lograr cargar y desplegar el

modelo. Por ahora nos olvidaremos de los materiales y las posibles texturas que el modelo pueda incluir.

Antes de comenzar

Organicemos un poco nuestro proyecto. Para mantener el orden, y en vista de que ya manipulamos textura,

archivos .off y ahora .obj lo más recomendable es tener una carpeta de recursos, donde coloquemos todos

esos archivos.

En mi caso he creado una carpeta en la raíz de mi proyecto y le llame “Files”.

¡Ahora si estamos listos!

Descarga el código fuente →ARRIBA

Formato .obj

El formato .obj en mi opinión es uno de los formatos más fácil de manipular y más versátiles, teniendo en

cuenta que soporta materiales, texturas, mapas de normales y lo mejor de todo es que esta en texto plano. La

estructura básica es muy simple. Recuerda que nos concentraremos en esta primera parte en el modelo en sí.

Vértices y normales

Cada vértice viene precedido por una letra (v) y al menos un espacio antes de los datos

Cada normal viene precedido de las letras (vn) y al menos un espacio antes de los datos

Sin importar cuál sea el caso, la normal y el vértice estará compuesta por tres valores flotantes separados por

espacios

v 0.5 0.5 0.5

vn 0.0 1.0 0.0

Page 2: cargador

Polígonos

Este formato no tiene ninguna limitante con la cantidad de vértices que puede tener cada polígono, sin

embargo, el hecho que nuestro modelo este formado con polígonos de más de 4 vértices nos podría traer

problemas al momento del despliegue. La razón es muy simple, el modelo podría contener polígonos no

convexos, y hasta el momento OpenGL (por lo menos, no que yo sepa) no soporta este tipo de polígonos a la

hora del despliegue.

En vista de nuestra limitante, y para simplificar nuestro trabajo, nos concentraremos en cargar modelos que

estén formados completamente por triángulos. Esto se podría ver inicialmente como una limitante para

nuestra clase, pero más adelante veras que es muy conveniente manipular solo triángulos en cualquier tipo de

formato de modelos 3d.

Ahora bien, el formato para la lectura de un polígono (triangulo) es el siguiente:

Todas las caras inician con la letra (f). Cada cara indicará 9 índices (agrupados de tres en tres y todos separados por /) que hacen referencia a algún vértice, coordenada de textura o normal ya agregado.Ejemplo – f 6/9/4 5/10/4 1/4/4

Aquí hay dos puntos importantes:

El orden en que mencione los datos es importante, es decir, primero está el índice del vértice, luego el de la coordenada de textura y por último el de la normal.

Los índices comienzan en 1 y no en 0, por tanto, deberemos hacer la compensación al momento de registrarlos (restar 1 a todos los índices).

ARRIBA

¡A programar!

Si recuerdas nuestra clase “offLoader”, podrás notar que en ella todo está encapsulado en una misma clase.

En esta oportunidad, y pensando en la complejidad futura que puede tener nuestra clase, separaremos

nuestro cargados en dos partes: Estructura y Cargador.

Estructura

Contendrá la información de nuestro modelo ya cargada desde el archivo. Tenemos dos clases principales:

Face: Guardará la información de cada polígono, por ahora solo serán los índices a las referencias de sus vértices y normales.

01 public class Face

02 {

Page 3: cargador

03    /// <summary>

04    /// Indices de los vértices

05    /// </summary>

06    public int[] IndicesVertex = new int[3];

07

08

    /// <summary>

09     /// indices de las normales

10     /// </summary>

11     public int[] IndicesNormal = new int[3];

12 }

Mesh: Aquí estará el modelo en si, representado por una lista de vértices, normales y polígonos.

01 class Mesh

02 {

Page 4: cargador

03     #region Properties

04

05    /// <summary>

06

    /// Lista de poligonos [Triangulos] del modelo

07    /// </summary>

08    public List<Face> Faces { set; get; }

09

10

    /// <summary>

11     /// Lista de vértices

12     /// </summary>

13     public List<Vector3d> Vertices { set; get; }

14

Page 5: cargador

15    /// <summary>

16    /// Lista de vértices

17

    /// </summary>

18

    public List<Vector3d> Normals { set; get; }

19

20

    #endregion

21

22

    #region Constructors

23

24

    /// <summary>

25     /// Constructor

26     /// </summary>

Page 6: cargador

27     public Mesh()

28     {

29        Faces = new List<Face>();

30        Vertices = new List<Vector3d>();

31         Normals = new List<Vector3d>();

32     }

33

34

    #endregion

35 }

ARRIBA

Cargador

Será el encargado de traducir la información del archivo a nuestra estructura. Su contenido es muy básico.

Una función que coloque el archivo completo en una variable “string” y otra que manipule ese cadena y

extraiga la información progresivamente. Vamos al código de nuestro cargador.

GetFileString

01/// <summary>

Page 7: cargador

02

/// Transfiere un archivo completo a un string

03/// </summary>

04

/// <param name="fileName">Archivo a cargar</param>

05

/// <returns>Archivo en el string</returns>

06

static public string GetFileString(string fileName)

07{

08    try

09    {

10

        var file = new StreamReader(fileName);

11         var fileSTR = file.ReadToEnd();

12         file.Close();

Page 8: cargador

13         return fileSTR;

14     }

15     catch { Console.Write("ERROR FILE"); }

16     return "";

17 }

.

No hay mucho que explicar acá. Cargamos el archivos, lo pasamos a nuestra variable.

GetMesh: Aquí es donde la magia ocurre. Iniciamos con la carga de nuestro archivo y algunas validaciones importantes.

1 mesh = new Mesh();

2

3 var fileToString = GetFileString(fileName);

4 if (String.IsNullOrEmpty(fileToString))

5{

6    Console.WriteLine("The file name can not be empty or

Page 9: cargador

null");

7     return false;

8 }

9

Ahora recorremos nuestra cadena línea por línea y vamos evaluando con cual carácter inicia cada una, y de

acuerdo a ese primer carácter la clase sabra que tipo de información contiene y como manipularla.

01 using (var reader = new StringReader(fileToString))

02 {

03    string line;

04    var initRead = true;

05     var materialIndex = -1;

06

07     while ((line = reader.ReadLine()) != null)

08     {

09         //#: representa comentarios dentro del archivo

10         if (line.Length > 0 && line.ElementAt(0) != '#')

Page 10: cargador

11        {

12

            string[] split;

13             switch (line.ElementAt(0))

14             {

15     ...

16

Manipulamos las líneas que inician con la letra “v”

01 ...

02

03 case 'v':

04

05//UPDATE STRING NUMBERS

06line = line.Replace('.', ',');

07 int space;

Page 11: cargador

08

var value = Vector3d.Zero;

09

10

for (space = 1; space < line.Length; space++)

11     if (!line.ElementAt(space).Equals(' ')) break;

12

13 split = line.Substring(space, line.Length - space).Split(' ');

14 switch (line.ElementAt(1))

15{

16

    //VERTEX RECORDING

17    case ' ':

18        if (double.TryParse(split[0]

Page 12: cargador

, out value.X) &&

19             double.TryParse(split[1], out value.Y) &&

20             double.TryParse(split[2], out value.Z))

21            {

22

                mesh.Vertices.Add(value);

23             }

24         break;

25

26

    //NORMAL RECORING

27    case 'n':

28

            if (double.TryParse(split[1], out value.X) &&

29                 double.TryParse(split[2], out value.Y) &&

30                 double.TryParse(split[3], out value.Z))

Page 13: cargador

31            {

32

                mesh.Normals.Add(value);

33             }

34

35         break;

36 }

37

38

break;

39

40 ...

Manipulamos las líneas que inician con la letra “f”

01 ...

02

Page 14: cargador

03 case 'f':

04

05 //ORDEN BY FACE - VERTEX / TEXTURE / NORMAL

06

07//FACE RECORDING

08

var currentsIndexs = line.Substring(2, line.Length - 2).Split(' ');

09 var currentFace = new Face();

10

11 //EVALUETE EAXH VERTEX

12 var index = 0;

13 foreach (var vertex in currentsIndexs)

14 {

15    if (index >= 3)

16        throw new ArgumentNullException("the polygon can have a maximum of 3 vertices

Page 15: cargador

[triangle]");

17

18

    var indices = vertex.Split('/');

19     int currentIndex;

20

21    //Vertex

22

    if (int.TryParse(indices[0], out currentIndex))

23         currentFace.VerticesIndices[index] = currentIndex - 1;

24

25    //Normal

26

    if (int.TryParse(indices[2], out currentIndex))

27         currentFace.NormalsIndices[index] = currentIndex - 1;

Page 16: cargador

28

29     index++;

30 }

31

32

if(index < 3)

33     throw new ArgumentNullException("Number of vertices is invalid");

34

35 mesh.Faces.Add(currentFace);

36

37 break;

38

39 ...

Excelente, ya tenemos nuestros cargador listos y totalmente operativo. Ahora veamos como desplegar el

modelo. En nuestra clase Mesh, agregamos la función Draw

01 /// <summary>

Page 17: cargador

02 /// Despliegue

03 /// </summary>

04 public void Draw()

05{

06

    GL.Color4(Color4.White);

07     for (int polygons = 0; polygons < Faces.Count; polygons++)

08     {

09         GL.Begin(BeginMode.Polygon);

10

11         for (int vertexs = 0; vertexs < 3; vertexs++)

12         {

13             GL.Normal3(Normals[ Faces[polygons].IndicesNormal[vertexs] ]);

14             GL.Vertex3(Vertices[ Faces[polygons].IndicesVertex[vertexs] ]);

15         }

Page 18: cargador

16

17         GL.End();

18     }

19}

20

Es un recorrido muy parecido al que hemos utilizado para el formato .off. Recorremos todas las caras. Luego,

con cada índice almacenado en la estructura interna de cada cara accedemos a las listas de normales y

vértices respectivamente.

ARRIBA

¡Mucho código y poco despliegue!

Es hora de correr nuestro proyecto. Vamos a probar con una esfera que tiene aproximadamente 800 caras

(incluida en los recursos del proyecto). Agreguemos una variable de tipo Mesh en nuestro proyecto y

carguemos el modelo.

1//Variable global

2private Mesh _objectMesh;

3

4

//En nuestra funcion de carga

Page 19: cargador

5 if(!ObjectLoader.GetMesh(@"..\..\Files\SphereC4D.obj", out _objectMesh))

6     Console.WriteLine("The file is Invalid");

7

8

//En nuestra función de despliegue

9 _objectMesh.Draw();

.

¿Y ahora que ha pasado? No veo absolutamente nada. ¿Qué hemos hecho mal?

Déjenme decirles que todo está correcto, es solo un simple detalle de ubicación. Concentrémonos en esta

línea, justo antes del despliegue de nuestro modelo.

Page 20: cargador

1 Matrix4 lookat = Matrix4.LookAt(Vector3.UnitZ, Vector3.Zero, Vector3.UnitY);

.

Como notarán estamos ubicados en la posición (0, 0, 1) y estamos observando hacia la posición (0, 0, 0) lo

cual me hace pensar que muy probablemente estamos dentro de la esfera y por eso no la vemos. Validemos

esa información. Vamos al archivo de nuestro modelo y abrámoslo con algún editor de texto.

Allí esta, el primer vértice tiene por coordenada x un valor de 40.6737, lo cual implica que tenemos una esfera

muy grande.

¿Sabes cómo solucionarlo? ¡Correcto! Solo hace falta cambiar la ubicación por una posición fuera de la

esfera. Yo usaré la siguiente posición (0, 0, 500). Compilo y ejecuto, y como arte de magia nuestra esfera

aparece en escena.

ARRIBA

Extra

Te enseñaré dos formas adicionales de cómo puedes desplegar tus modelos. Hasta el momento lo hemos

hecho mostrando la cara completa de cada polígono (relleno). Haciendo uso de una utilidad de OpenGL

puedes mostrar tu modelo con líneas o puntos. La función que utilizaremos es:

1 GL.PolygonMode(MaterialFace, PolygonMode);

.

La debes colocar justo antes de tu despliegue, y sus parametros son:

Page 21: cargador

MaterialFace: Indica a cuales caras quieres aplicarle el modo de despliegue

PolygonMode: Indica el modo de despliegue que deseas aplicar

Despliegue con líneas

1 GL.PolygonMode(MaterialFace.Front, PolygonMode.Line);

Despliegue con puntos

1 GL.PolygonMode(MaterialFace.Front, PolygonMode.Point);

Page 22: cargador

Una pregunta final

¿Porque en el modo línea y punto no se visualiza la parte de atrás de nuestra esfera?

Esto se puede resolver de varias formas, pero lo realmente importante ahora es saber porque no se muestra

las caras traseras. La razón básica es por el orden de los vértices y la dirección de la cara de nuestros

polígonos. En uno de nuestro tutoriales lo explicamos a detalle.

Ahora bien, actualmente nosotros tenemos configurado que solo se dibujen las caras frontales. Para

desactivar esta opción basta con comentar la siguiente línea en nuestra función de carga

1 //GL.Enable(EnableCap.CullFace);

Page 23: cargador

Aparentemente volvimos al modo de despliegue completo. En realidad no, lo que pasa es que ahora estamos

mostrando las caras frontales con puntos y las traseras con relleno. Es cuestión de cambiar la cara

seleccionada en nuestra función de modo de despliegue.

1 GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Point);

.

¡Y esta hecho!

Conclusiones

Page 24: cargador

Hemos aprendido un poco más el día de hoy. Ya somos capaces de manipular formatos de modelos 3D en

nuestras aplicaciones.

En el próximo tutorial complicaremos un poco más nuestra clase. Aprenderemos como manipular los archivos

de materiales que vienen asociados a nuestro modelo .obj. Esperamos como siempre que hayas aprendido

algo nuevo y no olvides unirte a cualquiera de nuestros servicios de redes sociales.

¡Hasta pronto!

ARRIBA

Post to Facebook

Post to Twitter

Llevo haciendo en mis ratos libres un juego en 3D para psp desde hace tiempo (aunque tengo pocos ratos libres). Una de las cosas más importantes que he hecho hasta ahora es el implementar una serie de clases y funciones que me permiten cargar en la psp modelos en 3D creados por blender, 3dMax, Milkshape... cuando han sido exportados al formato OBJ.

En este primer tutorial lo que voy a hacer, es introduciros a este formato para entender sus especificaciones, y más adelante poder hacer un cargador de modelos 3d básico.

Antes de nada tenemos que saber que este es un formato estático, quiero decir, que el formato no guarda animaciones, solo tendremos un objeto rígido en escena, aunque mediante scepspGu podremos rotarlo o escalarlo.El formato también admite texturas, así que cuando acabemosseremos capaces de cargar modelos texturizados en la psp.

El formato

Bien, lo primero que debemos saber sobre este formato, es que al contrario de otros, el modelo se guarda en un fichero de texto plano, que podríamos abrir si quisiéramos con el notepad.

Un modelo OBJ, consta de 2 ficheros, uno de ellos es el que guarda la información de los materiales utilizados (texturas, valores de luces etc) y el otro es el que contiene toda la "chicha" del modelo, en el que se incluyen las coordenadas, coordenadas de textura, normales, polígonos etc.

El fichero .MTL

Como os he dicho antes, este es el fichero que guarda la información de los materiales usados en el modelo. Un mismo modeloOBJ, puede tener varios ficheros MTL Nosotros solo vamos a usar un campo de este fichero, el resto de cosas nos van a dar igual, Y es que el campo que usaremos nos indicará la textura que vamos a usar en el modelo.

Por lo tanto cuando estemos cargando el modelo, lo único que tenemos que hacer es buscar la línea/s que empiecen por:

Código:

Page 25: cargador

map_Kd textura.tga

El formato OBJ admite varias texturas en un mismo objeto, así que es posible que encontremos varias texturas. El como tratar este tema lo explicaré en el tutorial de carga.

El fichero .OBJ

Este es el fichero que contiene toda la información del modelo y tiene (más o menos) la siguiente estructura:

[code linenumbers=true]# En el fichero puede haber comentarios, que estarán precedidos por el carácter #v 0.3243 -1.0234 0.4321v 0.3243 -1.9623 -6.0098v 10.6424 -1.2324 -11.5008v 0.6428 -1.0234 34.9562...vt -0.3423 0.4234vt 0.0000 0.2314...vn 0.3454 0.2340 0.9530...f v1/vt1/vn1 v1/vt1/vn1 v1/vt1/vn1....o [nombre de objeto] ó g [nombre de gurpo]usemtl nombre de material[/code]

Vale, ese es el formato del fichero OBJ a lo bestia, ahora voy a explicar un poco cada parte:

Como he puesto, los comentarios pueden aparecer en cualquier parte del fichero, no forman parte del objeto pero pueden contener alguna aclaración. y se expresan poniendo al principio el carácter #

Lo primero que encontraremos en el fichero obj es una lista de vértices que son las coordenadas de cada uno de los puntos que tiene en modelo, para saber que se trata de eso, antes de los valores, (x, y, z) estará el caracter v por ejemplo:

Código:

v -0.4354 1.2340 -34.0454

Con eso tendremos definido un punto en el espacio que esté en esa coordenada.

Mas tarde encontraremos las coordenadas de textura, Éstas también son importantes ya que las usaremos para "pegar" nuestra textura al modelo y que quede bonito. Estas coordenadas se componen de 2 valores (u, v) y significan que coordenada de la imagen se corresponde con la coordenada del modelo.

Page 26: cargador

ejemplo:

Código:

vt 0.3432 0.9348

los valores nunca serán mayores que 1 ya que son valores porcentuales, es decir:

Código:

vt 1.0000 1.0000

correspondería con la esquina inferior derecha de la imagen (aunque no estoy muy seguro, creo que sería el valor 0.9999 0.9999)

Lo siguiente que nos encontramos son los valores de las normales de los vértices, estos valores nos indican con qué ángulo está girado un vértice, con que fuerza brilla, etc... y serían así:

Código:

vn 0.1110 0.3495 0.3493

Una vez tenemos todos los datos del modelo sin orden y "a lo bestia" tenemos que ordenarlos en polígonos, para ir formando el modelo: Para eso están las caras que son así:

Código:

f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3

donde pone v vt y vn, habrá números enteros, y cada uno de ellos indicará cual de las coordenadas/Coordenadas de textura/normales conforman ese polígono del modelo

Una vez tenemos esto, tendremos en nuestro haber, toda la información del modelo, no obstante, falta un matiz.

Hay modelos que se dividen en diferentes objetos o grupos, que son como sub modelos, esto quiere decir, que si tu tienes un modelo de un coche, cuando lo estás modelando, tu puedes hacer que por ejemplo, cada puerta sea un objeto independiente, las ventanas también, el capó, así tienes todo el coche estructurado por si necesitas acceder a esas partes en el código.

Por lo tanto, despues de haber definido las caras, puede haber más cosas:

Código:

o nombre_objeto

Eso significa que el subModelo que acabas de recoger se llama nombre_objeto, y a partir de ahí si hay más sub modelos volveremos a tener

Cita:

v

Page 27: cargador

vtvnf

de ese sub modelo.

y estaremos haciendo lo mismo todo el tiempo hasta que no quede más por procesar.

ueno, siento la espera, aquí traigo la segunda entrega del tutorial de carga de modelos 3D, he decidido que serán 3 partes al final.

La primera es la que ya está puesta en la que explico la especificación del formato   OBJ

Y esta segunda, se centrará en el diagrama de clases del Cargador y en la explicación de lo que contribuye cada clase al resultado final.

Page 28: cargador

DIAGRAMA UML DE LAS CLASES

En el diagrama podemos apreciar las relaciones que hay entre las clases que conforman el Cargador.

La clase "Igrafica"

Lo primero que apreciamos es la clase "Igrafica", esta clase, es la que usaremos nosotros en el código, y que se encarga de inicializar las características de openGL que necesitamos, además nos sirve como fachada para simplificar las operaciones a realizar. En el método "Inicializa3D" a parte de encargarse de inicializar OpenGL, es donde se llama al método "Cargar" de la clasemodelo.

Ahora voy a seguir explicando de abajo a arriba, ya que para entender la clase "Modelo" primero hay que

Page 29: cargador

entender los elementos que la componen:

Struct "Vertice"

Esta estructura es de lo que están formadas todas las cosas que se dibujan en OpenGL, y un Modelo no podría ser menos, cada polígono está formado por 3 Vertices y cuando tengamos todos los vértices, OpenGL se encargará de dibujarlos por pantalla.

Ahora paso a explicar las componentes del "Vertice":

u y v son dos números reales, que utilizaremos para "enganchar" la textura a los polígonos, cada una de u y v son un porcentaje de la imagen que será la textura de nuestro modelo, por ejemplo, si tenemos un triángulo y queremos que uno de los vértices tenga el píxel de la esquina inferior derecha de la imagen atado a él, entonces u y v valdrían 0.

color es un entero, que nos dice en hexadecimal el color que tendrá ese polígono, como nosotros no usaremos colores, si no que usaremos texturas, este parámatero siempre estará a #FFFFF; que es el color blanco.

nx, ny, y nz, son las componentes del vector normal de ese vértice, el vector normal, es un vector que nos dice en qué dirección se refleja la luz en ese punto, además nos sirve para saber si el polígono que estamos mirando está al derecho o al revés.

Y por fín llegamos a x, y, z, estas son las coordenadas en 3 dimensiones de donde se localiza ese Vertice en el espacio.

Ahora paso a explicar la clase "Cara".

La clase "Cara"

Esta clase, es una clase de ayuda, sirve para almacenar el orden correcto de todos los vértices que hayamos leído en el fichero OBJ, equivale a las Faces (f) que vimos en la especificación del fichero, y es que cuando terminas de leer todos los vértices, las caras te dicen, para cada uno de los polígonos del modelo, que vértices deben ir.

La clase "Parte"

Esta clase es equivalente a los "subModelos" que expliqué en el anterior tutorial ya que cada modelo, puede estar dividido en varios modelos más pequeños, os puse el ejemplo del coche que es lo que cargaremos en el último tutorial:

Esta clase tiene los vectores ordenados como se lo ha dicho la clase "Cara" y ya es algo que se puede dibujar mediante OpenGL:

Page 30: cargador

Una parte del coche podría ser el techo, otra podría ser una rueda... así sucesivamente, hasta que al cargarlas todas y renderizarlas, tendremos el coche en la pantalla.Claro que también es posible que un modelo solo esté compuesto por 1 "SubModelo", en ese caso lo cargaríamos igual, pero tendríamos menos flexibilidad a la hora de mediante código hacer algo con esas partes. Por ejemplo, si tengo las 4 ruedas en diferentes "Partes" mediante código, podría girarlas cuando el usuario mueva el volante, y cosas así, mientras que si el coche está formado por una sola "Parte" no podría hacer eso.

La clase "Modelo"

Esta clase es un contenedor de todas las otras clases que he explicado, es en la que se agrupa todo, y mediante la cual haremos las operaciones.Tenemos el método Cargar() que recibe como parámetros, el fichero OBJ y el fichero MTL del que os hablé en el anterior tutorial. Este método se encargará de "poner cada cosa en su sitio", y es realmente el centro de esta serie de tutoriales, veremos como funciona detalladamente en el siguiente y último tutorial.

Por último, el método Render() es el que recorrerá todas las "Partes" del modelo y renderizará cada una de ellas.

Bueno, ya queda menos para acabar por completo los tutoriales, en el próximo, os enseñaré como cargar y renderizar, una escena en 3D formada por un coche y una "carretera", la carretera la pongo entre comillas porque me salió un poco desproporcionada respecto al coche, pero bueno, cumple su trabajo xD

Ahora os dejo unas imágenes de nuestra escena cuando acabemos los tutoriales, para que se os pongan los

dientes largos  :