Traçage de rayons vers l'arrière. Rendu avec réflexion diffuse

Lors de la Gamescom 2018, Nvidia a annoncé une série de cartes graphiques Nvidia GeForce RTX qui prendront en charge la technologie de traçage de rayons en temps réel Nvidia RTX. Nos rédacteurs ont compris comment cette technologie fonctionnera et pourquoi elle est nécessaire.

Qu’est-ce que Nvidia RTX ?

Nvidia RTX est une plate-forme contenant un certain nombre d'outils utiles pour les développeurs qui donnent accès à un nouveau niveau d'infographie. Nvidia RTX n'est disponible que pour la nouvelle génération de cartes graphiques Nvidia GeForce RTX, construites sur l'architecture Turing. La principale caractéristique de la plateforme est la disponibilité lancer de rayons en temps réel(également appelé lancer de rayons).

Qu’est-ce que le lancer de rayons ?

Le lancer de rayons est une fonctionnalité qui vous permet de simuler le comportement de la lumière, créant ainsi un éclairage crédible. Désormais, dans les jeux, les rayons ne bougent pas en temps réel, c'est pourquoi l'image, bien qu'elle soit souvent belle, n'est toujours pas assez réaliste - les technologies utilisées actuellement nécessiteraient une énorme quantité de ressources pour le lancer de rayons.

Ceci est corrigé par la nouvelle série de cartes vidéo Nvidia GeForce RTX, qui ont suffisamment de puissance pour calculer le trajet des rayons.

Comment cela marche-t-il?

RTX projette des rayons de lumière du point de vue du joueur (caméra) sur l'espace environnant et calcule ainsi où le pixel de couleur doit apparaître. Lorsque les rayons frappent quelque chose, ils peuvent :

  • Réfléchir - cela provoquera l'apparition d'un reflet sur la surface ;
  • Stop - cela créera une ombre sur le côté de l'objet que la lumière n'a pas touché
  • Réfracter - cela changera la direction du faisceau ou affectera la couleur.
La présence de ces fonctions vous permet de créer un éclairage plus crédible et des graphismes réalistes. Ce processus nécessite beaucoup de ressources et est utilisé depuis longtemps pour créer des effets cinématographiques. La seule différence est que lors du rendu d'une image de film, les auteurs ont accès à une grande quantité de ressources et, pourrait-on dire, à une durée illimitée. Dans les jeux, l'appareil ne dispose que d'une fraction de seconde pour générer des images, et le plus souvent, une seule carte vidéo est utilisée, et non plusieurs, comme lors du traitement de films.

Cela a incité Nvidia à introduire des cœurs supplémentaires dans les cartes graphiques GeForce RTX, qui assumeront l'essentiel de la charge, améliorant ainsi les performances. Ils sont également équipés d'une intelligence artificielle, dont la tâche est de calculer les erreurs possibles lors du processus de traçage, ce qui permettra de les éviter à l'avance. Ceci, comme le disent les développeurs, augmentera également la vitesse de travail.

Et comment le lancer de rayons affecte-t-il la qualité ?

Lors de la présentation des cartes vidéo, Nvidia a présenté un certain nombre d'exemples de lancer de rayons : on a notamment appris que certains jeux à venir, dont Shadow of the Tomb Raider et Battlefield 5, fonctionneraient sur la plateforme RTX. Cette fonction sera cependant facultative dans le jeu, puisque le traçage nécessite l'une des nouvelles cartes vidéo. Les bandes-annonces présentées par l'entreprise lors de la présentation peuvent être consultées ci-dessous :

Shadow of the Tomb Raider, qui sortira le 14 septembre de cette année :

Battlefield 5, qui sortira le 19 octobre :

Metro Exodus, sortie prévue le 19 février 2019 :

Contrôle dont la date de sortie est encore inconnue :

Parallèlement à tout cela, Nvidia, quels autres jeux recevront la fonction de lancer de rayons.

Comment activer RTX ?

En raison des caractéristiques techniques de cette technologie, seules les cartes vidéo dotées de l'architecture Turing prendront en charge le lancer de rayons - les appareils actuellement disponibles ne peuvent pas faire face à la quantité de travail requise par le traçage. À l'heure actuelle, les seules cartes vidéo dotées de cette architecture sont la série Nvidia GeForce RTX, dont les modèles sont disponibles en précommande entre 48 000 et 96 000 roubles.

AMD a-t-il des analogues ?

AMD possède sa propre version de la technologie de traçage de rayons en temps réel, présente dans son moteur Radeon ProRender. La société a annoncé son développement lors de la GDC 2018, qui a eu lieu en mars. La principale différence entre la méthode d'AMD et celle de Nvidia est qu'AMD donne accès non seulement au traçage, mais aussi à la rastérisation, une technologie désormais utilisée dans tous les jeux. Cela vous permet à la fois d'utiliser le traçage, d'obtenir un meilleur éclairage et d'économiser des ressources dans des endroits où le traçage constituerait une charge inutile sur la carte vidéo.

La technologie qui fonctionnera sur l'API Vulkan est encore en développement.

Comme Nvidia l'a déclaré lors de sa présentation, la maîtrise de la technologie RTX améliorera considérablement la composante graphique des jeux, élargissant ainsi l'ensemble des outils disponibles pour les développeurs. Cependant, il est trop tôt pour parler d'une révolution graphique générale : tous les jeux ne prendront pas en charge cette technologie et le coût des cartes vidéo qui la prennent en charge est assez élevé. La présentation de nouvelles cartes vidéo signifie qu'il y a des progrès dans les détails graphiques, et avec le temps, ils grandiront et grandiront.

Méthodes de lancer de rayons (Rayon Tracé) Aujourd’hui, ils sont considérés comme les méthodes les plus puissantes et les plus polyvalentes pour créer des images réalistes. Il existe de nombreux exemples de mise en œuvre d'algorithmes de traçage pour un affichage de haute qualité des scènes tridimensionnelles les plus complexes. On peut noter que l'universalité des méthodes de traçage tient en grande partie au fait qu'elles reposent sur des concepts simples et clairs qui reflètent notre expérience de perception du monde qui nous entoure.

Riz. 8.12. Modèles de réflexion : a – miroir idéal, b – miroir imparfait, c – diffus, d – somme du diffus et du spéculaire, d – inverse, f – somme du diffus, spéculaire et inverse

Comment percevons-nous la réalité environnante ? Tout d’abord, nous devons décider de ce que nous pouvons voir. Ceci est étudié dans des disciplines spéciales et, dans une certaine mesure, c'est une question philosophique. Mais ici, nous supposerons que les objets environnants ont les propriétés suivantes par rapport à la lumière :

    émettre;

    réfléchir et absorber ;

    passer à travers eux-mêmes.

Riz. 8.13. Rayonnement – ​​a – uniformément dans toutes les directions, b – directionnel

Chacune de ces propriétés peut être décrite par un certain ensemble de caractéristiques. Par exemple, le rayonnement peut être caractérisé par son intensité, sa direction et son spectre. Le rayonnement peut provenir d’une source relativement ponctuelle (une étoile lointaine) ou d’une source de lumière diffuse (par exemple, de la lave en fusion sortant d’un cratère volcanique). Le rayonnement peut se propager le long d'un faisceau assez étroit (faisceau laser focalisé) ou dans un cône (projecteur), ou uniformément dans toutes les directions (Soleil), ou autre chose. La propriété de réflexion (absorption) peut être décrite par les caractéristiques de diffusion diffuse et de réflexion spéculaire. La transparence peut être décrite par l'atténuation de l'intensité et la réfraction.

La répartition de l'énergie lumineuse dans les directions possibles des rayons lumineux peut être affichée à l'aide de diagrammes vectoriels dans lesquels la longueur des vecteurs correspond à l'intensité (Fig. 8.12 - 8.14).

Dans les paragraphes précédents, nous nous sommes déjà familiarisés avec les types de réflexion les plus souvent évoqués : spéculaire et diffuse. Moins souvent mentionné dans la littérature, le miroir inversé ou anti-miroir dedéfaite, dans lequel l'intensité maximale de réflexion correspond à la direction vers la source. Certains types de végétation à la surface de la Terre, observés depuis une hauteur de rizières, présentent une réflexion miroir inversée.

Deux cas extrêmes et idéalisés de réfraction sont présentés sur la Fig. 8.13.

Certains objets réels réfractent les rayons de manière beaucoup plus complexe, comme le verre recouvert de glace.

Un seul et même objet de réalité peut être perçu comme une source de lumière ou, vu différemment, être considéré comme un objet qui ne fait que réfléchir et transmettre la lumière. Par exemple, un dôme de ciel nuageux dans une scène tridimensionnelle peut être modélisé comme une source de lumière étendue (distribuée), tandis que dans d'autres modèles, le même ciel agit comme un milieu translucide éclairé depuis la direction du Soleil.

Riz. 8.14. Réfraction a – idéale, b – diffuse

En général, chaque objet est décrit par une combinaison des trois propriétés ci-dessus. À titre d'exercice, essayez de donner l'exemple d'un objet qui possède simultanément trois de ces propriétés : il émet de la lumière et, en même temps, réfléchit et transmet la lumière provenant d'autres sources. Votre imagination trouvera probablement d’autres exemples que, disons, du verre chauffé au rouge.

Voyons maintenant comment se forme l'image d'une scène contenant plusieurs objets spatiaux. Nous supposerons que les rayons lumineux émergent de points situés à la surface (volume) des objets émis. Nous pouvons appeler ces rayons primaires - ils éclairent tout le reste.

Un point important est l'hypothèse selon laquelle un faisceau lumineux dans l'espace libre se propage le long d'une ligne droite (bien que dans des sections spéciales de la physique, les raisons d'une éventuelle courbure soient également étudiées). Mais dans optique géométrique On suppose qu’un rayon lumineux se propage en ligne droite jusqu’à ce qu’il rencontre une surface réfléchissante ou la limite d’un milieu réfractif. Alors croirons-nous.

D'innombrables rayons primaires émanent de sources de rayonnement dans différentes directions (même un faisceau laser ne peut pas être parfaitement focalisé - de toute façon, la lumière ne se propagera pas selon une ligne idéalement fine, mais selon un cône, un faisceau de rayons). Certains rayons pénètrent dans l'espace libre et certains (ils sont également innombrables) frappent d'autres objets. Si un faisceau frappe un objet transparent, il se réfracte et se propage plus loin, tandis qu'une partie de l'énergie lumineuse est absorbée. De même, si une surface à réflexion spéculaire est rencontrée sur le trajet du faisceau, elle change également de direction et une partie de l’énergie lumineuse est absorbée. Si l'objet est en miroir et en même temps transparent (par exemple, du verre ordinaire), il y aura déjà deux faisceaux - dans ce cas, on dit que le faisceau est divisé.

On peut dire qu'à la suite de l'impact des rayons primaires sur les objets, des rayons secondaires apparaissent. D'innombrables rayons secondaires pénètrent dans l'espace libre, mais certains d'entre eux frappent d'autres objets. Ainsi, étant réfléchis et réfractés plusieurs fois, les rayons lumineux individuels arrivent au point d'observation - l'œil humain ou le système optique d'un appareil photo. Il est évident qu’une partie des rayons primaires directement issus des sources de rayonnement peuvent également atteindre le point d’observation. Ainsi, l'image de la scène est formée par un certain nombre de rayons lumineux.

La couleur des points d'image individuels est déterminée par le spectre et l'intensité des rayons primaires des sources de rayonnement, ainsi que par l'absorption de l'énergie lumineuse dans les objets rencontrés sur le trajet des rayons correspondants.

Riz. 8.15. Schéma de traçage de rayons arrière

La mise en œuvre directe de ce modèle d’imagerie par rayons semble difficile. Vous pouvez essayer de développer un algorithme pour construire une image en utilisant la méthode spécifiée. Dans un tel algorithme, il est nécessaire de prévoir une énumération de tous les rayons primaires et de déterminer lesquels d'entre eux frappent les objets et la caméra. Parcourez ensuite tous les rayons secondaires, et tenez également compte uniquement de ceux qui frappent les objets et la caméra. Et ainsi de suite. Cette méthode peut être appelée direct lancer de rayons. La valeur pratique d’une telle méthode sera discutable. En effet, comment prendre en compte le nombre infini de rayons allant dans toutes les directions ? Il est évident qu’une recherche complète d’un nombre infini de rayons est en principe impossible. Même si nous réduisons d'une manière ou d'une autre cela à un nombre fini d'opérations (par exemple, divisons toute la sphère de directions en secteurs angulaires et opérons non pas avec des lignes infiniment fines, mais avec des secteurs), le principal inconvénient de la méthode demeure - de nombreuses opérations inutiles associés au calcul des rayons, qui ne sont alors pas utilisés. C'est en tout cas ce qui apparaît à l'heure actuelle.

Méthode trace les rayons vous permettent de réduire considérablement la surpuissance des rayons lumineux. La méthode a été développée dans les années 80 ; les travaux considérés comme fondamentaux sont Witte-da Et Kay. Selon cette méthode, les rayons ne sont pas suivis à partir de sources lumineuses, mais dans la direction opposée, à partir du point d'observation. De cette façon, seuls les rayons qui contribuent à la formation de l'image sont pris en compte.

Voyons comment obtenir une image raster d'une scène 3D à l'aide du backtracing. Supposons que le plan de projection soit divisé en plusieurs carrés – pixels. Choisissons une projection centrale avec un centre fuyant à une certaine distance du plan de projection. Traçons une ligne droite depuis le centre de fuite jusqu'au milieu du carré (pixel) du plan de projection (Fig. 8.15). Ce sera le principal rayon de retour. Si la ligne droite de ce rayon touche un ou plusieurs objets de la scène, sélectionnez alors le point d'intersection le plus proche. Pour déterminer la couleur d'un pixel de l'image, vous devez prendre en compte les propriétés de l'objet, ainsi que le rayonnement lumineux qui tombe sur le point correspondant de l'objet.

Riz. 8.16. Retour en arrière pour les objets ayant des propriétés spéculaires et réfractives

Si l'objet est reflété (au moins partiellement), alors nous construisons un rayon secondaire - le rayon incident, en considérant le rayon tracé précédent, primaire, comme le rayon de réflexion. Ci-dessus, nous avons considéré la réflexion spéculaire et obtenu des formules pour le vecteur du rayon réfléchi étant donné les vecteurs de rayons normaux et incidents. Mais ici on connaît le vecteur du rayon réfléchi, mais comment trouver le vecteur du rayon incident ? Pour ce faire, vous pouvez utiliser la même formule de réflexion spéculaire, mais en définissant le vecteur de rayon incident requis comme rayon réfléchi. Autrement dit, le reflet est le contraire.

Pour un miroir idéal, il suffit ensuite de tracer uniquement le prochain point d'intersection du rayon secondaire avec un objet. Que signifie le terme « miroir parfait » ? Nous supposerons qu'un tel miroir a une surface polie parfaitement égale, donc à un rayon réfléchi correspond un seul rayon incident. Le miroir peut être obscurci, c'est-à-dire absorber une partie de l'énergie lumineuse, mais la règle est toujours respectée : un rayon est incident, un autre est réfléchi. Vous pouvez également considérer un « miroir imparfait ». Cela signifierait que la surface est inégale. La direction du rayon réfléchi correspondra à plusieurs rayons incidents (ou vice versa, un rayon incident génère plusieurs rayons réfléchis), qui forment un certain cône, éventuellement asymétrique, avec un axe le long de la ligne du rayon incident d'un miroir idéal. Le cône correspond à une certaine loi de répartition de l'intensité, dont la plus simple est décrite par le modèle de Phong - le cosinus de l'angle élevé à une certaine puissance. Un miroir imparfait complique grandement le traçage : vous devez tracer non pas un, mais plusieurs rayons incidents et prendre en compte la contribution du rayonnement d'autres objets visibles depuis un point donné.

