Les blobs sont des sphères et des cylindres couverts d'une "gomme" qui s'étire pour les joindre doucement (voir la section "Le Blob").
Idéaux pour modeler des atomes et des molécules, les blobs sont aussi de puissants outils pour créer des formes 'organiques'.
Une définition légèrement plus mathématique du blob serait de dire que c'est un objet fait de deux composants ou plus. Chaque pièce est un champ de force invisible qui commence à une certaine puissance et décline doucement jusqu'à zéro à un rayon donné. Si ces éléments se regroupent dans l'espace, leurs champs de force s'additionnent (et nous pouvons aussi avoir des forces négatives qui se soustrairont). Nous pouvons n'avoir qu'un seul composant dans un blob, mais, à part pour voir à quoi il ressemble, c'est maigre, car la vraie beauté des blobs est la façon dont les composants interagissent entre eux.
Prenons un exemple simple pour commencer. Maintenant, en fait, il y a deux types différents de composant, mais nous les verrons plus tard. Dans le but d'un premier exemple simple, nous parlerons seulement des composants sphériques. Voici un code POV-Ray donnant une caméra basique, une source de lumière, et un simple blob à deux composants :
#include "colors.inc" background {White} camera { angle 15 location <0, 2,-10> look_at <0, 0, 0> } light_source {<10, 20,-10> color White} blob { threshold .65 sphere {<.5, 0, 0>, .8, 1 pigment {Blue}} sphere {<-.5, 0, 0>, .8, 1 pigment {Pink}} finish {phong 1} }
Le seuil (threshold) est simplement la valeur de la force à partir de laquelle le blob devient visible. Tous les points du blob où la force égale le seuil forment la surface de la forme. Ceux plus faibles que le seuil sont en dehors, les autres dedans.
Nous notons que le composant sphérique ressemble à une simple sphère. Nous avons le mot clé sphere, le vecteur représentant la position du centre et, enfin, le rayon. Mais qu'est ce que la dernière valeur ? C'est la force. Dans le composant sphérique, c'est la valeur de la force du champ au centre de la sphère. Il baissera selon une progression linéaire, jusqu'à la valeur zéro au rayon de la sphère.
Avant de rendre cette image test, nous notons que nous avons donné un pigment différent à chacun des composants. POV-Ray autorise les textures séparées aux composants des blobs. Nous avons fait ceci pour visualiser ce qui appartient à chaque composant. Nous pouvons également donner une texture unique à tout le blob, comme les caractéristiques de finition à la fin, qui s'appliquent à tous les composants tant qu'elles apparaissent à la fin, en dehors de tous. Nous rendons la scène et obtenons un blob type de sphères embrassées.

L'image que nous voyons montre les sphères de chaque côté, mais elles sont doucement liées par un pont au centre. Ce pont représente la zone où les deux champs se rejoignent, et persistent au delà du seuil. Si ceci n'est pas totalement clair, nous ajoutons les deux objets suivants et rendons de nouveau la scène. Nous notons que ceux-ci ont pour intention d'être des sphères séparées, et non plus des éléments du blob.
sphere {<.5, 0, 0>, .8 pigment {Yellow transmit .75} } sphere {<-.5, 0, 0>, .8 pigment {Green transmit .75} }

