Forum |  HardWare.fr | News | Articles | PC | S'identifier | S'inscrire | Shop Recherche
719 connectés 

 


 Mot :   Pseudo :  
 
 Page :   1  2  3  4  5  6  7  8  9  10
Auteur Sujet :

Article: un raytracer de base en C++

n°486914
LeGreg
Posté le 14-08-2003 à 10:49:43  profilanswer
 

Reprise du message précédent :
J'ai pensé que les gens auraient envie de faire joujou
avec les cubemap donc j'ai inclu 6 fichiers TGA de 512x512.
 
Eheh c'était soit ça soit inclure le code de chargement des jpg dans le source qui fait exactement deux mégas :P
 
LeGreg
ps: ca y'est j'ai modifié les liens, il n'y a plus que 347 Ko (dont l'executable et le fichier scene).


Message édité par LeGreg le 14-08-2003 à 10:54:16
mood
Publicité
Posté le 14-08-2003 à 10:49:43  profilanswer
 

n°487603
LeGreg
Posté le 14-08-2003 à 19:35:45  profilanswer
 

Update :  
Les articles ont été remis à jours et sont disponibles sur mon site web:
 
premiers pas:
http://www.massal.net/article/raytrace/page1.html
éclairage spéculaire (blinn-phong), post processing et antialiasing:
http://www.massal.net/article/raytrace/page2.html
textures (Perlin noise, cubic environment mapping, bump mapping)
http://www.massal.net/article/raytrace/page3.html
Flou (depth of field), Fresnel, blobs (isosurfaces):
http://www.massal.net/article/raytrace/page4.html
HDR, loi de beer, aberration chromatique:
http://www.massal.net/article/raytrace/page5.html
Global ilumination, photon mapping:
http://www.massal.net/article/raytrace/page6.html  
 
Le code et les commentaires y sont plus récents. Vous pouvez continuer à utiliser ce topic pour les questions
et commentaires.
 
Voilà, si vous voulez l'historique du sujet, vous pouvez continuer à lire la suite.
Fin de l'Update  
 
Il n'aime pas les #pragmas et il n'aime
pas mes conversions à la hussarde mais à part ça ça marche:
 

Gregory@VILNIUSGREG ~/raytrace
$ g++ -o raytrace.exe cubemap.cpp perlin.cpp texture.cpp Raytrace.cpp -Wall
In file included from cubemap.cpp:1:
stdafx.h:5:9: warning: #pragma once is obsolete
In file included from cubemap.cpp:3:
cubemap.h:1:9: warning: #pragma once is obsolete
In file included from cubemap.h:3,
                 from cubemap.cpp:3:
raytrace.h:4:9: warning: #pragma once is obsolete
In file included from raytrace.h:5,
                 from cubemap.h:3,
                 from cubemap.cpp:3:
def.h:3:9: warning: #pragma once is obsolete
In file included from cubemap.cpp:4:
texture.h:1:9: warning: #pragma once is obsolete
cubemap.cpp:209:2: warning: no newline at end of file
In file included from perlin.cpp:1:
stdafx.h:5:9: warning: #pragma once is obsolete
In file included from perlin.cpp:2:
perlin.h:1:9: warning: #pragma once is obsolete
In file included from texture.cpp:1:
stdafx.h:5:9: warning: #pragma once is obsolete
In file included from texture.cpp:4:
texture.h:1:9: warning: #pragma once is obsolete
In file included from texture.h:2,
                 from texture.cpp:4:
def.h:3:9: warning: #pragma once is obsolete
texture.cpp: In function `couleur readTexture(const texture&, float, float,
   int)':
texture.cpp:12: warning: initialization to `int' from `float'
texture.cpp:12: warning: argument to `int' from `float'
texture.cpp:13: warning: initialization to `int' from `float'
texture.cpp:13: warning: argument to `int' from `float'
texture.cpp:14: warning: initialization to `int' from `float'
texture.cpp:14: warning: argument to `int' from `float'
texture.cpp:15: warning: initialization to `int' from `float'
texture.cpp:15: warning: argument to `int' from `float'
texture.cpp:57:2: warning: no newline at end of file
In file included from Raytrace.cpp:1:
stdafx.h:5:9: warning: #pragma once is obsolete
In file included from Raytrace.cpp:5:
Raytrace.h:4:9: warning: #pragma once is obsolete
In file included from Raytrace.h:5,
                 from Raytrace.cpp:5:
def.h:3:9: warning: #pragma once is obsolete
In file included from Raytrace.cpp:6:
cubemap.h:1:9: warning: #pragma once is obsolete
In file included from Raytrace.cpp:7:
perlin.h:1:9: warning: #pragma once is obsolete
 
Gregory@VILNIUSGREG ~/raytrace
$ ./raytrace scene.txt test.tga  
 
Gregory@VILNIUSGREG ~/raytrace
$


 
LeGreg
ps: j'ai du modifier deux lignes dans les sources -> raytrace.zip
pps: et je vous rassure j'ai plus de warnings dans vs.net..


Message édité par LeGreg le 12-07-2008 à 00:02:16

---------------
voxel terrain render engine | animation mentor
n°487669
Kristoph
Posté le 14-08-2003 à 20:27:55  profilanswer
 

Pour les warning dans gcc, je conseille :
 
"-W -Wall -pedantic"

n°487697
Tetedeienc​h
Head Of God
Posté le 14-08-2003 à 20:52:13  profilanswer
 

je confirme pour les warnings dans VS .net, mais ca marche chez moi :jap:
 
C'est ultra long mais ca marche ( j'imagine que le jour ou on aura du raytracing en temps réel est encore loin :/

n°487703
Kristoph
Posté le 14-08-2003 à 20:56:00  profilanswer
 

tetedeiench a écrit :

je confirme pour les warnings dans VS .net, mais ca marche chez moi :jap:
 
C'est ultra long mais ca marche ( j'imagine que le jour ou on aura du raytracing en temps réel est encore loin :/


 
Bah non. Le raytracing en temps réel c'est possible depuis longtemps. Tout dépend de la complexité de la scène et de la résolution. Comme pour la 3D polygonale quoi.

n°487710
LeGreg
Posté le 14-08-2003 à 21:04:26  profilanswer
 

tetedeiench a écrit :


C'est ultra long mais ca marche ( j'imagine que le jour ou on aura du raytracing en temps réel est encore loin :/


 
Le programme a été "désoptimisé" à la main.
 