Si l’objet est transparent, il est alors nécessaire de construire un nouveau rayon qui, une fois réfracté, produirait le rayon tracé précédent. Ici, nous pouvons également profiter de la réversibilité, ce qui est également vrai pour la réfraction. Pour calculer le vecteur du rayon souhaité, vous pouvez appliquer les formules discutées ci-dessus pour le vecteur du rayon réfractif, en supposant que la réfraction se produit dans la direction opposée (Fig. 8.16).

Si un objet a les propriétés de réflexion et de réfraction diffuses, alors, dans le cas général, comme pour un miroir non idéal, il faut retracer les rayons provenant de tous les objets existants. Pour la réflexion diffuse, l'intensité de la lumière réfléchie est connue pour être proportionnelle au cosinus de l'angle entre le rayon vecteur de la source lumineuse et la normale. Ici, la source lumineuse peut être n’importe quel objet visible depuis un point donné et capable de transmettre de l’énergie lumineuse.

S'il s'avère que le rayon de traçage actuel ne coupe aucun objet, mais est dirigé vers l'espace libre, alors le traçage de ce rayon se termine.

Le rétrotraçage des rayons sous la forme sous laquelle nous l'avons considéré ici, même s'il réduit la recherche, ne permet pas de s'affranchir du nombre infini de rayons analysés. En fait, cette méthode permet d'obtenir immédiatement un rayon de rétrotraçage primaire pour chaque point. dans l'image. Cependant, il peut déjà exister un nombre infini de rayons de réflexion secondaires. Ainsi, par exemple, si un objet peut réfléchir la lumière de n'importe quel autre objet, et si ces autres objets sont assez grands, alors quels points des objets émetteurs doivent être pris en compte pour construire les rayons correspondants, par exemple en réflexion diffuse. ? Évidemment, tous les points.

Dans la mise en œuvre pratique de la méthode de backtracing, des restrictions sont introduites. Certaines d'entre elles sont nécessaires pour pouvoir résoudre en principe le problème de la synthèse d'images, et certaines restrictions peuvent améliorer considérablement les performances du traçage. Exemples de telles restrictions.

1. Parmi tous les types d'objets, certains se démarquent, que nous appellerons sources lumière. Les sources lumineuses ne peuvent émettre que de la lumière, mais ne peuvent ni la réfléchir ni la réfracter (nous ne considérerons que indiquer sources lumineuses).

2. Les propriétés des surfaces réfléchissantes sont décrites par la somme de deux composantes : diffuse et spéculaire.

3. À son tour, la spécularité est également décrite par deux composantes. D'abord (réflexion) prend en compte les réflexions d'autres objets qui ne sont pas des sources lumineuses. Un seul faisceau réfléchi spéculaire est construit r pour un traçage plus approfondi. Deuxième composant. ( Spéculaire ) signifie l’éblouissement des sources lumineuses. Pour ce faire, les rayons sont dirigés vers toutes les sources lumineuses et les angles formés par ces rayons avec le rayon de traçage réfléchi spéculairement sont déterminés. (r). En réflexion spéculaire, la couleur d’un point sur la surface est déterminée par la couleur de ce qui est réfléchi. Dans le cas le plus simple, le miroir n’a pas sa propre couleur de surface.

4. En réflexion diffuse, seuls les rayons des sources lumineuses sont pris en compte. Les rayons provenant de surfaces spéculairement réfléchissantes sont ignorés. Si le faisceau dirigé vers une source lumineuse donnée est bloqué par un autre objet, alors ce point de l'objet est dans l'ombre. En réflexion diffuse, la couleur d'un point éclairé sur une surface est déterminée par la couleur propre de la surface et par la couleur des sources lumineuses.

5. Pour le transparent (1écart5rage() objets, la dépendance de l'indice de réfraction sur la longueur d'onde n'est généralement pas prise en compte. Parfois, la transparence est généralement modélisée sans réfraction, c'est-à-dire la direction du rayon réfracté. je coïncide avec la direction du faisceau incident.

    Pour prendre en compte l'éclairage des objets par la lumière diffusée par d'autres objets, une composante de fond est introduite bien).

7. Pour compléter le traçage, une certaine valeur d'éclairage limite est introduite, qui ne doit plus contribuer à la couleur résultante, ou le nombre d'itérations est limité.

Selon Modèles blanchis la couleur d'un certain point d'un objet est déterminée par l'intensité totale

JE() = KaIa()C() + IDKd()C() + KsIs() + KrIr() + KtIt()

où λ est la longueur d'onde,

C (λ) - la couleur initiale spécifiée du point objet,

K a, K d, K s, K r ​​​​et K t - coefficients qui prennent en compte les propriétés d'un objet particulier à travers les paramètres d'éclairage de fond, de diffusion diffuse, de spécularité, de réflexion et de transparence,

je un - l'intensité du rétroéclairage,

je d - intensité prise en compte pour la diffusion diffuse,

je s - intensité prise en compte pour la spécularité,

je r - l'intensité du rayonnement venant le long du faisceau réfléchi,

je t - l'intensité du rayonnement venant le long du faisceau réfracté.

Intensité du rétroéclairage (1 UN ) car un objet est généralement une constante. Écrivons des formules pour d'autres intensités. Pour une réflexion diffuse

je d =

je je (λ) - l'intensité du rayonnement je- ro source de lumière, θ je- l'angle entre la normale à la surface de l'objet et la direction vers je- vi source de lumière.

Pour la spécularité :

je d =

r- exposant de un à plusieurs centaines (selon le modèle de Phong), α je-l'angle entre le rayon réfléchi (traçage arrière) et la direction vers la r"ième source lumineuse.

L'intensité du rayonnement passant le long du faisceau réfléchi ( je r), ainsi que le long d'un rayon réfracté ( je t ) , multiplié par un facteur prenant en compte l'atténuation de l'intensité en fonction de la distance parcourue par le faisceau. Ce coefficient s'écrit sous la forme e - dd- la distance parcourue, – paramètre d'atténuation qui prend en compte les propriétés du milieu dans lequel se propage le faisceau.

Pour le rayon primaire, il faut définir la direction qui correspond à la projection sélectionnée. Si la projection est centrale, alors les rayons primaires divergent d'un point commun ; pour une projection parallèle, les rayons primaires sont parallèles. Le rayon peut être spécifié, par exemple, par les coordonnées des points de départ et d'arrivée du segment, les coordonnées du point de départ et la direction, ou d'une autre manière. Le réglage du rayon principal détermine de manière unique la projection scène représentée . Avec le lancer de rayons inverse, aucune transformation de coordonnées n'est nécessaire du tout. La projection est obtenue automatiquement - non seulement plate, mais aussi, par exemple, cylindrique ou sphérique. C'est l'une des manifestations de la polyvalence de la méthode de traçage.

Lors du lancer de rayons, il est nécessaire de déterminer les points d'intersection d'une ligne droite de rayons avec des objets. La méthode permettant de déterminer le point d'intersection dépend du type d'objet dont il s'agit et de la manière dont il est représenté dans un système graphique particulier. Ainsi, par exemple, pour les objets présentés sous forme de polyèdres et de maillages polygonaux, on peut utiliser des méthodes bien connues pour déterminer le point d'intersection d'une droite et d'un plan, considéré en géométrie analytique. Cependant, si la tâche consiste à déterminer l'intersection d'un rayon avec un visage, il est également nécessaire que le point d'intersection trouvé se situe à l'intérieur du contour du visage.

Il existe plusieurs façons de vérifier si un point arbitraire appartient à un polygone. Considérons deux variétés d'une méthode essentiellement identique (Fig. 8.17).

Première façon. Tous les points où le contour coupe la ligne horizontale, qui correspond à la coordonnée Y du point donné, sont trouvés. Les points d'intersection sont triés par ordre croissant des valeurs de coordonnées X. Les paires de points d'intersection forment des segments. Si le point vérifié appartient à l'un des segments (pour cela, les coordonnées X du point donné et les extrémités des segments sont comparées), alors il est interne.

Riz. 8.17. Un point est interne si : a - le point appartient à un segment sécant, b - le nombre d'intersections est impair

Deuxième façon. Un point est déterminé qui se trouve sur la même ligne horizontale que le point testé, et il est nécessaire qu'il se situe à l'extérieur du contour du polygone. Le point externe trouvé et le point de test sont les extrémités du segment horizontal. Les points d'intersection de ce segment avec le contour du polygone sont déterminés. Si le nombre d'intersections est impair, cela signifie que le point de test est interne.

Si un rayon coupe plusieurs objets, le point le plus proche dans la direction du rayon actuel est sélectionné.

Tirons des conclusions générales sur la méthode de lancer de rayons inversé.

Traits positifs

1. La polyvalence de la méthode, son applicabilité à la synthèse d'images de schémas spatiaux assez complexes. Incarne de nombreuses lois de l’optique géométrique. Diverses projections sont simplement mises en œuvre.

2. Même les versions tronquées de cette méthode permettent d'obtenir des images assez réalistes. Par exemple, si vous vous limitez aux seuls rayons primaires (du point de projection), cela entraîne alors la suppression des points invisibles. Le traçage d'un ou deux rayons secondaires donne des ombres, de la spécularité et de la transparence.

3. Toutes les transformations de coordonnées (le cas échéant) sont linéaires, il est donc assez facile de travailler avec des textures.

4. Pour un pixel d'une image raster, vous pouvez tracer plusieurs rayons rapprochés, puis faire la moyenne de leur couleur pour éliminer l'effet d'escalier (anticrénelage).

5. Étant donné que le calcul d'un seul point d'image est effectué indépendamment des autres points, cela peut être utilisé efficacement lors de la mise en œuvre de cette méthode dans des systèmes informatiques parallèles dans lesquels les rayons peuvent être tracés simultanément.

Défauts

1. Problèmes de modélisation de la réflexion et de la réfraction diffuses

2. Pour chaque point de l’image, il est nécessaire d’effectuer de nombreuses opérations de calcul. Le lancer de rayons est l'un des algorithmes de synthèse d'images les plus lents.

Je sais que c'est un peu décevant. Où sont les reflets, les ombres et la belle apparence ? Nous aurons tout, car nous ne faisons que commencer. Mais c'est un bon début : les sphères ressemblent à des cercles, ce qui est mieux que de les faire ressembler à des chats. Ils ne ressemblent pas à des sphères parce que nous avons manqué un élément important qui permet aux humains de déterminer la forme d'un objet : la façon dont il interagit avec la lumière.

Éclairage

La première étape pour ajouter du « réalisme » au rendu de notre scène consiste à simuler l’éclairage. L'éclairage est un sujet incroyablement complexe, je vais donc présenter un modèle très simplifié qui suffit à nos besoins. Certaines parties de ce modèle ne sont même pas une approximation des modèles physiques, elles sont simplement rapides et esthétiques.

Nous commencerons par quelques hypothèses simplificatrices qui nous faciliteront la vie.

Tout d’abord, nous déclarerons que tout l’éclairage est blanc. Cela nous permettra de caractériser toute source lumineuse par un seul nombre réel i, appelé luminositééclairage. Simuler un éclairage coloré n'est pas si compliqué (vous n'avez besoin que de trois valeurs de luminosité, une par canal, et calculer toutes les couleurs et l'éclairage par canal), mais pour nous faciliter la tâche, je ne le ferai pas.

Deuxièmement, nous nous débarrasserons de l'atmosphère. Cela signifie que les lumières ne deviennent pas moins lumineuses, quelle que soit leur portée. L'atténuation de la luminosité en fonction de la distance n'est pas non plus trop difficile à mettre en œuvre, mais par souci de clarté, nous l'ignorerons pour l'instant.

Sources lumineuses

La lumière doit de quelque part acte. Dans cette section, nous définirons trois types différents de sources lumineuses.

Sources ponctuelles

Source ponctuelleémet de la lumière à partir d'un point fixe dans l'espace appelé son position. La lumière est émise uniformément dans toutes les directions ; c'est pourquoi on l'appelle aussi éclairage omnidirectionnel. Par conséquent, une source ponctuelle est entièrement caractérisée par sa position et sa luminosité.

L’ampoule à incandescence est un bon exemple concret de ce qu’est une source lumineuse ponctuelle. Bien qu’une lampe à incandescence n’émette pas de lumière à partir d’un seul point et qu’elle ne soit pas complètement omnidirectionnelle, l’approximation est suffisante.

Définissons un vecteur comme la direction allant du point P de la scène à la source lumineuse Q. Ce vecteur, appelé vecteur de lumière, est simplement égal à . Notez que puisque Q est fixe et que P peut être n’importe quel point de la scène, il sera généralement différent pour chaque point de la scène.

Sources dirigées

Si une source ponctuelle est une bonne approximation d’une ampoule à incandescence, alors qu’est-ce qu’une bonne approximation du Soleil ?

C'est une question délicate et la réponse dépend de ce que vous souhaitez restituer.

À l’échelle du système solaire, le Soleil peut être considéré approximativement comme une source ponctuelle. Après tout, il émet de la lumière à partir d’un point (bien qu’assez grand) et l’émet dans toutes les directions, il répond donc aux deux exigences.

Cependant, si votre scène se déroule sur Terre, ce n'est pas une très bonne approximation. Le soleil est si loin que chaque rayon de lumière aura en fait la même direction (Remarque : cette approximation est valable à l'échelle de la ville, mais pas à de plus grandes distances - en fait, les anciens Grecs étaient capables de calculer le rayon de la Terre avec précision étonnante basée sur différentes directions de la lumière du soleil à différents endroits). Bien qu'il soit possible de s'en approcher en utilisant une source ponctuelle très éloignée de la scène, cette distance et la distance entre les objets de la scène sont si différentes en ampleur que des erreurs de précision numérique peuvent survenir.

Pour de tels cas, nous définirons sources d'éclairage directionnelles. Comme les sources ponctuelles, une source directionnelle a une luminosité, mais contrairement à elles, elle n’a pas de position. Au lieu de cela, il a direction. Vous pouvez le percevoir comme une source ponctuelle infiniment éloignée, brillant dans une certaine direction.

Dans le cas de sources ponctuelles, il faut calculer un nouveau vecteur lumière pour chaque point P de la scène, mais dans ce cas il est donné. Dans la scène avec le Soleil et la Terre, ce sera égal à .

Éclairage d'ambiance

Est-il possible de modéliser n’importe quel éclairage du monde réel en tant que source ponctuelle ou directionnelle ? Presque toujours oui (Remarque : mais cela ne doit pas nécessairement être facile ; l'éclairage d'une zone (pensez à une source derrière un diffuseur) peut être approché par plusieurs sources ponctuelles sur sa surface, mais cela est complexe, plus coûteux en calcul, et les résultats ne sont pas idéaux.). Ces deux types de sources sont-ils suffisants pour notre propos ? Malheureusement non.

Imaginez ce qui se passe sur la Lune. La seule source de lumière importante à proximité est le Soleil. C'est-à-dire que la « moitié avant » de la Lune par rapport au Soleil reçoit toute l'illumination, tandis que la « moitié arrière » est dans l'obscurité totale. Nous voyons cela sous différents angles sur Terre, et cet effet crée ce que nous appelons les « phases » de la Lune.

Cependant, la situation sur Terre est un peu différente. Même les points qui ne reçoivent pas de lumière directement d'une source lumineuse ne sont pas complètement dans l'obscurité (il suffit de regarder le sol sous la table). Comment les rayons lumineux atteignent-ils ces points si la « vue » des sources lumineuses est bloquée par quelque chose ?