Maintenant le secret des sphères embrassées est dévoilé. Ces sphères semi transparentes concrétisent les composants du blob. Si nous n'avons pas travaillé avec des blobs auparavant, nous pouvons être surpris de voir que les sphères ajoutées s'étendent beaucoup plus loin que les éléments du blob. Cela, bien sûr, est parce que nos sphères de blob ont eu un départ avec une force de un, qui a graduellement décliné jusqu'à zéro en s'éloignant du centre. Quand la puissance descend sous le seuil (0.65 ici) le reste de la sphère devient l'extérieur et n'est plus visible.
Regardons la partie où les deux sphères transparentes se recoupent. Nous notons que cela correspond exactement au pont entre les deux éléments. C'est la région où les deux composants contribuent tous deux à la force globale du blob. Voilà pourquoi le pont apparaît : cette région a une force combinée suffisante pour dépasser le seuil.
La forme montrée précédemment est intéressante mais limitée. POV-Ray a quelques trucs supplémentaires qui étendent leur utilité. Par exemple, comme nous l'avons vu, nous pouvons assigner des textures différentes aux éléments, nous pouvons aussi appliquer des transformations individuelles (déplacement, rotation et taille) pour étendre, tordre et écraser les pièces du blob. Et, peut-être plus intéressant, le code du blob a été étendu pour autoriser les cylindres.
Avant de parler des cylindres, il doit être mentionné que l'ancienne syntaxe des composants fonctionne toujours. Avant, tous les composants étaient des sphères, aussi il était inutile de les désigner comme sphère ou cylindre. Un composant ancienne mode a la syntaxe :
component Strength, Radius, <Center>
Ceci a le même effet qu'un élément sphérique. C'est seulement utile pour la compatibilité ascendante. Notons que l'ancienne syntaxe n'utilisait pas les parenthèses autour de la force, le rayon et le centre, et que nous ne pouvions pas transformer indépendamment leurs textures, aussi, si vous devez modifier un ancien travail, il sera grandement bénéfique de le convertir à la nouvelle syntaxe avec les éléments sphériques.
Maintenant, quelque chose de nouveau et de différent : le composant cylindrique. Il peut être avancé que tout ce dont nous avions besoin pour faire la portion cylindrique d'un blob était une suite de composants sphériques le long d'une ligne droite. C'est exact, si nous aimons avoir des quantités de lignes à entrer, et en supposant que le cylindre est orienté le long d'un axe. Sinon, nous devons calculer les positions de chaque élément, pour conserver la ligne droite. Mais passons ! L'élément cylindrique est arrivé.
Nous remplaçons le blob de l'exemple précédent avec ce qui suit, et nous le rendons. Nous pouvons également nous débarrasser des sphères transparentes, par la même occasion.
blob { threshold .65 cylinder {<-.75,-.75, 0>, <.75, .75, 0>, .5, 1} pigment {Blue} finish {phong 1} }
Nous avons seulement un composant pour voir la forme de base de l'élément cylindrique. Ce n'est pas vraiment un cylindre, plus une forme de saucisse, un cylindre fermé par deux demi sphères. Nous pouvons le penser comme une collection d'éléments sphériques bien serrés le long d'une ligne droite.
Voyons la déclaration du composant : simple, logique, exactement comme nous pouvions l'espérer (en assumant que nous avons l'esprit assez visionnaire). Il ressemble à la déclaration d'un cylindre, avec les vecteurs de position des deux extrémités et la valeur du rayon. La dernière valeur est, bien sûr, la force du composant. Exactement comme pour les éléments sphériques, la force détermine la nature et le degré d'interaction avec les composants voisins. Maintenant, donnons-lui quelque voisin pour interagir avec lui.
Commençons un nouveau fichier POV-Ray et entrons cet exemple quelque peu complexe :
#include "colors.inc" background {White} camera { angle 20 location <0, 2,-10> look_at <0, 0, 0> } light_source {<10, 20,-10> color White} blob { threshold .65 sphere {<-.23,-.32, 0>, .43, 1 scale <1.95, 1.05, .8>} // paume sphere {<.12,-.41, 0>, .43, 1 scale <1.95, 1.075, .8>} // paume sphere {<-.23,-.63, 0>, .45, .75 scale <1.78, 1.3, 1>} // mi-main sphere {<.19,-.63, 0>, .45, .75 scale <1.78, 1.3, 1>} // mi-main sphere {<-.22,-.73, 0>, .45, .85 scale <1.4, 1.25, 1>} // éperon sphere {<.19,-.73, 0>, .45, .85 scale <1.4, 1.25, 1>} // éperon cylinder {<-.65,-.28, 0>, <-.65, .28,-.05>, .26, 1} // auriculaire bas cylinder {<-.65, .28,-.05>, <-.65, .68,-.2>, .26, 1} // auriculaire haut cylinder {<-.3,-.28, 0>, <-.3, .44,-.05>, .26, 1} // annulaire bas cylinder {<-.3, .44,-.05>, <-.3, .9,-.2>, .26, 1} // annulaire haut cylinder {<.05,-.28, 0>, <.05, .49,-.05>, .26, 1} // majeur bas cylinder {<.05, .49,-.05>, <.05, .95,-.2>, .26, 1} // majeur haut cylinder {<.4,-.4, 0>, <.4, .512,-.05>, .26, 1} // index bas cylinder {<.4, .512,-.05>, <.4, .85,-.2>, .26, 1} // index haut cylinder {<.41,-.95, 0>, <.85,-.68,-.05>, .25, 1} // pouce bas cylinder {<.85,-.68,-.05>, <1.2,-.4,-.2>, .25, 1} // pouce haut pigment {Flesh} }

Comme nous pouvions l'imaginer avec les commentaires, nous avons construit une main. Après avoir rendu l'image, vous constatez quelques problèmes. La paume et l'éperon de la main seraient plus réalistes si nous utilisions deux douzaines de composants plus petits au lieu de la demi douzaine actuelle, et chaque doigt devrait avoir trois segments au lieu de deux, mais pour les besoins d'une simple démonstration, nous pouvons outrepasser ces points. Mais il y a une chose que nous devons déplorer ici : ce pauvre gars semble avoir d'horribles enflures douloureuses aux jointures !
Une revue de ce que nous savons des blobs nous révélera rapidement ce qui ne va pas. Les jointures sont des endroits où les éléments des blobs se rencontrent, alors les forces combinées de ces éléments provoquent l'extension de leurs surfaces. Pour corriger cela, nous avons besoin de composants correspondant aux régions incriminées, avec une force négative pour contrecarrer la combinaison des forces. Nous ajoutons les éléments suivants à notre blob.
sphere {<-.65, .28,-.05>, .26,-1} //pare la jointure de l'auriculaire sphere {<-.65,-.28, 0>, .26,-1} //pare la paume de l'auriculaire sphere {<-.3, .44,-.05>, .26,-1} //pare la jointure de l'annulaire sphere {<-.3,-.28, 0>, .26,-1} //pare la paume de l'annulaire sphere {<.05, .49,-.05>, .26,-1} //pare la jointure du majeur sphere {<.05,-.28, 0>, .26,-1} //pare la paume du majeur sphere {<.4, .512,-.05>, .26,-1} //pare la jointure de l'index sphere {<.4,-.4, 0>, .26,-1} //pare la paume de l'index sphere {<.85,-.68,-.05>, .25,-1} //pare la jointure du pouce sphere {<.41,-.7, 0>, .25,-.89} //pare la paume du pouce

