3d-interface-rapport/rapport/streaming.tex

257 lines
11 KiB
TeX

\part{Streaming de modèle 3D\label{streaming}}
Le but ultime de ce projet est de biaiser l'utilisateur avec les
recommandations de sorte à être capable de prévoir ses déplacements futurs, et
ainsi précharger les parties du modèle qui vont être vues. Cette section
présente le travail qui a été réalisé dans le domaine du chargement de modèle.
\paragraph{}
Évidemment, cette partie est celle qui comment après la fin de la première
partie : il faut nous seulement connaître l'influence des recommandations sur
l'utilisateur, ensuite être capable de prévoir le comportement de
l'utilisateur, et enfin s'en servir pour précharger les bonnes parties du
modèles. Tout ceci n'étant pas encore possible, le travail qui a été fait est
nettement plus simpliste : il n'y aura aucune prévision du comportement ici.
\section*{Introduction}
Notre problématique ici est de transférer des modèles 3D sur le réseau. Les
modèles sont stockés sur le serveur au format \texttt{.obj} et sont constitués
:
\begin{itemize}
\item des materiaux (\texttt{usemtl}) : cela définit le matériau utilisé
pour les faces qui vont suivre
\item de sommets (\emph{vertices}) : des points 3D
\item de coordonnées de textures : des points en 2D qui référence un point
d'une image
\item de normales : des vecteurs en 3D
\item de faces : une liste de 3 ou 4 sommets (représentés par leurs
indices), avec éventuellement leurs coordonnées de textures et/ou
normales
\end{itemize}
\paragraph{}
La seule contrainte d'ordre des éléments est qu'une face qui contient des
sommets, coordonnées de textures ou normales n'arrive pas avant ses sommets,
coordonnées de textures ou normales dans le fichier : si l'on parcourt les
lignes du fichier, on doit déjà avoir toutes les informations sur la face.
\paragraph{}
Généralement, le fichier \texttt{.obj} vient souvent avec un fichier
\texttt{.mtl} qui contient la définition des matériaux (leurs noms, leurs
textures, leurs constantes...).
\section{Architecture}
Puisque cette partie traite à la fois du client et du serveur, les fichiers
peuvent sembler éparpillés. Les fichiers concernés sont les fichiers du
répertoire \texttt{geo} pour le serveur et le fichier
\texttt{ProgressiveLoader.js} qui est le client.
\subsection{Serveur}
Nous avons rappelé que les modèles au format \texttt{.obj} contenaient
plusieurs \emph{sous-modèles} avec des matériau différents, le serveur connait
les classes suivantes :
\begin{itemize}
\item la classe \texttt{Mesh}, qui représente un \emph{sous-modèle}, avec
ses faces, ses coordonnées de textures, ses normales, son matériau
\item la classe \texttt{MeshContainer}, qui représente un modèle au format
\texttt{.obj}, en tant que liste de \texttt{Mesh}
\item la classe \texttt{MeshStreamer}, qui permettra d'envoyer un modèle au
client
\end{itemize}
\paragraph{}
Pour avoir une bonne performance, il faut éviter de re-parser les fichiers
\texttt{.obj} et de recréer les \texttt{MeshContainer} à chaque requête.
\paragraph{}
Dans le fichier \texttt{MeshContainer.js}, une liste de modèle à charger (ainsi
que leur chemin permet au serveur de créer les \texttt{MeshContainer} au
démarrage, et ainsi, le \texttt{MeshStreamer} n'a qu'à prendre une référence
vers ce \texttt{MeshContainer} (qui existe déjà, ce qui réduit la latence).
\subsection{Client}
Le client de la partie \emph{streaming} de ce projet est contenue dans la
classe \texttt{ProgressiveLoader}. Celle-ci crée des modèles 3D vides, puis
établit la connexion avec le serveur et remplit les modèles au fur et à mesure
que les informations arrivent du serveur. Dans les sections suivantes, nous
allons montrer les différentes stratégies de \emph{streaming} que nous avons
mises en place.
\section{Streaming linéaire}
La première étape de cette fonctionnalité a été de faire un système
client-serveur permettant le streaming de modèle 3D. En effet, les
\emph{loaders} de modèles présents dans la libraire \threejs ne permettent pas
le chargement progressif : ils se content d'envoyer une requête vers le fichier
contenant le modèle et à créer un modèle une fois que le fichier est chargé
complètement.
\paragraph{}
Pour commencer, nous avons donc utilisé \socketio, une librairie permettant de
gérer les sockets facilement avec JavaScript et Nodejs, pour faire une première
version simpliste du streaming : on travaillait sur un modèle ne contenant que
des sommets et des faces (donc pas de textures ni de normales) et le protocole
fonctionnait ainsi :
\begin{figure}[H]
\centering
\begin{tikzpicture}[]
\draw (0,0) node[above]{Client};
\draw (0,0) -- (0,-6);
\draw (5,0) node[above]{Serveur};
\draw (5,0) -- (5,-6);
\draw (2.5,-1.3) node[rotate=-12] {Path jusqu'au modèle};
\draw[->] (0,-1) -- (5, -2);
\draw (2.5,-2.3) node[rotate=12] {Des éléments};
\draw[<-] (0,-3) -- (5, -2);
\draw (2.5,-3.3) node[rotate=-12] {ACK};
\draw[->] (0,-3) -- (5, -4);
\draw (2.5,-4.3) node[rotate=12] {D'autres éléments};
\draw[<-] (0,-5) -- (5, -4);
\draw (2.5,-6) node{$\vdots$};
\end{tikzpicture}
\caption{Transmission élémentaire\label{transmission1}}
\end{figure}
\paragraph{}
Le serveur envoyait alors les éléments dans l'ordre dans lequel ils étaient
présents dans le fichier du modèle 3D. Le gros inconvéniant de cette méthode
est que souvent, les sommets sont présents au début du fichier, et les faces
vers la fin : nous recevons donc des informations de sommets au début qui ne
nous permettent rien d'afficher, puis toutes les faces d'un coup, ce qui fait
que le streaming n'est pas aussi progressif que l'on souhaiterais.
\paragraph{}
Dans le cas d'un modèle sans coordonnées de textures et sans normales, la seule
condition pour pouvoir afficher une face est d'avoir envoyé les sommets qui la
composent. On peut donc améliorer la fluidité en réarrangeant le fichier
\texttt{.obj} : il suffit de faire apparaître les faces dès que les sommets
sont disponibles.
\paragraph{}
Dans le cas où l'on souhaite gérer les textures et les normales, utiliser cette
technique est beaucoup plus compliqué puisqu'il faut aussi décider de l'ordre
relatif entre les sommets, coordonnées de textures et normales : en effet, pour
envoyer une face le plus tôt possible, il faut que toutes ces composantes
soient envoyés, et il est donc nécessaire de mélanger les sommets, coordonnées
de texture et normales.
\section{Streaming linéaire amélioré}
La remarque précédente conduit directement à cette méthode. Le principe reste
le même que celui précédent (figure \ref{transmission1}), mais les éléments
seront envoyés différemment : on va en fait parcourir les faces directement.
\paragraph{}
Le serveur va garder en mémoire ce qui a déjà été envoyé et ce qui ne l'a pas
été, et envoyer les faces dans l'ordre : si certains éléments des faces n'ont
pas encore été envoyé, on les enverra juste avant d'envoyer la face en
question.
\paragraph{}
On peut de cette façon à la fois gérer les coordonnées de texture et les
normales, tout en envoyant les faces le plus tôt possible (on peut aussi noter
que si certains sommets / coordonnées de textures / normales sont inutilisés
dans les faces, ils ne seront pas envoyés et on s'évite donc de transférer des
données inutiles).
\paragraph{}
C'est dans cette version que nous avons commencé à nous intéresser à la façon
de gérer les matériaux. Dans \threejs, un objet 3D est lié à un materiau, et
nous sommes donc obligés de créer autant d'objets que de materiaux. Pour cela,
nous avons légèrement modifié notre protocole :
\begin{figure}[H]
\centering
\begin{tikzpicture}[]
\draw (0,0) node[above]{Client};
\draw (0,0) -- (0,-8);
\draw (5,0) node[above]{Serveur};
\draw (5,0) -- (5,-8);
\draw (2.5,-1.3) node[rotate=-12] {Path jusqu'au modèle};
\draw[->] (0,-1) -- (5, -2);
\draw (2.5,-2.3) node[rotate=12] {Liste des matériaux};
\draw[<-] (0,-3) -- (5, -2);
\draw[dashed, ->] (-0.1,-3) -- (-0.1,-4);
\draw (2.5,-3.3) node[rotate=-12] {ACK};
\draw[->] (0,-3) -- (5, -4);
\draw (2.5,-4.3) node[rotate=12] {Des éléments};
\draw[<-] (0,-5) -- (5, -4);
\draw (2.5,-5.3) node[rotate=-12] {ACK};
\draw[->] (0,-5) -- (5, -6);
\draw (2.5,-6.3) node[rotate=12] {D'autres éléments};
\draw[<-] (0,-7) -- (5, -6);
\draw (2.5,-8) node{$\vdots$};
\end{tikzpicture}
\caption{Transmission avec gestion des matériaux}
\end{figure}
\paragraph{}
Les directives de matériau à utiliser (\texttt{usemtl}) seront ignorées, et les
faces seront envoyées avec l'indice de l'objet auquel elles appartiennent, ce
qui permettra au client de savoir dans quel objet (et donc avec quel matériau)
elles doivent être ajoutées.
\section{Streaming intelligent}
C'est la dernière version du streaming qui a été faite sur ce projet. À chaque
transfert, le client envoie sa position au serveur (ainsi que les plans
définissant son \emph{frustum}\footnote{les bords du champ de vision de la
caméra}) et le serveur va parcourir les faces du modèle en cherchant celles qui
apparaissent dans le \emph{frustum}\footnote{dans cette version, on considère
qu'une face apparaît dans le \emph{frsutum} si un de ces sommets y appartient.
Évidemment, une face très grande pourrait appraître dans le \emph{frustum}
sans qu'aucun de ses sommets n'y soit, mais nous n'avons pas traité ce cas
particulier ici.}. On évite ainsi d'envoyer les faces du modèle qui sont
derrière la caméra et que l'utilisateur ne voit pas.
\paragraph{}
Bien sûr, si il n'y a plus de faces dans le \emph{frustum} de la caméra, on va
envoyer les faces en suivant la méthode de la section précédente.
\begin{figure}[H]
\centering
\begin{tikzpicture}[]
\draw (0,0) node[above]{Client};
\draw (0,0) -- (0,-8);
\draw (5,0) node[above]{Serveur};
\draw (5,0) -- (5,-8);
\draw (2.5,-1.3) node[rotate=-12] {Path jusqu'au modèle};
\draw[->] (0,-1) -- (5, -2);
\draw (2.5,-2.3) node[rotate=12] {Liste des matériaux};
\draw[<-] (0,-3) -- (5, -2);
\draw[dashed, ->] (-0.1,-3) -- (-0.1,-4);
\draw (2.5,-3.3) node[rotate=-12] {Caméra / \emph{frustum}};
\draw[->] (0,-3) -- (5, -4);
\draw (2.5,-4.3) node[rotate=12] {Des éléments};
\draw[<-] (0,-5) -- (5, -4);
\draw (2.5,-5.3) node[rotate=-12] {Caméra / \emph{frustum}};
\draw[->] (0,-5) -- (5, -6);
\draw (2.5,-6.3) node[rotate=12] {D'autres éléments};
\draw[<-] (0,-7) -- (5, -6);
\draw (2.5,-8) node{$\vdots$};
\end{tikzpicture}
\caption{Version finale}
\end{figure}