Une introduction à...



Didier Verna - Décembre 95

(Voir aussi Une introduction à VRML 97)


  • Note: cette page a été écrite en Décembre 95 pour servir de support de cours aux élèves de l'ENST et du mastère Multimédia de l'école des Beaux Arts. Il n'est pas prévu d'y apporter de quelconques modifications, mise à part la maintenance des liens.
  • Le but de ce document est de fournir une premier contact interactif avec VRML 1.0. A ce titre, il ne constitue pas un manuel de référence du langage (qui serait d'ailleurs inutile puisque la spécification complète est publique), mais il ammène progressivement les notions importantes en même temps que les éléments du langage à connaître en priorité.
  • Vous pourrez faire vos premiers pas et tester directement le résultat des petits exercices grâce aux formulaires présents un peu partout. Vous devez disposer d'un logiciel de visualisation 3d installé sur votre système. Reportez-vous à la section Installation pour plus d'information.


Sommaire

Installation.
Obtenir un viewer 3d:
Si vous ne disposez pas déjà d'un logiciel de visualisation 3D, le mieux est sans doute de faire un tour au Web3D Repository, qui contient une liste de tels logiciels ainsi que des platformes sur lesquelles ils tournent.
Utiliser les formulaires:
Pour pouvoir utiliser les formulaires de ce document et visualiser directement le résultat de vos exercices, vous devez préciser à votre navigateur quel «helper» utiliser pour les données VRML 1.0 reçues. Pour éviter d'entrer en conflit avec un helper déja existant sur votre site, et dans la mesure où certains logiciels ne supportent pas simultanément VRML 1 et 2, j'ai choisi d'utiliser un type MIME particulier pour ce TP. Ce type est x-tp/x-vrml. Donc, utilisez le menu preferences->applications de votre navigateur pour ajouter votre helper à la liste.


Introduction.
VRML (Virtual Reality Modeling Langage)
est un langage de modélisation de scène 3d principalement destiné à ètre exploité sur le Web.
Pour modéliser une scène, on fabrique des objets, on les positionne dans la scène, on ajoute éventuellement des sources de lumière...
Pour modéliser des objets en 3d, on les décrit au moyen de formes de bases ou par des méthodes plus complexes, on leur applique des matériaux, des textures ...
Comme VRML est destiné au Web, il y a en plus des analogies avec HTML, comme la possibilité d'ajouter des ancres (href en HTML) : En cliquant sur un objet, on appelle alors un document qui peut ètre une page Web ...
La version 1.0 de VRML présentée ici premet de décrire des scènes 3d statiques. Dans le futur (VRML 2.0 alias Moving Worlds), la description de scènes avec mouvement sera possible.
Avant de rentrer dans le vif du sujet, encore 4 petites précisions:



Primitives géométriques.
VRML 1.0 reconnait 4 objets de base: cube, cylindre cône et sphere.
Ces objets donnent lieu à 4 noeuds du langage dont voici la syntaxe complète:


- Les champs 'width' 'height' 'depth' et 'radius' définissent les dimensions des objets de manière évidente. Ces champs doivent être suivit d'un nombre réel (l'unité de mesure est le mètre).
- Le champ 'parts' quand a lui, défini quelle(s) partie(s) de l'objet va (vont) être dessinée(s). Sa valeur est écrite sous la forme '( val1 | val2 | ... )'. Les parenthèses peuvent être ommises si une seule valeur est donnée. Par exemple, pour dessinner la base et le contour d'un cylindre, on écrira Cylinder { parts ( BOTTOM | SIDES ) }.
- Tous ces champs sont optionnels, c'est à dire que s'ils sont absents, des valeurs par défaut sont prises. Les valeurs par défaut sont les suivantes (vous remarquerez que cela produit des objets qui s'étendent de -1 à +1 sur les trois axes) :



A l'aide du champ ci-dessous :
Visualiser le cube, puis remplacez le par les 3 autres primitives et visualisez le résultat.
Faites ensuite la même chose en ajoutant des paramètres optionnels.
Pensez à bien terminer votre dernière ligne de code par quelques retours chariot !


Transformation et positionnement des objets.
Les objets précédents peuvent être manipulés
par les opérations géométriques usuelles de translation, rotation et homothétie au moyen des noeuds correspondants du langage :
- Les champs 'x' 'y' 'z' définissent les facteurs d'échelle ou le vecteur de translation sur chacun des axes. Ce sont des nombres réels.
- Le champ 'a' (nombre réel lui aussi) définit l'angle de la rotation. Il doit être exprimé en radians. - Tous ces champs sont optionnels, et les valeurs par défaut donnent l'Identité (Cela ne sert par conséquent à rien d'écrire Rotation {}) :
A partir du champ ci-dessus :
Tentez de déformer les objets de base en insérant d'abord un noeud de transformation, et ensuite un objet.
- Commencez par tester Scale {}. Vous remarquerez notament que les deux codes suivants sont équivalents :
Scale { scaleFactor x y z }
Cube { }
<==> Cube {width x height y depth z }
- Testez ensuite Rotation {}. - Finalement, testez Translation {}. Cela fait-il une différence ? En général, les viewer s'arrangent pour adapter le champ de vision par défault en centrant les objets et en adaptant la distance de vue en fonction de leur taille.


Scene graphs - Ordre des noeuds.
On souhaiterait maintenant pouvoir appliquer des transformations multiples à un même objet :
Pour cela, il faut d'abord bien comprendre le mécanisme des transformations, et en particulier l'importance de l'ordre des noeuds dans le code.
- Dans le champ ci-dessous, visualisez le contenu, puis tentez d'intervertir des lignes pour voir l'effet sur la scène. Que constatez-vous ?

- L'ordre des noeuds dans le fichier à une importance.
- Le Cone {} ne subit que les transformations spécifiés par les noeuds qui le précèdent.
- L'ordre des transformations influe sur la transformation globale de l'objet.
L'ordre des noeuds :
Après la petite manipulation précédente, on déduit que les noeuds de transformation influent sur tous les noeuds suivants dans le fichier. Plus généralement, tout noeud VRML qui ne soit pas un spécificateur d'objet a une portée sur toute la suite des évènements graphiques. Pour bien comprendre comment l'ordre des transformations influe sur le rendu, il faut savoir que ces transformations sont en fait des opérations matricielles sur les rères de définition des objets. Cela signifie que quand un noeud de transformation est ajouté, la matrice de transformation courante est multiplié à droite par celle du noeud en question. Du coup, à l'arrivée d'un objet dans le code VRML, il subit toutes les transformations décrites auparavant, et dans l'ordre inverse de leur apparition.
Si l'on veut avoir une idée de la manière dont un objet sera rendu, le meilleur moyen est sans doute de partir de l'objet, et de 'remonter' les opérations jusqu'au sommet.
Dans le champ précédent :
Inversez les noeuds Rotation {} et Scale {}.
Vous constaterez l'obtention d'un prisme. Voici ce qui se passe selon le raisonnement décrit précédemment :
- On fabrique un cube, mis à la position par défaut.
- On l'incline de 45 degrés autour de l'axe Z.
- On l'"étire" selon l'axe X, c'est à dire l'horizontale, d'un facteur 2. Cet étirement horizontal sur le cube incliné produit donc un étirement selon la diagonale du cube.
Vous constatez donc que l'on peut obtenir ainsi des transformations d'objets plus complexes que celles obtenues dans la section précédente.
Le noeud Separator {} :
Si l'on veut maintenant décrire plusieurs objets dans une même scène, il nous faut un mécanisme pour réduire la portée des transformations. Il est en effet inconcevable d'écrire une scène sachant que chaque transformation influe sur tout le reste du fichier.
Le mecanisme permettant de limiter la portée des noeuds est Separator {}. Chaque noeud présent dans un Separator {} n'aura d'influence qu'au sein de celui-ci (et toujours sur les noeuds qui le suivent, pas ceux qui le précèdent).
On peut alors considérer une scène VRML comme une arborescence dans laquelle les Separator {} définissent des nouvelles branches, qu'on appelle scene graphs. Il n'y a pas de limite au niveau d'imbrication des Separator {}.
Le champ suivant donne un exemple de définition de deux objets avec des transformations et des placements différents. La scène est un arbre à deux scene graphs. Essayez de refaire le raisonnement permettant de retrouver le résultat obtenu.
Le noeud Transform {} :
Pour terminer cette partie, introduisont le noeud Transform, qui permet de condenser les transformations décrites précédemment en une seule transformation complexe. La syntaxe est la suivante :

Transform {
translation x y z
rotation x y z a
scaleFactor x y z
scaleOrientation x y z a
center x y z
}

Les opérations effectuées sont les suivantes :
- Homothétie déterminée par scaleFactor, et selon un repère définie par scaleOrientation et centrée en center.
- Rotation autour du centre center.
- Translation de vecteur translation.
- Ici encore les valeurs par défaut donnent l'identité :

Transform {
translation 0 0 0
rotation 0 0 1 0
scaleFactor 1 1 1
scaleOrientation 0 0 1 0
center 0 0 0
}

Compte tenu de toutes les sections précédentes, vous devez maintenant être capables de comprendre que les deux codes suivants sont équivalents ... n'est-ce pas ?!
Transform {
translation T
rotation R
scaleFactor S
scaleOrientation R1
center T1
}
<==> Translation { translation T }
Translation { translation T1 }
Rotation { rotation R }
Rotation { rotation R1 }
Scale { scaleFactor S }
Rotation { rotation -R1 }
Translation { translation -T1 }


Apparence des objets.

Il ne suffit pas de pouvoir positionner des objets dans une scène pour la rendre réaliste. Il faut aussi pouvoir déterminer l'apparence des objets en termes de couleur, brillance, transparence ...
VRML propose plusieurs noeuds permettant de modifier l'apparence des objets. En voici quelques uns.
Définir un matériau.
Pour cela, on dispose du noeud suivant :

Material {
ambientColor r v b
diffuseColor r v b
specularColor r v b
emissiveColor r v b
shininess s
transparency t
}

- Les paramètres sont soit des composantes de couleurs en RVB comprises entre 0 et 1, soit des coefficients également compris entre 0 et 1.
- diffuseColor défini la couleur de l'objet, au sens le plus général du terme.
- specularColor défini la couleur des rayons lumineux réfléchis sur la surface de l'objet.
- emmissiveColor défini une couleur propre à l'objet, s'il était luminescent. C'est à dire qu'en l'abscence de lumière, l'objet emet cette couleur.
- ambientColor est la couleur la plus difficile à décrire. On pourrait dire "de quelle couleur l'objet est sombre", ce qui n'est pas tellement satisfaisant. Cette couleur influe en quelque sorte sur diffuseColor quand l'objet bouge et que des parties se retrouvent ombragées. Le mieux à faire est encore de tester différentes valeurs.
- Les champs shininess et transparency ne nécessitent sans doute pas plus de détail que leur propre nom n'en donne !
- Les valeurs par défaut sont :

Material {
ambientColor 0.2 0.2 0.2
diffuseColor 0.8 0.8 0.8
specularColor 0 0 0
emissiveColor 0 0 0
shininess 0.2
transparency 0
}

A l'aide du champ ci-dessous, amusez-vous à modifier le matériau de du cube. Mais attention, chaque viewer 3d a ses limites, et tous ne supportent pas nécessairement tous les paramètres. Le rendu peut donc être partiellement infidèle.
Définir une texture.
Une autre manière de modifier l'apparence d'un objet est de lui "coller" une image en surface. On parle de texture. A l'origine, l'idée était de pouvoir rendre l'aspect d'une texture au sens physique (rugosité ...) sans pour autant se perdre dans des calculs énormes de petites variations de relief. Le mot texture vient de là, mais il s'agit en fait de "plaquer" une image 2D sur une surface. VRML offre le noeud suivant :

Texture2 {
filename "..."
image ...
wrapS REPEAT ou CLAMP
wrapT REPEAT ou CLAMP
}

- filename permet de spécifier un fichier ou même un URL contenant une image (gif et maintenant d'autres).
- image permet d'inclure directement l'image dans le code. Le format est le suivant :
Pour 1 octet par pixel, on a l'intensité lumineuse.
Pour 2 octets par pixel, on a l'intensité, puis la transparence.
Pour 3 octets par pixel, on a les composantes RVB.
Pour 4 octets par pixel, on a les composantes RVB et la transparence.
Les pixels sont entrés de gauche à droite, et de bas en haut.
- Pour les champs WrapS et WrapT, reportez vous au point suivant.
Le champ suivant donne trois exemples d'objets texturés :
L'un avec le logo du LRDE, l'autre avec une image en ligne, et le troisième avec des briques. Notez en particulier que le logo du LRDE contient des parties transparentes. Visualisez, tentez entre autres, de changer et modifier les objets, pour bien voir comment et dans quel sens les textures sont appliquées.
Texture Mapping:
En modifiant la taille des objets ci-dessus, vous avez surement remarqué que les textures sont "étirées" dans toutes les directions, jusqu'à recouvrir toute la surface d'application. En quelque sorte, plus les objets sont gros, plus l'image de texture sera zoomée. Or il serait utile de pouvoir conserver la taille initiale de l'image puis de l'appliquer en la répliquant au lieu de l'étirer. En fait, c'est surtout ça l'effet "texture". Pour ce faire, on dispose d'un noeud dont l'utilisation est assez délicate : Texture2Transform, qui permet de modifier les textures avant de les appliquer aux surface. Voici la syntaxe du noeud, on expliquera ensuite.

Texture2Transform {
translation x y
rotation a
scaleFactor x y
center x y
}

L'emploi de ce noeud est particulièrement délicat car les transformations qu'il implique ne sont en fait pas relatives à l'image, mais au système de coordonnées de la texture. Voici ce qu'il faut savoir : Il existe trois étapes dans l'application d'une texture, chacune des étapes ayant son propre système de coordonnées.

Comment les transformations sont-elles produites ?
C'est là le point délicat. Les deux choses essentielles à comprendre sont les suivantes :
- Les transformations affectent le repère et non l'image.
- L'image de départ reste toujours comprise dans le carré [0,1][0,1].
Il faut donc penser le repère (U,V) comme une carte dans laquelle l'image est comprise entre 0 et 1, et ou elle se duplique comme une mosaïque dans tous les autres carrés du système.
Grace au noeud Texture2Transform, vous modifiez alors non pas l'image elle-même, mais la carte de texture. Si par exemple vous lui appliquez un scaleFactor 2 2, la carte sera 2 fois plus grande qu'au début, et vous obtiendrez alors 4 fois l'image de départ.

wrapS et wrapT :
Comme leurs noms l'indiquent, ils font référence à ce qui se passe sur les deux axes du repère (S,T).
- Pour REPEAT, l'image est effectivement reproduite comme une mosaïque si la carte a été modifiée.
- Pour CLAMP, les derniers pixels sont étirés jusqu'à la fin de la surface.
Exemples :
Le champ ci-dessous donne 3 exemples de textures appliquées à un cube. De plus, les textures sont répétées sur la verticale, mais clampées sur l'horizontale.
Raisonnement:
Le mieux pour raisonner est à mon avis de considérer les 4 sommet du carré d'origine ([0,1],[0,1]), et de regarder comment ils sont transformés. Cela donne directement la carte qui sera ensuite appliquée à la surface.
- Dans l'exemple 1, le sommet haut-droite (1,1) est transformé en (0.5, 0.25), ce qui délimite la zone de l'image appliquée.
- Dans l'exemple 2, l'homothétie est appliquée à partir du centre de l'image. Le point (1,1) devient donc (1.5, 1.5), et le point (0,0) devient (-0.5, -0.5). Cela délimite une zone 2 fois plus grande que l'image de départ, avec l'image de base au centre.
- Dans l'exemple 3, l'homothétie étant la carte jusqu'au point (2, 2), mais la translation transforme l'origine (0,0) en le point (0.25,0.25) et ainsi de suite. On obtient donc une carte comprise dans la diagonale (0.25, 0.25) - (2.25, 2.25)


Bien ! On a fait le plus gros. A partir de maintenant, il n'y aura plus de petits champs de test jusqu'à l'exemple final, mais quelques informations, noeuds et syntaxes utiles.


Lumière !

En général, les viewers s'occupent de mettre une lumière ambiante dans les scènes, et offrent souvent la possibilité de mettre une head light c'est à dire une lampe collée sur votre front ! Il peut parfois être utile de définir soi-même ses propres sources de lumière. Il en existe trois :
Source ponctuelle :
Ce noeud défini une source de lumière en un point donné, et qui rayonne de manière isotrope. Voici la syntaxe :

PointLight {
on TRUE ou FALSE
intensity i de 0 à 1
color r v b
location x y z
}

Projecteur :
Ce noeud produit une lumière de type projecteur, dans un cône. La syntaxe est la suivante :

SpotLight {
on TRUE ou FALSE
intensity i de 0 à 1
color r v b
location x y z
directionx y z
dropOffRate r
cutOffAngle c
}

- direction donne la direction, ou l'axe du cône.
- dropOffRate détermine le taux (exponentiel) avec lequel l'intensité lumineuse s'estompe avec l'angle.
- cutOffAngle détermine l'angle d'ouverture du cône.
Source directionnelle :
Ce noeud défini une source de lumière qui illumine son sous graphe selon des raies parrallèles. La syntaxe suit :

DirectionalLight {
on TRUE ou FALSE
intensity i de 0 à 1
color r v b
direction x y z
}



Complément : Quelques autres noeuds.

Pour finir, et avant de passer à un exemple complet, voici encore quelques noeuds qui peuvent s'avérer utiles.
Du texte en 2d.
Pour inclure du texte (plat) dans vos mondes, utilisez ce noeud :

AsciiText {
string "votre texte"
spacing s
justification LEFT ou RIGHT ou CENTER
width w
}

- Le champ justification sert à positionner le texte par rapport à l'origine.
- Le champ width borne la largeur du texte produit.
- Le champ spacing détermine l'écartement des lettres.
Des liens en 3d.
De la même manière que HTML permet de cliquer sur un mot ou une image pour atteindre un URL, VRML permet de cliquer sur des objets pour obtenir le même résultat. Le noeud en question est le suivant :

WWWAnchor {
name "votre URL"
description "si votre viewer est gentil, il vous affichera ça"
map NONE ou POINTS ...
}

- Laissons tomber le champ map pour cette fois.
- WWWAnchor fonctionne comme un Separator, avec en plus le fait que si vous sélectionnez un des objets contenus dedans (le mode de sélection peut varier d'un viewer à l'autre), l'URL spécifié sera appelé.
Réutilisabilité des noeuds.
Il peut être très utile de pouvoir réutiliser des noeuds, afin par exemple de ne pas dupliquer de grosses portions de code. VRML a un mécanisme d'instanciation qui permet de donner un nom à une branche du graphe scénique, et de l'appeler ensuite à nouveau par son nom.
- Pour nommer un noeud écrire : DEF LeNom LeNoeud { champ fils }
Par exemple, DEF boite Cube {widht 2 }.
- Pour l'utiliser, écrire : USE LeNom
Par exemple, USE boite.
- Attention, l'utilisation de DEF utilise une fois le noeud considéré. Il faut donc les définir la première fois où que l'on veut s'en servir.


L'exemple !

Un bon bout de code vaut mieux qu'un long discours ... Voici donc en cadeau une bille de clown, avec à peu près tout ce qu'on a pu voir dans ce tutoriel. Triturez, déformez, cliquez-lui sur le nez!



Page maintenue par Didier Verna, didier@lrde.epita.fr.