Aller au contenu
moorea
Vue aérienne au couchant d’un long littoral courbe, prise au très grand angle : l’horizon se courbe nettement sur les bords du cadre.

Ingénierie

Une caméra, trois surfaces : aplatir le monde en 360

Pourquoi les bords d’un panorama 360 s’étirent au dézoom, quelles projections y remédient, et pourquoi les trois tiennent dans une ligne de shader.

ParJean-Charles7 min de lecture

Ouvrez n’importe quelle visite virtuelle aérienne - la nôtre comprise - et dézoomez à fond. Regardez les coins de l’écran : le pin parasol y devient une serpillière, le bord de plage s’étire comme du caramel. Zoomez un peu et tout redevient normal.

Ce n’est pas un bug, et ce n’est pas votre écran. C’est un théorème. Et comme souvent avec les théorèmes, on ne le viole pas - on négocie.

Cet article raconte la négociation : pourquoi les bords s’étirent, quelles projections existent pour y remédier, pourquoi il n’y a pas une bonne réponse mais une par type d’image, et comment notre moteur les sert toutes les trois avec une seule ligne de shader de différence.

Le prix des lignes droites

Un panorama 360 est une sphère d’image dont vous occupez le centre. L’écran, lui, est plat. Il faut donc projeter - et toute projection choisit ce qu’elle sacrifie ; les cartographes se battent avec ça depuis Mercator.

Le choix par défaut de tout le monde - nous, Google Street View, votre appareil photo - est la projection rectilinéaire : on tend un plan devant la caméra et on y perce chaque rayon de lumière.

Sa vertu est unique, et c’est la raison de son hégémonie : c’est la seule projection qui garde droites toutes les lignes droites. Un ponton reste un ponton, une porte reste rectangulaire.

Le prix est écrit dans la trigonométrie : la taille apparente des objets croît comme 1/cos²θ en s’écartant du centre de l’image.

Écart au centre Grossissement
×1
45° ×2
60° ×4
75° ×15

À champ étroit, c’est invisible. Mais un player 360 laisse dézoomer bien au-delà d’un objectif photo.

Étirement en bord d’écran

×15

À 150° de champ, le bord de l’image est grossi quinze fois et les coins vingt. Voilà la serpillière.

Loi en 1/cos²θ de la projection rectilinéaire

Les photographes connaissent ça d’instinct - c’est l’étirement des visages en bord de photo de groupe au 16 mm.

Le premier réflexe, honnête mais frustrant, est de borner le champ : la plupart des players s’arrêtent vers 120°. C’est amputer le dézoom - et pour des panoramas de drone dont tout l’intérêt est d’embrasser un domaine entier, c’est amputer le produit.

Deuxième projection : préserver les formes

Il existe une projection qui fait le sacrifice inverse : la stéréographique. Elle est conforme - elle préserve localement les formes. Un arbre dans le coin reste un arbre, un cercle reste un cercle, à n’importe quelle excentricité. Son prix : les lignes droites courbent.

Vous la connaissez sans le savoir : poussée à l’extrême, c’est le little planet, ce globe miniature par lequel s’ouvrent nos visites.

L’idée moderne consiste à ne pas choisir une fois pour toutes, mais à glisser continûment du rectilinéaire vers la stéréographique au fil du dézoom.

Au champ étroit, lignes parfaites ; au champ extrême - où plus personne ne lit l’image comme une « photo » - formes préservées, et une courbure douce qui passe pour du grand angle naturel.

Et la géométrie fait un cadeau aux paysages côtiers.

La seule vraie ligne droite d’un panorama de bord de mer est précisément celle que la projection épargne.

Troisième projection : préserver les verticales

Reste un cas que la stéréographique sert mal : l’architecture. Dans un salon ou une cour d’hôtel, l’œil pardonne tout sauf une chose - des murs qui penchent. Il existe une projection taillée pour ça, et elle a trois siècles d’avance sur nos shaders.

Giovanni Paolo Panini peignait les vedute romaines - des places entières, des intérieurs de basiliques - sur des toiles qui couvrent bien plus de champ qu’aucune perspective classique ne le permet sans distorsion monstrueuse.

