267 lines
11 KiB
TeX
267 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 de 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 commence après la fin de la première
|
|
partie : il faut non seulement connaître l'influence des recommandations sur
|
|
l'utilisateur, mais aussi être capable de prévoir le comportement de
|
|
l'utilisateur, et enfin de s'en servir pour précharger les bonnes parties du
|
|
modèle. Tout ceci n'étant pas encore possible, le travail qui a été fait est
|
|
une version simplifiée : 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 matériaux (\texttt{usemtl}) : cela définit le matériau utilisé
|
|
pour les faces qui vont suivre. Un matériau est notamment défini par
|
|
ses constantes de réflexions optiques, ses textures, etc...
|
|
\item de sommets (\emph{vertices}) : des points 3D
|
|
\item de coordonnées de textures : des points en 2D qui référencent 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ériaux 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 le chemin permettant d'accéder aux fichiers) 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 simplifiée 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énient 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 souhaiterait.
|
|
|
|
\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és, 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}. On évite ainsi d'envoyer les faces du
|
|
modèle qui sont derrière la caméra et que l'utilisateur ne voit pas.
|
|
\begin{figure}[H]
|
|
\centering
|
|
\include{build/frustum}
|
|
\caption{Le frustum de la camera et différents objets\label{frustum-draw}}
|
|
\end{figure}
|
|
|
|
Dans cette version, on considère qu'une face apparaît dans le \emph{frustum} si
|
|
un de ses 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 (dans la figure
|
|
\ref{frustum-draw}, seuls les triangles verts et bleus seront envoyés, bien que
|
|
le triangle rouge devrait être envoyé lui aussi).
|
|
|
|
\paragraph{}
|
|
Bien sûr, s'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}
|
|
|
|
|