Voici une série d'exemples en partant du plus simple, pouvant être utiles (ou pas) lors de la découverte d'OpenGL en OCaml.
Aussi si vous préférez apprendre l'OpenGL compatible descendant, vous pouvez utiliser cette autre documentation.
L'OpenGL est un incontournable en programmation multimédia. Cette bibliothèque permet de réaliser de la 3D temps réel.
Les exemples présentés sur cette page utilisent l'interface openGL glMLite (GH). Vous pouvez visualiser la documentation de cette interface qui contient des liens vers les différentes pages de manuel des différentes fonctions de l'API openGL.
Les scripts suivants peuvent être exécuté avec la ligne
de commande suivante :
ocaml -I +glMLite GL.cma Glut.cma demo.ml
Ou bien en ajoutant les instructions suivantes au tout début du fichier :
#! /usr/bin/env ocaml
#directory "+glMLite"
#load "GL.cma"
#load "Glut.cma"
Ou encore compilés pour des performances optimales :
ocamlopt -I +glMLite GL.cmxa Glut.cmxa demo.ml -o demo.exe
Voici une explication ligne par ligne, en commençant par le point d'entré du script.
Première chose, l'appel à glutInit avec en paramètre le tableau d'arguments du script permet d'initialiser la bibliothèque Glut.
Ensuite la fenêtre est crée avec glutCreateWindow , avec un titre qui apparaîtra dans son bandeau supérieur.
Ensuite la fonction de rappel (callback) d'affichage est enregistré (glutDisplayFunc ). À chaque fois que le rendu d'une frame sera requis, cette fonction sera appelée pour remplir cette office. (Nous allons décortiquer le contenu de la fonction display plus bas.)
Enfin glutMainLoop permet au programme d'entrer dans la boucle d'exécution principale (cette fonction de retourne jamais).
Voici maintenant une description de la fonction d'affichage.
La première fonction efface le contenu de l'image, sinon l'image est dessinée
par dessus la frame précédente. Ce qui ne change pas grand chose ici sur une
image fixe, mais vous pourrez voir la différence si vous enlevez cette instruction
dans le script d'après.
Ensuite une couleur RGB
est définie avec glColor3
par 3 flottants entre 0.0 et 1.0.
Ici la couleur définie est le rouge, toutes les primitives qui suivront seront
dessinées avec cette couleur
jusqu'au prochain appel à cette fonction.
Si aucune couleur n'est définie, la couleur par défaut est le blanc.
Ensuite la fonction glBegin annonce que l'on va définir un triangle. Entre cette fonction et glEnd qui indique la fin de la primitive, il devra donc y avoir trois points définis avec glVertex2 .
open GL open Glut let display () = glClear [GL_COLOR_BUFFER_BIT]; glColor3 ~r:1.0 ~g:0. ~b:0.; glBegin GL_TRIANGLES; glVertex2 (-1.0) (-1.0); glVertex2 ( 0.0) ( 1.0); glVertex2 ( 1.0) (-1.0); glEnd(); ;; let () = ignore(glutInit Sys.argv); glutInitDisplayMode [GLUT_SINGLE]; ignore(glutCreateWindow ~title:"simple demo"); glutDisplayFunc ~display; glutMainLoop (); ;;
Voici un autre script dérivé du précédent. Il a quelques modifications pour faire une animation rudimentaire.
Pour que l'animation soit fluide, le mode d'affichage sélectionné avec glutInitDisplayMode est un affichage en double buffer, ce qui signifie qu'il y a deux images pour le rendu, une image en arrière plan est utilisée pour dessiner les primitives demandées, et lorsque ce rendu est finit il est échangé avec l'image affichée à l'écran, à l'aide de la fonction glutSwapBuffers placée à la fin de la fonction d'affichage display.
Une fonction de rappel en Idle est aussi définie, il s'agit d'une fonction qui est appelée lorsque l'ordinateur n'a plus rien à faire. Ici cette fonction déclenchera un nouvel affichage avec glutPostRedisplay .
Une rotation est défine avec glRotate basée sur la fonction Sys.time.
Une autre différence est la définition d'une nouvelle couleur avant chaque point, ce qui provoquera une facette avec trois couleurs, une à chacun de ces somments, vous pouvez voir l'effet produit sur l'image après le script :
open GL open Glut let display() = glClear [GL_COLOR_BUFFER_BIT]; glRotate ~angle:(Sys.time() *. 0.2) ~x:0.0 ~y:0.0 ~z:1.0; glBegin GL_TRIANGLES; glColor3 ~r:1.0 ~g:0.0 ~b:0.0; glVertex2 (-1.0) (-1.0); glColor3 ~r:0.0 ~g:1.0 ~b:0.0; glVertex2 ( 0.0) ( 1.0); glColor3 ~r:0.0 ~g:0.0 ~b:1.0; glVertex2 ( 1.0) (-1.0); glEnd (); glutSwapBuffers (); ;; let () = ignore(glutInit Sys.argv); glutInitDisplayMode [GLUT_DOUBLE]; ignore(glutCreateWindow ~title:"simple demo"); glutDisplayFunc ~display; glutIdleFunc ~idle:(glutPostRedisplay); glutMainLoop (); ;;
Voici les fonctions de rappels permettant de récupérer les interactions avec le clavier et la souris :
open GL open Glut (* mouse coordinates *) let xold = ref 0 let yold = ref 0 let b_down = ref false let angley = ref 0 let anglex = ref 0 let display () = glClear [GL_COLOR_BUFFER_BIT]; glLoadIdentity (); glRotate ~angle:(float(- !angley)) ~x:1.0 ~y:0.0 ~z:0.0; glRotate ~angle:(float(- !anglex)) ~x:0.0 ~y:1.0 ~z:0.0; glColor3 ~r:0. ~g:1.0 ~b:0.; glutWireCube ~size:1.0; glFlush (); glutSwapBuffers (); ;; (* active mouse motion *) let motion ~x ~y = if !b_down then (* if the left button is down *) begin (* change the rotation angles according to the last position of the mouse and the new one *) anglex := !anglex + (!xold - x); angley := !angley + (!yold - y); glutPostRedisplay (); end; xold := x; (* save mouse position *) yold := y; ;; (* mouse button event *) let mouse ~button ~state ~x ~y = match button, state with (* if we press the left button *) | GLUT_LEFT_BUTTON, GLUT_DOWN -> b_down := true; xold := x; (* save mouse position *) yold := y; (* if we release the left button *) | GLUT_LEFT_BUTTON, GLUT_UP -> b_down := false; | _ -> () ;; let keyboard ~key ~x ~y = match key with | '\027' (* escape key *) | 'q' -> exit 0 | _ -> () ;; let () = ignore(glutInit Sys.argv); glutInitDisplayMode [GLUT_DOUBLE]; ignore(glutCreateWindow ~title:Sys.argv.(0)); glutDisplayFunc ~display; glutKeyboardFunc ~keyboard; glutMouseFunc ~mouse; glutMotionFunc ~motion; glutMainLoop (); ;;
(utilisez la souris pour faire tourner le cube)
La bibliothèque GLU fournit des fonctions courantes utiles,
mais n'apporte pas vraiement de fonctionnalité OpenGL en plus,
car en fait toutes les fonctions GLU sont implémentées avec des
fonctions OpenGL.
ocamldoc > Glu
#load "Glu.cma" open Glu
Il est très utile également de fournir une fonction de rappel pour
gérer le redimentionnement éventuel de la fenêtre, car si
les dimentions de la fenêtre changent il faut alors ajuster la matrice de
projection des objets à la nouvelle taille de la fenêtre.
let reshape ~width:w ~height:h = (* set viewport to be the entire window *) glViewport 0 0 w h; (* aspect ratio of the window *) let aspect = (float w) /. (float h) in (* load the projection matrix to be modified *) glMatrixMode GL_PROJECTION; (* reinitialise the projection matrix (to the identity matrix) *) glLoadIdentity (); (* set the projection matrix, in other words: define the perspective view *) gluPerspective ~fovy:60.0 ~aspect ~zNear:0.5 ~zFar:80.0; (* switch to modelview matrix in order to set scene *) glMatrixMode GL_MODELVIEW; ;;
L'argument fovy
est très simple, il s'agit de l'angle de vue,
f.o.v. pour "field of view" en anglais, et le "y" parceque c'est l'angle entre
la gauche et la droite c'est à dire suivant l'axe Y qui est l'axe vertical.
Des valeurs élevées pour fovy augmenteront l'impression de perspective,
mais des valeurs trop élevées ne seront plus réaliste,
par exemple un angle de vue à 180 degrés.
Des valeurs classiques courantes sont entre 45 et 60.
Les arguments zNear et zFar définissent l'intervale dans lequel les objets seront affichés le long de l'axe Z, c'est à dire la profondeur dans l'image ou l'axe du regard dans l'image en son centre.
Il pourrait alors être tentant lorsque l'on débute de mettre respectivement une valeur très petite et une très grande, mais ce serait une erreur car alors ça réduirait la précision de l'affichage des objets au niveau de leur différence d'éloignement et en particulier la précision de l'affichage des intersections entre les objets proches ainsi que ceux qui s'intercroisent.
Plus précisément c'est lorsque deux facettes se coupent mutuellement ou lorsque l'une affleure le centre de l'autre que la précision de l'affichage de l'intersection entre les deux sera imprécise.
Plus l'écart entre zNear et zFar est petit et plus la précision de l'affichage de la profondeur sera précise. C'est pourquoi il est important de définir zFar en fonction de l'éloignement maximal que peut avoir un objet dans la représentation.
La fonction de rappel reshape sera appelée une première fois avant l'affichage de la première frame.
glutReshapeFunc ~reshape;
Voici un exemple qui montre comment transformer les coordonnées de la souris qui sont récupérées en coordonnées 2D au niveau de l'espace de la fenêtre, pour les transformer en coordonnées 3D utilisable dans la représentation 3D :
open GL open Glu open Glut (* position of the mouse pointer *) let px = ref 0.0 let py = ref 0.0 (* try to force refresh every "min_refresh" milliseconds *) let min_refresh = 30 (* coordinate bounds of the window *) let x_min = -6.0 and x_max = 6.0 and y_min = -6.0 and y_max = 6.0 and z_min, z_max = -6.0, 60.0 ;; (* left button down *) let ldown = ref false let display() = glClear [GL_COLOR_BUFFER_BIT]; glLoadIdentity (); glPointSize 5.0; glBegin GL_POINTS; if !ldown then glColor3 1.0 0.0 0.0 else glColor3 1.0 1.0 0.0; (* just display the mouse pointer *) glVertex2 !px !py; glEnd (); glFlush (); glutSwapBuffers (); ;; (* convert the coordinates of the mouse from window coordinates to the local representation *) let reg_unproject_coords ~x ~y = let mx, my, _ = gluUnProjectUtil ~x ~y in px := mx; py := my; ;; (* active mouse motion *) let motion ~x ~y = reg_unproject_coords ~x ~y; ;; (* passive mouse motion *) let passive ~x ~y = reg_unproject_coords ~x ~y; ;; (* mouse button event *) let mouse ~button ~state ~x ~y = reg_unproject_coords ~x ~y; match button, state with | GLUT_LEFT_BUTTON, GLUT_DOWN -> ldown := true; | GLUT_LEFT_BUTTON, GLUT_UP -> ldown := false; | _ -> () ;; let keyboard ~key ~x ~y = match key with | 'q' | '\027' -> exit 0 | _ -> () ;; let reshape ~width:w ~height:h = glViewport 0 0 w h; glMatrixMode GL_PROJECTION; glLoadIdentity (); if w <= h then glOrtho x_min x_max (y_min *. float h /. float w) (y_max *. float h /. float w) z_min z_max else glOrtho (x_min *. float w /. float h) (x_max *. float w /. float h) y_min y_max z_min z_max; glMatrixMode GL_MODELVIEW; glLoadIdentity (); ;; let idle () = glutPostRedisplay (); ;; let gl_init () = glClearColor 0.5 0.5 0.5 0.0; glShadeModel GL_FLAT; ;; let () = ignore(glutInit Sys.argv); glutInitDisplayMode [GLUT_DOUBLE; GLUT_RGB]; glutInitWindowSize 600 600; glutInitWindowPosition 100 100; ignore(glutCreateWindow Sys.argv.(0)); glutSetCursor GLUT_CURSOR_NONE; gl_init (); glutDisplayFunc ~display; glutReshapeFunc ~reshape; glutIdleFunc ~idle; glutMouseFunc ~mouse; glutKeyboardFunc ~keyboard; glutMotionFunc ~motion; glutPassiveMotionFunc ~passive; glutMainLoop (); ;;
Voilà c'était très court pour une première approche mais l'OpenGL n'est pas si compliqué car c'est un langage purement descriptif. Dans l'archive de glMLite vous trouverez plein de petits exemples intéressants dans les répertoire RedBook-Samples. Ce sont les exemples tirés de l'ouvrage de référence le red-book qui ont été converti en OCaml.
Vous trouverez également des exemples dans le répertoire examples/.