Mieux ! Les forces négatives des éléments sphériques contrecarrent approximativement les points où deux composants se rejoignent, ainsi les vilaines, irréalistes (et sans doute douloureuses) déformations ont disparu, améliorant notre main. Pendant que nous pouvions probablement faire une main plus réaliste avec deux douzaines de composants supplémentaires, nous avons effectué une formidable amélioration. Maintenant, nous avons assez de connaissance de base sur le fonctionnement des blobs pour fabriquer toute une variété de formes organiques !
Un height_field est un objet qui a une surface définie par la valeur de la couleur ou le numéro d'index de la palette d'une image prévue à cet effet. Avec les champs de niveaux, des montagnes réalistes et d'autres types de terrain peuvent être facilement réalisés. D'abord, nous avons besoin d'une image pour base au champ de niveaux. Justement, POV-Ray est idéal pour créer de telles images.
Nous faisons un nouveau fichier appelé image.pov et le modifions comme ceci :
#include "colors.inc" global_settings { assumed_gamma 2.2 hf_gray_16 }
Le mot clé hf_gray_16 crée une sortie dans une échelle de gris sur 16 bit parfaite pour les champs de niveaux. La sortie 8 bit ne donnera que des surfaces moins lissées.
Maintenant, nous plaçons une caméra pour qu'elle pointe dans le sens négatif des z.
camera { location <0, 0,-10> look_at 0 }
Nous créons, maintenant, un plan comme un mur à z=0. Il remplira complètement l'écran. Il sera coloré par des plis (wrinkles) blancs et gris.
plane {z, 10 pigment { wrinkles color_map { [0 0.3*White] [1 White] } } }
Enfin, créons la source de lumière.
light_source {<0, 20,-100> color White}
Nous rendons cette scène en 640x480 +A0.1 +FT. Nous obtenons une image qui produira un excellent champ de niveaux. Nous créons un nouveau fichier appelé hfdemo.pov et le modifions comme suit :
Note : Utilisateurs Windows, sauf si vous spécifiez +FT comme au-dessus, vous aurez un fichier .BMP (qui est la version de sortie par défaut de Windows). Dans ce cas, vous devrez utiliser sys au lieu de tga dans la déclaration height_field.
#include "colors.inc"
Nous ajoutons une caméra à deux unités au-dessus de l'origine et dix unités en arrière ...
camera { location <0, 2,-10> look_at 0 angle 30 }
... et une source de lumière.
light_source {<1000, 1000,-1000> White}
Maintenant, nous ajoutons le champ de niveaux. Dans la syntaxe suivante, une image Targa est demandée, le champ de niveaux est lissé, on lui donne un simple pigment blanc, il est déplacé pour se centrer sur l'origine et mis à l'échelle pour représenter des montagnes et remplir tout l'écran.
height_field { tga "image.tga" smooth pigment {White} translate <-.5,-.5,-.5> scale <17, 1.75, 17> }
Nous sauvegardons le fichier et le rendons en 320x240 -A. Plus tard, quand nous serons satisfaits, nous le rendrons à une meilleure résolution avec un anti-crénelage.

Wow ! L'Himalaya est dans votre écran !
| Vous savez que vous avez fait trop de raytracing quand ... |
| ... Vous vous trouvez à regretter de ne pas avoir porté attention en cours de mathématiques à toutes ces formules dont vous pensiez ne jamais avoir besoin dans la vraie vie. |
| -- Jeff Lee |
Les isosurfaces sont des formes décrites par des fonctions mathématiques.
A l'inverse des autres formes de POV-Ray basées sur les mathématiques, les isosurfaces sont évaluées pendant le rendu et, par conséquent, elles sont quelque fois plus difficiles à contrôler. Toutefois, elles offrent beaucoup de possibilités intéressantes, comme de véritables déformations et déplacements de surface.
Quelques connaissances sur les fonctions mathématiques et la géométrie sont utiles, mais pas nécessaires pour travailler avec les isosurfaces.
Pour débuter nous choisirons la plus simple des fonctions : x. La valeur de cette fonction est exactement la coordonnée x actuelle.
L'objet isosurface prend cette fonction comme une fonction définie par l'utilisateur :
isosurface { function {x} contained_by {box {-2, 2}} }

Le résultat est simple : une boîte.
Le fait que ce soit une boîte est seulement dû à l'objet conteneur qui est requis pour une isosurface. Vous pouvez utiliser une boîte ou une sphère pour cela.
Aussi, seulement une face de la boîte est faite par la fonction. Cette surface est l'endroit où la coordonnée x est à 0, puisque 0 est le seuil par défaut. Habituellement, il n'y a aucune raison de changer cela, puisque c'est la valeur la plus commune et la plus suggestive, mais vous pouvez spécifier quelque chose de différent en ajoutant
threshold 1
à la définition de l'isosurface.

Comme vous pouvez le voir, la surface est maintenant à la coordonnée 1 sur l'axe x.
Nous pouvons aussi enlever les surfaces visibles de l'objet conteneur en ajoutant le mot open à la définition de l'isosurface.

Pour savoir quelles surfaces sont l'isosurface ou l'objet conteneur, les couleurs seront différentes sur toutes les images suivantes.
Maintenant nous remplaçons la fonction utilisée par quelque chose de différent :
fonction { x+y }

function { x+y+z }

Note : ici max_gradient 4 est ajouté à la définition de l'isosurface, cela sera expliqué plus tard.
Toutes ces fonctions décrivent des plans passant par l'origine. La fonction décrit seulement le vecteur de la normale de ce plan.
Les fonctions suivantes conduisent aux mêmes résultats :
function { abs(x)-1 }
function { sqrt(x*x)-1 }

Vous pouvez voir qu'il y a deux plans maintenant. La raison est que les deux formules ont les mêmes deux solutions (où la valeur de la fonction est 0), nommément x=-1 et x=1.
Nous pouvons mélanger maintenant tous ces éléments selon différentes combinaisons, le résultat donne toujours des surfaces planes :
function { abs(x)-1+y }

function { abs(x)+abs(y)+abs(z)-2 }

Les surfaces courbes de différents types peuvent être faites avec des fonctions non linéaires.
function { pow(x, 2) + y }

Vous pouvez voir la forme parabolique donnée par la fonction carré.
Pour avoir une surface cylindrique nous pouvons utiliser la fonction suivante.
function { sqrt(pow(x, 2)- + pow(z, 2)) - 1 }
En deux dimensions, elle décrit un cercle, puisqu'elle est constante dans la troisième, nous avons un cylindre :

Bien sûr, il n'est pas difficile de changer cela en cône, nous avons seulement besoin d'ajouter un composant linéaire dans la direction y :
function { sqrt(pow(x, 2) + pow(z, 2)) + y }

Et nous pouvons, bien sûr, aussi faire une sphère :
function { sqrt(pow(x, 2) + pow(y, 2) + pow(z, 2)) - 2 }

