Está en la página 1de 7

M.T.I.

Orleny Espiridion Marino

Generación procedural de modelos 3D en Unity

Cuando queremos usar un modelo 3D para nuestro videojuego hecho en Unity, podemos crearlo usando
una herramienta externa como Autodesk 3DS Max, o bien usar los sencillos modelos 3D que Unity puede
generar (cubo, esfera, plano, etc.). Estos modelos que Unity puede generar se crean mediante un
algoritmo que se encarga de calcular todos los vértices y las caras de dicho modelo 3D. Del mismo modo
que Unity puede crear mediante programación estos modelos 3D, podemos escribir un script con C# para
generar nuestros propios modelos 3D. Esto es lo que se conoce como generación procedural (o
paramétrica) de modelos.

Cuanto más complejo sea el modelo 3D, más complejo será el algoritmo para generarlo. Por lo que es
importante valorar hasta qué punto nos puede resultar útil, o no, generar modelos de esta forma.

Ventajas de la generación paramétrica de «assets»


Las principales ventajas que nos ofrece esta forma de trabajar son:

• Podemos generar modelos sin necesidad de tener que abrir una aplicación externa a Unity, crear
un modelo, exportarlo y luego volverlo a importar en Unity, por lo que el flujo de trabajo es
óptimo.
• Al generarse estos modelos con un algoritmo matemático, podemos generar tantas variantes
como permita el algoritmo simplemente variando los parámetros de dicho algoritmo (de ahí el
nombre de generación paramétrica), e incluso generar modelos 3D bajo demanda según la
interacción del jugador. Esto nos va a permitir generar infinitas variantes del mismo tipo de
modelo 3D sin necesidad de modelar, con tan solo aplicar cambios a los parámetros que usa el
algoritmo de generación.
• Si al algoritmo de generación le añadimos cierta aleatoriedad, podemos hablar de generación
procedural de modelos 3D. Por lo que incluso podemos hacer que los modelos generados sean
hasta cierto punto aleatorios (dentro de lo que permita nuestro algoritmo de generación).
Ejemplos típicos de generación procedural podrían ser la generación de terrenos o la generación
de árboles.

Para lograr nuestro objetivo, necesitaremos tener los conocimientos matemáticos suficientes para poder
escribir un algoritmo que nos calcule la posición de cada vértice y nos calcule los índices de cada cara de
nuestro modelo 3D. Si el modelo es sencillo, por lo general, el apartado matemático será sencillo, pero si
el modelo es complejo la parte matemática puede serlo también. Obviamente, junto al tiempo que implica
programar el script de generación, estas son las principales desventajas de esta forma de trabajar.
M.T.I. Orleny Espiridion Marino

Generar un terreno proceduralmente en Unity


Aunque Unity ya incluye herramientas para generar terrenos, vamos a crear nuestro propio generador
(mucho más sencillo, claro). Y lo vamos a hacer, porque es una forma bastante sencilla de comprender
como funciona esta forma de trabajar y las posibilidades que nos ofrece.

1.- Primero vamos a crear un Game Object vacío (botón derecho sobre la jerarquía de objetos de la escena
y seleccionamos «Create Empty»). Después, a este Game Object, le vamos a añadir los siguientes 3
componentes:

Mesh Filter – este componente almacenará la información del modelo 3D.

Mesh Renderer – este componente lo pintará en pantalla usando el material que le indicamos. Por lo que
debemos de indicar un material en «Elemento 0» dentro de «Materials» o de lo contrario se utilizará el
material por defecto.

New Script – le ponemos de nombre «GeneradorDeTerreno.cs», lo abrimos para editar y le ponemos el