De plus il y a plein de choses que l'on ferait différemment si l'on voulait faire du temps réel.
 
LeGreg

n°487952
LeGreg
Posté le 15-08-2003 à 07:15:02  profilanswer
 

No update today, je suis maalaaade..
 
LeGreg

n°488617
LeGreg
Posté le 16-08-2003 à 11:29:51  profilanswer
 

Coming : le depth of field (profondeur de champ)
 
http://www.massal.net/article/raytrace/page4.html
 
Le modèle de caméra
 
Le modèle de caméra utilisé dans les jeux vidéo et en synthèse d'image simple simule la passage de la lumière par
un point unique d'inversion (pinhole ou trou d'épingle), généralement positionné à la position supposée de l'observateur.
C'est une approximation des phénomènes physiques qui fait qu'a un pixel sur l'image correspond un unique rayon lumineux non dévié.
 
En réalité un appareil photo ou un oeil possède un système optique composé de lentilles (qui dévient la lumière) et d'iris (qui laissent passer plus ou moins de rayons lumineux dépendant de la luminosité). Les appareils photos et les yeux ont des systèmes d'auto-adaptation mais ne peuvent néanmoins jamais avoir tous les plans nets simultanément.  
 
implementation dans le raytracer
 
Le bout de code suivant simule un système de lentille et d'ouverture d'iris de manière très grossière et en première approximation.
 
Les deux seuls paramétres de notre système optique sont la distance relative à la caméra où les points sont nets (fClearPoint) et la dispersion des points autour du trou d'épingle (fDispersion). En général on caractérise une lentille par une distance focale mais cette notion n'a aucun intérêt pour nous. (tout ce que l'on veut savoir c'est à quelle distance l'on voit "net" ).
 
Bon ce qui suit est un peu tricky. Je résume.
On fait partir le rayon de la position de l'observateur.
Je n'introduis pas encore les matrices de transformation ça arrivera bientot. Le rayon part véritablement de la position de l'observateur et non pas derrière parce que la position "derrière l'observateur" est invisible en projection conique. La direction est égale au vecteur différence entre ce point d'observation et le pixel correspondant sur un plan de projection virtuel de la taille de l'écran (là encore je présenterai les matrices plus tard).
 
Ensuite on dévie ce rayon en fonction des paramètres que l'on a rentré. On calcule la position du point net sur notre rayon projeté. (vous noterez que par simplification le plan "net" est une sphère mais ce n'est pas très important).
Ce point net est l'unique point qui apparaitra net quel que soit le chemin que l'on suit dans le système optique. Une fois que l'on a la position de ce point net, on déplace le point de départ du rayon d'une distance aléatoire au point de départ (ramené en première approximation au centre optique de la lentille). Ensuite on fait pointer le nouveau rayon dans la direction telle que l'on rencontrerait à nouveau le point net.
(nouvelle direction = point net - nouveau point de départ).
 
Et voilà !
 

Code :
  1. for (int i = 0 ; i < 4 ; i++)
  2. {
  3.     float sampleRatio = 0.25f * 0.25 ;
  4.     float fClearPoint = 450.0f;
  5.     float fDispersion = 50.0f;
  6.     ray viewRay = { {float(fragmentx), float(fragmenty), 0.0f}, { 0.0f, 0.0f, 1.0f}};
  7.    
  8.     if (myScene.viewer.z == 0.0f)
  9.     {
  10.         viewRay.start.z = -10000.0f;
  11.     }
  12.     else
  13.     {
  14.         viewRay.dir = - (1.0f / myScene.viewer.z ) * (viewRay.start - myScene.viewer);
  15.         float temp = viewRay.dir * viewRay.dir;
  16.         if (temp == 0.0f)
  17.           break;
  18.         viewRay.dir = ( 1.0f / sqrtf(temp) )* viewRay.dir;
  19.         viewRay.start = myScene.viewer;
  20.         vecteur vDisturbance;                       
  21.         vDisturbance.x = (fDispersion / RAND_MAX) * (1.0f * rand());
  22.         vDisturbance.y = (fDispersion / RAND_MAX) * (1.0f * rand());
  23.         vDisturbance.z = 0.0f;
  24.         point ptVise = viewRay.start + fClearPoint * viewRay.dir;
  25.         viewRay.start = viewRay.start + vDisturbance;
  26.         viewRay.dir = ptVise - viewRay.start;
  27.         temp = viewRay.dir * viewRay.dir;
  28.         if (temp == 0.0f)
  29.           break;
  30.         viewRay.dir = ( 1.0f / sqrtf(temp) )* viewRay.dir;
  31.     }
  32. couleur contrib =  addRay (viewRay, myScene);
  33.    
  34. // on fait desormais l'exposition avant l'écriture du fragment
  35. // exposition photo
  36. output.blue += sampleRatio * (1.0f - expf(contrib.blue * exposure));
  37. output.red += sampleRatio * (1.0f - expf(contrib.red * exposure));
  38. output.green += sampleRatio * (1.0f - expf(contrib.green * exposure));
  39. }


 
Je repete l'opération plusieurs fois par pixel afin de réduire
(mais pas éliminer) l'effet de bruit. Cette réduction est encore très coûteuse mais est due à notre méthode de procéder pas très économe.
 
La distance du point net est ici placée pour correspondre aux contours de la sphère violette.
 
Si je mets une distance arbitrairement grande en fClearPoint, j'obtiendrai un arrière plan clair et les boules floues.
http://www.massal.net/article/raytrace/page4.html
 
LeGreg


Message édité par LeGreg le 29-02-2008 à 20:14:04

---------------
voxel terrain render engine | animation mentor
n°488694
Tetedeienc​h
Head Of God
Posté le 16-08-2003 à 12:58:38  profilanswer
 

hey wow.

n°489981
LeGreg
Posté le 18-08-2003 à 09:42:48  profilanswer
 

Nouvel update today:  
 
La formule de la reflectance de Fresnel pour les matériaux diéléctriques (comme le verre, ie des matériaux qui conduisent la lumière).
 
http://www.massal.net/article/raytrace/page4.html
 
Le code source
 