Ici, le 2 spécifie le rayon.
Comme nous l'avons vu, les fonctions utilisées pour définir l'isosurface sont écrites dans le bloc function{...}.
Sont autorisées :
Les fonctions définies par l'utilisateur (comme les équations). Tout opérateur et expression numérique (voir la section "Les fonctions définies par l'utilisateur") qui est légal dans POV-Ray, peut être utilisé.
Avec l'équation d'une sphère "x^2+y^2+z^2 = Threshold" nous obtenons :
isosurface { function {pow(x, 2) + pow(y, 2) + pow(z, 2)} threshold Threshold ... }
Les fonctions peuvent être déclarées en premier (voir la section "Déclaration des fonctions") puis utilisées dans l'isosurface.
#declare Sphere = function {pow(x, 2) + pow(y, 2) + pow(z, 2)} isosurface { function {Sphere(x, y, z)} threshold Threshold ... }
Par défaut, une fonction prend trois paramètres (x, y, z) et vous n'avez pas à spécifier explicitement les noms des paramètres lors de la déclaration.
Lors de l'utilisation de l'identificateur, les paramètres doivent être spécifiés.
Par contre, si vous avez besoin de plus ou moins de trois paramètres lors de la déclaration de la fonction, vous devez aussi spécifier explicitement les noms des paramètres.
#declare Sphere = function(x, y, z, Radius) { pow(x, 2) + pow(y, 2) + pow(z, 2) - pow(Radius, 2) } isosurface { function {Sphere(x, y, z, 1)} ... }
Il y a beaucoup de fonctions internes disponibles dans POV-Ray. Par exemple, une sphère peut aussi être générée avec function { f_sphere(x, y, z, 2) }. Ces fonctions sont déclarées dans le fichier inclus functions.inc. La plupart d'entre elles sont plus compliquées et sont habituellement plus rapides à utiliser que le code équivalent fait à la main. Voir la liste complète pour les détails.
Ce qui suit fait un tore exactement comme l'objet tore de POV-Ray :
#include "functions.inc" isosurface { function {f_torus(x, y, z, 1.6, 0.4)} contained_by {box {-2, 2}} }

Les quatrième et cinquième paramètres sont les rayons majeur et mineur, comme les valeurs correspondantes dans l'objet torus{}.
Les paramètres x, y et z sont requis, parce que c'est une fonction déclarée. Vous pouvez aussi déclarer vous-même des fonctions comme c'est expliqué dans la section de référence.
Nous pouvons aussi simuler quelques CSG avec les fonctions isosurface. Si vous ne connaissez pas les CSG, nous vous suggérons de regarder à "Qu'est-ce qu'un CSG ?" ou à la partie correspondante de la section de référence.
Nous prendrons deux fonctions: un cylindre et une boîte tournée :
#declare fn_A = function {sqrt(pow(y, 2) + pow(z, 2)) - 0.8} #declare fn_B = function {abs(x)+abs(y)-1}
Si nous les combinons de la manière suivante, nous aurons une "fusion" :
function { min(fn_A(x, y, z), fn_B(x, y, z)) }

Une "intersection" peut être obtenue en utilisant max() au lieu de min() :
function { max(fn_A(x, y, z), fn_B(x, y, z)) }

Bien sûr, la "différence" est possible, nous avons juste à ajouter un moins (-) avant la seconde fonction :
function { max(fn_A(x, y, z), -fn_B(x, y, z)) }

Au delà des CSG de base, vous pouvez aussi obtenir de douces transitions entre les différentes surfaces (comme avec l'objet blob)
#declare Blob_threshold = 0.01; isosurface { function { (1+Blob_threshold) -pow(Blob_threshold, fn_A(x, y, z)) -pow(Blob_threshold, fn_B(x, y, z)) } max_gradient 4 contained_by {box {-2, 2}} }

La valeur Blob_threshold influence le lissé de la transition entre les formes. Une valeur basse donne des bords plus tranchés.
La fonction pour un blob négatif ressemble à ceci :
function {fn_A(x, y, z) + pow(Blob_threshold, (Fn_B(x, y, z) + Strength))}
Quelques unes des fonctions internes ont une structure aléatoire ou ressemblant au bruit.
Avec les fonctions de pigment, elles sont parmis les outils les plus puissants pour définir les isosurfaces. Nous pouvons ajouter de réels déplacements de surface aux objets plutôt qu'une perturbation de normale venant de la déclaration normal{}.
Les fonctions internes concernées sont :
f_noise3d(x, y, z)global_settings{} et génère des structures comme le modèle bozo.f_noise_generator(x, y, z, noise_generator)f_ridged_mf(x, y, z, H, Lacunarity, Octaves, Offset, Gain, noise_generator)f_ridge(x, y, z, Lambda, Octaves, Omega, Offset, Ridge, noise_generator)f_hetero_mf(x, y, z, H, Lacunarity, Octaves, Offset, T, noise_generator)L'utilisation de noise3d pur comme une fonction donne l'image suivante :
function { f_noise3d(x, y, z)-0.5 }

noise3d)Note : le -0.5 n'est ici que pour le faire correspondre à la valeur de seuil 0, la fonction f_noise3d retourne des valeurs entre 0 et 1.
Avec ceci et les autres fonctions, vous pouvez générer des objets similaires aux champs de niveaux, avec l'avantage qu'une haute résolution peut être atteinte sans d'énormes besoins en mémoire.
function { x+f_noise3d(0, y, z) }

La fonction de bruit peut aussi être sosutraite, bien sûr, avec un résultat donnant une version 'inversée' :
function { x-f_noise3d(0, y, z) }