siguiente contenido a dicho script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(MeshFilter))]
public class GeneradorDeTerreno : MonoBehaviour
{
public int m_Seed = 0; // semilla
public int m_Rows = 25; // número de filas
public int m_Columns = 25; // número de columnas
public float m_CellSize = 1.0f; // ancho de la celda
public float m_Height = 3.0f; // altura
public float m_NoiseScale = 0.25f; // escala del ruido
public float m_TexCoordScale = 1.0f; // escala del mapa UV

Vector3[] vertex; // para los vértices


Vector2[] texcoor; // para las coordenadas de textura
int[] index; // para las triángulos
Mesh mesh; // nuestro modelo 3D

void RegenerateMesh()
{
Random.InitState(m_Seed);
float rdm = Random.Range(0f, 10000f);
float uvscale = 1.0f / m_TexCoordScale;

// generación de los vértices (posición y coordenadas de


textura)
int vtxCount = (m_Rows + 1) * (m_Columns + 1);
M.T.I. Orleny Espiridion Marino

vertex = new Vector3[vtxCount];


texcoor = new Vector2[vtxCount];
int vtx = 0;
float x = 0f; // posición X
float y = 0f; // posición Y
float z = 0f; // posición Z
for (int i=0;i<=m_Rows;i++)
{
x = 0f;
for (int j=0;j<=m_Columns;j++)
{
// posición
y = m_Height * Mathf.PerlinNoise(
(x * m_NoiseScale) + rdm,
(z * m_NoiseScale) + rdm);
vertex[vtx] = new Vector3(x, y, z);

// coordenada de textura
texcoor[vtx] = new Vector2(x * uvscale, z * uvscale);

// siguiente vértice
vtx++;
x = x + m_CellSize;
}
z = z + m_CellSize;
}

// generación de los indices de los triángulos


int indexCount = m_Rows * m_Columns * 2 * 3;
index = new int[indexCount];
int idx = 0;
for (int i=0;i<m_Rows;i++)
{
int offset = i * (m_Columns + 1);
for (int j=0;j<m_Columns;j++)
{
// obtenemos los indices de los 4 vértices que forman la
celda
int a = offset + j;
int b = a + m_Columns + 1;
int c = b + 1;
int d = a + 1;

// primer triángulo
index[idx ] = a;
index[idx + 1] = b;
index[idx + 2] = d;
M.T.I. Orleny Espiridion Marino

// segundo triángulo
index[idx + 3] = d;
index[idx + 4] = b;
index[idx + 5] = c;

// siguiente celda
idx = idx + 6;
}
}

// asignamos información al modelo 3D


mesh.Clear();
mesh.vertices = vertex;
mesh.uv = texcoor;
mesh.triangles = index;
mesh.RecalculateNormals();
}

void Start()
{
mesh = new Mesh();
MeshFilter mf = transform.GetComponent<MeshFilter>();
mf.mesh = mesh;
RegenerateMesh();
}

}
IMPORTANTE: Para poder ver el terreno generado deberemos de pulsar el botón de Play.

Si hemos seguido los pasos correctamente, podremos observar que el terreno ha sido generado con éxito.
M.T.I. Orleny Espiridion Marino

Todos los modelos 3D de Unity están formados por triángulos. Y cada triangulo viene definido por sus 3
vértices. Para cada vértice necesitamos básicamente su posición y sus coordenadas de textura
(mapeado UV).

Algunos triángulos comparten vértices con otros triángulos del modelo 3D. Es por ello que para indicar
cada triangulo se debe de indicar el índice de cada uno de sus 3 vértices.

En el caso del terreno que estamos generando, la malla del modelo 3D es básicamente una rejilla, tal y
como se puede apreciar en la siguiente imagen:

Vista superior de una rejilla de un terreno creado con generación procedural en Unity

Lo único que diferencia esa rejilla de un terreno es que en la rejilla todos los vértices tienen la misma
altura, y en un terreno la altura de los vértices va variando.

El método RegenerateMesh
Lo primero que hace este método de este script es inicializar un valor aleatorio (semilla/seed) que se usará
para que el azar nos permita generar diferentes terrenos. Hay que tener en cuenta que tal y como está
hecho, cada vez que le demos al Play no va a generar un terreno diferente. Para ello, antes debemos de
modificar el valor de Seed en el Inspector de Unity. La idea es que un mismo valor de Seed generará
siempre el mismo terreno, pero cambiando ese valor el terreno cambiará considerablemente por uno
totalmente diferente. Esto es lo que diferencia un modelo generado paramétricamente de uno
proceduralmente.

