Piero V.

LibGDX e formati personalizzati di modelli

LibGDX

Di tanto in tanto mi diletto con motori di rendering 3D e game engine. Questa volta è satato il turno di LibGDX.

LibGDX è un framework per lo sviluppo di videogiochi 2D e 3D scritto in Java.

Il suo scopo è quello di rendere disponibili delle API Crossplatform (Windows, Linux, Android, OS X, iOS etc…) per il rendering 2D e 3D (OpenGL ES), la matematica associata, l’audio (OpenAL, Ogg ed MP3) e la fisica (Box2D e Bullet). Inoltre, dove possibile, usa metodi nativi scritti in C per aumentare le prestazioni, ma essendo scritto principalmente in Java, permette l’uso qualsiasi classe dell’enorme libreria standard o qualunque libreria scritta nello stesso linguaggio.

Un altro punto di forza di questo progetto è la grande community che vi è attorno, constatabile anche nel numero di stelle su GitHub.

Forse è merito anche della licenza, molto permissiva: la Apache License 2.0.

Formati di modelli personalizzati

Uno dei miei obiettivi, che prima o poi potrebbe diventare realtà, è fare un gioco, magari anche piccolo, ma compatibile con le mappe dei vecchi Call of Duty 1 e 2, con cui ho giocato molto in LAN con i miei amici.

Questo però richiede o la conversione a priori del modello in un formato leggibile dal motore (tra i più comuni il Wavefront OBJ), oppure la stesura di un importer per il motore stesso, via che finora ho prediletto, in modo da non dover duplicare il materiale, che potrebbe essere potenzialmente illegale.

In LibGDX questo processo non è così semplice, ma il fatto che sia Open Source permette di studiare le parti della libreria stessa, capendone il funzionamento.

Partendo dal loader degli OBJ, ed escludendo tutta la parte generica del caricamento delle risorse mi sono fatto queste idee.

Se non specificato le classi appartengono al pacchetto com.badlogic.gdx.graphics.g3d.model.data.

I dati

Il modello che vogliamo caricare, solitamente un file binario o ASCII, contiene le varie informazioni sui vertici (posizione, normale, UV) e sulle facce (materiali, indici etc…).

Queste informazioni vengono comunicate a LibGDX mediante due classi: ModelMesh e ModelMeshPart.

La prima contiene tutte le informazioni sui vertici. Non ho trovato particolari informazioni sul numero di mesh da creare. L’unica avvertenza riguarda il numero di vertici: non deve superare il max short, ovvero 65535, tenendo conto che OpenGL lavora con indici senza segno.

Tutti i vertici devono avere uguale struttura, che viene specificata con un array di com.badlogic.gdx.graphics.VertexAttribute, per esempio in questo modo:

VertexAttribute[] attributes = {
	VertexAttribute.Position(), // 3: x, y, z
	VertexAttribute.Normal(), // 3: x, y, z
	VertexAttribute.ColorPacked(), // 1, esistono apposite funzioni per crearlo
	VertexAttribute.TexCoords(2) // 2: u, v
};

Inoltre devono essere contenuti tutti in un singolo array di float, di #vertici * #informazioni elementi.

Il numero di informazioni dipende dai VertexAttribute, nel mio caso 9, come specificato nei commenti del precedente listato di codice.

L’array è diviso per vertici: ci sono prima tutte le informazioni del primo vertice, nell’ordine stabilito da attributes, poi quelle del secondo e così via.

Le facce invece sono definite tramite la classe ModelMeshPart. Ogni istanza può contenere più di una faccia, purché abbiano tutte lo stesso materiale e può essere definita così:

ModelMeshPart part = new ModelMeshPart();
part.id = "lamiapartedimesh1"; // Deve essere univoca
part.indices = indices; // Un array di short con gli indici
part.primitiveType = GL20.GL_TRIANGLES;

Un’istanza di di ModelMesh invece può essere creata così:

ModelMesh mesh = new ModelMesh();
mesh.id = "lamiamesh";
mesh.attributes = attributes; // L'array di VertexAttribute
mesh.vertices = vertices; // L'array di float dei vertici
mesh.parts = meshparts; // Array di ModelMeshPart

Un altro tipo di dato è costituito dai materiali. Possono essere creati con la classe ModelMaterial.

Un esempio di un materiale con colore di diffusione rosso è questo:

ModelMaterial material = new ModelMaterial();
material.id = "miomateriale1";
material.diffuse = new Color(1.0, 0, 0, 1.0f);

Per vedere tutti i campi disponibili vi rimando alla sua documentazione. Non ho provato ad usare gli shader invece, so solo che sono in formato OpenGL.

La struttura

Nel modello esterno i dati sono in un ordine arbitrario, invece LibGDX lavora con strutture ad albero, in cui ogni nodo eredita dal genitore le trasformazioni (posizione, rotazione e scaling).

Un modo per inizializzare un nodo (istanza di ModelNode) è questo:

ModelNode node = new ModelNode();
node.id = "ilmionodo";
node.meshId = "lamiamesh"; // Deve coincidere con la stringa usata precedentemente
node.scale = new Vector3(1, 1, 1);
node.translation = new Vector3();
node.rotation = new Quaternion();

Per un modello statico volendo si può usare un unico nodo, anzi, meno nodi si usano migliori sono le prestazioni, perché ad un nodo corrisponde una chiamata di rendering.

Questo non è un limite, perché ogni nodo si può articolare in più ModelNodePart, che sono collegate direttamente con le ModelMeshPart e stabiliscono un legame tra dati e struttura.

Per ogni ModelMeshPart dobbiamo creare una ModelNodePart così:

ModelNodePart pm = new ModelNodePart();
pm.meshPartId = "lamiapartedimesh";
pm.materialId = "miomateriale";

Affinché siano riconosciute, le parti devono essere parte di un array, che poi deve essere assegnato a node.parts.

Creazione del modello effettivo

Struttura, mesh e materiali devono poi essere raggruppati in un’istanza di ModelData, con cui si può finalmente creare un com.badlogic.gdx.graphics.g3d.Model:

ModelData data = new ModelData();
data.nodes.add(node);
data.meshes.add(mesh);
data.materials.add(aterial);

Model model = new Model(data, null);

Anche gli altri loader ritornano un Model, quindi a questo punto basta creare una ModelInstance e passarla ad ogni ciclo di rendering al ModelBatch, come si farebbe per qualsiasi altro modello.

Volendo si potrebbero aggiungere anche i vari metodi per creare proprio un AssetLoader, ma magari su quello scriverò un altro articolo, se continuerò con LibGDX.

Bibliografia