Code :
  1. float fViewProjection = viewRay.dir * vNormal;
  2. float fReflectance;
  3. if((currentMat.reflection != 0.0f) &&(currentMat.density != 0.0f))
  4. {
  5.     // material de type glass on va calculer le coefficient de reflexion de fresnel
  6.     float fDensity1 = 1.0f;
  7.     // on considere que le rayon incident traverse un ether proche du vide (ou de l'air)
  8.     float fDensity2 = currentMat.density;
  9.     // ici on tient compte du fait que le déplacement de la lumiere
  10.     // est symetrique, de l'observateur a la source de la lumiere ou de la source de lumiere
  11.     // a l'observateur. On fait donc les calculs du coefficient en prenant en compte le rayon  
  12.     // provenant du point d'observation.
  13.     float fCosThetaI = fabsf(fViewProjection);
  14.     if (fCosThetaI * fCosThetaI >= 0.99f)
  15.     {
  16.         // cas du rayon incident selon la normale a la surface
  17.         fReflectance = (fDensity1 - fDensity2) / (fDensity1 + fDensity2);
  18.         fReflectance = currentMat.reflection * fReflectance * fReflectance;
  19.     }
  20.     else
  21.     {
  22.         float fSinThetaI = sqrtf(1 - fCosThetaI * fCosThetaI);
  23.         // le signe de Sin ThetaI n'a aucune importance, c'est le meme que celui de Sin ThetaT
  24.         // et ils s'annihilent mutuellement dans le calcul des coefficients de Reflexion
  25.         float fSinThetaT = (fDensity1 / fDensity2) * fSinThetaI;
  26.         if (fSinThetaT * fSinThetaT > 0.9999f)
  27.         {
  28.             // On a atteint l'angle critique au dela duquel
  29.             // la surface est entierement reflexive
  30.             fReflectance = currentMat.reflection ;
  31.         }
  32.         else
  33.         {
  34.             float fCosThetaT = sqrtf(1 - fSinThetaT * fSinThetaT);
  35.             // tout d'abord la reflectance de la lumiere polarisé dans le  
  36.             // plan orthogonal au plan de reflexion
  37.             float fReflectanceOrtho = (fDensity2 * fCosThetaT - fDensity1 * fCosThetaI )
  38.                 / (fDensity2 * fCosThetaT + fDensity1  * fCosThetaI);
  39.             fReflectanceOrtho = fReflectanceOrtho * fReflectanceOrtho;
  40.             // Ensuite la reflectance de la lumiere polarisé dans le  
  41.             // plan parallele au plan de reflexion
  42.             float fReflectanceParal = (fDensity1 * fCosThetaT - fDensity2 * fCosThetaI )
  43.                 / (fDensity1 * fCosThetaT + fDensity2 * fCosThetaI);
  44.             fReflectanceParal = fReflectanceParal * fReflectanceParal;
  45.             // le coefficient de reflectance global est égal à la moyenne
  46.             // des deux pour une lumière de base non polarisée
  47.             fReflectance = currentMat.reflection * 0.5f * (fReflectanceOrtho + fReflectanceParal);
  48.         }
  49.     }
  50. }
  51. else
  52. {
  53.     // reflexion de type "metal", la reflectance est la meme dans toutes les directions
  54.     // Les métaux sont conducteurs et induisent un changement dans la polarisation de la lumière
  55.     // que l'on ignore royalement.
  56.     fReflectance = currentMat.reflection;
  57. }


 
Les explications.
 