Dans les deux dernières images, nous avons ajouté la fonction de bruit à une fonction plane. La paramètre x a été mis à 0 pour que la fonction soit constante dans la direction x. Ainsi nous achevons la structure typique du champ de niveaux.
Bien sûr, nous pouvons aussi ajouter du bruit à toute autre fonction. Si la fonction de bruit est très forte, cela peut donner plusieurs surfaces séparées.
function { f_sphere(x, y, z, 1.2)-f_noise3d(x, y, z) }

noise3d sur sphère)Ceci est une fonction de bruit appliquée à une surface de sphère, nous pouvons influencer l'intensité du bruit en la multipliant à l'aide d'un facteur et en changeant la taille par la multiplication des paramètres de coordonnée :
function { f_sphere(x, y, z, 1.6)-f_noise3d(x*5, y*5, z*5)*0.5 }

noise3d sur sphère agrandie)Comme alternative aux fonctions de bruit, nous pouvons aussi utiliser tout pigment dans une fonction :
#declare fn_Pigm = function { pigment { agate color_map { [0 color rgb 0] [1 color rgb 1] } } }
Cette fonction est une fonction vectorielle retournant un vecteur (color). Pour l'utilisation dans des fonctions d'isosurface nous devons spécifier le composant à utiliser. Lors de l'utilisation de l'identificateur, nous devons spécifier le composant du vecteur de couleur à utiliser. Pour cela la notation avec le point est utilisée : Function(x,y,z).red
Un vecteur de couleur a cinq composants. Les types d'accès supportés sont :
x | F( ).u | F( ).redy | F( ).v | F( ).greenz | F( ).bluefilter | F( ).ttransmitgrayhffunction { f_sphere(x, y, z, 1.6)-fn_Pigm(x/2, y/2, z/2).gray*0.5 }

