:::TITOLO: TRUE BELIEVER MOBILE
by Luca Ziliani, Davide Puopolo 773198
:::INTRODUZIONE:
l'applicazione da noi sviluppata come progetto d'esame per il corso di Geometria Computazionale è
un videogioco per dispositivi Smartphone e Tablet con sistema operativo Android.
Il motore di rendering è stato sviluppato ex novo appoggiandosi alle librerie OpenGLES 2.0
Caratteristiche principali:
-Motore di Rendering:
.SceneManager
.WidgetManager
.Rendering oggetti 2D con Texture
.Traslazioni oggetti 2D
.EntityManager
.Rendering entità3D con Texture
.Rendering Mesh 3D con texsture e struttura scheletrica
.AnimationManager
.gestione delle animazioni scheletriche per keyframe
.Color Picking:
.CameraManager:
.TextureManager:
.ShaderManager:
-Exporter\Importer per Blender:
.Esportazione da Blender di Mesh, Texture, Scheletro, animazioni nel formato proprietario
TBM
.Importazione di Mesh, Texture, Scheletro, animazioni in formato TBM
-MazeMagaer:
.Generatore pseudocasuale di labirinti
.importazione labiritni da file esportati dall editor
.Gestione dell albero dei cammini per navigare nel labirito tramite Dijkstra
-AImanager:
.implementazione di Cerberus: AI engine basato sul Q-Learning
-Editor di Livelli:
.Generazione da parte degli utenti di livelli
.Esportazione nel formato TBM
-Multiplayer:
.gestione Match tra due giocatori
.gestione profilo giocatori
-Motore Audio:
.Riproduzione di effetti Sonori
.Riproduzione di Musiche da file in streaming
:::RICHIAMI DI TEORIA
1 Vertici
Ogni punto dello spazio è definito da una terna di punti (px, py, pz) e viene considerato come
destinazione di un vettore Vp centrato nell'origine degli assi del suo sistema di riferimento.
Un poligono è semplicemente una lista ordinata di punti, e quindi di vettori, essendo ogni punto un
vertice del poligono. Tutte le trasformazioni per il movimento dei poligoni vengono calcolate sui
vettori dei suoi vertici.
1.1 Coordinate Omogenee
In computer grafica vengono utilizzate le coordinate omogenee che permettono di rappresentare
tutte le trasformazioni affini tramite operazioni matriciali.
Esse sono utilizzate dentro al sistema grafico invece delle coordinate cartesiane.
Un punto e' descritto da una terna di coordinate cartesiane P = (X,Y,Z).
Lo stesso punto e' descritto da infinite quadruple di coordinate omogenee P = (x,y,z,w), tutte quelle
per cui:
w != 0
x/w = X
y/w = Y
z/w = Z
Rappresentazione canonica: w=1, dunque P = (X,Y,Z,1).
Le quadruple di coordinate omogenee con w=0, cioè della forma (x,y,z,0) identificano punti
all'infinito nella direzione del vettore (x,y,z).
Le coordinate omogenee consentono di rappresentare punti all'infinito (con w=0) come per esempio
sorgenti luminose.
Inoltre permettono di esprimere tutte le trasformazioni di coordinate in forma matriciale.
Dato un punto P (vettore di 4 elementi) e una matrice di trasformazione M (matrice 4x4), il
risultato di applicare la trasformazione a P e' dato da P' = M P (moltiplicazione matrice vettore).
2 Trasformazioni di Vertici
Le trasformazioni dei vertici, quali rotazioni, traslazioni, scala e le differenti proiezioni (prospettiva
e ortografica), possono essere rappresentate applicando un'appropriata matrice 4x4 alle coordinate
del vertice.
2.1 Matrice di traslazione
dove x,y,z è il valore della traslazione lungo il rispettivo asse.
2.2 Matrice di Scala
dove x,y,z sono i fattori di scala lungo il rispettivo asse.
2.3 Matrice di Rotazione
Attorno all'asse X
Attorno all'asse Y
Attorno all'asse Z
dove a indica i gradi di rotazione.
Il sistema grafico OpenGLES 2.0 fornisce istruzioni per definire:
Matrici di traslazioni dato il vettore di traslazione (_tx,_ty,_tz)
Matrix.translateM(_TranslateMatrix, 0, _tx, _ty, _tz);
Matrici di scala con punto fermo l'origine dati i fattori di scala (sca_X, sca_Y, sca_Z)
Matrix.scaleM(_ScaleMatrix, 0, sca_X, sca_Y, sca_Z);
Matrici di rotazione attorno ad una retta passante per l'origine dato l'angolo di rotazione _angle ed
un vettore (_rx,_ry,_rz) parallelo all'asse di rotazione
Matrix.rotateM(mRotateMatrix, 0, _angle, _rx, _ry, _rz);
L'ordine da sinistra a destra in cui scrivo le matrici nella moltiplicazione e' opposto all'ordine in cui
applico le trasformazioni.
Se P e' un punto, ed M1, M2 sono due matrici di trasformazione, la moltiplicazione M1 M2 P
applica a P prima M2 e poi M1.
3 Gestione della scena
3.1 SceneManager
L'oggetto SceneManager si occupa di mantenere una struttura dati che contiene i nodi della scena.
Ogni nodo della scena contiene le matrici
public float[] mTranslateMatrix;
public float[] mRotateMatrix;
public float[] mScaleMatrix;
Inoltre contiene una lista di nodi della scena figli e un oggetto a cui verranno applicate le matrici di
trasformazioni del nodo.
Le trasformazioni di un nodo sono ottenute ereditando le trasformazioni dei nodi padri in aggiunta
alle proprie trasformazioni.
L'oggetto del nodo della scena può essere di tipo Entità o Camera.
3.1.1 Oggetto Entità
L'oggetto entità contiene le informazioni relative alla geometria, ovvero una lista di vertici che ne
determinano la forma geometrica.
Per ogni vertice, a seconda del tipo di Entità a cui si riferiscono, avremo al più informazioni
riguardanti: coordinate X,Y,Z, Vettore della Normale, Coordinate Texture UV, quattro riferimenti e
quattro pesi delle Matrici di trasformazioni dello scheletro.
3.1.2 Oggetto Camera
L'oggetto camera contiene due Matrici di proiezione.
3.1.2.1 Matrice di proiezione ortografica:
caso particolare di proiezione parallela dove il fascio di rette e' perpendicolare al piano di
proiezione. Il volume di vista è dato da un parallelepipedo. Tale parallelepipedo e' descritto dalle
sue coordinate xyz minime e massime.
Matrix.orthoM(orthoMatrix,0, x_min, x_max,y_min,y_max, z_min, z_max);
Questa matrice viene passata allo Shader delle entità che compongono la parte bidimensionale della
scena, come ad esempio i menu di gioco e i widgets.
3.1.2.2 Matrice di proiezione prospettica:
Proietta con un fascio di rette centrate in un punto, non le mantiene le proporzioni (amplifica gli
oggetti vicini, riduce quelli lontani). Il volume di vista è dato da un tronco di piramide.
Tale volume di vista è definito tramite :
fovy (Field Of View) = angolo di apertura della piramide in gradi rispetto all'asse Y.
aspect = deformazione larghezza/altezza della base del tronco di piramide (in generale la si fa
coincidere con quella della viewport).
znear, zfar = distanza delle due basi del tronco di piramide dallo occhio (valori positivi) .
setPerspective(perpMatrix, fovy, aspect, zNear, zFar);
Questa matrice viene passata allo Shader delle entità che compongono la parte tridimensionale della
scena come le entità di tipo Mesh3D
4 Ciclo di Rendering in OpenGL ES 2.0
4.1 OpenGL ES 2.0
OpenGL ES 2.0 eliminano la maggior parte della pipeline di rendering a funzioni fisse in favore di
una programmabile: quasi tutte le funzioni di rendering della pipeline di transform & lighting, come
il settaggio dei parametri di luci e materiali, vengono sostituite da vertex shaders e fragment shaders
programmati separatamente.
La CPU invia le istruzioni e le coordinate 3D della scena alla GPU.
Nel vertex shader programmabile viene trasformata la geometria e vengono applicati alcuni effetti
di illuminazione.
La geometria viene riprodotta in triangoli, che vengono ulteriormente trasformati in fragment.
Un fragment può essere pensato come l'insieme delle informazioni necessarie per colorare un pixel
e per determinare se il fragment diventerà un pixel e quindi disegnato nel frame buffer.
Per ogni fragment vengono applicati ulteriori effetti tramite il pixel shader.
Viene effettuato il test di visibilità (z-test): se un pixel è visibile, viene scritto nel framebuffer per
l'output su schermo.
4.1.1 Vertex Shader
Il vertex shader è una sequenza di istruzioni, usate nella computer grafica, che permette di
modificare gli attributi dei vertici della geometria assegnando il calcolo alla GPU presente sulla
scheda video. I vertex shader possono modificare le coordinate dei vertici, i parametri di
illuminazione (tra cui il colore), la parte di texture visualizzata, eccetera.
Il vertex Shader viene eseguito per ogni vertice all'interno del frustum di visione.
4.1.2 Fragment Shader
Il fragment shader o pixel shader è una sequenza di istruzioni, un breve programma, che viene
eseguito da una GPU per ciascun pixel da elaborare.
L'uso dei pixel shader consente di applicare effetti come bump mapping, ombre, esplosioni, effetti
di diffrazione, rifrazione e la simulazione dell'effetto fresnel, permettendo una migliore simulazione
degli effetti dell'illuminazione e un aspetto più realistico di superfici dalle proprietà ottiche
particolari.
4.2 Implemetazione
Ad ogni ciclo del Mainloop dell'applicazione viene eseguita la funzione di disegno per le
componenti 2D e 3D della scena tramite la chiamata di:
mSceneManager.draw();
mGuiManager.draw();
La classe SceneManager e GUIManager contengono rispettivamente gli alberi degli oggetti 3D e
2D che compongono la scena.
Partendo dalla radice dell'albero, viene richiamata per ogni nodo la funzione drawEntity.
Questa funzione prende in input le matrici di trasformazione World, View e Proj.
La ViewMatrix rappresenta la le rototraslazioni necessarie per portarsi in coordinate camera, la
ProjMatrix contiene le trasformazioni sul tipo di proiezione da applicare.
SceneManager passa sempre una matrice di proiezione perspettica e GUIManager passa sempre una
matrice di proiezione ortogonale.
La WorldMatrix contiene le trasformazioni necessarie per posizionare l'oggeto in coordinate mondo.
La WorldMatrix viene settata inizialmente alla matrice identità.
Ogni SceneNode, dopo aver chiamato la procedura per disegnare la propria entità, passa ai nodi
figli la sua WorldMatrix che è formata dalla WorldMatrix del padre moltiplicata per le sue
trasformazioni locali. Questo procedimento permette di ereditare le trasformazioni dei nodi padri in
modo gerarchico.
La procedura di disegno consiste nel caricare nella GPU le informazioni riguardanti la geometria, le
texture, gli shader e le matrici da utilizzare.
Successivamente viene chiamata la funzione Opengl drawArrays che comporta l'inizio della fase di
rendering.
5 Animazioni Scheletriche
5.1 Introduzione
L'animazione tramite scheletro è una tecnica per la Computer Animation.
Il modello 3D animato con questa tecnica dev'essere composto essenzialmente da due parti: una
Mesh Poligonale e uno Scheletro.
Una Mesh Poligonale è una collezione di vertici, spigoli e facce che definiscono la forma di un
oggetto poliedrico .
Nell'ambito delle animazioni in Real-Time la Mesh Poligonale viene prodotta da un tool di
modellazione quali Maya o Blender e poi importata nel formato del motore di Rendering utilizzato.
Essa è la parte del modello 3D che viene effettivamente disegnata (Renderizzata ) dall'applicazione.
Lo Scheletro è un insieme gerarchico di ossa, utilizzato per animare la Mesh Poligonale a cui è
associato.
Un osso dello scheletro è composto da una trasformazione tridimensionale (che include la sua
posizione, scala e orientamento ) .
L'osso è associato ad una porzione di mesh (insieme di vertici ), questo procedimento di
associazione viene chiamato Skinning.
La trasformazione che effettua un osso figlio sulla sua porzione di mesh, è data dall'applicazione
della trasformazione del padre, più la sua trasformazione. Una porzione di vertici può essere
associata contemporaneamente a più ossa, ogni ossa contiene l'informazione di quanta influenza
deve avere la sua trasformazione sulla porzione di vertici.
Mediante questa tecnica e ad esempio possibile muovere l'intera gamba del personaggio applicando
una rotazione all'osso femore. Questa trasformazione verrà propagata all'osso stinco e all'osso piede,
ognuna di queste ossa eseguirà la trasformazione della porzione di mesh associata, creando il
movimento della gamba, e i relativi effetti grafici di deformazione della mesh (contrazioni dei
muscoli, pieghe della pelle, ecc.).
Questa tecnica di animazione è quella che viene utilizzata per la produzione di animazioni
nell'industria cinematografica e dei videogiochi.
Tra le tecniche di animazione utilizzate per produrre animazioni tramite scheletro distinguiamo il
Key-Frame Animation, Motion Capture Animation e Animazioni Procedurali.
5.2 Animazioni con key-frame
La tecnica del Key-Frame è la più semplice utilizzabile per produrre animazioni.
Il termine inglese Key-Frame (traducibile come fotogramma chiave) è un tipo di fotogramma che
può rappresentare la posizione iniziale, finale o intermedia di un animazione, normalmente i
momenti più rilevanti di una sequenza.
Nell'animazione classica i fotogrammi chiave vengono disegnati dagli artisti principali, e la
transizione tra un fotogramma chiave ed un altro viene disegnata dai suoi assistenti. Per la computer
grafica le cose sono simili. L'animatore modifica l'orientamento delle ossa e lascia al software di
animazione il compito di eseguire l'interpolazione tra i due fotogrammi (posizioni) chiave.
5.3 Implementazione
5.3.1 Importazione Mesh
Per modellare un entità 3D abbiamo utilizzato il software di modellazione Blender.
Una volta completato il modello abbiamo realizzato un exporter che esportasse la Mesh nel nostro
formato TBM. Questo procedimento genera un file al cui interno sono memorizzate per ogni
vertice: le coordinate X-Y-Z , le coordinate della Normale, le coordinate UV della Texture, fino a
quattro identificativi di ossa che influenzano la posizione del vertice durante l'animazione, e i
relativi pesi.
Queste informazioni vengono importare nell'applicazione e successivamente utilizzate nella fase di
disegno.
5.3.2 Importazione Scheletro
Insieme alle informazioni relative alla mesh, durante la fase di esportazione, vengono esportare
anche le informazioni relative allo scheletro.
Dello scheletro vengono memorizzate la gerarchia delle ossa e per ogni osso la relativa matrice di
trasformazione per portarlo in posizione iniziale.
5.3.3 Importazione Animazione
Il File dell'animazione esportato da Blender contiene per ogni animazione una lista di KeyFrame
che la compongono, con le relative informazioni temporali.
Ogni KeyFrame contiene la Matrice di trasformazione locale di ogni osso.
Per ottenere l'animazione completa occorre moltiplicare le matrici di trasformazione di ogni osso in
modo ricorsivo partendo dall'osso Radice.
In questo modo ogni osso figlio eredita le trasformazioni dai parenti creando una matrice di
trasformazione globale. Questa matrice viene usata in fase di disegno dai vertici, per modificarne la
posizione e generare l'animazione finale completa.
5.3.4 Elaborazione dei dati delle animazioni
Come abbiamo detto nel punto 5.3.2 lo scheletro non è altro che un albero gerarchico di ossa con
relativa matrice di trasformazione di posa iniziale (detta “Pose Matrix”).
La Pose Matrix contiene generalmente una traslazione e una rotazione (e se previsto anche una
scala).
La traslazione viene applicata al relativo osso usando come sistema di riferimento il sistema di
riferimento dell'osso padre (l'osso radice avrà come sistema di riferimento iniziale le coordinate
mondo).
Al termine della traslazione il sistema di riferimento non cambia. Si applica poi una rotazione, in
questo modo si porta il sistema di riferimento in coordinate locali osso (dal sistema di riferimento
del padre si passa al sistema di riferimento dell'osso figlio).
In queste tre immagini viene mostrato un esempio banale di animazione
scheletrica. L'immagine di sinistra corrisponde all'istante t0, quella centrale
all'istante t1 e quella di destra all'istante t2.
Lo scheletro è composto da due ossa 1 e 2. L'osso radice è il numero 1 e l'osso
numero 2 è figlio dell'osso 1.
Nell'immagine di sinistra vediamo le due ossa in posizione iniziale (notare gli assi
dei due sistemi di riferimento in cima alle ossa), con sistema di riferimento uguale.
Nell'immagine centrale abbiamo applicato una rotazione di 90° lungo l'asse X
all'osso numero 2, e il suo sistema di riferimento è cambiato rispetto a quella dell'osso numero 1 (in
precedenza i tre assi coincidevano, adesso l'unico asse comune ad entrambi è l'asse X essendo l'asse
su cui è stata fatta la rotazione).
Successivamente nella figura di destra abbiamo applicato una rotazione di -90° all'osso 1 sempre
lungo l'asse X.
Come si può notare la rotazione applicata all'osso 1 è stata ereditata dall'osso 2, tenendo come
centro di rotazione il punto di origine dell'osso 1.
Questa rotazione ereditata dall'osso 1 la si può vedere come una traslazione e poi una rotazione
direttamente applicata all'osso 2 (la traslazione traslerebbe l'osso 2 dalla sua origine locale in su
lungo il proprio asse -z visibile nella figura centrale).
In ogni KeyFrame sono descritte delle trasformazioni per ogni osso. Queste sono le trasformazioni
locali delle ossa. Applicare una trasformazione locale ad un osso vuol dire applicarla a tutti i vertici
associati all'osso (a seconda del peso dell'osso sul vertice, se previsto), ma la posizione dei vertici è
descritta secondo il sistema di riferimento del modello 3D, non secondo il sistema di riferimento
dell'osso. Bisogna quindi portare i vertici associati all'osso nelle coordinate dell'osso stesso e poi
applicare le trasformazioni del KeyFrame.
Per fare questo occorre memorizzare per ogni osso una matrice di trasformazione che tenga traccia
delle trasformazioni gerarchiche delle ossa parenti (per l'osso radice questa matrice corrisponde alla
matrice identità) e invertirla così da ottenere una matrice inversa che porti l'osso in coordinate
locali.
BonePoseMatrix = ParentTransformMatrix * LocalTransformMatrix;
InverseTransformMatrix = (BonePoseMatrix) -1
Ottenuta questa matrice possiamo applicare le trasformazioni del KeyFrame e successivamente in
coordinate globali moltiplicando per BonePoseMatrix.
globalFrameTransformMatrix = BonePoseMatrix * localFrameTransformMatrix *
InverseTransformMatrix
Inoltre bisogna ereditare dalle ossa parenti le trasformazioni che hanno fatto nel frame e quindi si
moltiplicano alla globalFrameTransformMatrix le parentGlobalFrameTransformMatrix.
FrameTransformMatrix = (rootFrameTransformMatrix * . . . . *
parentGlobalFrameTranformMatrix) * globalFrameTranformMatrix
A questo punto abbiamo ottenuto frameTranformMatrix, che sarà applicata a tutti i vertici associati
all'osso e verrà ereditata da tutte le ossa figlie.
Questo procedimento dovrà essere fatto anche per le normali dei vertici. Quando un vertice viene
ruotato in un'animazione anche la normale associata ruoterà in base alle trasformazioni subite dal
vertice. Le normali ricavate da questa trasformazione verranno poi utilizzate nella fase shading in
cui vengono creati gli effetti di luce.
6 Level Editor
Per costruire i livelli di gioco abbiamo implementato un software stand-alone in grado di generare
livelli su file importabili da TrueBeliever Mobile tramite un proprio importer.
Il level editor è un tool che permette di generare labirinti e popolarli con unità nemiche.
Per ogni unità controllata dalla CPU è possibile scegliere la modalità di intelligenza artificiale:
Modalità Hold – l'unità stà ferma in attesa dell'ingaggio.
Modalità Patrol – l'unità si muove secondo un percoso scelto dal creatore del livello.
Modalità Cerberus -l'unità nemica è controllata dall'algoritmo di intelligenza artificiale Cerberus da
noi implementato per il corso di Sistemi Intelligenti.
Il tool permette di posizionare le basi per entrambi i player, trappole e power-up, oltre che
posizionare i muri che compongono il labirinto.
:::MANUALE UTENTE
1 Introduzione
True Believer Mobile è un gioco di strategia in tempo reale sviluppato per dispositivi Android.
In questa breve Demo Giocabile si avrà la possibilità di vedere il motore di rendering da noi
sviluppato in azione.
2 Regole
Lo scopo del gioco è quello di conquistare la base nemica portandoci sopra una propria unità prima
che il nemico conquisti la propria.
Le basi dei giocatori sono rappresentate dalla seguente icona.
Nella mappa di gioco sono presenti delle trappole rappresentate dalla seguente icona.
Le trappole uccidono istantaneamente qualsiasi unità gli passi sopra.
In questa Demo Giocabile l'utente ha a disposizione due unità:
Beast e Hunter.
L'unità Beast attacca in corpo a corpo.
Essa vince uno scontro corpo a corpo contro un Hunter e rimane in stallo se coinvolta in uno
scontro corpo a corpo con un altra Beast.
Per portare la Beast in combattimento è necessario muoverla vicino ad un unità nemica.
L'unità hunter attacca a distanza.
L'arma dell Hunter ha una gittata di 3 caselle. Quando un nemico entra a distanza di tiro con un
Hunter comparirà su di esso un mirino Giallo.
Toccando il mirino l'Hunter comincerà a sparare sul nemico.
2.1 Come schierare in campo un unità:
Per ogni livello il numero di unità che si possono schierare dalla panchina è variabile.
Il DeployButton [situato nella parte alta destra del display] indica il numero di unità che si possono
schierare.
Come viene mostrato nella figura sopra, le icone sotto il bottone di Deploy indicano il numero di
unità che si possono schierare. Quando una unità viene schierata il l'icona diventerà da grigia a
rossa. Quando un unità muore l'icona diventerà da rossa a nera.
Per schierare un unità è necessario premere sul bottone di Deploy [1].
A questo punto viene visualizzata la panchina, con le unità disponibili per superare il livello.
Scegliere l'unità desiderata premendo sulla sua icona[2].Compariranno delle celle evidenziate sulla
mappa, queste celle rappresentano le postazioni in cui è possibile schierare l'unità precedentemente
selezionata. Per schierare l'unità premere su una cella evidenziata[3].
Per poter schierare un ulteriore unità è necessario aspettare che la barra di Deploy si ricarichi, al
termine del caricamento lo barra comincerà a lampeggiare e sarà possibile schierare un'altra unità.
2.2 Come muovere un unità sul campo
Dopo aver schierato un unità è possibile muoverla all'interno del labirinto.
Come prima cosa è necessario selezionare l'unità.
Per selezionare un unità è necessario premere su di essa[1]. Una volta selezionata l'unità comparirà
un indicatore azzurro sulla sua cella corrente. Dopo aver selezionato un unità premere sulla cella del
labirinto in cui la si vuole far muovere[2].
2.3 Sparare con un unità tipo Hunter.
L'unità Hunter ha la possibilità di sparare contro un unità nemica a 3 celle di distanza.
Per effettuare un azione di sparo, selezionare l'unità hunter e muoverla a tre celle di distanza dal
nemico.
Una volta giunti a tre celle di distanza comparirà un icona triangolare gialla sul bersaglio.
Per sparare al nemico è necessario premere sul mirino[1].
Una volta premuto sul mirino esso diventerà rosso e l'unità huner comincerà a sparare fino ad
ucciderlo.
Scarica

l`applicazione da noi sviluppata come pr