Mapa 3D z mapy 2D

Chciałbym przekształcić “zwykłą, płaską” mapę, np. taką:

w postać mapy 3D, czyli, np. taką:

Czy zna ktoś algorym przeliczania współrzędnych ekranowych, aby coś takiego uzyskać? Na razie w opcji najprostszej zakładam, że wszystkie obiekty na mapie są płaskie.

Próbowałem to wygooglować, ale nie mogę znaleźć. :frowning: Chyba to ma związek z projekcją perspektywiczną.

Od razu skojarzyło mi się to ze SNES-owym Mode 7 :wink:
Może to Ci pomoże?
http://gamedev.stackexchange.com/questions/24957/doing-an-snes-mode-7-affine-transform-effect-in-pygame
Masz 2 sposoby na implementację tego efektu. Przed albo po rasteryzacji. Przed wymaga mniej obliczeń (przekształcasz tylko punkty), ale po daje Ci w gratisie perspektywę także dla grubości dróg (jak w widoku 3D który oferują od zawsze zwykłe samochodowe navi.)

Spotkałem się już na wielu stronach z tego typu wzorami jak na tej stronie co podałeś:

 sx = px / pz;
 sy = py / pz; 

Tylko problem w tym, że jak je stosuję to nic ciekawego mi nie wychodzi.

Wyobraźmy sobie szkolny układ współrzęcznych i cztery punkty: A = (-2, -5), B = (2, -5), C = (2, 5), D = (-2, 5). Ten prostokąt jest dla mnie drogą. Odcinki AD i BC stanowią jej krawężniki, a oś Y pas rozdzielający kierunki jazdy. W widoku 3D chciałbym uzyskać trapez: odcinek AB powinien się wydłużyć, a odcinek CD powinien się skrócić i obniżyć. Wszystko to powinno zależeć od punktu, w którym znajduje się oko oraz punktu w którym zbiegają sie linie perspektywy (to się chyba fachowo nazywa “vanishing point”).

Jak moje punkty zastosowałem do tego pseudokodu ze strony, którą wskazałeś to dostałem:
A’ = (-0,1333, 13,333), B’ = (0,1333, 13,333), C’ = (0,08, 8) D’ = (-0,08, 8).
Jest to trapez, ale odwrócony i przesunięty do góry. Więc nie jest to tym, czego oczekuję. W ten sposób przeanalizowałem już kilka różnych stron i nic sensownego mi nie wychodzi :frowning:

Wydaje mi się, że problem polega na tym, że nie chodzi tylko o rozciągnięcie/skurczenie względem osi X, ale również względem osi Y. Zrób sobie zdjęcie wmiarę płaskiego terenu zobaczysz wtedy, że pierwsze 100 px w osi Y zawiera np. 1m odległości, a kolejne 100px już kilka metrów. Poszukaj pod hasłem “perspective projection matrix”. W przypadku podejścia typowo geometrycznego macierz perspektywy nie jest skomplikowana, niestety nie wiem jak będzie to wyglądać gdy się używa jakiejś projekcji geograficznej.

Przede wszystkim jesli to ma byc rzut z perspektywa to musisz ustalic gdzie jest kamera, niech bedzie w punkcie E. Kiedy chcesz przeksztalcic punkt to musisz sprawdzic czy jest przed kamera (np. Ay > Ey). Kamera musi miec nie-zerowa wysokosc jesli ma cokolwiek widziec… pz to odleglosc danego punktu od kamery w linii prostej, px to x punktu odjac x kamery, py to wysokosc punktu (0) odjac wysokosc kamery. Wtedy policz jeszcze raz A’, B’, C’ i D’ i powinno byc okej.

Moze lepiej nie kombinowac tylko zrobic to w OpenGL, nawet w aplikacji webowej mozesz go uzyc. Mozna nawet przkesztalcic zwykle kafelki OSM (czyli po rasteryzacji wedlug tego co pisze RicoElectrico) za pomoca shaderow bez dotykania 3D.

Zatem niech punkt E = (0, -8, 2) będzie kamerą. Załóżmy jeszcze, że punkt F będzie środkiem odcinka AB, czyli F = (0, -5), punkt G rzutem punktu E na płaszczyznę XY, czyli G = (0, -8). Stosując Twój przepis mamy:

  • warunek Ay > Ey jest spełniony ponieważ -5 > -8.
  • “pz to odleglosc danego punktu od kamery w linii prostej”, czyli pz = sqrt((EFEF + GFGF) + EG*EG) = sqrt(17) = 4,123.
  • “px to x punktu odjac x kamery”, czyli -2 - 0 = -2.
  • “py to wysokosc punktu (0) odjac wysokosc kamery”, czyli 0 - 2 = -2.