Il y d'autres choses possibles avec les fonctions pigment, mais vous avez probablement remarqué que la génération était lente.
Les directives conditionnelles sont autorisées
#declare Rough = yes; #include "functions.inc" isosurface { function {y #if(Rough=1)-f_noise3d(x/0.5, y/0.3, z/0.4)*0.8 #end} ... }
Les boucles peuvent aussi être utilisées dans les fonctions :
#include "functions.inc" #declare Thr = 1/1000; #declare Ang = radians(45); #declare Offset = 1.5; #declare Scale = 1.2; #declare TrSph = function {f_sphere(x-Offset, y, z, 0.7*Scale)} function { (1-Thr) #declare A = 0; #while (A < 8) -pow(Thr, TrSph(x*cos(A*Ang) + y*sin(A*Ang), y*cos(A*Ang) -x*sin(A*Ang), z)) #declare A = A+1; #end }
Note : les boucles et les conditions sont évaluées lors de l'analyse, pas pendant le rendu.
La transformation d'un objet isosurface est faite comme pour tout autre objet POV-Ray. Utilisez simplement les modificateurs d'objet (taille, déplacement, rotation, ...).
Toutefois, quand vous voulez transformer les fonctions dans l'objet conteneur, vous devez substituer les paramètres dans les fonctions.
Les résultats semblent l'inverse de ce que vous attendez normalement. Voici une explication :
Prenons une Sphere(x, y, z). Nous savons qu'elle se tient à l'origine car x=0. Nous la voulons à x=2 (déplacement de deux unités vers la droite), nous devons écrire la seconde équation de la même façon : x-2=0
Maintenant que les deux équations sont égales à 0, nous pouvons remplacer le paramètre x avec x-2
Ainsi notre Sphere(x-2, y, z) se déplace de deux unités vers la droite.
Changeons la taille de notre sphère de 0.5 dans la direction y. La taille par défaut est y=1 (une unité). Nous voulons y=0.5.
Pour avoir cette équation sous la même forme que la première, nous devons multiplier les deux cotés par deux. y*2 = 0.5*2, ce qui donne y*2=1
Maintenant nous pouvons remplacer le paramètre y dans notre sphère : Sphere(x, y*2, z). Cela partage la taille y de la sphère par la moitié.
Bien, c'est l'idée générale des substitutions.
Voici un aperçu de quelques substitutions utiles :
Utilisation d'un objet déclaré P(x, y, z)
Tailledimensionnement de x : remplace "x" par "x/taille" (de même pour les autres paramètres)
scale x*2 donne P(x/2,y,z)
Dimension à l'infinidimensionne x à l'infini : remplace "x" par "0" (de même pour les autres paramètres)
dimensionne y à l'infini donne P(x,0,z)
Déplacementdéplacement selon x : remplace "x" par "x - déplacement" (de même pour les autres paramètres)
translate z*3 donne P(x,y,z-3)
Cisaillement
cisaillement dans le plan XY : remplace "x" par "x + y*tan(radians(Angle))" (de même pour les autres paramètres)
un cisaillement de 45 degrés à gauche donne P(x+y*tan(radians(45)), y, z)
Rotation
Note : cette substitution de rotation fonctionne comme les rotations normales de POV-Ray : elle compensent également pour le travail inverse
rotation autour de X :
remplace "y" par "z*sin(radians(Angle)) + y*cos(radians(Angle))"
remplace "z" par "z*cos(radians(Angle)) - y*sin(radians(Angle))"
rotation autour de Y :
remplace "x" par "x*cos(radians(Angle)) - z*sin(radians(Angle))"
remplace "z" par "x*sin(radians(Angle)) + z*cos(radians(Angle))"
rotation autour de Z :
remplace "x" par "x*cos(radians(Angle)) + y*sin(radians(Angle))"
remplace "y" par "-x*sin(radians(Angle)) + y*cos(radians(Angle))"
rotate z*75 donne :
P(x*cos(radians(75)) + y*sin(radians(75)), -x*sin(radians(75)) + y*cos(radians(75)), z)
Basculebascule X - Y : remplace "x" par "y" et remplace "y" par "-x"
bascule Y - Z : remplace "y" par "z" et remplace "z" par "-y"
bascule X - Z : remplace "x" par "-z" et remplace "z" par "x"
bascule x et y donne P(y, -x, z)
Torsion
torsion de N tours/unités autour de X :
remplace "y" par "z*sin(x*2*pi*N) + y*cos(x*2*pi*N)"
remplace "z" par "z*cos(x*2*pi*N) - y*sin(x*2*pi*N)"
Pour optimiser l'approximation d'une isosurface et avoir un maximum de vitesse de rendu, il est important d'adapter certaines valeurs.
accuracy
La valeur de accuracy influence la précision de calcul de la géométrie d'une surface. De basses valeurs donnent plus de précision, mais des résultats plus lents. La valeur par défaut 0.001 est acceptable. Nous avons utilisé cette valeur dans tous les exemples précédents, mais vous pouvez l'augmenter un peu et rendre les choses plus rapides.
max_gradient
Pour trouver la surface actuelle, il est important pour POV-Ray de connaître le gradient maximal de la fonction, c'est à dire la vitesse de changement de la valeur de la fonction. Nous pouvons spécifier une valeur avec le mot clé max_gradient. De basses valeurs donnent des rendus plus rapides, mais si vous spécifiez une valeur inférieure au gradient maximal de la fonction, il peut y avoir des trous ou d'autres artefacts dans la surface.
Pour la même raison, les fonctions avec un gradient infini ne doivent pas être utilisées. Cela s'applique pour les fonctions pigment avec le modèle brique ou damier par exemple. Vous devez également prendre garde à l'utilisation de select() dans les fonctions isosurface pour la même raison.
Si le maximum réel du gradient diffère trop de la valeur spécifiée, POV-Ray affiche une alarme avec le maximum trouvé. Il est d'habitude suffisant d'utiliser ce nombre pour le paramètre max_gradient pour obtenir des résultats rapides et corrects.
POV-Ray peut aussi changer dynamiquement le max_gradient quand vous spécifiez evaluate avec 3 paramètres. Concernant les détails de ceci et d'autres choses, voir la section de référence.
contained_by
Assurez-vous que contained_by 'objet' correspond autant que cela se peut. Un conteneur surdimensionné peut faire exploser les temps de rendu.
Quand le conteneur a beaucoup d'espace vide autour de l'isosurface, POV-Ray doit faire de nombreux échantillons superflus : avec des fonctions complexes, cela peut devenir très vorace en temps. Au-dessus de tout ça, le max_gradient nécessaire pour avoir un surface correcte augmentera rapidement aussi (souvent proportionnel au surdimensionnement !).
Vous pouvez utiliser une copie transparente du conteneur (en utilisant exactement les mêmes transformations) pour vérifier le placement. Récupérer min_extent et max_extent de isosurface n'est pas utile car cela ne donne que l'extension du conteneur et non de l'isosurface.
L'objet polynomial (et ses versions "raccourcies": cubic, quartic et quadric) de POV-Ray est l'une des primitives les plus complexes et mathématiques du programme. Certains peuvent penser que ce n'est plus très utilisé et quelque peu obsolète, mais nous devons nous rappeler que, par exemple, la primitive tore est juste un raccourci pour le quartic équivalent, qui est juste un raccourci pour l'objet poly équivalent. Les poly sont, toutefois, rarement utilisées dans les scènes parce qu'elles sont difficiles à définir et c'est loin d'être trivial d'obtenir la forme désirée avec seulement une équation polynomiale. C'est surtout utilisé par les utilisateurs de POV-Ray à orientation mathématique.
Ce cours explique le procédé de fabrication d'un objet polynomial dans POV-Ray.
Note : Depuis la version 3.5, POV-Ray inclut le nouvel objet isosurface qui rend l'objet polynomial plus ou moins obsolète. L'isosurface est plus souple (vous pouvez spécifier toute fonction mathématique, pas seulement polynomiale), plus facile à utiliser. Vous pouvez écrire la fonction telle qu'elle est, sans être obligé de poser les valeurs dans un énorme vecteur. Les isosurfaces se rendent souvent considérablement plus vite que leurs polynomiales équivalentes.
Toutefois, ceux à orientation mathématique continuent d'apprécier les polynomiales parce que les isosurfaces sont calculées par approximation, alors que les polynomiales sont calculées selon une véritable méthode mathématique. Malgré tout, les isosurfaces sont plus qu'assez bonnes pour la plupart des applications.
Note : seulement un maximum de septième degré polynomial peut être représenté avec l'objet polynomial. Si un degré polynomial plus élevé ou une fonction non polynomiale doit être représentée, alors il est nécessaire de prendre les isosurfaces.
Le premier stade est de créer la fonction polynomiale à représenter. Vous aurez besoin de quelques connaissances mathématiques pour cela (niveau universitaire).
1) Commençons avec un exemple simple : une sphère.
La fonction de la sphère est :
Maintenant nous devons convertir cela sous une forme polynomiale :
Nous aurons besoin d'un polynome de second degré pour représenter cela.
2) Un exemple plus élaboré :
Prenons la fonction :