Son truc, formalisé depuis sous le nom de projection Panini : comprimer doucement les angles horizontaux, comme la stéréographique, tout en gardant la projection verticale d’une perspective classique.

Résultat : toutes les verticales restent droites et verticales, les radiales qui fuient vers le centre aussi, et seules les horizontales lointaines s’incurvent. Le centre de l’image est rigoureusement identique à une photo normale - la magie n’opère que vers les bords.

C’est la projection des logiciels de panorama d’architecture, et c’est celle qu’attendent les visites d’intérieur.

Le twist : c’est trois fois la même caméra

Voici la partie qui nous a fait sourire en l’implémentant. Ces trois projections ont l’air de trois mondes mathématiques différents.

En réalité, c’est exactement la même caméra - une perspective ordinaire, linéaire, celle que tout GPU sait projeter avec une matrice 4×4 - qu’on recule d’une distance d derrière le centre du panorama.

Ce qui change, c’est la surface sur laquelle on pose les points avant de les photographier.

On projette… …et on obtient
le plan image lui-même (d = 0) la perspective rectilinéaire
la sphère unité le continuum fisheye → stéréographique
le cylindre vertical unité le continuum → Panini

Deux identités ferment la boucle. Sur la sphère, un rayon d’écran à l’angle φ atteint l’image à l’angle θ = φ + asin(d·sin φ) : à d = 1, θ = 2φ - le doublement d’angle, signature de la stéréographique connue depuis l’Antiquité.

Sur le cylindre, la projection depuis la caméra reculée redonne mot pour mot les formules Panini des logiciels de panorama, et d = 1 est le Panini classique. Un seul paramètre, deux surfaces, trois rendus.

Pour le moteur, c’est une aubaine. Nos panoramas sont streamés en tuiles multirésolution dessinées par le GPU ; la caméra reculée reste une projection linéaire, donc la matrice existante fait tout le travail.

À une condition : que les sommets soient posés sur la bonne surface. On subdivise chaque tuile en grille 16×16 et le vertex shader place chaque sommet :

vec3 s = (uSurface == 0)
  ? normalize(p)                                  // sphère
  : p / length(p - dot(p, uUp) * uUp);            // cylindre

C’est l’unique différence entre le mode fisheye et le mode Panini : une normalisation contre une division par la norme horizontale.

Triangles pour la sphère entière

≈ 49 000

La grille 16×16 par tuile maintient l’erreur géométrique de la subdivision sous le pixel en 1080p.

Géométrie de la subdivision, moteur Moorea

Une paille pour un téléphone de 2020. Et la pleine résolution du streaming de tuiles est préservée dans tous les modes, à tous les niveaux de zoom.

Qui choisit ? Celui qui connaît l’image

Il n’y a pas de projection universellement bonne - il y a des images.

C’est pourquoi le choix n’apparaît ni dans le player ni sous les doigts du visiteur - qui ne devrait jamais avoir à savoir ce qu’est une projection - mais dans l’éditeur, comme réglage de la visite, à côté de l’intro little planet et de l’autorotation.

L’auteur choisit une fois, en connaissance de son contenu ; le visiteur, lui, ne voit qu’une chose : il peut embrasser le panorama entier, et les coins ne bavent plus.

Ce qu’il faut en retenir

L’étirement des bords en 360 n’est pas un défaut d’implémentation : c’est le contrat de la projection rectilinéaire, le même qui garantit les lignes droites.

Les bons players ne le « corrigent » pas, ils négocient - en glissant vers une projection conforme, qui préserve les formes, ou cylindrique, qui préserve les verticales, précisément dans la plage de zoom où les droites comptent moins que le reste.

Et parfois la géométrie est bonne fille : trois projections, une seule caméra, une ligne de shader d’écart - et l’horizon qui reste droit, gratuitement.

Jean-Charles

Développeur et télépilote de drone. Construit Moorea au sein d’Elvn Studio.

Pour aller plus loin

Un lieu à montrer, vu du ciel ?

Le plan gratuit suffit pour publier une première visite et la montrer.