Comme je l'ai mentionné dans la section Modèles de couleurs Lorsque la lumière frappe un objet, une partie est absorbée, mais le reste est dispersé dans la scène. Cela signifie que la lumière peut provenir non seulement de sources lumineuses, mais également d'autres objets qui la reçoivent des sources lumineuses et la diffusent en retour. Mais pourquoi s'arrêter là ? L'éclairage diffus, à son tour, tombe sur un autre objet, une partie est absorbée et une partie est à nouveau diffusée dans la scène. A chaque réflexion, la lumière perd un peu de son éclat, mais en théorie vous pouvez continuer à l'infini(Remarque : en fait non, car la lumière est de nature quantique, mais assez proche.)

Cela signifie que vous devez tenir compte de la source de lumière chaque objet. Comme vous pouvez l'imaginer, cela augmente considérablement la complexité de notre modèle, nous n'emprunterons donc pas cette voie (Remarque : mais vous pouvez au moins rechercher Global Illumination sur Google et regarder les superbes images.).

Mais nous ne voulons toujours pas que chaque objet soit directement éclairé ou complètement sombre (sauf si nous restituons un modèle du système solaire). Pour surmonter cet obstacle, nous définirons un troisième type de sources lumineuses, appelées éclairage ambiant, qui se caractérise uniquement par la luminosité. On pense qu’il apporte la contribution inconditionnelle de l’éclairage à chaque point de la scène. Il s’agit d’une simplification considérable de l’interaction extrêmement complexe entre les lumières et les surfaces de la scène, mais cela fonctionne.

Éclairage d'un point