La conversion en sa forme polynomiale donne :
Bien que la puissance la plus haute soit 4, nous aurons besoin d'un polynôme de cinquième ordre pour représenter cette fonction (parce que nous ne pouvons pas représenter y4z avec un quatrième ordre).
3) Et puisque nous avons parlé du tore, prenons-le comme exemple.
Un tore peut être représenté avec la fonction :
où r1 est le rayon majeur et r2 le rayon mineur.
Maintenant, c'est plus dur de le convertir, mais finalement nous avons :
Une polynomiale de quatrième ordre est suffisante pour représenter cela.
Note : toutes les fonctions ne peuvent pas être représentées par des polynômes. Seulement les fonctions qui utilisent l'addition (et la soustraction), la multiplication (et la division) et les puissances scalaires (incluant les puissances rationnelles, ex. la racine carrée) peuvent être représentées. De même, les objets polynomiaux ne supportent que les polynomiales de septième degré au maximum.
La conversion d'une fonction en sa forme polynomiale peut être une tâche laborieuse pour certaines fonctions. Certains programmes mathématiques sont une aide précieuse dans ces cas là.
Maintenant que nous avons la fonction sous sa forme polynomiale, nous devons l'écrire avec la syntaxe de POV-Ray. La syntaxe est spécifiée dans les chapitres "Poly, Cubique et Quartique" et "Quadrique" de la section sur le langage de description de scène. Il y a aussi une table dans ce chapitre qui sera utilisée pour faire le vecteur polynomial. Il est plus facile d'avoir cette table sur papier.
Note : Il est aussi possible de faire un petit programme avec votre langage de programmation favori qui imprimera le vecteur polynomial de la fonction polynomiale, mais cela est de votre ressort.
1) Commençons avec le plus facile, c.a.d. la sphère.
Puisque la sphère peut être représentée avec un polynôme de second degré, nous regardons à la ligne titrée "2nd" dans la table. Nous voyons qu'elle a 10 éléments, c.a.d. nous avons besoin d'un vecteur de taille 10. Chaque élément du vecteur sera un facteur des termes listés dans la table.
Le polynôme était :
L'écriture donne :
#declare Radius = 1; poly { 2, <1, 0, 0, 0, 1, 0, 0, 1, 0,-Radius*Radius> }
Placez chaque groupe de facteurs (séparés par des lignes dans la table) sur leur propre ligne.
Dans la table, nous voyons que le premier élément est le facteur pour x2, qui est 1 dans la fonction. L'élément suivant est xy. Puisque ce n'est pas dans la fonction, le facteur est 0. De même pour l'élément suivant qui est xz. Et ainsi de suite. Le dernier élément est un terme scalaire, qui est dans ce cas -r2.
Si nous faisons la scène et la rendons, nous obtenons :
camera {location y*4-z*5 look_at 0 angle 35} light_source {<100, 200,-50> 1} background {rgb <0, .25, .5>} #declare Radius = 1; poly { 2, <1, 0, 0, 0, 1, 0, 0, 1, 0,-Radius*Radius> pigment {rgb <1, .7, .3>} finish {specular .5} }

Note : il y a un raccourci pour les polynômes de second degré : la primitive quadric. L'utilisation d'un raccourci, chaque fois que c'est possible, peut conduire à des générations plus rapides. Nous pouvons écrire le code de la sphère décrite plus haut ainsi :
quadric { <1, 1, 1>, <0, 0, 0>, <0, 0, 0>,-Radius*Radius pigment {rgb <1, .7, .3>} finish {specular .5} }
2) Maintenant essayons le second. Nous le faisons de la même manière, mais cette fois nous devons regarder la colonne titrée "5th" dans la table.
La polynomiale était :
L'écriture de la primitive poly nous donne :
poly {
5,
<0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 1, 0,
0, 0, 0, 0, 0,
-2, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0>
}
Avec la scène correcte, nous avons :
camera {location <8, 20,-10>*.7 look_at x*.01 angle 35} light_source {<100, 200, 20> 1} background {rgb <0, .25, .5>} poly { 5, <0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0> clipped_by {box {<-4,-4,-1> <4, 4, 1>}} bounded_by {clipped_by} pigment {rgb <1, .7, .3>} finish {specular .5} rotate <0, 90,-90> }

3) Et finalement le tore :
La polynomiale était :
Et nous obtenons la primitive poly de quatrième degré correspondante :
camera {location y*4-z*5 look_at 0 angle 35} light_source {<100, 200,-50> 1} background {rgb <0, .25, .5>} #declare r1 = 1; #declare r2 = .5; poly { 4, <1, 0, 0, 0, 2, 0, 0, 2, 0,-2*(r1*r1+r2*r2), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 2*(r1*r1-r2*r2), 0, 0, 0, 0, 1, 0,-2*(r1*r1+r2*r2), 0, pow(r1, 4)+pow(r2, 4)-2*r1*r1*r2*r2> pigment {rgb <1, .7, .3>} finish {specular .5} }
Après génération, nous avons :

Il y a un raccourci pour les polynomiales de quatrième ordre : la primitive quartic. Nous pouvons écrire le tore comme ceci :
quartic { <1, 0, 0, 0, 2, 0, 0, 2, 0,-2*(r1*r1+r2*r2), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 2*(r1*r1-r2*r2), 0, 0, 0, 0, 1, 0,-2*(r1*r1+r2*r2), 0, pow(r1, 4)+pow(r2, 4)-2*r1*r1*r2*r2> pigment {rgb <1, .7, .3>} finish {specular .5} }
Quelque fois, nous voulons faire quelque chose qui n'a pas les angles parfaits d'une boîte. Pour cela, l'ellipsoïde superquadrique faite par le superellipsoid, est utile. Elle est décrite avec la simple syntaxe suivante :
superellipsoid {<Value_E, Value_N>}
Où Value_E et Value_N sont des nombres plus grands que zéro et plus petits ou égaux à un. Faisons un superellipsoïde et expérimentons des valeurs Value_E et Value_N pour voir ce que ça donne. Nous créons un fichier appelé supellps.pov et le modifions comme suit :
#include "colors.inc" camera { location <10, 5,-20> look_at 0 angle 15 } background {color rgb <.5, .5, .5>} light_source {<10, 50,-100> White}
L'addition du fond gris permet de mieux voir l'objet. Maintenant, tapons :
superellipsoid {<.25, .25> pigment {Red} }
Nous sauvons le fichier, et le rendons à 200x150 -A pour voir la forme.

Elle ressemble à une boîte, mais les angles sont arrondis. Maintenant, expérimentons avec d'autres valeurs pour Value_E et Value_N. Essayons <1, 0.2>.

Elle ressemble à un cylindre avec les extrémités arrondies. Maintenant, essayons <0.1, 1>.