Zatem otrzymujemy, że
sx = -2 / 4,123 = -0,485
sy = -2 / 4,123 = -0,485.

Czyli A’ = (-0,485, -0,485). Z symetrii punkt B’ = (0,485, -0,485). Długość A’B’ = 0,97. Długość AB = 4.

Licząc analogicznie otrzymamy, że D’ = (-0,15, -0,15) i C’ = (-0,15, -0,15). Długość C’D’ = 0,3. Długość CD = 4.

Zatem A’B’ jest trzy razy dłuższy niż C’D’ i A’B’ “podjechał” do góry, a C’D “zjechał” na dół.

Muszę poeksperymentować z ustawieniem kamery. Zobaczymy co uzyskam.

Proszę o sprawdzenie, czy dobrze zrozumieałem formuły, które podałeś.

A propos OpenGL. Nie chcę stosować armaty na muchę. Policzenie powyższego, jak widzę, wymaga kilku prostych działań.

Efekt moich prób jest następujący. Działam na komputerze z rozdzielczością 1920 x 1080. Dane wejściowe do przeliczeń stanowią granice Polski, co w obrazie 2D wygląda następująco:

Polska 2D

Wspórzędne każdego z wyświetlonych punktów przetwarzam za pomocą metody transform:


import java.awt.geom.Point2D;

public class Transformator {

	double cameraX;
	double cameraY;
	double cameraZ;

	public Transformator(int screenWidth, int screenHeight) {
		cameraX = screenWidth / 2;
		cameraY = screenHeight + 800;
		cameraZ = 60;
	}

	private double getCameraToPointDistance(Point2D.Double point) {
		double distance;
		distance = (point.x - cameraX) * (point.x - cameraX)
			 + (point.y - cameraY) * (point.y - cameraY);
		distance = Math.sqrt(distance + cameraZ * cameraZ);
		return distance;
	}

	public Point2D.Double transform(Point2D.Double screenPoint) {
		double pz = getCameraToPointDistance(screenPoint);
		double px = screenPoint.x - cameraX;
		double py = cameraZ;
		return new Point2D.Double(px / pz,  py / pz);
	}

}

i otrzymuję w zasadzie kropkę ponieważ wszystkie punkty wyznaczają się jako (0, 0). Zacząłem wiec wprowadzać współczyniki w ostatniej linii wspomnianej metody. Przy takiej postaci tej linii:


return new Point2D.Double(cameraX + (2000 * px / pz), 10000 * py / pz);

otrzymuję wynik:

Polska 3D

Niby coś się spłaszczyło i rozciągnęło, ale nie wydaje mi się że to tak powinno wyglądac.

Help me!

Chyba tak, tylko wynikowe wspolrzedne beda w ukladzie gdzie x=-1 to powiedzmy lewy brzeg ekranu, x=1 to prawy brzeg, y=-1 to dol, a y=1 to gora. Wiec trzeba to pomnozyc przez rozdzielczosc jaka bys chcial dostac, odpowiednio przesunac, odwrocic.

Wszystko zalezy w jakim zakresie masz dane wejsciowe. Mozna zrobic to tak jak masz w kodzie ale osobiscie nie mieszalbym rozmiaru ekranu do ustalenia pozycji kamery, a przeliczenie na wpolrzedne ekranowe zostawilbym na pozniej, gdzies tam, gdzie rysujesz linie. Kamere ustawilbym tak, zeby bylo widac wszystkie dane. Jesli x i y punktow sa w zakresie -10, 10 to ustaw kamere np. w (0, -15, 3).

Dla mnie ok… jesli nie chcesz zeby horyzont byl w polowie ekranu to przyjmij np. y=0 za gore ekranu.

Żeby rozpoznać jeszcze ten temat. Możesz podać szczgóły w jaki sposób przekształcić kafelek za pomocą OpenGL?

Wsytarczy zaladowac widok mapy jako teksture dla odpowieniej wielkosc plaszczyzny i zrobic takie samo przeksztalcenie jak juz masz w jej pixel shaderze. Zamiast tego przeksztalcenia mozna tez manipulowac pozycja kamery i dostac taki sam wynik.

Może jakiś przykładzik z OpenGl?
IMHO kilogram przykładu jest lepszy niż tona teorii.