En général, une scène aura une source de lumière ambiante (car la lumière ambiante n'a qu'une valeur de luminosité, et un nombre quelconque d'entre elles se combineront trivialement en une seule source de lumière ambiante) et un nombre arbitraire de lumières ponctuelles et directionnelles.

Pour calculer l’éclairage d’un point, il suffit de calculer la quantité de lumière apportée par chaque source et de les additionner pour obtenir un nombre représentant la quantité totale d’éclairage reçue par le point. Nous pouvons ensuite multiplier la couleur de la surface à ce stade par ce nombre pour obtenir la couleur correctement éclairée.

Alors, que se passe-t-il lorsqu'un rayon de lumière ayant une direction provenant d'une source directionnelle ou ponctuelle atteint le point P d'un objet de notre scène ?

Intuitivement, nous pouvons classer les objets en deux classes générales en fonction de leur comportement avec la lumière : « mat » et « brillant ». Puisque la plupart des objets qui nous entourent peuvent être considérés comme « mats », nous commencerons par eux.

Diffusion diffuse

Lorsqu'un rayon de lumière tombe sur un objet mat, en raison de l'irrégularité de sa surface au niveau microscopique, il réfléchit le rayon dans la scène de manière uniforme dans toutes les directions, c'est-à-dire qu'une réflexion « dispersée » (« diffuse ») est obtenue .

Pour le vérifier, regardez attentivement un objet mat, par exemple un mur : si vous vous déplacez le long du mur, sa couleur ne change pas. Autrement dit, la lumière que vous voyez réfléchie par un objet est la même, quel que soit l’endroit où vous regardez l’objet.

D’autre part, la quantité de lumière réfléchie dépend de l’angle entre le faisceau lumineux et la surface. Ceci est intuitivement clair - l'énergie transférée par le faisceau, en fonction de l'angle, doit être répartie sur une surface plus petite ou plus grande, c'est-à-dire que l'énergie par unité de surface réfléchie dans la scène sera respectivement supérieure ou inférieure :

Pour exprimer cela mathématiquement, caractérisons l'orientation d'une surface par sa vecteur normal. Un vecteur normal, ou simplement « normal », est un vecteur perpendiculaire à la surface en un certain point. C'est aussi un vecteur unitaire, c'est-à-dire que sa longueur est 1. Nous appellerons ce vecteur .

Modélisation de la réflexion diffuse

Ainsi, un rayon de lumière avec une direction et une luminosité tombe sur une surface avec une normale. Quelle partie est réfléchie sur la scène en fonction de , et ?

Pour une analogie géométrique, considérons la luminosité de la lumière comme la « largeur » du faisceau. Son énergie est répartie sur une surface de taille . Lorsque et ont la même direction, c'est-à-dire que le faisceau est perpendiculaire à la surface, ce qui signifie que l'énergie réfléchie par unité de surface est égale à l'énergie incidente par unité de surface ;< . С другой стороны, когда угол между и приближается к , приближается к , то есть энергия на единицу площади приближается к 0; . Но что происходит в промежутках?

La situation est illustrée dans le schéma ci-dessous. Nous savons, et ; J'ai ajouté des angles et , ainsi que des points , et , pour faciliter les notes associées à ce diagramme.

Puisque techniquement un faisceau lumineux n’a pas de largeur, on supposera donc que tout se passe sur une surface plane infinitésimale de la surface. Même s’il s’agit de la surface d’une sphère, la zone en question est si infinitésimale qu’elle est presque plate par rapport à la taille de la sphère, tout comme la Terre paraît plate à petite échelle.

Un rayon de lumière d'une largeur frappe une surface en un point selon un angle. La normale en un point est égale à , et l'énergie transférée par le faisceau est répartie sur . Nous devons calculer.

L'un des angles est égal à , et l'autre est égal à . Alors le troisième angle est égal à . Mais il convient de noter qu’ils forment également un angle droit, c’est-à-dire qu’ils doivent également l’être. Ainsi, :

Regardons un triangle. Ses angles sont égaux à , et . Le côté est égal et le côté est égal.

Et maintenant... la trigonométrie vient à la rescousse ! Par définition ; remplacez par , et par , et nous obtenons


ce qui est converti en
Nous avons presque terminé. - est l'angle entre et , c'est-à-dire qu'il peut être exprimé comme
Et enfin
Nous avons donc une équation très simple qui relie la partie réfléchie de la lumière à l’angle entre la normale à la surface et la direction de la lumière.

Notez qu'à des angles plus grands, la valeur devient négative. Si nous utilisons cette valeur sans hésitation, le résultat sera des sources lumineuses soustractif lumière. Cela n'a aucun sens physique ; un angle plus grand signifie simplement que la lumière atteint réellement dos surface, et ne contribue pas à l’éclairage du point éclairé. Autrement dit, s’il devient négatif, alors nous le considérons égal à .

Équation de réflexion diffuse

Nous pouvons maintenant formuler une équation pour calculer la quantité totale de lumière reçue par un point de normale dans une scène avec une luminance ambiante et des sources de lumière ponctuelles ou directionnelles avec une luminance et des vecteurs de lumière soit connus (pour les sources directionnelles) soit calculés pour P (pour sources ponctuelles) :
Il convient de répéter encore une fois que les termes dans lesquels ne doivent pas être ajoutés à l'éclairage du point.

Normales de sphère

La seule petite chose qui manque ici est d’où viennent les normales ?

Cette question est bien plus délicate qu’il n’y paraît, comme nous le verrons dans la deuxième partie de l’article. Heureusement, pour le cas que nous considérons, il existe une solution très simple : le vecteur normal de tout point de la sphère se trouve sur une droite passant par le centre de la sphère. Autrement dit, si le centre de la sphère est , alors la direction de la normale au point est égale à :

Pourquoi ai-je écrit « direction normale » et non « normal » ? En plus d'être perpendiculaire à la surface, la normale doit être un vecteur unitaire ; cela serait vrai si le rayon de la sphère était égal à , ce qui n'est pas toujours vrai. Pour calculer la normale elle-même, nous devons diviser le vecteur par sa longueur, obtenant ainsi la longueur :


Ceci présente surtout un intérêt théorique car l'équation d'éclairage écrite ci-dessus implique une division par , mais une bonne approche serait de créer de « vraies » normales ; Cela facilitera notre travail à l’avenir.

Rendu avec réflexion diffuse

Traduisons tout cela en pseudocode. Tout d'abord, ajoutons quelques lumières à la scène :

Lumière ( type = intensité ambiante = 0,2 ) lumière ( type = intensité ponctuelle = 0,6 position = (2, 1, 0) ) lumière ( type = intensité directionnelle = 0,2 direction = (1, 4, 4) )
Notez que la luminosité se résume commodément à , car l'équation d'éclairage implique qu'aucun point ne peut avoir une luminosité supérieure à un. Cela signifie que nous ne nous retrouverons pas avec des zones « trop exposées ».

L'équation d'éclairage est assez simple à convertir en pseudocode :

ComputeLighting(P, N) ( i = 0,0 pour la lumière dans la scène.Lights ( if light.type == ambient ( i += light.intensity ) else ( if light.type == point L = light.position - P sinon L = light.direction n_dot_l = dot(N, L) si n_dot_l > 0 i += light.intensity*n_dot_l/(length(N)*length(L)) ) return i )
Et il ne reste plus qu'à utiliser ComputeLighting dans TraceRay. Nous remplacerons la chaîne qui renvoie la couleur de la sphère

Retourner le fichier close_sphere.color
à ce fragment :

P = O + close_t*D # calculer l'intersection N = P - close_sphere.center # calculer la normale de la sphère au point d'intersection N = N / length(N) return close_sphere.color*ComputeLighting(P, N)
Juste pour s'amuser, ajoutons une grosse sphère jaune :

Sphère ( couleur = (255, 255, 0) # Centre jaune = (0, -5001, 0) rayon = 5000 )
Nous lançons le moteur de rendu, et voilà, les sphères commencent enfin à ressembler à des sphères !

Mais attendez, comment la grosse sphère jaune s’est-elle transformée en un sol plat jaune ?

Ce n'était pas le cas, c'est juste qu'elle est si grande par rapport aux trois autres et que la caméra est si proche d'elle qu'elle semble plate. Tout comme notre planète semble plate lorsque nous nous trouvons à sa surface.

Réflexion sur une surface lisse

Tournons maintenant notre attention vers les objets « brillants ». Contrairement aux objets « mats », les objets « brillants » changent d’apparence lorsque vous les regardez sous différents angles.

Prenons une boule de billard ou une voiture juste lavée. Ces objets présentent un modèle particulier de propagation de la lumière, généralement avec des zones lumineuses qui semblent bouger lorsque vous les contournez. Contrairement aux objets mats, la façon dont vous percevez la surface de ces objets dépend en réalité de votre point de vue.

Notez que les boules de billard rouges restent rouges si vous reculez de quelques pas, mais la tache blanche et brillante qui leur donne leur aspect « brillant » semble bouger. Cela signifie que le nouvel effet ne remplace pas la réflexion diffuse, mais la complète.

Pourquoi cela se produit-il ? Nous pouvons commencer par pourquoi Pas se produit sur les objets mats. Comme nous l'avons vu dans la section précédente, lorsqu'un rayon de lumière frappe la surface d'un objet mat, il est uniformément diffusé dans la scène dans toutes les directions. Intuitivement, cela est dû à l'irrégularité de la surface de l'objet, c'est-à-dire qu'au niveau microscopique, il ressemble à de nombreuses petites surfaces dirigées dans des directions aléatoires :

Mais que se passe-t-il si la surface n’est pas si inégale ? Prenons l'autre extrême : un miroir parfaitement poli. Lorsqu’un rayon lumineux frappe un miroir, il est réfléchi dans une seule direction, symétrique de l’angle d’incidence par rapport à la normale du miroir. Si nous nommons la direction de la lumière réfléchie et convenons qu'elle indique sur source de lumière, nous obtenons la situation suivante :

Selon le degré de « polissage » de la surface, elle ressemble plus ou moins à un miroir ; c'est-à-dire que nous obtenons une réflexion « miroir » (réflexion spéculaire, du latin « spéculum », c'est-à-dire « miroir »).

Pour un miroir parfaitement poli, le rayon lumineux incident est réfléchi dans une seule direction. C'est ce qui nous permet de voir clairement les objets dans le miroir : pour chaque rayon incident il y a un seul rayon réfléchi. Mais tous les objets ne sont pas parfaitement polis ; bien que la majeure partie de la lumière soit réfléchie dans la direction, une partie est réfléchie dans des directions proches de ; plus on se rapproche de , plus la lumière est réfléchie dans cette direction. La « brillance » d'un objet détermine la rapidité avec laquelle la lumière réfléchie diminue à mesure qu'elle s'éloigne de :

Nous souhaitons déterminer la quantité de lumière réfléchie vers notre point de vue (car c'est la lumière que nous utilisons pour déterminer la couleur de chaque point). Si le « vecteur de vue » pointe vers la caméra et que l’angle est entre et , alors voici ce que nous avons :

Quand toute la lumière est réfléchie. La lumière n'est pas réfléchie. Comme pour la réflexion diffuse, nous avons besoin d'une expression mathématique pour déterminer ce qui se passe à des valeurs intermédiaires de .

Simulation de réflexion "miroir"

Rappelez-vous comment j'ai mentionné plus tôt que tous les modèles ne sont pas basés sur des modèles physiques ? Eh bien, en voici un exemple. Le modèle ci-dessous est arbitraire, mais il est utilisé car il est facile à calculer et semble beau.

Prenons-le. Il a de bonnes propriétés : , , et les valeurs diminuent progressivement de à le long d'une très belle courbe :

Répond à toutes les exigences de la fonction miroir, alors pourquoi ne pas l'utiliser ?

Mais il nous manque encore un détail. Dans cette formulation, tous les objets brillent de la même manière. Comment puis-je modifier l’équation pour produire différents degrés de brillance ?

N'oubliez pas que cette brillance est une mesure de la rapidité avec laquelle la fonction de réflectance diminue à mesure que vous augmentez. Un moyen très simple d’obtenir différentes courbes de lumière consiste à calculer la puissance d’un exposant positif. Puisque, c'est une évidence ; c'est-à-dire qu'il se comporte exactement de la même manière que « déjà ». Voici pour différentes valeurs :

Plus la valeur est grande, plus la caractéristique devient « étroite » au voisinage de et plus l'objet apparaît brillant.

Habituellement appelé indicateur de réflectance, et c'est une propriété de la surface. Étant donné que le modèle n'est pas basé sur la réalité physique, les valeurs ne peuvent être déterminées que par essais et erreurs, c'est-à-dire en ajustant les valeurs jusqu'à ce qu'elles commencent à paraître « naturelles » (Remarque : pour utiliser un modèle basé sur la physique, voir fonction de réflectance à double faisceau (DRF) )).

Rassemblons tout cela. Le faisceau tombe sur la surface en un point où la normale est égale à et l'indice de réflexion est . Quelle quantité de lumière sera réfléchie dans la direction de visualisation ?

Nous avons déjà décidé que cette valeur est égale à , où est l'angle entre et , qui à son tour se reflète par rapport à . Autrement dit, la première étape consistera à calculer à partir de et .

On peut décomposer en deux vecteurs et , tel que , où est parallèle et perpendiculaire :

Il s'agit d'une projection sur ; par les propriétés du produit scalaire et basé sur le fait que , la longueur de cette projection est égale à . Nous avons donc déterminé que ce serait parallèle.

Parce que nous pouvons l'obtenir tout de suite.

Maintenant, regardons ; puisqu'il est symétrique par rapport à , sa composante parallèle est la même que celle de , et sa composante perpendiculaire est opposée à celle de ; c'est :

En remplaçant les expressions obtenues précédemment, on obtient


et en simplifiant un peu, on obtient

La signification du reflet « miroir »

Nous sommes maintenant prêts à écrire l’équation de réflexion « miroir » :

Comme pour l’éclairage diffus, cela peut être négatif et là encore, nous devrions l’ignorer. De plus, tous les objets ne doivent pas nécessairement être brillants ; pour de tels objets (que nous représenterons à travers ) la valeur de « spécularité » ne sera pas du tout calculée.

Rendu avec reflets "miroir"

Ajoutons à la scène les reflets "miroir" sur lesquels nous avons travaillé. Commençons par apporter quelques modifications à la scène elle-même :

Sphère ( centre = (0, -1, 3) rayon = 1 couleur = (255, 0, 0) # Rouge spéculaire = 500 # Brillant ) sphère ( centre = (-2, 1, 3) rayon = 1 couleur = ( 0, 0, 255) # Bleu spéculaire = 500 # Brillant ) sphère ( centre = (2, 1, 3) rayon = 1 couleur = (0, 255, 0) # Vert spéculaire = 10 # Un peu brillant ) sphère ( couleur = (255, 255, 0) # Centre jaune = (0, -5001, 0) rayon = 5000 spéculaire = 1000 # Très brillant )
Dans le code, nous devons modifier ComputeLighting pour qu'il calcule la valeur de "spécularité" si nécessaire et l'ajoute à l'éclairage global. Notez qu'il nécessite maintenant et :

ComputeLighting(P, N, V, s) ( i = 0,0 pour la lumière dans la scène.Lights ( si light.type == ambient ( i += light.intensity ) else ( if light.type == point L = light.position - P else L = light.direction # Diffus n_dot_l = dot(N, L) si n_dot_l > 0 i += light.intensity*n_dot_l/(length(N)*length(L)) # Spécularité si s != -1 ( R = 2*N*dot(N, L) - L r_dot_v = dot(R, V) si r_dot_v >
Enfin, nous devons modifier TraceRay pour qu'il transmette les nouveaux paramètres ComputeLighting. évident; il est extrait des données de la sphère. Mais qu'en est-il ? est un vecteur pointant de l'objet vers la caméra. Heureusement, dans TraceRay, nous avons déjà un vecteur dirigé de la caméra vers l'objet - c'est la direction du rayon tracé ! Autrement dit, c'est simple.

Voici le nouveau code TraceRay avec réflexion "miroir" :

TraceRay(O, D, t_min, t_max) ( plus proche_t = inf plus proche_sphère = NULL pour la sphère dans scene.Spheres ( t1, t2 = IntersectRaySphere(O, D, sphère) si t1 dans et t1< closest_t closest_t = t1 closest_sphere = sphere if t2 in and t2 < closest_t closest_t = t2 closest_sphere = sphere } if closest_sphere == NULL return BACKGROUND_COLOR P = O + closest_t*D # Вычисление пересечения N = P - closest_sphere.center # Вычисление нормали сферы в точке пересечения N = N / length(N) return closest_sphere.color*ComputeLighting(P, N, -D, sphere.specular) }
Et voici notre récompense pour tout ce jonglage avec les vecteurs :

Ombres

Là où il y a de la lumière et des objets, il doit aussi y avoir des ombres. Alors où sont nos ombres ?

Commençons par une question plus fondamentale. Pourquoi devraitêtre des ombres ? Les ombres apparaissent là où il y a de la lumière, mais ses rayons ne peuvent pas atteindre l'objet car il y a un autre objet sur leur chemin.

Vous remarquerez que dans la section précédente, nous nous sommes intéressés aux angles et aux vecteurs, mais nous n'avons pris en compte que la source de lumière et le point que nous devons colorer, et avons complètement ignoré tout ce qui se passe dans la scène - comme un objet qui entre dans la scène. le chemin.

Au lieu de cela, nous devons ajouter une logique disant " s'il y a un objet entre le point et la source, alors il n'est pas nécessaire d'ajouter de l'éclairage provenant de cette source".

Nous souhaitons souligner les deux cas suivants :

Il semble que nous disposions de tous les outils nécessaires pour y parvenir.

Commençons par la source directionnelle. Nous le savons ; c'est le point qui nous intéresse. Nous le savons ; cela fait partie de l’identification de la source lumineuse. Ayant et , on peut définir un rayon, à savoir , qui passe d'un point à une source lumineuse infiniment éloignée. Ce rayon coupe-t-il un autre objet ? Sinon, il n'y a rien entre le point et la source, c'est-à-dire que nous pouvons calculer l'éclairage de cette source et l'ajouter à l'éclairage total. Si cela se croise, alors nous ignorons cette source.

Nous savons déjà comment calculer l'intersection la plus proche entre un rayon et une sphère ; nous l'utilisons pour tracer les rayons de la caméra. Nous pouvons à nouveau l'utiliser pour calculer l'intersection la plus proche entre le rayon lumineux et le reste de la scène.

Cependant, les paramètres sont légèrement différents. Au lieu de partir de la caméra, les rayons sont émis depuis le . La direction n'est pas , mais . Et nous nous intéressons aux intersections avec tout ce qui se trouve après à une distance infinie ; cela signifie que et .

Nous pouvons traiter les sources ponctuelles de manière très similaire, à deux exceptions près. Premièrement, , n’est pas spécifié, mais il est très facile de calculer à partir de la position source et . Deuxièmement, nous nous intéressons à toutes les intersections à partir de , mais seulement jusqu'à (sinon, les objets pour la source de lumière pourrait créer des ombres !) ; c'est-à-dire dans ce cas et .

Il existe un cas limite que nous devons considérer. Prenons une poutre. Si nous recherchons des intersections commençant à , alors nous trouverons très probablement à , car c'est réellement sur la sphère, et ; en d'autres termes, chaque objet projettera des ombres sur lui-même (Remarque : plus précisément, nous voulons éviter une situation dans laquelle un point, et non l'objet entier, projette une ombre sur lui-même ; un objet avec une forme plus complexe qu'une sphère (c'est-à-dire n'importe quel objet concave) peut projeter de véritables ombres sur lui-même !

La façon la plus simple de résoudre ce problème consiste à utiliser small comme limite inférieure au lieu de . Géométriquement, nous voulons nous assurer que le rayon part un peu loin de la surface, c'est-à-dire près, mais pas exactement. Autrement dit, pour les sources directionnelles, l'intervalle sera , et pour les sources ponctuelles - .

Rendu avec des ombres

Transformons cela en pseudocode.

Dans la version précédente, TraceRay calculait l'intersection rayon-sphère la plus proche, puis calculait l'éclairage à l'intersection. Nous devons extraire le code d'intersection le plus proche car nous voulons le réutiliser pour calculer les ombres :

ClosestIntersection(O, D, t_min, t_max) ( closest_t = inf close_sphere = NULL pour la sphère dans scene.Spheres ( t1, t2 = IntersectRaySphere(O, D, sphère) si t1 dans et t1< closest_t closest_t = t1 closest_sphere = sphere if t2 in and t2 < closest_t closest_t = t2 closest_sphere = sphere } return closest_sphere, closest_t }
En conséquence, TraceRay est beaucoup plus simple :

TraceRay(O, D, t_min, t_max) ( la plus proche_sphère, la plus proche_t = ClosestIntersection(O, D, t_min, t_max) si la plus proche_sphère == NULL renvoie BACKGROUND_COLOR P = O + la plus proche_t*D # Calcule l'intersection N = P - la plus proche_sphère.centre # Calculer la sphère normale à l'intersection N = N / length(N) return close_sphere.color*ComputeLighting(P, N, -D,sphere.specular) )
Nous devons maintenant ajouter une vérification d'ombre à ComputeLighting :

ComputeLighting(P, N, V, s) ( i = 0,0 pour la lumière dans la scène.Lights ( if light.type == ambient ( i += light.intensity ) else ( if light.type == point ( L = light. position - P t_max = 1 ) else ( L = light.direction t_max = inf ) # Vérifier l'ombre shadow_sphere, shadow_t = ClosestIntersection(P, L, 0.001, t_max) if shadow_sphere != NULL continuer # Diffusion n_dot_l = dot(N, L ) if n_dot_l > 0 i += light.intensity*n_dot_l/(length(N)*length(L)) # Spécularité if s != -1 ( R = 2*N*dot(N, L) - L r_dot_v = dot(R, V) si r_dot_v > 0 i += light.intensity*pow(r_dot_v/(length(R)*length(V)), s) ) ) ) return i )
Voici à quoi ressemblera notre scène restituée :


Code source et démo fonctionnelle >>

Maintenant Nous faisons déjà quelque chose.

Réflexion

Nous avons des objets brillants. Mais est-il possible de créer des objets qui se comportent réellement comme des miroirs ? Bien entendu, leur mise en œuvre dans un traceur de rayons est en réalité très simple, mais cela peut paraître déroutant au premier abord.

Voyons comment fonctionnent les miroirs. Lorsque nous nous regardons dans un miroir, nous voyons des rayons de lumière réfléchis par le miroir. Les rayons lumineux sont réfléchis symétriquement par rapport à la normale à la surface :

Disons que nous traçons un rayon et que l'intersection la plus proche est un miroir. De quelle couleur est le rayon de lumière ? Évidemment, ce n’est pas la couleur du miroir, mais n’importe quelle couleur du rayon réfléchi. Tout ce que nous avons à faire est de calculer la direction du rayon réfléchi et de déterminer quelle était la couleur de la lumière provenant de cette direction. Si seulement nous avions une fonction qui renvoie, pour un rayon donné, la couleur de la lumière tombant de cette direction...

Oh attendez, nous en avons un : il s'appelle TraceRay.

Nous commençons donc par la boucle principale TraceRay pour voir ce que "voit" le rayon émis par la caméra. Si TraceRay détermine que le rayon voit un objet réfléchissant, il lui suffit alors de calculer la direction du rayon réfléchi et de s'appeler... lui-même.

À ce stade, je vous suggère de relire les trois derniers paragraphes jusqu’à ce que vous les compreniez. Si c'est la première fois que vous lisez sur le lancer de rayons récursif, vous devrez peut-être le lire plusieurs fois et réfléchir un peu avant de réellement le faire. tu comprendras.

Prends ton temps, j'attendrai.

Maintenant que l'euphorie de ce merveilleux moment Eurêka ! j'ai un peu dormi, formalisons un peu cela.

La chose la plus importante dans tous les algorithmes récursifs est d’éviter une boucle infinie. Cet algorithme a une condition de sortie évidente : soit lorsque le faisceau touche un objet non réfléchissant, soit lorsqu'il ne touche rien. Mais il existe un cas simple dans lequel on peut se retrouver pris dans une boucle infinie : l’effet couloir sans fin. Cela se manifeste lorsque vous placez un miroir devant un autre miroir et que vous y voyez des copies infinies de vous-même !

Il existe de nombreuses façons de prévenir ce problème. Nous entrerons limite de récursion algorithme; il contrôlera la « profondeur » à laquelle il pourra aller. Appelons-le. Quand , on voit des objets, mais sans reflets. Quand nous voyons certains objets et les reflets de certains objets. Quand on voit certains objets, les reflets de certains objets et reflets de certains reflets de certains objets. Et ainsi de suite. En général, il ne sert à rien d’aller plus loin que 2-3 niveaux, car à ce stade la différence est à peine perceptible.

Nous allons créer une autre distinction. « Réflectivité » ne signifie pas nécessairement « oui ou non » : les objets peuvent être en partie réfléchissants et en partie colorés. Nous attribuerons à chaque surface un numéro de à , qui détermine sa réflectivité. Après quoi nous mélangerons la couleur localement éclairée et la couleur réfléchie proportionnellement à ce nombre.

Enfin, nous devons décider quels paramètres l'appel récursif à TraceRay doit recevoir ? Le rayon part de la surface de l'objet, un point. La direction du rayon est la direction de la lumière réfléchie par ; dans TraceRay, nous avons , c'est-à-dire la direction de la caméra vers , opposée au mouvement de la lumière, c'est-à-dire que la direction du rayon réfléchi sera réfléchie par rapport à . Semblable à ce qui se passe avec les ombres, nous ne voulons pas que les objets se reflètent. Nous voulons voir les objets réfléchis, quelle que soit leur distance, donc . Et enfin, la limite de récursion est inférieure d’une unité à la limite de récursion dans laquelle nous nous trouvons actuellement.

Rendu avec réflexion

Ajoutons la réflexion au code du traceur de rayons.

Comme précédemment, nous changeons tout d’abord de scène :

Sphère ( centre = (0, -1, 3) rayon = 1 couleur = (255, 0, 0) # Rouge spéculaire = 500 # Brillant réfléchissant = 0,2 # Légèrement réfléchissant ) sphère ( centre = (-2, 1, 3) rayon = 1 couleur = (0, 0, 255) # Bleu spéculaire = 500 # Réfléchissant brillant = 0,3 # Légèrement plus réfléchissant ) sphère ( centre = (2, 1, 3) rayon = 1 couleur = (0, 255, 0) # Vert spéculaire = 10 # Un peu brillant réfléchissant = 0,4 # Encore plus réfléchissant ) sphère ( couleur = (255, 255, 0) # Centre jaune = (0, -5001, 0) rayon = 5000 spéculaire = 1000 # Réfléchissant très brillant = 0,5# Demi-réfléchissant)
Nous utilisons la formule du « rayon de réflexion » à plusieurs endroits, afin de pouvoir nous en débarrasser. Il reçoit le rayon et la normale, renvoyant le relatif réfléchi :

ReflectRay(R, N) ( return 2*N*dot(N, R) - R; )
Le seul changement dans ComputeLighting consiste à remplacer l'équation de réflexion par un appel à ce nouveau ReflectRay .

Un petit changement a été apporté à la méthode main - nous devons transmettre au TraceRay de niveau supérieur une limite de récursion :

Couleur = TraceRay (O, D, 1, inf, récursion_profondeur)
La constante récursion_profondeur peut être définie sur une valeur raisonnable, telle que 3 ou 5.

Les seuls changements importants se produisent vers la fin de TraceRay, où nous calculons les réflexions de manière récursive :

TraceRay(O, D, t_min, t_max, profondeur) ( la plus proche_sphère, la plus proche_t = ClosestIntersection(O, D, t_min, t_max) si la plus proche_sphère == NULL renvoie BACKGROUND_COLOR # Calcule la couleur locale P = O + la plus proche_t*D # Calcule le point d'intersection N = P - close_sphere.center # Calcule la normale à la sphère au point d'intersection N = N / length(N) local_color = close_sphere.color*ComputeLighting(P, N, -D,sphere.specular) # Si nous avons atteint le limite de récursion ou l'objet n'est pas réfléchissant, alors c'est fini r = close_sphere.reflective si profondeur<= 0 or r <= 0: return local_color # Вычисление отражённого цвета R = ReflectRay(-D, N) reflected_color = TraceRay(P, R, 0.001, inf, depth - 1) return local_color*(1 - r) + reflected_color*r }
Laissez les résultats parler d’eux-mêmes :

Pour mieux comprendre la limite de profondeur de récursion, examinons de plus près le rendu avec :

Et voici la même vue zoomée de la même scène, cette fois rendue avec :

Comme vous pouvez le voir, la différence est de savoir si nous voyons des reflets de reflets d'objets, ou uniquement des reflets d'objets.

Caméra personnalisée

Au tout début de notre discussion sur le lancer de rayons, nous avons fait deux hypothèses importantes : la caméra est fixée et pointée vers elle, et la direction « vers le haut » est . Dans cette section, nous allons supprimer ces restrictions afin de pouvoir placer la caméra n'importe où dans la scène et la pointer dans n'importe quelle direction.

Commençons par le poste. Vous avez peut-être remarqué qu'il n'est utilisé qu'une seule fois dans le pseudocode : comme point de départ des rayons émanant de la caméra dans la méthode de niveau supérieur. Si nous voulons changer la position de la caméra. Que la seule chose ce que vous devez faire est d'utiliser une valeur différente pour .

Le changement affecte-t-il dispositions sur direction des rayons ? En aucun cas. La direction des rayons est le vecteur passant de la caméra au plan de projection. Lorsque nous déplaçons la caméra, le plan de projection se déplace avec la caméra, ce qui signifie que leurs positions relatives ne changent pas.

Tournons maintenant notre attention vers la direction. Disons que nous avons une matrice de rotation qui tourne dans la direction de visualisation souhaitée et - dans la direction "haut" souhaitée (et comme il s'agit d'une matrice de rotation, par définition elle doit faire ce qui est requis pour ). Position La caméra ne change pas si vous faites simplement pivoter la caméra. Mais la direction change, elle subit simplement la même rotation que l'ensemble de la caméra. Autrement dit, si nous avons une direction et une matrice de rotation, alors la rotation est simple.

Seule la fonction de niveau supérieur change :

Pour x dans [-Cw/2, Cw/2] ( pour y dans [-Ch/2, Ch/2] ( D = camera.rotation * CanvasToViewport(x, y) color = TraceRay(camera.position, D, 1, inf) toile.PutPixel(x, y, couleur) ) )
Voici à quoi ressemble notre scène vue depuis une position et une orientation différentes :

Où aller ensuite

Nous terminerons la première partie de l'article par un bref aperçu de quelques sujets intéressants que nous n'avons pas explorés.

Optimisation

Comme indiqué dans l’introduction, nous avons examiné la manière la plus compréhensible d’expliquer et de mettre en œuvre diverses possibilités. Par conséquent, le traceur de rayons est entièrement fonctionnel, mais pas particulièrement rapide. Voici quelques idées que vous pouvez explorer par vous-même pour accélérer votre traceur. Juste pour vous amuser, essayez de mesurer le temps d'exécution avant et après leur implémentation. Vous serez très surpris !

Parallélisation

Le moyen le plus évident d’accélérer un traceur de rayons est de tracer plusieurs rayons à la fois. Étant donné que chaque rayon émanant de la caméra est indépendant de tous les autres et que la plupart des structures sont en lecture seule, nous pouvons tracer un rayon vers chaque cœur de processeur sans trop de difficulté ou de complexité en raison de problèmes de synchronisation.

En fait, les traceurs de rayons appartiennent à une classe d'algorithmes appelés extrêmement parallélisable précisément parce que leur nature même permet de les paralléliser très facilement.

Mise en cache des valeurs

Considérez les valeurs calculées par IntersectRaySphere, où le traceur de rayons passe généralement la plupart de son temps :

K1 = point(D, D) k2 = 2*point(OC, D) k3 = point(OC, OC) - r*r
Certaines de ces valeurs sont constantes tout au long de la scène - une fois que vous savez comment les sphères sont positionnées, r*r et dot(OC, OC) ne changent plus. Vous pouvez les calculer une fois lors du chargement de la scène et les stocker dans les sphères elles-mêmes ; il vous suffira de les compter si les sphères doivent se déplacer dans l'image suivante. dot(D, D) est une constante pour un rayon donné, vous pouvez donc le calculer dans ClosestIntersection et le transmettre à IntersectRaySphere .

Optimisations des ombres

Si un point sur un objet est dans l'ombre par rapport à la source lumineuse parce qu'un autre objet est détecté en cours de route, alors il y a une forte probabilité que le point adjacent soit également dans l'ombre par rapport à la source lumineuse à cause du même objet ( cela s'appelle cohérence des ombres):

Autrement dit, lorsque nous recherchons des objets entre un point et une source de lumière, nous pouvons d'abord vérifier si le dernier objet qui a projeté une ombre sur le point précédent par rapport à la même source de lumière projette une ombre sur le point actuel. Si tel est le cas, nous pouvons terminer ; sinon, nous continuons simplement à vérifier les objets restants de la manière habituelle.

De même, lors du calcul de l'intersection entre un rayon de lumière et des objets dans la scène, nous n'avons pas réellement besoin de l'intersection la plus proche - sachant simplement qu'il existe au moins une intersection. Nous pouvons utiliser une version spéciale de ClosestIntersection qui renvoie le résultat dès qu'elle trouve la première intersection (et pour cela, nous devons calculer et renvoyer non pas close_t, mais juste une valeur booléenne).

Structures spatiales

Calculer l'intersection d'un rayon avec chaque sphère est un gros gaspillage de ressources. Il existe de nombreuses structures de données qui vous permettent de supprimer des groupes entiers d'objets d'un seul coup sans avoir à calculer des intersections individuelles.

Un examen détaillé de telles structures dépasse le cadre de notre article, mais l’idée générale est la suivante : supposons que nous ayons plusieurs sphères proches les unes des autres. On peut calculer le centre et le rayon de la plus petite sphère contenant toutes ces sphères. Si le rayon ne coupe pas cette sphère limite, vous pouvez être sûr qu'il ne coupe aucune sphère contenue, et cela peut être fait en une seule vérification d'intersection. Bien sûr, s’il coupe une sphère, nous devons encore vérifier s’il coupe l’une des sphères qu’il contient.

Vous pouvez en apprendre davantage à ce sujet en lisant sur hiérarchie des volumes englobants.

Sous-échantillonnage

Voici un moyen simple de rendre un traceur de rayons plus rapide : calculez des fois moins de pixels !

Disons que nous traçons des rayons pour les pixels et , et qu'ils tombent sur un objet. Vous pouvez logiquement supposer que le rayon du pixel tombera également sur le même objet, ignorer la recherche initiale des intersections avec la scène entière et passer directement au calcul de la couleur à ce stade.

Si vous effectuez cette opération dans les directions horizontale et verticale, vous pouvez effectuer au maximum 75 % de moins de calculs d'intersection rayons-scène initiaux.

Bien entendu, cela peut facilement laisser passer un objet très subtil : contrairement à ceux évoqués précédemment, il s’agit d’une « mauvaise » optimisation, car les résultats de son utilisation ne sont pas identiqueà ce que nous aurions reçu sans cela ; dans un sens, nous « trichons » sur ces économies. L’astuce consiste à deviner comment sauvegarder correctement tout en garantissant des résultats satisfaisants.

Autres primitives

Dans les sections précédentes, nous avons utilisé des sphères comme primitives car elles sont faciles à manipuler d'un point de vue mathématique. Mais après avoir atteint cet objectif, vous pouvez simplement ajouter d'autres primitives.

Notez que du point de vue TraceRay, n'importe quel objet peut être utilisé à condition que seules deux valeurs doivent être calculées pour lui : la valeur de l'intersection la plus proche entre le rayon et l'objet et la normale au point d'intersection. Tout le reste dans le traceur de rayons est indépendant du type d'objet.

Les triangles sont un bon choix. Vous devez d'abord calculer l'intersection entre le rayon et le plan contenant le triangle, et s'il y a une intersection, déterminer si le point est à l'intérieur du triangle.

Géométrie des blocs constructifs

Il existe un type d’objet très intéressant et relativement simple à mettre en œuvre : une opération booléenne entre d’autres objets. Par exemple, l’intersection de deux sphères peut créer quelque chose qui ressemble à une lentille, tandis que la soustraction d’une petite sphère d’une sphère plus grande peut créer quelque chose qui ressemble à l’étoile de la mort.

Comment cela marche-t-il? Pour chaque objet, les emplacements où le rayon entre et sort de l'objet peuvent être calculés ; par exemple, dans le cas d'une sphère, le rayon entre et sort à . Disons que nous devons calculer l'intersection de deux sphères ; le rayon est à l'intérieur de l'intersection lorsqu'il est à l'intérieur des deux sphères, et à l'extérieur dans le cas contraire. Dans le cas de la soustraction, le rayon est à l'intérieur lorsqu'il est à l'intérieur du premier objet, mais pas à l'intérieur du second.

Plus généralement, si nous voulons calculer l'intersection entre ray et (où est un opérateur booléen), alors nous devons d'abord calculer séparément l'intersection de ray- et ray- , ce qui nous donne l'intervalle « interne » de chaque objet et . Nous évaluons ensuite , qui se trouve dans l'intervalle "intérieur" . Il suffit de trouver la première valeur qui se trouve à la fois dans l’intervalle « intérieur » et dans l’intervalle qui nous intéresse :

La normale au point d'intersection est soit la normale de l'objet créant l'intersection, soit son opposée, selon que l'on regarde depuis « l'extérieur » ou « depuis l'intérieur » de l'objet d'origine.

Bien sûr, ils ne doivent pas nécessairement être primitifs ; ils peuvent eux-mêmes être le résultat d’opérations booléennes ! Si nous mettons cela en œuvre proprement, nous n'avons même pas besoin de savoir comment ils sont aussi longs que nous pouvons en obtenir des intersections et des normales. Ainsi, vous pouvez prendre trois sphères et calculer, par exemple, .

Transparence

Tous les objets ne doivent pas nécessairement être opaques ; certains peuvent être partiellement transparents.

La mise en œuvre de la transparence est très similaire à la mise en œuvre de la réflexion. Lorsqu'un rayon tombe sur une surface partiellement transparente, nous calculons, comme auparavant, la couleur locale et réfléchie, mais nous calculons également une couleur supplémentaire - la couleur de la lumière qui la traverse. à travers l'objet obtenu par un autre appel à TraceRay. Ensuite, il faut mélanger cette couleur avec les couleurs locales et réfléchies, en tenant compte de la transparence de l'objet, et c'est tout.

Réfraction

Dans la vraie vie, lorsqu'un faisceau de lumière traverse un objet transparent, il change de direction (c'est pourquoi lorsqu'une paille est plongée dans un verre d'eau, elle apparaît « cassée »). Le changement de direction dépend de indice de réfraction chaque matériau selon l'équation suivante :
Où et sont les angles entre le rayon et la normale avant et après l'intersection de la surface, et et sont les indices de réfraction du matériau à l'extérieur et à l'intérieur des objets.

Par exemple, approximativement égal à et approximativement égal à . Autrement dit, pour un faisceau entrant dans l'eau sous un angle, nous obtenons




Arrêtez-vous un instant et réalisez : si vous mettez en œuvre une géométrie de bloc constructive et de la transparence, vous pouvez modéliser une loupe (l'intersection de deux sphères) qui se comportera comme une loupe physiquement correcte !

Suréchantillonnage

Le suréchantillonnage est à peu près l’opposé du sous-échantillonnage, dans lequel nous recherchons la précision plutôt que la vitesse. Supposons que les rayons correspondant à deux pixels voisins tombent sur deux objets différents. Nous devons colorer chaque pixel avec la couleur appropriée.

Cependant, n'oubliez pas l'analogie par laquelle nous avons commencé : chaque rayon doit définir la couleur « définissant » de chacun. carré"grille" à travers laquelle nous regardons. En utilisant un rayon par pixel, on décide classiquement que la couleur du rayon lumineux passant par le milieu du carré définit le carré entier, mais ce n'est peut-être pas le cas.

La solution à ce problème consiste à tracer plusieurs rayons par pixel - 4, 9, 16, etc., puis à en faire la moyenne pour obtenir la couleur du pixel.

Bien sûr, cela rend le traceur de rayons 4, 9 ou 16 fois plus lent, pour la même raison que le sous-échantillonnage le rend plusieurs fois plus rapide. Heureusement, il existe un compromis. Nous pouvons supposer que les propriétés d’un objet changent progressivement le long de sa surface, ce qui signifie qu’émettre 4 rayons par pixel frappant le même objet à des points légèrement différents n’améliorera pas beaucoup l’apparence de la scène. Par conséquent, nous pouvons commencer avec un rayon par pixel et comparer les rayons voisins : s’ils tombent sur d’autres objets ou si leur couleur diffère de plus d’une valeur seuil redivisée, alors nous appliquons la subdivision des pixels aux deux.

Pseudocode du traceur de rayons

Vous trouverez ci-dessous la version complète du pseudocode que nous avons créé dans les chapitres sur le lancer de rayons :

CanvasToViewport(x, y) ( return (x*Vw/Cw, y*Vh/Ch, d) ) ReflectRay(R, N) ( return 2*N*dot(N, R) - R; ) ComputeLighting(P, N, V, s) ( i = 0,0 pour la lumière dans la scène. Lumières ( si light.type == ambient ( i += light.intensity ) else ( if light.type == point ( L = light.position - P t_max = 1 ) else ( L = light.direction t_max = inf ) # Vérification des ombres shadow_sphere, shadow_t = ClosestIntersection(P, L, 0.001, t_max) if shadow_sphere != NULL continuer # Diffusion n_dot_l = dot(N, L) if n_dot_l > 0 i += light.intensity*n_dot_l/(length(N)*length(L)) # Brille si s != -1 ( R = ReflectRay(L, N) r_dot_v = dot(R, V) si r_dot_v > 0 i += light.intensity*pow(r_dot_v/(length(R)*length(V)), s) ) ) return i ) ClosestIntersection(O, D, t_min, t_max) ( close_t = inf close_sphere = NULL pour la sphère dans scene.Spheres ( t1, t2 = IntersectRaySphere(O, D, sphère) si t1 dans et t1< closest_t closest_t = t1 closest_sphere = sphere if t2 in and t2 < closest_t closest_t = t2 closest_sphere = sphere } return closest_sphere, closest_t } TraceRay(O, D, t_min, t_max, depth) { closest_sphere, closest_t = ClosestIntersection(O, D, t_min, t_max) if closest_sphere == NULL return BACKGROUND_COLOR # Вычисление локального цвета P = O + closest_t*D # Вычисление точки пересечения N = P - closest_sphere.center # Вычисление нормали сферы в точке пересечения N = N / length(N) local_color = closest_sphere.color*ComputeLighting(P, N, -D, sphere.specular) # Если мы достигли предела рекурсии или объект не отражающий, то мы закончили r = closest_sphere.reflective if depth <= 0 or r <= 0: return local_color # Вычисление отражённого цвета R = ReflectRay(-D, N) reflected_color = TraceRay(P, R, 0.001, inf, depth - 1) return local_color*(1 - r) + reflected_color*r } for x in [-Cw/2, Cw/2] { for y in [-Ch/2, Ch/2] { D = camera.rotation * CanvasToViewport(x, y) color = TraceRay(camera.position, D, 1, inf) canvas.PutPixel(x, y, color) } }
Et voici la scène utilisée pour rendre les exemples :

Viewport_size = 1 x 1 projection_plane_d = 1 sphère ( centre = (0, -1, 3) rayon = 1 couleur = (255, 0, 0) # Rouge spéculaire = 500 # Brillant réfléchissant = 0,2 # Légèrement réfléchissant ) sphère ( centre = (-2, 1, 3) rayon = 1 couleur = (0, 0, 255) # Bleu spéculaire = 500 # Brillant réfléchissant = 0,3 # Légèrement plus réfléchissant) sphère (centre = (2, 1, 3) rayon = 1 couleur = (0, 255, 0) # Vert spéculaire = 10 # Un peu brillant réfléchissant = 0,4 # Encore plus réfléchissant ) sphère ( couleur = (255, 255, 0) # Centre jaune = (0, -5001, 0) rayon = 5000 spéculaire = 1000 # Très brillant réfléchissant = 0,5 # Semi-réfléchissant ) lumière ( type = intensité ambiante = 0,2 ) lumière ( type = intensité ponctuelle = 0,6 position = (2, 1, 0) ) lumière ( type = intensité directionnelle = 0,2 direction = (1, 4, 4) )

Balises : ajouter des balises

Cet article discutera de l'utilisation de la méthode de lancer de rayons inversé pour visualiser des images dans les jeux informatiques. Ses avantages et inconvénients par rapport à la technologie traditionnelle sont pris en compte. L'histoire concerne un jeu conceptuel en 3D, qui utilise pour la première fois un moteur graphique entièrement construit sur le principe du lancer de rayons inversé. Le développement d’accélérateurs vidéo de jeux est également évoqué.

Technologie traditionnelle

Pour ceux qui ne connaissent pas la théorie du graphisme 3D, j'expliquerai brièvement ce qu'est la méthode de lancer de rayons inversé et en quoi elle diffère de la méthode traditionnelle de graphisme de jeu. Dans la méthode traditionnelle de visualisation d'images dans les jeux informatiques, la scène, ou, si vous préférez, le monde du jeu, est représentée par un ensemble de triangles. Pour chaque triangle, des textures et des niveaux d'éclairage sont spécifiés. Ensuite, les triangles sont poussés en masse dans un accélérateur 3D et dessinés, un peu comme un artiste dessine un triangle solide sur une feuille de papier. La différence réside dans l'utilisation d'un tampon de profondeur. Un tampon de profondeur est requis pour éviter de dessiner des triangles recouverts par d'autres objets de la scène. Lors du dessin des points d'un nouveau triangle, la valeur du tampon de profondeur correspondante est vérifiée. Le tampon de profondeur, également appelé tampon Z, stocke la distance entre l'observateur et l'image déjà dessinée. Si la distance jusqu'au point du nouveau triangle est inférieure à la valeur enregistrée dans le tampon Z, alors ce point n'est pas couvert par les points de triangles plus rapprochés, et il peut être dessiné, et la valeur du tampon de profondeur est également mis à jour. Cette méthode permet de construire une image d'une scène constituée de triangles de complexité arbitraire. L'un des avantages de cette méthode est qu'elle pourrait être implémentée - c'est-à-dire visualiser une scène de jeu assez significative en temps réel et en haute résolution - sur des processeurs « anciens » de génération i386, i486.

Différentes méthodes de construction d'une image peuvent différer par la rapidité de travail, ainsi que par la qualité, le réalisme ou la beauté de l'image construite. Naturellement, les méthodes qui permettent de dessiner une image plus réaliste nécessitent également plus de ressources informatiques. Bien entendu, nous ne considérons pas les méthodes manifestement mauvaises qui fonctionnent lentement et dessinent mal. À l'aube du développement de l'industrie des jeux informatiques, lorsque les ordinateurs personnels étaient relativement peu gourmands en énergie, la méthode de rendu la plus rapide et la moins exigeante en termes de calcul a été choisie, la méthode du tampon Z mentionnée ci-dessus.

Cependant, une scène tridimensionnelle ne se compose pas seulement de détails géométriques ; elle est inconcevable sans lumière, car autrement nous ne la verrions tout simplement pas. Et la méthode Z-buffer permet de dessiner uniquement la géométrie de la scène. Ce qu'il faut faire? Le modèle physique exact de la propagation de la lumière est très complexe ; on peut parler de quelques approximations de la lumière naturelle. Il est nécessaire qu'il fasse sombre dans les endroits ombragés où les rayons de lumière directe n'atteignent pas, et qu'il soit éclairé à proximité des sources lumineuses. Pour créer des images de scène réalistes, en termes d'éclairage, ils ont commencé à utiliser des textures pré-calculées, appelées lightmap, contenant les valeurs d'éclairage des objets statiques de la scène. Cette texture s'applique en place avec la texture habituelle du matériau et l'assombrit en fonction de la position de l'objet sur la scène et de son éclairage. Bien entendu, cela nécessite une staticité complète de la scène et des sources lumineuses, car le calcul de ces lightmaps prend un temps extrêmement long. Cette technologie est utilisée dans les jeux informatiques depuis de nombreuses années et son utilisation a conduit au fait que les jeux 3D en termes de moteur graphique ont commencé à différer uniquement par le nombre de triangles et de textures par niveau. Tout comme il n'y avait pas de sources de lumière dynamiques ni de possibilité de détruire le niveau, il n'y en a pas, puisqu'il n'y a pas de calcul dynamique de l'éclairage et de l'ombrage. Si vous déplacez une lampe ou fermez une fenêtre, l'éclairage de la scène ne changera pas, ce n'est donc pas une option dans les jeux. Il n’existe que des solutions dites fausses, lorsque quelque chose peut être fait à un certain endroit, car cette possibilité est prévue à l’avance et tout est calculé à l’avance.

Ce n'est que récemment que des ombres de modèles dynamiques, de toutes sortes de monstres et de robots ont commencé à apparaître. Nous aborderons également le sujet de la façon dont ces ombres sont calculées, mais souvent elles ne semblent pas naturelles, car, par exemple, il existe de nombreuses sources de lumière, mais l'ombre vient d'une seule, elle est nette et pas belle.
Les progrès du graphisme gaming ont été associés pendant de nombreuses années exclusivement à l'émergence de nouvelles générations d'accélérateurs graphiques. En effet, il s'est avéré très pratique de transférer le travail de dessin des triangles à l'accélérateur. La tâche de pixellisation et de texturation d'un triangle est au cœur des graphismes du jeu, il est donc naturel que cette opération très particulière et spécifique ait été considérablement accélérée par la création d'un matériel spécialement optimisé. Cependant, l'utilisation d'accélérateurs n'a conduit qu'à une qualité d'image améliorée, des modes de superposition de texture de haute qualité, trois filtrages linéaires et anisotropes, la possibilité d'utiliser des résolutions plus élevées et un anticrénelage d'image plein écran. En termes de calcul de l’éclairage et de la dynamique de la scène, rien n’a encore changé. Le manque d’éclairage dynamique rend les niveaux de jeu modernes ennuyeux. L’éclairage et la scène statiques peuvent progressivement devenir ennuyeux. C’est comme si le temps s’était arrêté et que les joueurs couraient dans ce temps arrêté. De nos jours, lorsqu'ils explorent les capacités des nouveaux accélérateurs, ils aiment regarder l'écran à la loupe, à la recherche de la prochaine légère amélioration de la qualité de l'image, extrêmement difficile à discerner dans un jeu.

Méthode de lancer de rayons


Je me demande quelle méthode est utilisée pour calculer un éclairage réaliste lors du rendu de scènes réalistes, de dessins animés, de scènes animées, quel principe sous-tend la construction des mêmes lightmaps ? Dans ce domaine, la méthode du lancer de rayons et ses modifications se sont généralisées.

Les critiques de processeurs mentionnent souvent les résultats de tests dans des packages graphiques 3D tels que 3DMax, LightWave et autres. Le temps nécessaire pour restituer une scène complexe avec un éclairage, une réflexion et une réfraction réalistes de la lumière est mesuré. C'est exactement la scène qui est dessinée à l'aide de la méthode de lancer de rayons.

Contrairement à la méthode Z-buffer, la méthode du lancer de rayons est initialement conçue pour construire une image réaliste avec un modèle d'éclairage complexe. Le principe du lancer de rayons inversé est qu'un rayon de lumière vers l'arrière est tracé à travers chaque point de l'écran jusqu'à ce qu'il croise l'objet le plus proche de la scène, puis un rayon est tracé à partir de ce point en direction de la source lumineuse, simulant ainsi la propagation de la lumière. Si un faisceau tiré sur une source lumineuse ne coupe rien sur son trajet, alors ce point est éclairé, sinon il se trouve dans l'ombre. Si un faisceau frappe une surface de miroir, alors, conformément aux lois de l'optique, un faisceau réfléchi est émis, ce qui permet de construire une réflexion. Selon les propriétés du milieu traversé par le faisceau, celui-ci peut être réfracté, ce qui permet de simuler des effets de lumière complexes et réalistes. Cette méthode vous permet non seulement d'obtenir les ombres des objets, mais également de calculer l'éclairage secondaire lorsque la faible lumière réfléchie frappe directement les zones ombragées et rend les ombres floues.

Cependant, il est facile de comprendre que cette méthode est extrêmement complexe en termes de calcul. Lors des tests de processeur dans les programmes de modélisation 3D, vous pouvez prêter attention à la durée pendant laquelle même une image est prise en compte. Il n’y a aucune odeur de temps réel ici. De plus, auparavant, il fonctionnait encore plus lentement sur les ordinateurs personnels, ce qui ne laissait aucune place à son utilisation dans les jeux informatiques.
Mais au cours des dernières années, la puissance des ordinateurs personnels a considérablement augmenté et a permis de mettre en œuvre le lancer de rayons presque en temps réel, bien qu'avec de grandes limitations en termes de qualité et de résolution d'image.

Étant donné que pour chaque point de l'écran, il est nécessaire d'effectuer une procédure de lancer de rayons très complexe, la vitesse de traçage dépend beaucoup de la résolution de l'écran et de sa superficie. Autrement dit, le rendu d'une image 1024 x 768 prendra 10 fois plus de temps que le rendu d'une image 320 x 240. Il est possible de mettre en œuvre la méthode du ray tracing en temps réel, la seule question est de savoir dans quelle résolution et avec quelle qualité d'image.


Jusqu'à récemment, le lancer de rayons en temps réel sur PC était l'apanage de petits programmes de démonstration qui dessinaient de belles images, mais fonctionnaient à faible vitesse et dans de faibles résolutions. Il existe de nombreux programmes de ce type sur www.scene.org. Cependant, j'ai réussi, en sacrifiant temporairement de nombreux avantages de la méthode de lancer de rayons, à créer un moteur 3D à part entière et, sur cette base, le premier jeu informatique utilisant le lancer de rayons en temps réel.

Jeu conceptuel avec un moteur 3D basé sur le lancer de rayons inversé

Lors de divers salons automobiles, des concept-cars, véritables prototypes des futures voitures de série, sont présentés. Ils sont extrêmement chers, non débogués du point de vue du consommateur, mais ils représentent de nouvelles idées. J'ai créé un jeu-concept. Qu'avez-vous réussi à mettre en œuvre pour fonctionner en temps réel sur les ordinateurs personnels modernes ?
Pour le moteur de lancer de rayons, deux exigences principales ont été initialement fixées : que l'éclairage de la scène entière soit calculé en temps réel et qu'aucune information de niveau pré-calculée ne soit utilisée. Tout cela devrait vous permettre de modifier arbitrairement le niveau de manière dynamique. Quelque chose que les moteurs modernes ne peuvent pas offrir.
Couplé au calcul dynamique de l'éclairage, le manque d'informations préalables permet de dessiner assez facilement des mondes infinis, puisqu'il suffit de stocker des informations peu volumineuses sur la géométrie des niveaux.

Le respect de ces exigences strictes imposées aux processeurs modernes a nécessité l'introduction d'autres restrictions sérieuses, heureusement non fondamentales. Cependant, avec la croissance de la puissance de calcul disponible, ces restrictions seront supprimées, mais l'essentiel restera.
Tout d’abord, j’ai abandonné la modélisation de la réalité terrestre au profit de mondes extraterrestres. Cela a permis d'abandonner l'utilisation d'un triangle, peu pratique pour le raytracing, comme primitive principale pour construire une scène. Un monde extraterrestre n’a pas besoin d’être anguleux, qu’il soit rond. Une sphère a été choisie comme primitive pour construire la scène. Étant donné que les jeux modernes doivent fonctionner à des résolutions élevées, comme 1024x768, nous avons dû abandonner le calcul des réflexions et des réfractions, car cela rendait très difficile le traitement du rayon correspondant au point de l'écran. Mais avec une puissance de calcul croissante, il sera possible d'élargir à la fois l'ensemble des primitives et la profondeur du lancer de rayons, c'est-à-dire d'ajouter des réflexions, des réfractions, etc.

Alors, quelles sont les principales caractéristiques de VirtualRay - un moteur 3D construit sur la méthode du lancer de rayons ? Sur les processeurs les plus modernes pour ordinateurs personnels, il fonctionne à une vitesse plus ou moins acceptable dans une résolution de 1024x768x32. Nous supposerons qu'il s'agit de la résolution utilisée, car si vous utilisez une résolution inférieure, les paramètres de performances peuvent être différents.

Rendu de scènes composées peut-être de milliers de sphères qui se croisent. En réalité, la scène peut être infinie, c'est-à-dire uniquement la zone visible.

Calcul image par image de tous les éclairages et ombrages. Toutes les sources de lumière sont dynamiques (même statiques), car elles sont en réalité dynamiques et ne changent tout simplement pas de position d'une image à l'autre.

Calcul de l'éclairage par pixel et superposition des ombres par pixel, naturellement dynamiques.

Rendu d'ombres douces basé sur une approximation physique de sources lumineuses volumétriques. C'est-à-dire que la limite de l'ombre n'est pas nette, mais très floue, le degré de flou peut être ajusté. Certes, ce ne sont pas des ombres douces entièrement réelles et physiquement fiables, mais approximatives.

Jusqu'à 8 sources de lumière peuvent éclairer une sphère, respectivement, une sphère peut projeter jusqu'à 8 ombres. Ce n’est pas une limitation fondamentale, c’est juste que lorsqu’il y a de nombreuses sources lumineuses dans une zone, tout ralentit bien sûr beaucoup.

Prend en charge les sources lumineuses ponctuelles et les sources lumineuses infiniment distantes telles que le soleil. En règle générale, la scène est éclairée par une source lumineuse « solaire » et plusieurs sources locales.

Une scène complètement dynamique, c'est-à-dire que la position des objets peut changer de n'importe quelle manière.

Cartographie de texture et filtrage bilinéaire.

Utilisation limitée de sphères transparentes à opacité dynamique.

Une représentation pas si subtile de la surface de la planète comme une grande sphère, créant un effet d'horizon où les objets éloignés sont cachés derrière la ligne d'horizon.

Les inconvénients locaux du moteur incluent, tout d'abord, le fait qu'il est avare d'effets « bon marché », tels que les flashs de sprites, etc., que les accélérateurs vidéo modernes font si bien.
Quel genre de jeu était-il possible de créer en utilisant le moteur VirtualRay ? En général, vous pouvez y créer une grande variété de jeux, depuis un simulateur spatial jusqu'à un univers multijoueur en ligne. À propos, dans ce dernier type, les avantages du moteur dans la mise en œuvre de changements de scène dynamiques sont particulièrement évidents. En tant que "jeu conceptuel", j'ai créé un projet appelé AntiPlanet - un simple jeu de tir en 3D avec des monstres simples au comportement semblable à celui de Doom. Les niveaux du jeu sont des morceaux de terrain extraterrestre de différentes tailles, éclairés par les soleils locaux. À propos, le Soleil se déplace dans le ciel en fonction de quoi l'éclairage et les ombres de la scène changent. Au total, il y a 5 niveaux disponibles dans la version actuelle du jeu, dont un intérieur, un labyrinthe de grottes. Les autres sont pour la plupart ouverts. Le moteur est suffisamment polyvalent pour dessiner des scènes ouvertes et fermées sans optimisations particulières.

Il existe 5 types de monstres chassant pour le joueur, les monstres diffèrent par le type d'arme utilisé, la vitesse et la force. À propos, le joueur dispose de dix types d'armes, tirant une variété d'obus, de missiles et de bombes. La nature sphérique de l'arme la rend quelque peu monotone, mais les obus explosent en un tas de fragments lorsqu'ils explosent. Il existe 3 principaux types de jeux : la simple chasse aux monstres, lorsque le joueur doit détruire un certain nombre de monstres dans un certain temps. Le deuxième type de jeu consiste à trouver des artefacts spéciaux cachés dans le niveau. Et dans le troisième cas, le joueur doit simplement survivre un certain temps sur une planète inconnue. Lors du choix d'un jeu, vous pouvez définir le nombre de trousses de premiers secours, d'armes et de monstres dans le niveau, ils seront placés à des endroits aléatoires. Bien entendu, si vous définissez un très grand nombre de monstres, le jeu fonctionnera lentement.

Malheureusement, le jeu ne révèle pas tout le potentiel du moteur, puisque le modéliste et moi n'avons tout simplement pas eu le temps de le faire. Par exemple, il n'y a pas de destruction de niveau, seulement des parties dynamiques individuelles, car alors les monstres stupides ne trouveront pas le chemin, par contre, cela n'est pas prévu par les idées du jeu ; Les capacités du moteur en termes d'animation de modèles ne sont pas pleinement exploitées. Le moteur permet des modifications arbitraires et indépendantes des modèles sur chaque image, ce qui permet de mettre en œuvre l'animation la plus sophistiquée.
J'ai décidé de ne fournir aucune capture d'écran du jeu, car elles ne traduisent pas du tout les avantages du moteur, tels que l'éclairage dynamique et les ombres douces. Téléchargez la version démo, cela ne prend que quelques mégaoctets. Imaginez donc un terrain extraterrestre surréaliste, composé d'un grand nombre de balles, de monstres issus de petites sphères, qui, lorsqu'elles explosent, se dispersent en petits morceaux. Vous pouvez télécharger la version de démonstration actuelle à partir de ce lien.

Le jeu nécessite Windows 95 et supérieur, de préférence plus de 128 Mo de mémoire, sinon désactivez la musique, DirectX, une carte vidéo avec prise en charge des couleurs 32 bits et, surtout, un processeur plus puissant. Par exemple, un processeur Intel Pentium 4 prenant en charge la technologie Hyper-Threading, ou le nouvel AthlonXP. Le jeu devrait fonctionner sur n'importe quel processeur doté de la technologie MMX, mais pour bénéficier de fonctionnalités complètes, vous avez besoin du support SSE, c'est-à-dire un processeur commençant par un Pentium-III. Aucun accélérateur vidéo requis. À propos, le moteur prend en charge le multitraitement, y compris la technologie Hyper-Threading. Tous les programmes n'utilisent pas plusieurs threads pour utiliser avec succès l'Hyper-Threading, mais la boucle principale de lancer de rayons est parallélisée et un gain de plusieurs dizaines de pour cent est obtenu. Et sur un système multiprocesseur, le gain est proportionnel au nombre de processeurs.

Développement du moteur VirtualRay

Pour le moment, le moteur permet d'afficher des scènes fantastiques. Mais l’utilisation de sphères comme primitives ne constitue pas une limitation fondamentale ; pour le moment, l’utilisation de primitives plus complexes est problématique du point de vue de la rapidité. À mesure que les performances augmentent, vous pouvez implémenter un traitement d'ellipses au lieu de sphères, ce qui enrichira vos scènes.

Et maintenant, nous pouvons implémenter le traitement des triangles avec les sphères. Mais pour afficher quelque chose de significatif à l’aide de triangles, il en faut beaucoup et ils sont traités à la vitesse des sphères. Il est facile d'étendre la variété des primitives en introduisant des sphères entaillées, des segments sphériques et des triangles sphériques. Mais cela affectera également négativement la vitesse.
Il existe différentes méthodes pour améliorer les performances du lancer de rayons au détriment de la qualité de l'image. Tous les rayons ne sont pas tracés, mais seulement les plus importants, et l'interpolation est utilisée pour construire la partie manquante de l'image. Cependant, cette approche n'est pas applicable à toutes les scènes ; parfois elle peut donner des résultats plus ou moins qualitatifs, et parfois elle peut sérieusement gâcher l'image.

Au fait, à propos de la qualité. Il y a beaucoup de place à l’amélioration ici. Le fait est que la procédure de texturation n'est effectuée qu'une seule fois par point et ne prend pas beaucoup de temps. Actuellement, environ 10 % du temps de rendu est consacré à la texturation. Ainsi, pour améliorer la qualité de la texturation, il est prévu d'implémenter un filtrage trilinéaire par pixel, cela ne devrait pas réduire significativement la vitesse.

Ray tracing et accélérateurs 3D modernes

Récemment, l'industrie des accélérateurs 3D a fait une transition vers l'utilisation généralisée de ce que l'on appelle les pixel shaders et les vertex shaders. Lors de la rastérisation d'un triangle, pour chaque fragment d'image, l'accélérateur exécute un programme prédéterminé qui modifie la couleur du fragment de manière complexe. Il peut faire bien plus, par exemple écrire des calculs intermédiaires dans des textures, qui seront ensuite lues et utilisées lors du dessin d'autre chose. Un exemple typique de pixel shader moderne, ou comme on l'appelle aussi, de fragment shader, est un shader qui calcule l'éclairage d'un point donné d'un triangle. Il est structuré comme suit : un vecteur est pris - la position globale de la source, la coordonnée actuelle du point du triangle dans l'espace tridimensionnel est prise, qui est calculée dans la puce accélératrice lors de la rastérisation du triangle, et la normale au triangle à ce stade. Ensuite, le vecteur de ce point dans la direction de la source lumineuse est calculé et, en fonction de l'angle qu'il forme avec le vecteur perpendiculaire normal, l'éclairage est calculé. Plus l’angle sous lequel tombe la lumière est grand, moins elle est intense.
Comme nous pouvons le constater, un shader moderne peut constituer un programme de géométrie significatif. Il est désormais d'usage de tester de nouveaux accélérateurs en mesurant la vitesse d'exécution de ces shaders et de plus complexes. La productivité est très élevée. Le shader qui effectue un éclairage par pixel fonctionne à une résolution de 1024x768 à une vitesse de 100-200 images par seconde sur les derniers accélérateurs, tels que Radeon9700 ou GeForceFX. Cela se réfère uniquement à la durée de fonctionnement du shader lui-même. À cet égard, l’idée a longtemps été d’utiliser une puissance de calcul aussi considérable à des fins très diverses, même loin du graphisme 3D. Et, entre autres, essayez d’utiliser la méthode du lancer de rayons pour l’implémenter.

Cependant, si l’on considère cette puissance en termes de nombre de calculs scalaires et vectoriels à virgule flottante par unité de temps, elle s’avère comparable à la puissance de calcul des processeurs modernes. Prenons le dernier accélérateur actuel, GeForceFX5900Ultra, il a une fréquence de 450 MHz, 4 processeurs de pixels, chacun pouvant effectuer 1 opération vectorielle par cycle d'horloge. En fait, il peut y avoir plus d'opérations par cycle d'horloge, mais nous ne nous intéressons qu'aux calculs avec une précision float32 totale, car les calculs avec une précision inférieure ont du sens principalement pour le calcul de la couleur, dont la plage est encore limitée par la résolution couleur pas très grande. du moniteur. Et pour les calculs géométriques, une bonne précision est requise. Cela équivaut à 450 Mx4 = 1 800 millions d'opérations vectorielles par seconde comme estimation approximative des performances. Si nous prenons le Pentium 4, alors en utilisant SSE, nous pouvons réaliser une opération vectorielle en un cycle d'horloge et demi, c'est-à-dire qu'à une fréquence de 2 700 MHz, nous obtenons les mêmes 1 800 millions d'opérations vectorielles par seconde. Dans les deux cas, il s’agit naturellement de performances optimales, alors que tout le code est constitué de calculs.
Il est clair que le VPU n’a pas de supériorité en puissance de calcul. Son avantage en graphisme réside dans la possibilité, en parallèle des calculs de shader, d'effectuer les calculs d'accompagnement nécessaires à la pixellisation d'un triangle. Calculez d'une manière ou d'une autre la valeur du tampon de profondeur, interpolez les valeurs définies aux sommets le long de la surface du triangle, et échantillonnez et filtrez les textures en un seul cycle d'horloge. Tout cela est réalisé par divers blocs accélérateurs vidéo fonctionnant en parallèle.

Ainsi, naturellement, nous n'obtiendrons aucun avantage particulier dans la mise en œuvre du lancer de rayons grâce à l'utilisation d'un accélérateur vidéo, puisque l'accélérateur est entièrement optimisé et construit du point de vue de l'optimisation du dessin des triangles.
Concernant l'optimisation du ray tracing à l'aide d'un accélérateur vidéo, il y a une autre idée : dessiner toute la géométrie sur le VPU, et effectuer le calcul de l'éclairage en utilisant la méthode de ray tracing à l'aide du CPU, puis combiner le résultat. Mais cela ne sera pas d’une grande utilité, car les principales difficultés informatiques surviennent précisément dans le calcul de l’éclairage. De plus, plus la scène est complexe et, par conséquent, plus l'utilisation du VPU est grande, plus il faudra de ressources pour calculer l'éclairage d'une scène complexe, et dessiner la scène prendra beaucoup moins de temps par rapport au temps de calcul. l'éclairage.

Calcul de l'éclairage à l'aide d'accélérateurs modernes

D'accord, comment est-il proposé de calculer l'ombrage de scène dans les nouveaux jeux avec des sources de lumière dynamiques, comme Doom III ? Sommes-nous désormais condamnés à jamais à voir un éclairage statique pré-calculé dans les jeux informatiques ? Non, des méthodes intéressantes de calcul des ombres sont connues depuis longtemps en utilisant la méthode standard de dessin de triangles texturés à l'aide d'un tampon z. Ils sont connus depuis longtemps, mais ils sont si exigeants en ressources informatiques que leur utilisation dans les jeux informatiques, bien que limitée, n'est devenue possible que récemment avec l'avènement d'une nouvelle génération d'accélérateurs vidéo.

Examinons d'abord la méthode par laquelle les ombres dynamiques sont dessinées dans le jeu Doom III susmentionné. Un jeu que de nombreux joueurs attendent avec impatience. Cette méthode est appelée méthode Shadow Volumes, ou méthode de dessin d'ombres à l'aide d'un tampon de pochoir. Voici un schéma de base de son fonctionnement : tout d'abord, une scène non éclairée est dessinée, puis pour chaque objet de la scène projetant une ombre, son volume d'ombre est construit. Le volume d'ombre est un chiffre qui limite la région d'ombre, cette zone de l'espace dans laquelle la lumière ne tombe pas, qui est ombragée par un objet donné. On semble imaginer la noirceur s'étendant derrière l'objet sous la forme d'un corps. Le volume de l'ombre peut même être vu dans la réalité si vous éclairez avec une lumière vive une pièce dans laquelle volent des particules de poussière. Les particules non ombrées brilleront, tandis que les particules ombrées formeront une zone noire derrière l'objet bloquant la lumière. L'étape suivante consiste à dessiner les triangles qui constituent la bordure de ce volume d'ombre. En comparant la valeur du tampon de profondeur avec la profondeur des parois avant et arrière du volume d'ombre, il est déterminé si un point donné se trouve à l'intérieur du volume d'ombre et est donc ombré ou non. Lors de la comparaison de la profondeur des murs du volume d'ombre et de la profondeur de l'image, un tampon stensil est utilisé - un tableau de valeurs correspondant aux pixels de l'écran. Il stocke les résultats intermédiaires de la comparaison de la profondeur des murs du volume d'ombre avec la profondeur de l'image. Cette méthode est « bonne » car elle utilise pleinement le taux de remplissage de l’accélérateur, car les volumes d’ombre ont généralement une plus grande surface sur l’écran que l’objet projetant l’ombre. La méthode était disponible pour une mise en œuvre sur les accélérateurs Riva TNT2, mais elle est si exigeante que son utilisation n'est devenue possible que récemment.

D’un autre côté, la construction de volumes d’ombre optimaux pour des objets complexes non convexes est une tâche difficile en termes de calcul. Une solution simple conduira à l'apparition d'un grand nombre de murs supplémentaires du volume d'ombre, dont le rendu nécessitera des ressources supplémentaires. Le temps nécessaire pour trouver le volume effectif augmente très vite avec le degré de détail du modèle. C'est peut-être pour cette raison que les modèles de monstres de NewDoom sont moins détaillés que prévu.
Mais ce ne sont pas toutes les lacunes. Pour de nombreux petits objets, la surface des murs du volume d'ombre peut atteindre une taille gigantesque. Par exemple, au peigne. Sa zone d'ombre n'est pas grande, mais très sinueuse. De plus, le procédé est peu compatible avec les surfaces transparentes. Par exemple, si une surface transparente tombe dans le volume d'ombre, l'objet derrière elle ne laisse pas ses informations dans le tampon de profondeur, car ces informations sont perdues en raison de la profondeur de la surface transparente. Et il est impossible de déterminer si un objet se trouve dans le volume d'ombre. Tous les cas de ce type devront être traités séparément, ce qui entraînera une augmentation du nombre de passes de rendu.

Cette méthode est difficile à perfectionner pour produire des ombres floues. Ceux qui ont regardé la version préliminaire de Doom III ont peut-être remarqué la netteté des ombres. Et, en fait, cette méthode ne convient que pour dessiner des ombres ; l'éclairage secondaire ne peut pas être calculé avec son aide, ni la réfraction et la réflexion de la lumière. Le cône d’ombre de l’objet est simplement dessiné sur le front et le tour est joué.

Une autre façon populaire de représenter des ombres dynamiques dans les jeux modernes consiste à utiliser le mappage de texture projectif. Les accélérateurs modernes ont appris à projeter une texture sur un objet, tout comme un rétroprojecteur projette une diapositive sur un écran. Simplement, lors du dessin d'un objet, il calcule quel point de texture est projeté sur un point donné de l'objet. Vous pouvez maintenant, en regardant la source de lumière, dessiner un objet en noir dans la texture, vous obtiendrez une silhouette d'ombre. C’est la même chose que l’ombre d’un objet sur un mur blanc vertical. Et cette texture avec une ombre s'appelle un masque d'ombre, elle peut être projetée sur les objets ombrés.

C'est cette méthode qui est utilisée dans les nouveaux jeux pour représenter les ombres d'objets dynamiques, de monstres et de voitures. Avec lui, vous pouvez dessiner des ombres floues ; pour cela, la texture originale avec l'ombre est floue, passant du noir et blanc au blanc et gris.

Je ne sais même pas laquelle des méthodes décrites ci-dessus est la plus exigeante en termes de taux de remplissage de l'accélérateur. Le fait est que pour obtenir une bonne qualité d’ombre, la texture de l’ombre doit être de très haute résolution. De nouveaux jeux comme Splinter Cell utilisent des textures de plusieurs milliers de pixels. La raison en est que lorsqu’ils sont projetés, les plus petits détails augmentent en taille plusieurs fois. Les pixels qui composent l'image deviennent visibles. Par conséquent, cette méthode ne peut être utilisée que pour projeter des ombres sur des objets proches. Le deuxième inconvénient de cette méthode est l'impossibilité d'ombrager automatiquement un objet ; il est nécessaire de sélectionner avec précision l'objet projetant l'ombre, et ses parties ne projetteront pas d'ombre les unes sur les autres. Et en plus, bien entendu, cette méthode n'implique aucune généralisation pour le calcul de l'éclairage secondaire, des réflexions et de la réfraction de la lumière.

Et enfin, regardons quelle est, à mon avis, la méthode la plus prometteuse pour construire des ombres à utiliser dans les jeux modernes. Il s’agit d’un développement de la méthode projective précédente. Seulement, au lieu de la silhouette de l'objet, la distance entre les points de l'objet et la source de lumière est enregistrée dans la texture de l'ombre. Ensuite, lors de la projection d'une texture d'ombre, ces informations sont utilisées pour déterminer si le point d'un objet potentiellement ombragé se trouve plus loin ou plus près de la source de lumière que l'ombre. L'avantage de cette méthode est l'auto-ombrage correct de l'objet. Et ses inconvénients sont similaires à ceux de la méthode précédente. Cette méthode de création d'ombres dynamiques n'est pas populaire parmi les développeurs de jeux. Le "défaut" de la méthode est qu'elle nécessite des capacités de carte vidéo spécifiques qui sont apparues pour la première fois dans GeForce3 - GeForce4, mais ont été supprimées de Geforce4MX - une version abrégée de GeForce4. La méthode ne peut pas être implémentée sans support matériel, vous devez donc utiliser une méthode réalisable sur toutes les cartes vidéo populaires.

L'avantage de toutes les méthodes ci-dessus est une bonne compatibilité avec le matériel existant. Pour eux, en effet, rien n'est nécessaire à part un taux de remplissage et des opérations simples. De ce fait, on peut conclure que les accélérateurs vidéo sont encore loin de pouvoir calculer l’éclairage d’une scène en temps réel. Et rien de révolutionnaire n’est attendu. Des ombres de certains objets dynamiques sont apparues, une lumière dynamique limitée dans le nouveau Doom III, ces technologies seront maîtrisées sur une longue période de temps.

Développement d'accélérateurs du point de vue du ray tracing

Comme je l'ai déjà mentionné, les accélérateurs modernes sont de plus en plus programmables et leur puissance augmente régulièrement. Les fabricants de cartes graphiques utilisent même le terme « processeur visuel » pour désigner de nouveaux produits. En effet, en termes de capacités, les accélérateurs rappellent de plus en plus les processeurs classiques pour ordinateurs personnels. C'est précisément avec l'augmentation du degré de programmabilité du VPU que les espoirs reposent sur la mise en œuvre de méthodes d'imagerie intelligentes, telles que la méthode de traçage de rayons. Pour que l'accélérateur puisse être reprogrammé de manière adaptée.

Évaluons les perspectives de développement d'accélérateurs dans ce sens. Aujourd'hui, les derniers accélérateurs fonctionnent à des fréquences d'environ 500 MHz, comme les processeurs d'il y a cinq ans, et disposent de 4 à 8 pipelines fonctionnant en parallèle. De nos jours, la plupart des opérations vectorielles shader, addition, produit scalaire, sont effectuées par cycle d'horloge. De nombreuses opérations auxiliaires, telles que l'interpolation de valeurs sur la surface d'un triangle, sont également effectuées par cycle d'horloge. Le calcul des fonctions trigonométriques telles que sin et cos, bien qu'approximatif, s'effectue également selon un cycle d'horloge. Cela utilise des sélections de tableaux avec des valeurs pré-calculées, mais les performances sont néanmoins étonnantes. De plus, il est étrange que les processeurs modernes pour ordinateurs personnels ne puissent rien faire de tel. Au contraire, la tendance est de se débarrasser des commandes complexes et de les remplacer par quelques commandes simples. Ces mesures sont nécessaires pour pouvoir augmenter la fréquence. Sans entrer dans les détails techniques, on peut dire que le cycle du processeur, qui diminue de plus en plus avec la fréquence, nécessite des instructions plus courtes. Les instructions complexes sont encore divisées en micro-opérations dans les processeurs modernes. Cette division est également un problème distinct ; des blocs de processeur entiers s’en occupent.

Qu’en est-il des accélérateurs vidéo ? Il est probable que pour augmenter la fréquence, l’architecture des VPU modernes devra être sérieusement repensée. Mais ce n'est pas si mal. La véritable programmabilité nécessite que le processeur exécute des branches, c'est-à-dire des commandes pour contrôler l'exécution du programme. Et c’est toujours le plus gros problème. Comment les processeurs modernes souffrent-ils des branches conditionnelles imprévisibles dans les programmes ? Ici, les vertex shaders de GeForceFX ont reçu des commandes de branchement conditionnelles, vous pouvez voir les derniers tests pour voir à quel point les performances ont chuté. Et ceci à une fréquence relativement basse inférieure à 500 MHz. Et à mesure que la fréquence augmente, les pertes dues aux transitions conditionnelles ne feront qu'augmenter et leur mise en œuvre elle-même deviendra plus difficile. À propos, les performances fantastiques des accélérateurs sont obtenues lors de l'exécution d'opérations dites de streaming, lorsque les données circulent dans une bande continue et sont traitées selon un schéma strictement défini, sans transitions conditionnelles aléatoires, etc. Tous ces faits indiquent que l’on ne peut pas s’attendre à une augmentation de la fréquence des accélérateurs vidéo dans un avenir proche.

Un paramètre important d'une carte vidéo est le nombre de processeurs de pixels. Ils peignent des pixels en parallèle, donc plus il y en a, mieux c'est. Sur les dernières Radeon, il y en a déjà huit. De plus en plus de processeurs de fragments sont attendus des nouveaux accélérateurs. Mais ce n'est pas si simple. Le fait est que lorsque la taille du triangle est comparable au nombre de pipelines de pixels, ils ne peuvent pas tous fonctionner ensemble. Il n’y a pas assez de place pour eux dans le petit triangle. C'est également la raison pour laquelle les fabricants d'accélérateurs vidéo sont si friands des modes d'anti-aliasing, qui restituent la scène entière en double résolution. Ensuite, les petits triangles deviennent plus grands. En effet, si une scène de grands triangles est divisée en plus petits sans changer la forme, alors les performances des pixel shaders diminueront considérablement, même si la surface totale des triangles restera la même.

Le développement d'accélérateurs graphiques de jeux modernes se heurte déjà à de grandes difficultés et passe presque exclusivement par l'amélioration du processus technologique de production des puces vidéo. NVIDIA et ATI réfléchissent tous à la manière de créer efficacement des ombres dynamiques simples. Il n’y a pas de bonne solution – ils n’ont pas le temps de faire du lancer de rayons.

Accélérateur spécialisé pour le lancer de rayons

Si les VPU de jeu modernes ont été conçus à l'origine pour accélérer l'algorithme standard de dessin de triangles et ne sont pas très adaptés à la mise en œuvre du lancer de rayons, alors peut-être est-il logique de construire initialement un accélérateur pour mettre en œuvre le lancer de rayons ? Hélas, accélérer le lancer de rayons est une tâche ingrate.


L'algorithme de lancer de rayons est si complexe que l'accélérateur de lancer de rayons est presque un processeur universel. Les algorithmes de streaming sans branches aléatoires se prêtent bien à l’accélération matérielle, mais le lancer de rayons est complètement différent. Autrement dit, créer un accélérateur de lancer de rayons revient à créer un véritable processeur.


Mais le lancer de rayons présente un autre avantage : il est bien parallélisé. Chaque rayon peut être calculé indépendamment, ce qui permet une mise en œuvre efficace de l'algorithme sur des systèmes multiprocesseurs. En tant qu’accélérateur de lancer de rayons bon marché, vous pouvez envisager de deviner quoi ? Un système avec quatre Celerons avec une fréquence de 3 gigahertz ou plus, ou quatre AthlonXP avec un cache réduit. L'algorithme de lancer de rayons, s'il est correctement optimisé, ne nécessite pas une grande taille de cache, il sera donc bon marché et multifonctionnel. La puissance de calcul combinée dépassera de loin les ordinateurs de bureau actuels. Mais cela n'arrivera pas, puisque les systèmes multiprocesseurs sont destinés à un marché différent et non aux systèmes domestiques.

Conclusion

Sur la base de tout ce qui précède, nous pouvons conclure que la visualisation réaliste de scènes en temps réel sur des ordinateurs personnels est très difficile et que de nombreux problèmes sont de nature fondamentale. Il faut suffisamment de temps pour que l'infographie de jeu atteigne un niveau qualitativement nouveau. Et il est désormais très difficile de dire quelles méthodes seront utilisées pour la visualisation dans les futures applications graphiques.

Links


http://www.art-render.com/

Site Web destiné aux fabricants d'"accélérateurs de lancer de rayons" pour optimiser le rendu dans 3DMax et autres éditeurs graphiques. Un accélérateur est un ensemble de plusieurs processeurs, parmi 8, optimisés pour le lancer de rayons. Ils peuvent effectuer une opération de traçage typique – trouver l’intersection d’un rayon avec un triangle – en un seul cycle d’horloge. Mais apparemment, ils fonctionnent à une fréquence pas très élevée. L'accélération est obtenue grâce à un fonctionnement parallèle. Il est difficile de trouver les prix sur le site actuellement, mais je les ai déjà vus et ils ne sont pas petits du tout.


http://www.acm.org/tog/resources/RTNews/html

Une liste complète de diverses ressources sur le thème du lancer de rayons.


http://www.realstorm.com/

Moteur basé sur le lancer de rayons. Permet de dessiner en temps réel un grand nombre d'effets typiques de traçage, de réflexion et de réfraction de la lumière, par exemple. Mais cela fonctionne dans de petites résolutions et utilise l'approximation. Un jeu de simulation de bowling est construit sur le moteur.


http://www.kge.msu.ru/workgroups/compcenter/dmitri/projects/sphericworld/index.htm

http://www.kge.msu.ru/workgroups/compcenter/dmitri/projects/polyworld/index.htm

Un autre projet dédié à la méthode du ray tracing. Un traceur de rayons sphériques et polygonaux a été mis en œuvre, créant des images réalistes de très haute qualité, mais lentement en haute résolution.


http://www.virtualray.ru/

Il s'agit en fait d'un site dédié au sujet de l'article - le moteur VirtualRay et le jeu AntiPlanet - le premier jeu de tir 3D basé sur le moteur ray trace.

Les techniques de lancer de rayons sont aujourd’hui considérées comme les méthodes les plus puissantes pour créer des images réalistes. La polyvalence des méthodes de traçage est due en grande partie au fait qu'elles reposent sur des concepts simples et clairs qui reflètent notre expérience de perception du monde qui nous entoure.

Voyons comment se forme une image. L'image est produite par la lumière entrant dans la caméra. Libérons de nombreux rayons des sources lumineuses. Appelons-les rayons primaires. Certains de ces rayons s’envoleront dans l’espace libre et d’autres toucheront des objets. Les rayons peuvent être réfractés et réfléchis sur eux. Dans ce cas, une partie de l’énergie du faisceau sera absorbée. Les rayons réfractés et réfléchis forment de nombreux rayons secondaires. Ensuite, ces rayons seront à nouveau réfractés et réfléchis et formeront une nouvelle génération de rayons. Finalement, certains rayons frapperont la caméra et formeront une image.

Il existe des algorithmes qui fonctionnent selon cet algorithme. Mais ils sont extrêmement inefficaces, puisque la plupart des rayons émanant de la source n'atteignent pas la caméra. Mais une image acceptable est obtenue si vous tracez un grand nombre de rayons, ce qui prendra très longtemps. Cet algorithme est appelé lancer de rayons direct.

La méthode de lancer de rayons inversé peut réduire considérablement la recherche de rayons lumineux. Cette méthode a été développée dans les années 1980 par Whitted et Kaye. Dans cette méthode, les rayons ne sont pas suivis à partir de sources, mais à partir de la caméra. Ainsi, un certain nombre de rayons sont tracés, égal à la résolution de l'image.

Supposons que nous ayons une caméra et un écran situés à une distance h de celle-ci. Divisons l'écran en carrés. Ensuite, nous dessinerons à tour de rôle des rayons depuis la caméra vers le centre de chaque carré (rayons primaires). Trouvons l'intersection de chacun de ces rayons avec les objets de la scène et sélectionnons celui le plus proche de la caméra parmi toutes les intersections. Ensuite, en appliquant le modèle d'éclairage souhaité, vous pouvez obtenir une image de la scène. Il s’agit de la méthode de lancer de rayons la plus simple. Il vous permet uniquement de couper les bords invisibles.

Mais nous pouvons aller plus loin. Si nous voulons simuler des phénomènes tels que la réflexion et la réfraction, nous devons lancer des rayons secondaires depuis l'intersection la plus proche. Par exemple, si la surface réfléchit la lumière et qu’elle est parfaitement plane, alors il faut réfléchir le rayon primaire de la surface et envoyer un rayon secondaire dans cette direction. Si la surface est inégale, il est alors nécessaire de lancer de nombreux rayons secondaires. Cela n'est pas fait dans le programme, car cela ralentirait considérablement le traçage.

Si l'objet est transparent, il est alors nécessaire de construire un rayon secondaire tel que lorsqu'il est réfracté, il produise le rayon d'origine. Certains corps peuvent avoir la propriété de réfraction diffuse. Dans ce cas, non pas un, mais plusieurs rayons réfractés se forment. Comme pour la réflexion, je néglige cela.

Ainsi, le rayon primaire, ayant trouvé une intersection avec l'objet, se divise généralement en deux rayons (réfléchi et réfracté). Ensuite, ces deux rayons sont divisés en deux autres et ainsi de suite.

La principale procédure de lancer de rayons inversé dans mon programme est la procédure Ray. Il a la structure suivante :

Si la génération de faisceau est égale à la profondeur de récursion maximale, alors nous renvoyons la luminosité moyenne pour tous les composants. Si ce n'est pas le cas, continuez

Nous déterminons le triangle le plus proche avec lequel le rayon coupe.

S'il n'y a pas de tel triangle, renvoyez la couleur d'arrière-plan ; si c'est le cas, continuez.

Si la surface avec laquelle l'intersection a été trouvée est réfléchissante, alors nous formons un rayon réfléchi et appelons la procédure Ray de manière récursive avec la génération de rayon augmentée de 1.

Si la surface avec laquelle l'intersection a été trouvée se réfracte, alors nous formons un rayon réfracté et appelons la procédure Ray de manière récursive avec la génération de rayon augmentée de 1.

Nous déterminons l'éclairement final du pixel en tenant compte de l'emplacement des sources, des propriétés du matériau, ainsi que de l'intensité du faisceau réfléchi et réfracté.

J'ai déjà évoqué un certain nombre de limites de la méthode de traçage lorsque nous avons parlé de la réfraction diffuse et du miroir irrégulier. Regardons quelques autres.

Seuls des objets spéciaux – des sources lumineuses – peuvent éclairer la scène. Ils sont ponctuels et ne peuvent pas absorber, réfracter ou réfléchir la lumière.

Les propriétés d'une surface réfléchissante se composent de deux composants : diffus et spéculaire.

En réflexion diffuse, seuls les rayons des sources lumineuses sont pris en compte. Si la source éclaire un point à travers un miroir (avec un lapin), alors on considère que le point n'est pas éclairé.

La spécularité est également divisée en deux composantes.

réflexion - prend en compte la réflexion d'autres objets (pas de sources lumineuses)

spéculaire - prend en compte l'éblouissement des sources lumineuses

Le tracé ne prend pas en compte les dépendances à la longueur d'onde de la lumière :

indice de réfraction

coefficient d'absorption

coefficient de réflexion

Comme je ne modélise pas la réflexion et la réfraction diffuses, je ne pourrai pas obtenir de rétroéclairage. Par conséquent, nous introduisons un éclairage de fond minimum. Souvent, cela permet simplement d’améliorer considérablement la qualité de l’image.

L'algorithme de traçage vous permet de dessiner des ombres de très haute qualité. Cela ne nécessitera pas beaucoup de refonte de l’algorithme. Il faudra y ajouter quelque chose. Lors du calcul de l’éclairement des points, il est nécessaire de placer un « Shadow Front » dans chacune des sources lumineuses. Le « front d'ombre » est un rayon qui vérifie s'il y a quelque chose entre le point et la source. S'il y a un objet opaque entre eux, alors le point est dans l'ombre. Cela signifie que cette source ne contribue pas à l'éclairement final du point. Si un objet transparent repose, l'intensité de la source diminue. Dessiner des ombres prend beaucoup de temps. Ainsi, dans certaines situations, ils sont handicapés.

Mon programme a la capacité d'activer le lissage de l'image. L'anticrénelage consiste à déterminer la couleur d'un pixel. Non pas un rayon, mais quatre, est lancé et la valeur moyenne de couleur de ces rayons est déterminée. S'il faut trouver la couleur d'un pixel (i,j), alors 4 rayons sont envoyés vers des points du plan écran de coordonnées (i-0.25,j-0.25), (i-0.25,j+0.25), (je+0,25,j-0,25) , (je+0,25,j+0,25).



Avez-vous aimé l'article? Partagez avec vos amis !