Je travaille actuellement sur une simple visionneuse de panorama 3D pour un site Web. Pour des raisons de performances mobiles, j'utilise le three.js
rendu CSS3 . Cela nécessite une carte de cube, divisée en 6 images simples.
J'enregistre les images sur l'iPhone avec l'application Google Photosphere ou des applications similaires qui créent des panoramas équirectangulaires 2: 1. J'ai ensuite redimensionné et converti ceux-ci en cubemap avec ce site Web: http://gonchar.me/panorama/ (Flash)
De préférence, j'aimerais faire la conversion moi-même, soit à la volée en trois.js, si cela est possible, ou dans Photoshop. J'ai trouvé les actions Photoshop d'Andrew Hazelden et elles semblent assez proches, mais aucune conversion directe n'est disponible. Existe-t-il un moyen mathématique de convertir ceux-ci ou une sorte de script qui le fait? Si possible, j'aimerais éviter d'utiliser une application 3D telle que Blender.
Peut-être que c'est un long coup, mais je pensais que je demanderais. Je connais bien le javascript, mais je suis assez nouveau pour three.js
. J’hésite également à faire confiance à la fonctionnalité WebGL, qui semble être lente ou défectueuse sur les appareils mobiles. Le support est également toujours irrégulier.
Si vous voulez le faire côté serveur, il y a beaucoup d'options. http://www.imagemagick.org/ propose de nombreux outils en ligne de commande permettant de découper votre image en morceaux. Vous pouvez mettre la commande à cet effet dans un script et l'exécuter à chaque fois que vous avez une nouvelle image.
Il est difficile de dire exactement quel algorithme est utilisé dans le programme. Nous pouvons essayer de faire de l'ingénierie inverse en intégrant une grille carrée dans le programme. J'ai utilisé un grid de wikipedia
Qui donne Cela nous donne un indice sur la construction de la boîte.
Sphère imageuse avec lignes de latitude et de longitude et un cube l’entourant. Maintenant, projeter à partir du point situé au centre de la sphère produit une grille déformée sur le cube.
Prendre mathématiquement les coordonnées polaires r, θ, ø, pour la sphère r = 1, 0 <θ <π, -π/4 <ø <7π/4
projeter centralement ceux-ci sur le cube Nous divisons d'abord en quatre régions par la latitude -π/4 <ø <π/4, π/4 <ø <3π/4, 3π/4 <ø <5π/4, 5π/4 <ø <7π/4. Ceux-ci seront soit projeter à l'un des quatre côtés en haut ou en bas.
Supposons que nous sommes dans le premier côté -π/4 <ø <π/4. La projection centrale de .__ (sin θ cos ø, sin θ sin ø, cos θ) sera (un sin θ cos ø, un sin θ sin ø, un cos θ) qui frappe le plan x = 1 lorsque
alors
et le point projeté est
Si | lit θ/cos ø | <1 ce sera sur la face avant. Sinon, il sera projeté en haut ou en bas et vous aurez besoin d'une projection différente pour cela. Un meilleur test pour le sommet utilise le fait que la valeur minimale de cos ø sera cos π/4 = 1/√2, de sorte que le point projeté est toujours au sommet si cot θ/(1/√2)> 1 ou tan θ <1/√2. Cela correspond à θ <35º ou 0,615 radian.
Mettez cela ensemble en python
import sys
from PIL import Image
from math import pi,sin,cos,tan
def cot(angle):
return 1/tan(angle)
# Project polar coordinates onto a surrounding cube
# assume ranges theta is [0,pi] with 0 the north poll, pi south poll
# phi is in range [0,2pi]
def projection(theta,phi):
if theta<0.615:
return projectTop(theta,phi)
Elif theta>2.527:
return projectBottom(theta,phi)
Elif phi <= pi/4 or phi > 7*pi/4:
return projectLeft(theta,phi)
Elif phi > pi/4 and phi <= 3*pi/4:
return projectFront(theta,phi)
Elif phi > 3*pi/4 and phi <= 5*pi/4:
return projectRight(theta,phi)
Elif phi > 5*pi/4 and phi <= 7*pi/4:
return projectBack(theta,phi)
def projectLeft(theta,phi):
x = 1
y = tan(phi)
z = cot(theta) / cos(phi)
if z < -1:
return projectBottom(theta,phi)
if z > 1:
return projectTop(theta,phi)
return ("Left",x,y,z)
def projectFront(theta,phi):
x = tan(phi-pi/2)
y = 1
z = cot(theta) / cos(phi-pi/2)
if z < -1:
return projectBottom(theta,phi)
if z > 1:
return projectTop(theta,phi)
return ("Front",x,y,z)
def projectRight(theta,phi):
x = -1
y = tan(phi)
z = -cot(theta) / cos(phi)
if z < -1:
return projectBottom(theta,phi)
if z > 1:
return projectTop(theta,phi)
return ("Right",x,-y,z)
def projectBack(theta,phi):
x = tan(phi-3*pi/2)
y = -1
z = cot(theta) / cos(phi-3*pi/2)
if z < -1:
return projectBottom(theta,phi)
if z > 1:
return projectTop(theta,phi)
return ("Back",-x,y,z)
def projectTop(theta,phi):
# (a sin θ cos ø, a sin θ sin ø, a cos θ) = (x,y,1)
a = 1 / cos(theta)
x = tan(theta) * cos(phi)
y = tan(theta) * sin(phi)
z = 1
return ("Top",x,y,z)
def projectBottom(theta,phi):
# (a sin θ cos ø, a sin θ sin ø, a cos θ) = (x,y,-1)
a = -1 / cos(theta)
x = -tan(theta) * cos(phi)
y = -tan(theta) * sin(phi)
z = -1
return ("Bottom",x,y,z)
# Convert coords in cube to image coords
# coords is a Tuple with the side and x,y,z coords
# Edge is the length of an Edge of the cube in pixels
def cubeToImg(coords,Edge):
if coords[0]=="Left":
(x,y) = (int(Edge*(coords[2]+1)/2), int(Edge*(3-coords[3])/2) )
Elif coords[0]=="Front":
(x,y) = (int(Edge*(coords[1]+3)/2), int(Edge*(3-coords[3])/2) )
Elif coords[0]=="Right":
(x,y) = (int(Edge*(5-coords[2])/2), int(Edge*(3-coords[3])/2) )
Elif coords[0]=="Back":
(x,y) = (int(Edge*(7-coords[1])/2), int(Edge*(3-coords[3])/2) )
Elif coords[0]=="Top":
(x,y) = (int(Edge*(3-coords[1])/2), int(Edge*(1+coords[2])/2) )
Elif coords[0]=="Bottom":
(x,y) = (int(Edge*(3-coords[1])/2), int(Edge*(5-coords[2])/2) )
return (x,y)
# convert the in image to out image
def convert(imgIn,imgOut):
inSize = imgIn.size
outSize = imgOut.size
inPix = imgIn.load()
outPix = imgOut.load()
Edge = inSize[0]/4 # the length of each Edge in pixels
for i in xrange(inSize[0]):
for j in xrange(inSize[1]):
pixel = inPix[i,j]
phi = i * 2 * pi / inSize[0]
theta = j * pi / inSize[1]
res = projection(theta,phi)
(x,y) = cubeToImg(res,Edge)
#if i % 100 == 0 and j % 100 == 0:
# print i,j,phi,theta,res,x,y
if x >= outSize[0]:
#print "x out of range ",x,res
x=outSize[0]-1
if y >= outSize[1]:
#print "y out of range ",y,res
y=outSize[1]-1
outPix[x,y] = pixel
imgIn = Image.open(sys.argv[1])
inSize = imgIn.size
imgOut = Image.new("RGB",(inSize[0],inSize[0]*3/4),"black")
convert(imgIn,imgOut)
imgOut.show()
La fonction projection
prend les valeurs theta
et phi
et renvoie les coordonnées dans un cube de -1 à 1 dans chaque direction. CubeToImg prend les coordonnées (x, y, z) et les traduit en coordonnées d'image de sortie.
L'algorithme ci-dessus semble obtenir la géométrie correcte en utilisant un image de buckingham palace nous obtenons Cela semble obtenir la plupart des lignes dans le pavage droit.
Nous obtenons quelques artefacts d’image. Cela est dû au fait de ne pas avoir une carte de pixels de 1 à 1. Ce que nous devons faire, c'est utiliser une transformation inverse. Plutôt que de parcourir en boucle chaque pixel de la source et de trouver le pixel correspondant dans la cible, nous parcourons en boucle les images cibles et trouvons le pixel source correspondant le plus proche.
import sys
from PIL import Image
from math import pi,sin,cos,tan,atan2,hypot,floor
from numpy import clip
# get x,y,z coords from out image pixels coords
# i,j are pixel coords
# face is face number
# Edge is Edge length
def outImgToXYZ(i,j,face,Edge):
a = 2.0*float(i)/Edge
b = 2.0*float(j)/Edge
if face==0: # back
(x,y,z) = (-1.0, 1.0-a, 3.0 - b)
Elif face==1: # left
(x,y,z) = (a-3.0, -1.0, 3.0 - b)
Elif face==2: # front
(x,y,z) = (1.0, a - 5.0, 3.0 - b)
Elif face==3: # right
(x,y,z) = (7.0-a, 1.0, 3.0 - b)
Elif face==4: # top
(x,y,z) = (b-1.0, a -5.0, 1.0)
Elif face==5: # bottom
(x,y,z) = (5.0-b, a-5.0, -1.0)
return (x,y,z)
# convert using an inverse transformation
def convertBack(imgIn,imgOut):
inSize = imgIn.size
outSize = imgOut.size
inPix = imgIn.load()
outPix = imgOut.load()
Edge = inSize[0]/4 # the length of each Edge in pixels
for i in xrange(outSize[0]):
face = int(i/Edge) # 0 - back, 1 - left 2 - front, 3 - right
if face==2:
rng = xrange(0,Edge*3)
else:
rng = xrange(Edge,edge*2)
for j in rng:
if j<Edge:
face2 = 4 # top
Elif j>=2*Edge:
face2 = 5 # bottom
else:
face2 = face
(x,y,z) = outImgToXYZ(i,j,face2,Edge)
theta = atan2(y,x) # range -pi to pi
r = hypot(x,y)
phi = atan2(z,r) # range -pi/2 to pi/2
# source img coords
uf = ( 2.0*Edge*(theta + pi)/pi )
vf = ( 2.0*Edge * (pi/2 - phi)/pi)
# Use bilinear interpolation between the four surrounding pixels
ui = floor(uf) # coord of pixel to bottom left
vi = floor(vf)
u2 = ui+1 # coords of pixel to top right
v2 = vi+1
mu = uf-ui # fraction of way across pixel
nu = vf-vi
# Pixel values of four corners
A = inPix[ui % inSize[0],clip(vi,0,inSize[1]-1)]
B = inPix[u2 % inSize[0],clip(vi,0,inSize[1]-1)]
C = inPix[ui % inSize[0],clip(v2,0,inSize[1]-1)]
D = inPix[u2 % inSize[0],clip(v2,0,inSize[1]-1)]
# interpolate
(r,g,b) = (
A[0]*(1-mu)*(1-nu) + B[0]*(mu)*(1-nu) + C[0]*(1-mu)*nu+D[0]*mu*nu,
A[1]*(1-mu)*(1-nu) + B[1]*(mu)*(1-nu) + C[1]*(1-mu)*nu+D[1]*mu*nu,
A[2]*(1-mu)*(1-nu) + B[2]*(mu)*(1-nu) + C[2]*(1-mu)*nu+D[2]*mu*nu )
outPix[i,j] = (int(round(r)),int(round(g)),int(round(b)))
imgIn = Image.open(sys.argv[1])
inSize = imgIn.size
imgOut = Image.new("RGB",(inSize[0],inSize[0]*3/4),"black")
convertBack(imgIn,imgOut)
imgOut.save(sys.argv[1].split('.')[0]+"Out2.png")
imgOut.show()
Les résultats de ceci sont
Compte tenu de l'excellente réponse acceptée, je voulais ajouter mon implémentation c ++ correspondante, basée sur OpenCV.
Pour ceux qui ne connaissent pas OpenCV, considérez Mat
comme une image. Nous construisons d’abord deux cartes qui remappent de l’image équirectangulaire à notre face correspondante. Ensuite, nous faisons le gros du travail (c'est-à-dire un remappage avec interpolation) en utilisant OpenCV.
Le code peut être rendu plus compact si la lisibilité n’est pas un problème.
// Define our six cube faces.
// 0 - 3 are side faces, clockwise order
// 4 and 5 are top and bottom, respectively
float faceTransform[6][2] =
{
{0, 0},
{M_PI / 2, 0},
{M_PI, 0},
{-M_PI / 2, 0},
{0, -M_PI / 2},
{0, M_PI / 2}
};
// Map a part of the equirectangular panorama (in) to a cube face
// (face). The ID of the face is given by faceId. The desired
// width and height are given by width and height.
inline void createCubeMapFace(const Mat &in, Mat &face,
int faceId = 0, const int width = -1,
const int height = -1) {
float inWidth = in.cols;
float inHeight = in.rows;
// Allocate map
Mat mapx(height, width, CV_32F);
Mat mapy(height, width, CV_32F);
// Calculate adjacent (ak) and opposite (an) of the
// triangle that is spanned from the sphere center
//to our cube face.
const float an = sin(M_PI / 4);
const float ak = cos(M_PI / 4);
const float ftu = faceTransform[faceId][0];
const float ftv = faceTransform[faceId][1];
// For each point in the target image,
// calculate the corresponding source coordinates.
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
// Map face pixel coordinates to [-1, 1] on plane
float nx = (float)y / (float)height - 0.5f;
float ny = (float)x / (float)width - 0.5f;
nx *= 2;
ny *= 2;
// Map [-1, 1] plane coords to [-an, an]
// thats the coordinates in respect to a unit sphere
// that contains our box.
nx *= an;
ny *= an;
float u, v;
// Project from plane to sphere surface.
if(ftv == 0) {
// Center faces
u = atan2(nx, ak);
v = atan2(ny * cos(u), ak);
u += ftu;
} else if(ftv > 0) {
// Bottom face
float d = sqrt(nx * nx + ny * ny);
v = M_PI / 2 - atan2(d, ak);
u = atan2(ny, nx);
} else {
// Top face
float d = sqrt(nx * nx + ny * ny);
v = -M_PI / 2 + atan2(d, ak);
u = atan2(-ny, nx);
}
// Map from angular coordinates to [-1, 1], respectively.
u = u / (M_PI);
v = v / (M_PI / 2);
// Warp around, if our coordinates are out of bounds.
while (v < -1) {
v += 2;
u += 1;
}
while (v > 1) {
v -= 2;
u += 1;
}
while(u < -1) {
u += 2;
}
while(u > 1) {
u -= 2;
}
// Map from [-1, 1] to in texture space
u = u / 2.0f + 0.5f;
v = v / 2.0f + 0.5f;
u = u * (inWidth - 1);
v = v * (inHeight - 1);
// Save the result for this pixel in map
mapx.at<float>(x, y) = u;
mapy.at<float>(x, y) = v;
}
}
// Recreate output image if it has wrong size or type.
if(face.cols != width || face.rows != height ||
face.type() != in.type()) {
face = Mat(width, height, in.type());
}
// Do actual resampling using OpenCV's remap
remap(in, face, mapx, mapy,
CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0, 0, 0));
}
Compte tenu de l'entrée suivante:
Les faces suivantes sont générées:
Image reproduite avec l'aimable autorisation de Optonaut .
J'ai écrit un script pour couper le cubemap généré en fichiers individuels (posx.png, negx.png, posy.png, negy.png, posz.png et negz.png). Il va également emballer les 6 fichiers dans un fichier .Zip.
La source est ici: https://github.com/dankex/compv/blob/master/3d-graphics/skybox/cubemap-cut.py
Vous pouvez modifier le tableau pour définir les fichiers d’image:
name_map = [ \
["", "", "posy", ""],
["negz", "negx", "posz", "posx"],
["", "", "negy", ""]]
Les fichiers convertis sont:
Voici une version (naïvement) modifiée de la réponse absolument fantastique de Salix Alba qui convertit un visage à la fois, crache six images différentes et conserve le type de fichier de l'image d'origine.
Mis à part le fait que la plupart des cas d'utilisation prévoient probablement six images distinctes, le principal avantage de la conversion d'une face à la fois est que le travail sur des images de grande taille nécessite beaucoup moins de mémoire.
#!/usr/bin/env python
import sys
from PIL import Image
from math import pi, sin, cos, tan, atan2, hypot, floor
from numpy import clip
# get x,y,z coords from out image pixels coords
# i,j are pixel coords
# faceIdx is face number
# faceSize is Edge length
def outImgToXYZ(i, j, faceIdx, faceSize):
a = 2.0 * float(i) / faceSize
b = 2.0 * float(j) / faceSize
if faceIdx == 0: # back
(x,y,z) = (-1.0, 1.0 - a, 1.0 - b)
Elif faceIdx == 1: # left
(x,y,z) = (a - 1.0, -1.0, 1.0 - b)
Elif faceIdx == 2: # front
(x,y,z) = (1.0, a - 1.0, 1.0 - b)
Elif faceIdx == 3: # right
(x,y,z) = (1.0 - a, 1.0, 1.0 - b)
Elif faceIdx == 4: # top
(x,y,z) = (b - 1.0, a - 1.0, 1.0)
Elif faceIdx == 5: # bottom
(x,y,z) = (1.0 - b, a - 1.0, -1.0)
return (x, y, z)
# convert using an inverse transformation
def convertFace(imgIn, imgOut, faceIdx):
inSize = imgIn.size
outSize = imgOut.size
inPix = imgIn.load()
outPix = imgOut.load()
faceSize = outSize[0]
for xOut in xrange(faceSize):
for yOut in xrange(faceSize):
(x,y,z) = outImgToXYZ(xOut, yOut, faceIdx, faceSize)
theta = atan2(y,x) # range -pi to pi
r = hypot(x,y)
phi = atan2(z,r) # range -pi/2 to pi/2
# source img coords
uf = 0.5 * inSize[0] * (theta + pi) / pi
vf = 0.5 * inSize[0] * (pi/2 - phi) / pi
# Use bilinear interpolation between the four surrounding pixels
ui = floor(uf) # coord of pixel to bottom left
vi = floor(vf)
u2 = ui+1 # coords of pixel to top right
v2 = vi+1
mu = uf-ui # fraction of way across pixel
nu = vf-vi
# Pixel values of four corners
A = inPix[ui % inSize[0], clip(vi, 0, inSize[1]-1)]
B = inPix[u2 % inSize[0], clip(vi, 0, inSize[1]-1)]
C = inPix[ui % inSize[0], clip(v2, 0, inSize[1]-1)]
D = inPix[u2 % inSize[0], clip(v2, 0, inSize[1]-1)]
# interpolate
(r,g,b) = (
A[0]*(1-mu)*(1-nu) + B[0]*(mu)*(1-nu) + C[0]*(1-mu)*nu+D[0]*mu*nu,
A[1]*(1-mu)*(1-nu) + B[1]*(mu)*(1-nu) + C[1]*(1-mu)*nu+D[1]*mu*nu,
A[2]*(1-mu)*(1-nu) + B[2]*(mu)*(1-nu) + C[2]*(1-mu)*nu+D[2]*mu*nu )
outPix[xOut, yOut] = (int(round(r)), int(round(g)), int(round(b)))
imgIn = Image.open(sys.argv[1])
inSize = imgIn.size
faceSize = inSize[0] / 4
components = sys.argv[1].rsplit('.', 2)
FACE_NAMES = {
0: 'back',
1: 'left',
2: 'front',
3: 'right',
4: 'top',
5: 'bottom'
}
for face in xrange(6):
imgOut = Image.new("RGB", (faceSize, faceSize), "black")
convertFace(imgIn, imgOut, face)
imgOut.save(components[0] + "_" + FACE_NAMES[face] + "." + components[1])
Trouvé cette question, et même si les réponses sont bonnes, je pense qu'il reste encore du terrain à couvrir, alors voici mes deux sous.
Premièrement: à moins que vous n’ayez réellement à convertir les images vous-même (c’est-à-dire à cause de logiciels spécifiques), non.
La raison en est que, même s'il existe un mappage très simple entre projection équirectangulaire et projection cubique, le mappage entre les zones n'est pas simple: lorsque vous établissez une correspondance entre un point spécifique de votre image de destination et un point de la source avec un calcul élémentaire, dès que vous convertissez les deux points en pixels en arrondissant vous effectuez une approximation brute very _ qui ne prend pas en compte la taille des pixels et la qualité de l'image est forcément basse.
Deuxièmement: même si vous devez effectuer la conversion au moment de l'exécution, êtes-vous sûr de devoir effectuer la conversion? Sauf en cas de problème de performances très strict, si vous avez juste besoin d’une skybox, créez une très grande sphère, cousez-y la texture équirectangulaire, et c'est parti. Trois JS fournit déjà la sphère, autant que je m'en souvienne ;-)
Troisièmement: la NASA fournit un outil pour convertir toutes les projections imaginables (je viens de le découvrir, de le tester et de fonctionner comme un charme). Vous pouvez le trouver ici:
G.Projector - Projecteur de carte globale
et je trouve raisonnable de penser que les gars savent ce qu’ils font ;-)
J'espère que cela t'aides
(UPDATE:} _ il s'avère que les "gars" savent ce qu'ils font jusqu'à un certain point: le cubemap généré a une bordure hideuse qui rend la conversion difficile ...
(UPDATE 2: _ a trouvé l'outil de conversion ultime équirectangulaire en cubemap, appelé erect2cubic
.
C'est un petit utilitaire qui génère un script à envoyer à Hugin, de la manière suivante:
$ erect2cubic --erect=input.png --ptofile=cube.pto
$ nona -o cube_prefix cube.pto
(informations extraites de Vinay's Hacks page)
et générera toutes les 6 faces cubemap. Je l'utilise pour mon projet et il fonctionne comme un charme!
Le seul inconvénient de cette approche est que le script erect2cubit
n'est pas dans la distribution standard d'Ubuntu (c'est ce que j'utilise) et j'ai dû suivre les instructions à ce lien:
Blog décrivant comment installer et utiliser erect2cubic
pour savoir comment l'installer.
Vraiment la peine!
(cmft} _ Studio prend en charge conversion/filtering
de diverses projections HDR/LDR
à cubemaps
.
Peut-être me manque quelque chose ici. Mais il semble que la plupart sinon la totalité du code de transformation présenté peut être quelque peu incorrect. Ils prennent un panorama sphérique (équirectangulaire - 360 degrés horizontalement et 180 degrés verticalement) et semblent se convertir en faces de cube en utilisant une transformation cartésienne <-> cylindrique. Ne devraient-ils pas utiliser une transformation cartésienne <-> sphérique. Voir http://mathworld.wolfram.com/SphericalCoordinates.html
Je suppose que tant qu'ils inversent le calcul pour passer des faces du cube au panorama, alors cela devrait fonctionner. Mais les images des faces du cube peuvent être légèrement différentes lorsque vous utilisez la transformation sphérique.
Si je commence par ce équirectangulaire (panorama sphérique):
Ensuite, si j'utilise une transformation cylindrique (ce dont je ne suis pas sûr à 100% est correct à ce stade), j'obtiens le résultat suivant:
Mais si j'utilise une transformation sphérique, j'obtiens le résultat suivant:
Ils ne sont pas les mêmes. Mais mon résultat de transformation sphérique semble correspondre au résultat de Danke Xie, mais son lien ne montre pas le type de transformation qu'il utilise, autant que je puisse le lire.
Alors, est-ce que je comprends mal le code utilisé par beaucoup de contributeurs à ce sujet?
J'ai créé une solution à ce problème en utilisant OpenGL et créé un outil de ligne de commande autour de ce problème. Cela fonctionne à la fois avec des images et des vidéos, et c'est l'outil le plus rapide que j'ai trouvé là-bas.
Convert360 - Projet sur GitHub.
OpenGL Shader - Le fragment shader utilisé pour la re-projection.
L'utilisation est aussi simple que:
$ pip install convert360
$ convert360 -i ~/Pictures/Barcelona/sagrada-familia.jpg -o example.png -s 300 300
Pour obtenir quelque chose comme ça:
Une application C++ très simple qui convertit un panorama équirectangulaire en carte cube basée sur la réponse de Salix Alba => https://github.com/denivip/panorama
Voici une version JavaScript du code de Benjamn Dobell. convertFace
doit recevoir deux objets ìmageData
et un ID de visage (0-6).
Le code fourni peut être utilisé en toute sécurité dans un outil Web, car il n’a pas de dépendances.
// convert using an inverse transformation
function convertFace(imgIn, imgOut, faceIdx) {
var inPix = shimImgData(imgIn),
outPix = shimImgData(imgOut),
faceSize = imgOut.width,
pi = Math.PI,
pi_2 = pi/2;
for(var xOut=0;xOut<faceSize;xOut++) {
for(var yOut=0;yOut<faceSize;yOut++) {
var xyz = outImgToXYZ(xOut, yOut, faceIdx, faceSize);
var theta = Math.atan2(xyz.y, xyz.x); // range -pi to pi
var r = Math.hypot(xyz.x,xyz.y);
var phi = Math.atan2(xyz.z,r); // range -pi/2 to pi/2
// source img coords
var uf = 0.5 * imgIn.width * (theta + pi) / pi;
var vf = 0.5 * imgIn.width * (pi_2 - phi) / pi;
// Use bilinear interpolation between the four surrounding pixels
var ui = Math.floor(uf); // coord of pixel to bottom left
var vi = Math.floor(vf);
var u2 = ui+1; // coords of pixel to top right
var v2 = vi+1;
var mu = uf-ui; // fraction of way across pixel
var nu = vf-vi;
// Pixel values of four corners
var A = inPix.getPx(ui % imgIn.width, clip(vi, 0, imgIn.height-1));
var B = inPix.getPx(u2 % imgIn.width, clip(vi, 0, imgIn.height-1));
var C = inPix.getPx(ui % imgIn.width, clip(v2, 0, imgIn.height-1));
var D = inPix.getPx(u2 % imgIn.width, clip(v2, 0, imgIn.height-1));
// interpolate
var rgb = {
r:A[0]*(1-mu)*(1-nu) + B[0]*(mu)*(1-nu) + C[0]*(1-mu)*nu+D[0]*mu*nu,
g:A[1]*(1-mu)*(1-nu) + B[1]*(mu)*(1-nu) + C[1]*(1-mu)*nu+D[1]*mu*nu,
b:A[2]*(1-mu)*(1-nu) + B[2]*(mu)*(1-nu) + C[2]*(1-mu)*nu+D[2]*mu*nu
};
rgb.r=Math.round(rgb.r);
rgb.g=Math.round(rgb.g);
rgb.b=Math.round(rgb.b);
outPix.setPx(xOut, yOut, rgb);
} // for(var yOut=0;yOut<faceSize;yOut++) {...}
} // for(var xOut=0;xOut<faceSize;xOut++) {...}
} // function convertFace(imgIn, imgOut, faceIdx) {...}
// get x,y,z coords from out image pixels coords
// i,j are pixel coords
// faceIdx is face number
// faceSize is Edge length
function outImgToXYZ(i, j, faceIdx, faceSize) {
var a = 2 * i / faceSize,
b = 2 * j / faceSize;
switch(faceIdx) {
case 0: // back
return({x:-1, y:1-a, z:1-b});
case 1: // left
return({x:a-1, y:-1, z:1-b});
case 2: // front
return({x: 1, y:a-1, z:1-b});
case 3: // right
return({x:1-a, y:1, z:1-b});
case 4: // top
return({x:b-1, y:a-1, z:1});
case 5: // bottom
return({x:1-b, y:a-1, z:-1});
}
} // function outImgToXYZ(i, j, faceIdx, faceSize) {...}
function clip(val, min, max) {
return(val<min?min:(val>max?max:val));
}
function shimImgData(imgData) {
var w=imgData.width*4,
d=imgData.data;
return({
getPx:function(x,y) {
x=x*4+y*w;
return([ d[x], d[x+1], d[x+2] ]);
},
setPx:function(x,y,rgb) {
x=x*4+y*w;
d[x]=rgb.r;
d[x+1]=rgb.g;
d[x+2]=rgb.b;
d[x+3]=255; // alpha
}
});
} // function shimImgData(imgData) {...}
Il existe différentes représentations de cartes d'environnement. Voici un bel aperçu.
Si vous utilisez Photosphere (ou n'importe quelle application de panorama), vous avez probablement déjà la représentation horizontale latitude/longitude . Vous pouvez alors simplement dessiner un trois.js SphereGeometry . Voici un tutoriel sur la façon de rendre la terre.
Tutoriel - Comment créer la Terre dans WebGL?
Bonne chance :).