Certains matériaux ont la propriété de transmettre la lumière (le verre, l'eau) mais affectent sa progression ce qui conduit a deux phénomènes. Le premier est la réfraction, le rayon lumineux est propagé mais est dévié de sa trajectoire initiale selon une formule formulée par Descartes (ou Snell pour les anglophones).
 
En gros si ThetaI est l'angle d'incidence par rapport à la normale à la surface alors on a ThetaT, l'angle de refraction (angle du rayon transmis) qui est tel que
n2 * sin(Thetat) = n1 * sin(ThetaI).
 
n est appelée indice de refraction du milieu (sans unité calculée en fonction de celle du vide). n1 est du coté de l'incidence, n2 est du coté de la transmission.
Bien évidemment la loi est entièrement symétrique. (ThetaT et ThetaI sont interchangeables).
 
Symétriques à une exception près. si  n1 > n2, alors il y a des angles ThetaI pour lesquels il n'y a pas d'angle ThetaT solution. Au delà d'un certain angle d'incidence, il n'y a donc tout simplement pas de rayonnement transmis.
 
Le deuxième effet est la reflexion d'une partie de la lumière.
Cette reflexion est toujours dans la direction refletée par la normale par contre son intensité varie en fonction de la quantité de lumière transmise par réfraction.
 
Dans le code que j'ai présenté ci dessus, on ne tient compte que du deuxième effet. La lumière qui serait transmise est ici simplement diffusée. On compliquera les choses plus tard.
 
Coéfficients de Fresnel, Réflectance
 
Le calcul de la réflectance (ratio de la lumière purement réfléchie par la surface en fonction de l'angle d'incidence) découle directement des deux indices de réfraction n1 et n2.
(que j'ai appelé fDensity1 et fDensity2).
 
La reflectance effective est la moyenne de deux réflectances.
La lumière par sa nature ondulatoire peut être décomposé en deux sous-ondes de polarisation orthogonale. La premiere polarisation est celle qui est dans le plan d'incidence, c'est à dire le plan formé par le vecteur normal et le rayon incident. La deuxième est dans le plan orthogonal à ce plan mais qui comprend également le rayon incident.
Parce que l'effet de Fresnel et la loi de Snell-Descartes sont dues à des interactions de l'onde avec les éléments chargés qui composent les matériaux, l'angle d'incidence ainsi que le plan d'incidence de l'onde lumineuse vont changer la manière dont l'onde va affecter les particules chargées situées en surface. Ces particules en retour vont modifier l'onde transmise et forcer une partie à être réfléchie.
 
On a donc deux formules la reflectance dans le plan de polarisation orthogonale est egale au carré de sin(ThetaT - ThetaI)/(sin(ThetaT + ThetaI)) (on fait un cas particulier lorsque ThetaI est égal à zéro mais la fonction reste continue en ce point). La reflectance du plan de polarisation parallele est egal au carré de tan(ThetaT - ThetaI)/ tan(ThetaT + ThetaI). (avec la meme limite lorsque ThetaI tend vers zero)
 
J'ai simplifié ces deux formules dans le code et j'ai pris la moyenne pour rendre compte d'une lumière non polarisée.
 
La reflectance diminue d'autant la quantité de lumière diffusée par le matériau :
 

Code :
  1. lambert = lambert * (1.0f - fReflectance);


(on aura peut-etre mieux bientot)
 
Voilà pour cette nuit.
 
To be continued.
 
LeGreg
edit: l'indice de refraction est un mot plus approprié que "constante diéléctrique".


Message édité par LeGreg le 29-02-2008 à 20:14:37

---------------
voxel terrain render engine | animation mentor
mood
Publicité
Posté le 18-08-2003 à 09:42:48  profilanswer
 

n°490002
Harkonnen
Modérateur
Un modo pour les bannir tous
Posté le 18-08-2003 à 10:13:37  profilanswer
 

[:blueflag]
Merci à toi LeGreg pour cet excellent topic :jap:


---------------
J'ai un string dans l'array (Paris Hilton)
n°490154
Krueger
tout salaire demande dutravail
Posté le 18-08-2003 à 12:20:41  profilanswer
 

Au point où ça en est, chapeau. J'en suis tout :eek:. Je vois venir les effets de brume et les ombres volumétriques. Je ne maîtrise pas le domaine, mais c'est toujours aussi impressionnant de voir l'effet d'ajouter un simple bout de code pour une nouvelle technique. :)


---------------
"Colère et intolérance sont les ennemis d'une bonne compréhension." Gandhi
n°490253
bjone
Insert booze to continue
Posté le 18-08-2003 à 14:36:58  profilanswer
 

:jap: sympa le topic :jap:

n°494197
LeGreg
Posté le 22-08-2003 à 11:02:28  profilanswer
 

Pas beaucoup d'updates cette semaine. desolé j'ai été pas mal occupé.
 
Pour me rattraper, j'essaie de reflechir a comment expliquer une méthode de rendus de blobs qui ne soit pas trop indigeste.
(bon je préviens que c'est mal parti. Au pire tant pis je balance la sauce).
 
A+
LeGreg
 

n°494403
red factio​n
Posté le 22-08-2003 à 14:16:30  profilanswer
 

cool des blobs
 
j'ai tjs essaye dans faire mais jai jamais eu trop le cours de continuer  
 
  marching cube :ouch:  
 
sinon les seuls que jai fait cetait en 2D  :whistle:

n°495015
LeGreg
Posté le 22-08-2003 à 22:18:19  profilanswer
 

Non pas de marching cubes
mais une solution approchée maison
peut-etre pas aussi bien qu'une solution
analytique parfaite mais qui a le mérite
d'essayer :)
(et puis si je profite pas de ce topic
pour essayer de nouveaux trucs hein..)
 
LeGreg

n°495637
LeGreg
Posté le 24-08-2003 à 01:13:25  profilanswer
 

Incoming:
 
http://www.massal.net/article/raytrace/page4.html
 
LeGreg


Message édité par LeGreg le 29-02-2008 à 20:14:54

---------------
voxel terrain render engine | animation mentor
n°495658
LeGreg
Posté le 24-08-2003 à 09:31:45  profilanswer
 

Qu'est-ce qu'un blob?
 
C'est le nom commun d'une surface d'isopotentiel.
Le potentiel c'est un champ de valeurs scalaires dans l'espace tel qu'à chaque point de l'espace (x,y,z) corresponde une unique valeur f(x,y,z).
La surface isopotentiel est constitué des points tels que
f(x,y,z) soit égal à une constante.
 
En général pour représenter des blobs on utilise des champs types électriques ou gravitationnels.  
Si on dispose d'une source de champ ponctuelle en P0, la valeur du champ au point P est égale à E0/(P-P0)^2.
De plus la valeur du champ créé par deux sources en un point est égal à la somme des deux champs créés par chacune de ces sources. (c'est LA propriété primordiale que l'on utilise par la suite).
 
On définit donc un blob comme la surface d'isopotentiel = 1 dans un champ défini par n sources ponctuelles tel que donc:
f(P) = Somme sur n (Taillen^2 / (P-Pn)^2 )
 
Note: Grâce a notre définition, si n = 1, notre blob est simplement une sphere de rayon = taille.
 
Resolution rayon/blob
 
Bon c'est pas tout d'avoir cette definition implicite. On aimerait que ça donne quelque chose à l'écran.
 
Le problème de cette définition du potentiel c'est qu'il est possible d'obtenir une solution exacte au problème d'intersection mais que pour cela il faut résoudre des équations polynomiales de degrés de plus en plus importants à chaque nouvelle source ajoutée. C'est sympa de savoir le faire mais ça dépasse le cadre de mon article donc je vais trouver un autre moyen.
 
L'idée c'est d'approximer la fonction 1/dist^2 sous forme de fonctions linéaires par morceaux de dist^2.
En gros pour chaque source on construit une série de sphères de plus en plus resserrées et entre deux de ces sphères c'est  
f(P) = gamma * (P-Pn)*(P-Pn) + beta + delta(P);
Avec delta(P) que l'on néglige.
Ca ne pose pas de problème tant que dist n'est pas nul et l'on a une bonne approximation si l'on découpe en suffisamment de zones.
 
Maintenant que se passe t'il quand on a n spheres ? Le probleme c'est que les zones d'influences s'interpénètrent de manière non déterministe et que notre rayon, passe allègrement d'une sphère d'influence à l'autre sans ordre bien déterminé. Par contre le gros avantage de cette méthode c'est que la valeur du champ dans chaque sous zone est égal à une combinaison des gamma * dist ^2 + beta. Dit sous cette forme ce n'est pas très intéressant mais si l'on remplace dist par la valeur paramétrée par t du rayon on obtient une equation du second degré facile à résoudre et la somme d'équations du second degré faciles à résoudre est encore une équation du second degré facile à résoudre (pratique n'est-ce pas ?).
 
Le code source  
 

Code :
  1. // fonction polynomiale du second degré définie par ses trois coéfficients a * x^2 + b * x + c
  2. struct poly
  3. {
  4.     float a, b, c;
  5. };
  6. const int zoneNumber = 10;
  7. // une zone est définie par un rapport du carré de la distance à la sphère sur le carré de son rayon
  8. // puis par les gamma et beta incrémentaux qui approchent la courbe 1/ dist^2
  9. // ces deux coefficients ne sont calculées qu'une seule fois par exécution dans l'initZones.
  10. struct
  11. {
  12.     float fCoef, fGamma, fBeta;
  13. } zoneTab[zoneNumber] =
  14.     {10.0f, 0, 0},
  15.     {5.0f,  0, 0},
  16.     {3.33333f,  0, 0},
  17.     {2.5f,  0, 0},
  18.     {2.0f,  0, 0},
  19.     {1.66667f,  0, 0},
  20.     {1.42857f,  0, 0},
  21.     {1.25f,  0, 0},
  22.     {1.1111f,  0, 0},
  23.     {1.0f,  0, 0}
  24. };
  25. static void initZones()
  26. {
  27.     float fLastGamma = 0.0f, fLastBeta = 0.0f;
  28.     float fLastInvRSquare = 0.0f;
  29.     for (int i = 0; i < zoneNumber - 1; i++)
  30.     {
  31.         float fInvRSquare = 1.0f / zoneTab[i + 1].fCoef;
  32.         // fGamma est la pente entre le point d'entrée et le point de sortie
  33.         // on ne stocke que la valeur incrémentale par rapport à la zone précédente
  34.         zoneTab[i].fGamma = (fLastInvRSquare - fInvRSquare) / (zoneTab[i].fCoef - zoneTab[i + 1].fCoef) - fLastGamma;
  35.         fLastGamma = zoneTab[i].fGamma + fLastGamma;
  36.         // en faisant - fLastGamma et + fLastGamma à la ligne suivante  
  37.         // on s'économise l'utilisation d'une temporaire
  38.         // je sais c'est débile..
  39.         // fBeta est la valeur de la droite approchant la courbe pour dist = 0
  40.         // on ne stocke que la valeur incrémentale par rapport à la zone précédente
  41.         zoneTab[i].fBeta = fInvRSquare - fLastGamma * zoneTab[i+1].fCoef - fLastBeta;
  42.         fLastBeta = zoneTab[i].fBeta + fLastBeta;
  43.         fLastInvRSquare = fInvRSquare;
  44.     };
  45.     // la derniere zone d'influence agit comme un signal que l'on traite comme un cas particulier
  46.     zoneTab[zoneNumber - 1].fGamma = 0.0f;
  47.     zoneTab[zoneNumber - 1].fBeta = 1.0f;
  48. }
  49. bool isBlobIntersected(const ray &r, const blob& b, float &t)
  50. {
  51.     map<float, poly> polynomMap;
  52.     static bool bInit = false;
  53.     if (!bInit)
  54.     {
  55.         // on initialise les coéfficients gamma et beta une seule fois par execution
  56.         initZones();
  57.         bInit = true;
  58.     }
  59.    
  60.     for (unsigned int i= 0; i< b.sphereList.size(); i++)
  61.     {
  62.         float A, B, C, rSquare, rInvSquare;
  63.         float fDelta, t0, t1;
  64.         sphere &currentSphere = *b.sphereList[i];
  65.         rSquare = currentSphere.size * currentSphere.size;
  66.         // Cela ne sert à rien d'avoir des spheres de taille zero
  67.         // mais soyons de bons paranoiaques tout de meme
  68.         if (rSquare == 0.0f)
  69.             continue;
  70.         rInvSquare = 1.0f / rSquare;
  71.      vecteur vDist = currentSphere.pos - r.start;
  72.         A = 1.0f;
  73.     B = - 2.0f * r.dir * vDist;
  74.         C = vDist * vDist;
  75.         // on parcourt la liste des zones d'influences de la sphère courante
  76.         // on calcule la nouvelle version du polynome qui a cours dans  
  77.         // cette zone d'influence et on le stocke de manière incrémentale
  78.         // ce qui importe c'est la différence avec la zone précédente, ce qui permet
  79.         // de bien gérer le cas de sphères imbriquées les unes dans les autres
  80.         for (int j=0; j < zoneNumber; j++)
  81.         {
  82.             // On calcule le Delta de l'équation s'il est négatif
  83.             // il n'y a pas de solution donc pas de point d'intersection
  84.             fDelta = B*B - 4.0f * (C - zoneTab[j].fCoef * rSquare);
  85.         if (fDelta < 0.0f)
  86.                 break;
  87.             t0 =  0.5f * ( - B - sqrtf(fDelta));
  88.             // cool on ne s'occupe pas de l'ordre il est ici explicite t0 < t1
  89.         t1 =  0.5f * ( - B + sqrtf(fDelta));
  90.             poly poly0 = {zoneTab[j].fGamma * A * rInvSquare ,zoneTab[j].fGamma * B * rInvSquare , zoneTab[j].fGamma * C * rInvSquare + zoneTab[j].fBeta };
  91.             poly poly1 = {- poly0.a, - poly0.b, - poly0.c};
  92.            
  93.             // les variations du polynome sont trièes par leur position sur le rayon
  94.             // au fur et à mesure qu'elles sont insérées. C'est la map qui nous garantit cela.
  95.             // ce serait peut-etre plus optimal de placer dans un vector sans se soucier de l'ordre
  96.             // et trier ensuite mais je me fiche de l'optimisation en fait.
  97.             polynomMap.insert(pair<float, poly>(t0, poly0));
  98.             polynomMap.insert(pair<float, poly>(t1, poly1));
  99.         };
  100.     }
  101.     poly currentPolynom = {0.0f,0.0f,0.0f};
  102.     // la variable inside permet d'ignorer les équations
  103.     // dont on sait qu'elles n'auront pas d'influence
  104.     // sur notre affichage puisqu'elles se trouvent
  105.     // à l'intérieur d'une sphère incluse dans le
  106.     // blob.
  107.     int inside = 0;
  108.     for (map<float, poly>::const_iterator it = polynomMap.begin(); it != polynomMap.end(); ++it  )
  109.     {
  110.         if (((*it).second.a == 0) &&((*it).second.b == 0) &&((*it).second.c ==1.0f))
  111.         {
  112.             if (inside == 0)
  113.             {
  114.                 if (( (*it).first < t ) && ((*it).first > 0.01f))
  115.                 {
  116.                     t = (*it).first;
  117.                     return true;
  118.                 }
  119.             }
  120.             inside = inside + 1;
  121.         }
  122.         else if (((*it).second.a == 0) &&((*it).second.b == 0) &&((*it).second.c == -1.0f))
  123.         {
  124.             inside = inside -1;
  125.         }
  126.         else
  127.         {
  128.             // comme on a stocké les polynomes de manière incrémentale on  
  129.             // reconstruit le polynome de la zone d'influence courante
  130.             currentPolynom.a += (*it).second.a;
  131.             currentPolynom.b += (*it).second.b;
  132.             currentPolynom.c += (*it).second.c;
  133.         }
  134.         map<float, poly>::const_iterator itNext = it;
  135.         ++itNext;
  136.         // ça ne sert à rien de résoudre l'équation si la zone d'influence est avant le point de départ
  137.         // ou après le point d'arrivée sur le rayon
  138.         if ((inside == 0) && (t > (*it).first ) && ((*itNext).first) > 0.01f)
  139.         {
  140.             // on peut se permettre de résoudre la dernière équation de manière exacte
  141.             // après toutes les approximations que l'on a fait
  142.             // avec un nombre suffisant de découpage,
  143.             // il devrait être difficile de faire la distinction
  144.             // entre le blob et son découpage.
  145.             // on remarque que l'on a un blob qui est constitué de morceaux
  146.             // de sphères imbriquées.
  147.             float fDelta = currentPolynom.b * currentPolynom.b - 4.0f * currentPolynom.a * (currentPolynom.c - 1.0f) ;
  148.             if (fDelta < 0.0f)
  149.                 continue;
  150.             float t0 = (0.5f / currentPolynom.a) * (- currentPolynom.b - sqrtf(fDelta));
  151.             float t1 = (0.5f / currentPolynom.a) * (- currentPolynom.b + sqrtf(fDelta));
  152.             bool retValue = false;
  153.             if ((t0 > 0.01f ) && (t0 >= (*it).first ) && (t0 < (*itNext).first) && (t0 <= t ))
  154.             {
  155.                 t = t0;
  156.                 retValue = true;
  157.             }
  158.            
  159.             if ((t1 > 0.01f ) && (t1 >= (*it).first ) && (t1 < (*itNext).first) && (t1 <= t ))
  160.             {
  161.                 t = t1;
  162.                 retValue = true;
  163.             }
  164.             if (retValue == true)
  165.                 return true;       
  166.         }
  167.     }
  168.     return false;
  169. }


 