Después, tenemos un doble bucle for que generará filas de vértices; para cada vértice calculará la posición
y las coordenadas de textura. La posición es sencilla, ya que simplemente conforme vamos añadiendo
vértices nos vamos desplazando hacia la derecha, y cada vez que cambiamos de fila nos desplazamos hacia
adelante (hacia arriba si estamos viendo la rejilla desde una vista superior).
M.T.I. Orleny Espiridion Marino

Lo más difícil de entender de este código probablemente sea el cálculo de la «altura» (posición Y) de cada
vértice. Para ello se usa el método Mathf.PerlinNoise() de Unity. Este método nos ira dando diferentes
valores para la altura pero no lo hará simplemente de forma aleatoria, sino que son valores que siguen un
patrón de «ondas» llamado ruido perlín que dan ese ese aspecto de terreno a nuestra rejilla de vértices.
Todos los vértices generados se van guardando en un array.

El Perlin Noise es un tipo de ruido usado frecuentemente en la generación procedural de modelos


orgánicos.

Se pueden hacer terrenos más convincentes generando una altura para el terreno global mediante un
ruido perlín de baja frecuencia en sus ondulaciones (lo que serian las montañas y valles del terreno),
mientras que para los detalles locales del terreno se podría usar otra altura adicional mediante otro ruido
perlín de mayor frecuencia en sus ondulaciones (lo que serían las rocas, agujeros, etc. del terreno). Para
ello, para calcular la altura bastaría con llamar varias veces al método PerlinNoise() con diferentes
parámetros, pero en nuestro caso vamos a mantener el ejemplo sencillo y solo lo vamos a usar una única
vez.
M.T.I. Orleny Espiridion Marino

A continuación, tenemos otro doble bucle for para generar los triángulos. Una vez generados los vértices,
para generar cada triangulo simplemente hemos de indicar la posición de sus 3 vértices en el array de
vértices. Sin embargo, hay que tener en cuenta que los vértices del triángulo deben de indicarse
siguiendo un orden clockwise. Es decir, hemos de indicar los vértices de forma que sigan el sentido de las
agujas del reloj. Además, hay que tener en cuenta que para cada celda de la rejilla necesitamos un par
de triángulos que comparten un par de vértices. Por lo que tenemos 4 vértices por celda a los que
podemos llamar a, b, c y d.

Tal y como se puede observar en la anterior ilustración, cada celda de nuestro terreno está formada por
2 triángulos que comparten 2 vértices. Los vértices a y d pertenecen a la misma fila de vértices generados
en el primer paso, mientras que los vértices b y c pertenecen a la siguiente fila de vértices. Una vez
averiguados los valores de los indices de dichos vértices, para indicar el triangulo azul debemos de indicar
los valores de a, b y d, y hacerlo además en dicho orden (sentido de las agujas del reloj). También
podríamos indicar los valores de b, d y a, o los valores de d, a y b; lo importante es respetar el sentido de
las manecillas del reloj. Lo mismo se aplica para el triangulo rojo, podemos elegir cualquiera de sus 3
vertices para empezar, pero el orden ha de ser el de las manecillas del reloj.

Una vez tenemos listos los arrays con las posiciones de los vértices, las coordenadas UV para el mapeado
de textura y los indices de los vértices que forman los triángulos, tan solo hay que indicárselos al objeto
Mesh que tenemos en el Mesh Filter. Como no hemos calculado las normales de cada vértice, con el
método RecalculateNormals() Unity las calculará por nosotros. Para ello simplemente calculará la normal
de cada triangulo al que pertenece cada vértice, las sumará, y el vector resultante lo normalizará. En el
caso del terreno que estamos generando, estas normales generadas por Unity son adecuadas, pero en
otro tipo de modelo 3D, tal vez sea mejor generar las normales «a mano». La normal de un vértice indica
hacia donde apunta dicho vértice y es muy importante a nivel de iluminación.

También podría gustarte