I. Introduction▲
Cet article est le premier d'une série consacré à la réalisation d'un raytracer en haskell. Il s'agit d'un tutoriel pas à pas suivant la construction d'une application, où chacune des fonctionnalités est d'abord commentée puis implémentée. Certains points relatifs au langage seront commentés pour plus de clarté, notamment les fonctions les plus complexes. Une archive contenant l'ensemble du code produit au long de l'article, prêt à compiler, vous est fournie ici.
Ce code compile avec GHC 7.6.3. Si vous avez des difficultés pour compiler le code avec une version antérieur, il est possible que votre version d'Haskell requière le module Control.Monad.Instances.
II. Génération d'une image▲
II-0. Avant-propos▲
Nous travaillerons dans un répertoire /src. Pour compiler, vous pourrez utiliser ghc avec la ligne de commande :
ghc -O -prof -rtsopts -fprof-auto --make Main
GHC compilera tous les fichiers .hs et cherchera l'entry-point dans le fichier Main.hs.
Les options -prof, -rtsopts et -fprof-auto servent au profiling, c'est-à-dire quand nous souhaiterons mesurer les performances de notre programme. L'option -O active les optimisations du compilateur, ce qui peut réduire le temps d'exécution de façon importante.
II-1. Production d'un fichier et initialisation de DevIl▲
On va d'abord mettre en place le nécessaire pour produire une image, puis l'on se concentrera sur le principe du raytracing, et son implémentation en haskell.
Commençons par le nécessaire en matière d'IO. Notre programme enregistrera l'image raytracée dans le fichier "image.png". Puisque ce fichier peut déjà exister, on va prendre la peine de le déplacer.
module
Main (
main,
) where
import
System.Directory
main =
do
ilInit
exists <-
doesFileExists "image.png"
if
exists
then
renameFile "image.png"
"image_back.png"
else
return ()
return () -- Ici viendra le code de l'enregistrement de l'image
La ligne ilInit permet d'initialiser la bibliothèque. La seconde ligne « décapsule » un booléen, nous informe de l'existence ou non du fichier. Par la suite, s'il existe, on le déplace. Dans le cas contraire, on retourne une valeur monadique qui sera ignorée.
II-2. Production d'une image▲
Comment enregistrer une image ? Cela se fait en deux temps. D'abord, produire un tableau de données, ensuite, l'écrire au format d'image voulue. On doit produire un tableau tridimensionnel, où les deux premières dimensions sont largeur et hauteur, puis la troisième les couleurs. Dans notre cas, il y a trois couleurs : Rouge, Vert et Bleu suivies de la Transparence (0 signifie transparent et 255 opaque).
On veut donc un tableau de type UArray (Int, Int, Int) Word8, où un Word8 correspond à la plage de valeurs 0-255, avec 0 signifiant l'absence de couleur et 255 la saturation maximale.
On rajoute donc les lignes suivantes dans les imports :
import
Codec.Image.DevIL
import
Data.Array.Base
Puis on construit notre tableau, accompagné de quelques constantes :
tableData ::
Int
->
Int
->
UArray (Int
,Int
,Int
) Word8
tableData width height =
listArray ((0
,0
,0
), (height-
1
, width-
1
, 3
)) $
repeat (111
::
Word8)
La troisième ligne construit un tableau tridimensionnel, avec des coordonnées comprises entre (0,0,0) (couleur rouge du pixel en bas à gauche) et (height-1, width-1, 2) (couleur bleue du pixel en haut à droite). La dernière instruction produit la liste infinie [111, 111, 111, …] dans laquelle les valeurs seront piochées.
Enregistrons maintenant cette image :
--Remplaçons la dernière ligne
return ()
--Par
writeImage "image.png"
(tableData 800
600
)
Nous avons alors une magnifique image grise, de 800 par 600.