Je suis d'accord que ce n'est franchement pas clair donc si vous avez des questions n'hésitez pas.
 
Les normales
 
J'ai lu quelque part que pour calculer les normales on pouvait se contenter de faire une moyenne arithmétique de la normale de chaque sphère rapportée à la distance.  
C'est pas tout à fait faux mais on peut faire mieux.
 
Une surface isopotentielle a une normale en un point de la surface dirigée par le gradient du champ de potentiel en ce point.
 
Comme le champ est quelque chose comme:
Somme (taille * taille/dist * dist)
 
que le gradient de dist*dist = {2 * (x - xn), 2 * (y - yn), 2 * (z-zn)} (c'est un vecteur);
 
on en déduit que le gradient de notre champ c'est quelque chose comme :
- 2 * { Somme (taille * taille * (x-xn) / dist ^4), Somme(taille * taille * (y-yn)/ dist ^ 4, Somme(taille * taille * (z-zn) / dist ^4)}; (c'est aussi un vecteur)
 
Donc on peut calculer directement notre vecteur normal en ce point avec la formule ci dessous (en normalisant et en prenant l'opposé puisque l'extérieur est du coté des potentiels decroissants).
 

Code :
  1. void blobInterpolation(point pos, const blob& b, vecteur &vOut)
  2. {
  3.     vecteur gradient = {0.0f,0.0f,0.0f};
  4.     float fSomme = 0.0f;
  5.     for (unsigned int i= 0; i< b.sphereList.size(); i++)
  6.     {
  7.         vecteur normal = pos - b.sphereList[i]->pos;
  8.         float fDistSquare = normal * normal;
  9.         if (fDistSquare <= 0.001f)
  10.             continue;
  11.         float fDistFour = fDistSquare * fDistSquare;
  12.         float fRSquare = b.sphereList[i]->size * b.sphereList[i]->size;
  13.         normal = (fRSquare/fDistFour) * normal;
  14.         gradient = gradient + normal;
  15.     }
  16.     vOut = gradient;
  17. }


 
Voilà c'est tout pour cette nuit.
 
A plus.
LeGreg


Message édité par LeGreg le 24-08-2003 à 09:39:32
n°495661
LeGreg
Posté le 24-08-2003 à 09:48:04  profilanswer
 

Voilà une vue d'un autre blob:
 
http://www.massal.net/article/raytrace/page4.html
 
et voilà ce qui arrive quand on s'emmele dans les
coéfficients (ce que j'ai fait au début puisque je
les rentrais à la main à la place de les calculer automatiquement: morale du jour, ne jamais faire soi meme ce que l'on peut faire faire par une machine)
 
http://www.massal.net/article/raytrace/page4.html
 
LeGreg


Message édité par LeGreg le 29-02-2008 à 20:15:24

---------------
voxel terrain render engine | animation mentor
n°495677
chrisbk
-
Posté le 24-08-2003 à 11:01:55  profilanswer
 

roh la belle skybox faite sous terragen [:dawa]
 
(sympa le tomik, et les metaballes c tjs un succes [:ddr555])

n°495686
LeGreg
Posté le 24-08-2003 à 11:34:57  profilanswer
 

chrisbk a écrit :

roh la belle skybox faite sous terragen [:dawa]
 
(sympa le tomik, et les metaballes c tjs un succes [:ddr555])


 
C'est la skybox que j'avais fait avec amour pour mon viewer Quake 3 (cherchez pas le rapport entre Quake 3 et les paysages alpins, c'est de l'art..). Ceci dit ça prend un certain temps
à faire ces bestioles là.. Et il ne faut pas gacher.
 
J'ai aussi des maps hdr de Debevec mais je suis en train de cogiter comment en extraire l'éclairage pour mon raytracer sans qu'il devienne une usine à gaz. (a mon avis jamais)
 
LeGreg

n°496231
LeGreg
Posté le 25-08-2003 à 04:14:47  profilanswer
 

Que pensez-vous des explications? imbuvables ?
devraient être développées avec des schémas,
plus de formules ?
 
Je peux commencer à développer les explications en prenant plus de temps pour chaque nouvel update ou au contraire le rythme actuel ne doit pas être rallenti et le code parle de lui-meme ?
 
Je demande ça parce que je ne voudrais pas commencer à parler dans le vide parce que mes explications ont fait fuir tout le monde :P .
 
LeGreg
 

n°496236
souk
Tourist
Posté le 25-08-2003 à 07:04:14  profilanswer
 

jusque la c'est parfait :love:
 
va falloir que je me fasse un petit ray-tracer un de ces 4 :bounce:

n°496929
Krueger
tout salaire demande dutravail
Posté le 25-08-2003 à 15:09:47  profilanswer
 

Ça va, j'ai encore toute ma tête. :D Je suis le rythme. Je n'ai pas besoin d'un ralentissement. J'attends le prochain round...


Message édité par Krueger le 25-08-2003 à 15:11:22

---------------
"Colère et intolérance sont les ennemis d'une bonne compréhension." Gandhi
n°499465
LeGreg
Posté le 27-08-2003 à 11:42:07  profilanswer
 

Teaser du jour (enfin trois heures du mat c'est plutot la nuit) :
 
http://www.massal.net/article/raytrace/page4.html
 
LeGreg


Message édité par LeGreg le 29-02-2008 à 20:15:41

---------------
voxel terrain render engine | animation mentor
n°499474
Krueger
tout salaire demande dutravail
Posté le 27-08-2003 à 11:45:19  profilanswer
 

En attendant les explications, pourquoi y a-t-il un petit point au centre de la boule en haut à gauche ?
 
En tous cas, c'est très beau. :jap:


---------------
"Colère et intolérance sont les ennemis d'une bonne compréhension." Gandhi
n°499490
chrisbk
-
Posté le 27-08-2003 à 11:54:45  profilanswer
 

legreg a écrit :

Teaser du jour (enfin trois heures du mat c'est plutot la nuit) :
 
http://small.massal.net/refractionreflection.png
 
LeGreg


 
un ptit fresnel ?

n°499924
LeGreg
Posté le 27-08-2003 à 18:53:56  profilanswer
 

Krueger a écrit :

En attendant les explications, pourquoi y a-t-il un petit point au centre de la boule en haut à gauche ?


 
Quel point ?
 
LeGreg

n°499943
Krueger
tout salaire demande dutravail
Posté le 27-08-2003 à 19:45:50  profilanswer
 

:heink:
C'est pas bien de remplacer ses posts. :lol:


---------------
"Colère et intolérance sont les ennemis d'une bonne compréhension." Gandhi
n°500326
LeGreg
Posté le 28-08-2003 à 10:45:19  profilanswer
 

La refraction
 
Certains matériaux ont la propriété de conduire la lumière. Cependant comme les différents matériaux conducteurs ont des propriétés différente, le passage de l'un à l'autre ne se fait pas sans mal. On dit qu'il y a réfraction.
 
D'après la loi de Snell-Descartes ci-dessus. Le rayon réfracté est dans le plan formé par le rayon incident et la normale à la surface et forme un angle ThetaT avec cette surface tel que  
sin ThetaT = n1/n2 sin ThetaI
dans le cas du passage d'un matériau avec n1>n2, le rayon est ramené vers la normale. Dans le cas d'un matériel avec n2>n1, le rayon réfracté s'éloigne de la normale jusqu`à atteindre le seuil critique ou aucun rayon n'est transmis, tout est reflechi.
 
On a deja calculé la reflectance dans la direction du rayon reflechi. Le rapport des intensités dans la direction du rayon transmis est appelé transmittance et dans le cas d'un matériau comme le verre elle est simplement égal à 1 - reflectance.
 
Implementation
 
J'ai donc modifié mon code pour accepter des matériaux transparents. A la place de calculer la valeur du rayon réflechi lors de l'intersection, on tire à la roulette russe.
 
Cette méthode est appelé importance sampling. Plutot que de suivre tous les rayons émergeants d'une surface, on classe les directions par probabilité de tirer l'une ou l'autre. Cela permet de limiter les calculs lorsqu'une surface est plutot reflechissante ou plutot transparente. L'inconvénient c'est que comme d'un rayon à l'autre on peut prendre des chemins totalement différents le résultat est un peu bruité. On s'en sort en prenant plus de samples par pixel. Il existe aussi d'autres méthodes basées sur du filtrage.
 
Il a fallu également légèrement modifier le code du tracé et d'intersection pour prendre en compte le fait qu'on soit à l'intérieur ou à l'extérieur d'un objet en volume.
Par exemple en intérieur je ne calculerai pas les ombres et la spécularité (glossiness).
 
Pour savoir si l'on est à l'intérieur ou à l'extérieur j'interprète le produit scalaire entre le rayon incident et la normale "sortante".
 
Le code
 

Code :
  1. // on determine si on est à l'intérieur ou à l'éxtérieur
  2. if (vNormal * viewRay.dir > 0.0f)
  3. {
  4.     bInside = true;
  5. }
  6. else
  7. {
  8.     bInside = false;
  9. }


 
 

Code :
  1. float totalWeight = fReflectance + fTransmittance;
  2.         if (totalWeight > 0.0f)
  3.         {
  4.             float fRoulette = (totalWeight / RAND_MAX) * (1.0f * rand());
  5.             if (fRoulette <= fReflectance)
  6.             {
  7.                 coef *= currentMat.reflection;
  8.                 // Le rayon viewRay est réflechi par la normale.
  9.                 // Pour calculer le rayon refléchi on fait la projection
  10.                 // du vecteur direction du rayon vue
  11.                 // sur la direction de la normale.
  12.                 // Pour avoir le vecteur tangent il faudrait retrancher
  13.                 // cette projection au vecteur direction
  14.                 // mais ici on veut la reflexion et donc il faut retrancher
  15.                 // la projection deux fois au vecteur direction.
  16.                 float fReflection = - 2.0f * fViewProjection;
  17.                 // on fait partir le nouveau rayon du point d'intersection avec la sphère courante
  18.                 // et on le dirige selon le vecteur reflexion que l'on vient de calculer
  19.                 viewRay.start = ptHitPoint;
  20.                 viewRay.dir += fReflection * vNormal;
  21.             }
  22.             else
  23.             {
  24.                 coef *= currentMat.refraction;
  25.                 float fOldRefractionCoef = myContext.fRefractionCoef;
  26.                 if (bInside)
  27.                 {
  28.                     myContext.fRefractionCoef = context::getDefaultAir().fRefractionCoef;
  29.                 }
  30.                 else
  31.                 {
  32.                     myContext.fRefractionCoef = currentMat.density;
  33.                 }
  34.                 // ici on calcule le rayon transmis avec la formule de Snell-Descartes
  35.                 viewRay.start = ptHitPoint;
  36.                 viewRay.dir += fCosThetaI * vNormal;
  37.                 viewRay.dir = (fOldRefractionCoef / myContext.fRefractionCoef) * viewRay.dir;
  38.                 viewRay.dir += (-fCosThetaT) * vNormal;
  39.             }
  40.         }
  41.         else
  42.         {
  43.             coef = 0.0f;
  44.         }


 
pour savoir d'ou je sors ma formule de direction refractée.
J'applique simplement Snell Descartes à la projection du vecteur incident et refracté sur la normal et sur le plan orthogonal à cette normale (respectivement cos Theta et sin Theta).
 
Par contre l'intersection rayon/blob est plus lente maintenant parce qu'on peut être à l'intérieur de ce blob donc j'ai du jeter la variable "inside". On pourrait la resusciter le jour où l'on mélangera des objets opaques avec des objets transparents.
 
Voila c'était court j'espère que ça vous a plu et que c'était suffisamment clair ;)
 
LeGreg

n°500333
LeGreg
Posté le 28-08-2003 à 10:50:13  profilanswer
 

Quand la surface est plus transparente (indice de réfraction proche de 1) ça donne ça:
 
http://www.massal.net/article/raytrace/page4.html
 
LeGreg


Message édité par LeGreg le 29-02-2008 à 20:16:20

---------------
voxel terrain render engine | animation mentor
n°500343
fykman
Errare Humanum Est
Posté le 28-08-2003 à 11:04:45  profilanswer
 

LeGreg t'es une bete !  

n°500346
fykman
Errare Humanum Est
Posté le 28-08-2003 à 11:06:58  profilanswer
 

legreg a écrit :

Quand la surface est plus transparente (indice de réfraction proche de 1) ça donne ça:
 
http://small.massal.net/refraction.png
 
LeGreg


 
On dirais du verre...

n°500353
Krueger
tout salaire demande dutravail
Posté le 28-08-2003 à 11:13:54  profilanswer
 

C'en est pas loin. Mais je ne me souviens plus de l'indice de réfraction pour le verre.
 
Sinon legreg, saurais-tu implémenter des volumes à indice de réfraction variable ? Ça serait super cool de voir l'effet d'un mirage. :)


Message édité par Krueger le 28-08-2003 à 11:16:30

---------------
"Colère et intolérance sont les ennemis d'une bonne compréhension." Gandhi
n°500355
*syl*
--&gt; []
Posté le 28-08-2003 à 11:15:02  profilanswer
 

fykman a écrit :

LeGreg t'es une bete !  
 

+1, tu m'impressionnes :jap:

n°503946
LeGreg
Posté le 01-09-2003 à 11:34:35  profilanswer
 

Pour célébrer Labor day  
une petite env map en HDR:
 
http://www.massal.net/article/raytrace/page5.html
 
LeGreg


Message édité par LeGreg le 29-02-2008 à 20:16:42

---------------
voxel terrain render engine | animation mentor
n°503951
Taz
bisounours-codeur
Posté le 01-09-2003 à 11:36:13  profilanswer
 

Citation :


  // on determine si on est à l'intérieur ou à l'éxtérieur
  if (vNormal * viewRay.dir > 0.0f)
  {
        bInside = true;
  }
  else
  {
        bInside = false;
  }

:sol:

n°503966
Krueger
tout salaire demande dutravail
Posté le 01-09-2003 à 11:45:02  profilanswer
 

legreg a écrit :

Pour célébrer Labor day  
une petite env map en HDR:
 
http://small.massal.net/hdrenvmap2.png
 
LeGreg


C'est pas de la réflexion tout simplement ? :??:
Et c'est quoi HDR ?


---------------
"Colère et intolérance sont les ennemis d'une bonne compréhension." Gandhi
n°503967
LeGreg
Posté le 01-09-2003 à 11:45:21  profilanswer
 

Taz a écrit :

Citation :


  // on determine si on est à l'intérieur ou à l'éxtérieur
  if (vNormal * viewRay.dir > 0.0f)
  {
        bInside = true;
  }
  else
  {
        bInside = false;
  }

:sol:  


 
quoi ce n'est pas assez clair pour toi ?
 
LeGreg

n°503990
LeGreg
Posté le 01-09-2003 à 11:54:03  profilanswer
 

Krueger a écrit :


C'est pas de la réflexion tout simplement ? :??:
Et c'est quoi HDR ?


 
Dans la version précédente, le cube env map provenait d'un fichier TGA. Les valeurs d'intensité étaient limitées de 0 à 1 avec des pas fixes de 256 valeurs. Ce qui implique notamment que l'exposition était précalculée et figée dans le fichier texture.
 
Dans la nouvelle version, j'ai adapté le code pour lire des fichiers HDR qui codent la luminosité sous un format proche d'un format flottant mais avec un seul exposant par canal (RGBE = rouge, vert, bleu, exposant).  
 
En gros si je varie l'exposition dans la premiere version, la map va devenir progressivement toute noire. Si je varie l'exposition dans la deuxième version, la map va s'assombrir par endroit et ne vont rester blanche que les sources de lumières (ou les reflets) les plus forts.  
 
Il y a aussi moyen d'extraire l'eclairage à partir d'une telle map mais ce sera pour plus tard (il y a plusieurs methodes mais qui prennent plus que quelques lignes).
 
LeGreg

n°504122
Krueger
tout salaire demande dutravail
Posté le 01-09-2003 à 13:53:52  profilanswer
 

Ah, ok. C'est comme utiliser du Phong au lieu du Gouraud, les variations sont plus mises en évidence dans le résultat.


---------------
"Colère et intolérance sont les ennemis d'une bonne compréhension." Gandhi
mood
Publicité
Posté le   profilanswer
 

 Page :   1  2  3  4  5  6  7  8  9  10

Aller à :
Ajouter une réponse
 

Sujets relatifs
c koi un nombre entier en base octale ou hexadécimale ??Phpbb et base de données
Formulaire de modification d'une base mysqlTransformer/Intégrer un XLS dans une base SQL/mySQL
[PHP] question de base sur la structure du if...then...else ?[SGBD] Base de données sans serveur ?
ResourceBundle basé sur un fichier situé à une url spécifiqueformulaire --> direction email à la place de la base mySQL
Temps de transfert Base Access ...SQL serveurplacé un element sous plusieurs catégorie dans une base de donnéés
Plus de sujets relatifs à : Article: un raytracer de base en C++


Copyright © 1997-2022 Hardware.fr SARL (Signaler un contenu illicite / Données personnelles) / Groupe LDLC / Shop HFR