Cette forme est singulière ! Nous ne savons pas comment l'appeler, mais c'est intéressant. Finalement, essayons <1, 1>.

Bon, c'est plus familier : une sphère !
Il y a deux choses que nous devons savoir sur les superellipsoïdes. Premièrement, nous ne devons pas utiliser une valeur 0 pour Value_E ou pour Value_N. Cela pousse POV-Ray à faire une boîte noire au lieu de la forme désirée. Deuxièmement, de très petites valeurs de Value_E et Value_N donnent de très étranges résultats. Enfin, le calculateur de racine de Sturmian ne fonctionne pas avec les superellipsoïdes.
Les superellipsoïdes sont des objets finis, qui conviennent à l'auto-encapsulage (auto_bounding) et peuvent être utilisées dans les CSG.
Maintenant, utilisons la superellipsoïde pour faire quelque chose d'utile dans une scène. Nous allons faire un sol carrelé et placerons quelques superellipsoïdes dessus. Nous pouvons commencer avec le fichier déjà entamé.
Nous le renommons tiles.pov et le modifions comme ceci :
#include "colors.inc" #include "textures.inc" camera { location <10, 5,-20> look_at 0 angle 15 } background {color rgb <.5, .5, .5>} light_source {<10, 50,-100> White}
Note : nous avons ajouté #include "textures.inc", pour pouvoir utiliser des textures prédéfinies. Maintenant, nous voulons définir la superellipsoïde qui servira de carreau.
#declare Tile = superellipsoid {<0.5, 0.1> scale <1, .05, 1> }
Les superellipsoïdes font brutalement 2*2*2 unités sauf si nous les retaillons. Si nous voulons les grouper bord à bord, nous devons les écarter un peu les unes des autres, pour qu'elles ne se chevauchent pas. Nous pouvons sélectionner une valeur offset qui doit être légèrement plus grande que 2, ainsi nous aurons un peu d'espace que nous remplirons avec du ciment. Donc nous ajoutons ceci :
#declare Offset = 2.1;
Nous voulons maintenant poser une rangée de carreaux. Chacun sera écarté de l'original par une valeur toujours croissante dans les directions z et -z. Nous nous referons à notre offset et le multiplions par le rang de notre carreau pour trouver la position de chaque carreau dans le rang. Nous unissons aussi tous ces carreaux dans un seul objet appelé Row, comme ceci :
#declare Row = union { object {Tile} object {Tile translate z*Offset} object {Tile translate z*Offset*2} object {Tile translate z*Offset*3} object {Tile translate z*Offset*4} object {Tile translate z*Offset*5} object {Tile translate z*Offset*6} object {Tile translate z*Offset*7} object {Tile translate z*Offset*8} object {Tile translate z*Offset*9} object {Tile translate z*Offset*10} object {Tile translate -z*Offset} object {Tile translate -z*Offset*2} object {Tile translate -z*Offset*3} object {Tile translate -z*Offset*4} object {Tile translate -z*Offset*5} object {Tile translate -z*Offset*6} }
Cela nous donne une rangée de 17 carreaux, largement suffisante pour remplir l'écran. Maintenant, nous devons faire des copies de Row en les déplaçant, toujours avec la valeur de offset, dans les deux directions x et -x en augmentant toujours les valeurs de la même manière.
object {Row} object {Row translate x*Offset} object {Row translate x*Offset*2} object {Row translate x*Offset*3} object {Row translate x*Offset*4} object {Row translate x*Offset*5} object {Row translate x*Offset*6} object {Row translate x*Offset*7} object {Row translate -x*Offset} object {Row translate -x*Offset*2} object {Row translate -x*Offset*3} object {Row translate -x*Offset*4} object {Row translate -x*Offset*5} object {Row translate -x*Offset*6} object {Row translate -x*Offset*7}
Finalement, nos carreaux sont tous là. Mais il nous faut une texture pour eux. Pour cela, nous unissons tous les Rows et appliquons le pigment White Marble, avec une surface un peu réflective :
union { object {Row} object {Row translate x*Offset} object {Row translate x*Offset*2} object {Row translate x*Offset*3} object {Row translate x*Offset*4} object {Row translate x*Offset*5} object {Row translate x*Offset*6} object {Row translate x*Offset*7} object {Row translate -x*Offset} object {Row translate -x*Offset*2} object {Row translate -x*Offset*3} object {Row translate -x*Offset*4} object {Row translate -x*Offset*5} object {Row translate -x*Offset*6} object {Row translate -x*Offset*7} pigment {White_Marble} finish {phong 1 phong_size 50 reflection .35} }
Nous devons ajouter le ciment, maintenant. Cela peut être simplement un plan blanc. Nous avons un peu augmenté ambient, pour le rendre plus blanc.
plane { y, 0 //ceci est le ciment pigment {color White} finish {ambient .4 diffuse .7} }
Pour compléter notre scène, ajoutons cinq superellipsoïdes différents, de différentes couleurs, de manière à ce qu'ils planent au-dessus de notre carrelage, et s'y reflètent.
superellipsoid { <0.1, 1> pigment {Red} translate <5, 3, 0> scale .45 } superellipsoid { <1, 0.25> pigment {Blue} translate <-5, 3, 0> scale .45 } superellipsoid { <0.2, 0.6> pigment {Green} translate <0, 3, 5> scale .45 } superellipsoid { <0.25, 0.25> pigment {Yellow} translate <0, 3,-5> scale .45 } superellipsoid { <1, 1> pigment {Pink} translate y*3 scale .45 }
Nous traçons la scène à 320x200 -A pour voir le résultat. Si nous en sommes contents, nous faisons un tracé final à 640x480 +A0.2.

Complément sur les fonctions définies par l'utilisateur
Complément sur la déclaration de fonctions
Complément sur les fonctions internes
| 2.3.4 Les options de texture évoluées |