2013-05-23 11 views
6

Próbuję wykonać projekcję poza osią w mojej aplikacji i próbuję zmienić perspektywę sceny zgodnie z pozycją głowy użytkownika. Normalnie, biorąc pod uwagę, że musiałem narysować pole na ekranie, chciałbym narysować ramkę na ekranie jako:
Wypaczona projekcja frustum/poza osią dla śledzenia głowy w OpenGL

ofBox(350,250,0,50); //ofBox(x, y, z, size); where x, y and z used here are the screen coordinates 

Aby wykonać projekcję pozaosiowych tutaj, jestem świadomy, że muszę zmienić rzutem perspektywicznym, w następujący sposób:

vertFov = 0.5; near = 0.5; aspRatio = 1.33; 
glMatrixMode(GL_PROJECTION); 
glLoadIdentity(); 
glFrustum(near * (-vertFov * aspRatio + headX), 
      near * (vertFov * aspRatio + headX), 
      near * (-vertFov + headY), 
      near * (vertFov + headY), 
      near, far); //frustum changes as per the position of headX and headY 
glMatrixMode(GL_MODELVIEW); 
glLoadIdentity(); 
gluLookAt(headX * headZ, headY * headZ, 0, headX * headZ, headY * headZ, -1); 
glTranslate(0,0,headZ); 

przypadku symetrycznej bryły w powyższym przykładzie (gdzie headX i mocny jest zero), left, right params wyjdzie się -0.33, 0.33 i bottom, top parametry wychodzą być -0.25, 0.25 i ustalenia moja objętość obcinania wzdłuż tych współrzędnych. Próbowałem zasymulować poza osią za pomocą myszki do testu i zrobił, co następuje:

double mouseXPosition = (double)ofGetMouseX(); 
double mouseYPosition = (double)ofGetMouseY(); 
double scrWidth = (double)ofGetWidth(); 
double scrHeight = (double)ofGetHeight(); 

headX = ((scrWidth -mouseXPosition)/scrWidth) - 0.5; 
headY = (mouseYPosition/scrHeight) - 0.5; 
headZ = -0.5; //taken z constant for this mouse test 

Jednak mam zamiar użyć Kinect który daje mi współrzędne głowy rzędu (200, 400, 1000), (-250, 600, 1400), (400 , 100, 1400) itd. I nie jestem w stanie zrozumieć, jak zmienić parametry ścięgna, kiedy mam te pozycje głowy. Na przykład: Biorąc pod uwagę, że 0 jest w centrum dla Kinect, jeśli użytkownik porusza się tak, że jego pozycja to (200, 400, 1000), to jak zmienią się tutaj parametry frustum?
W jaki sposób przedmioty będą musiały być rysowane, gdy z-distance uzyskane z Kinect będą również musiały zostać wzięte pod uwagę? Obiekty muszą zostać zmniejszone, ponieważ zwiększają się o z i mogą się zdarzyć dzięki wywołaniu glTrasnlate() wewnątrz powyższego kodu poza osią, ale dwie skale układów współrzędnych są różne (glFrustum ustawia teraz objętość obcinania na [-0.25,0.33] na [ 0,25, -0,33], gdzie Kinect jest rzędu setek (400,200,1000)). Jak zastosować wartości z na glFrustum/gluLookAt?

+0

Ciekawy projekt. Zastanawiam się, jakie są szanse na efekt końcowy, że dzieci siedzą cal od telewizora, aby uzyskać maksymalny widok świata? ;) –

+0

Czy udało ci się go uruchomić z Kinect? Film z perspektywy użytkownika byłby naprawdę fajny. –

Odpowiedz

4

Po pierwsze, nie chcesz używać gluLookAt. gluLookAt obraca kamerę, ale fizyczny ekran, na który patrzy użytkownik, nie obraca się. gluLookAt działałby tylko wtedy, gdyby ekran był obrócony tak, aby normalny ekran nadal wskazywał użytkownika. Perspektywiczne zniekształcenie projekcji poza osią zajmie się wszystkimi potrzebnymi obrotami.

To, co musisz uwzględnić w swoim modelu, to pozycja ekranu w ciele ściętym. Rozważ następujący obraz. Czerwone punkty to granice ekranu. To, co musisz osiągnąć to to, że te pozycje pozostają stałe w 3D WCS, ponieważ fizyczny ekran w realnym świecie również (mam nadzieję) nie porusza się. Myślę, że to kluczowy wgląd w rzeczywistość wirtualną i stereoskopię. Ekran jest czymś w rodzaju okna wirtualnej rzeczywistości, a aby dopasować rzeczywisty świat do rzeczywistości wirtualnej, trzeba wyrównać tę płaszczyznę z tym oknem.

Awesome MSPaint skills

Aby to zrobić trzeba określić położenie ekranu w układzie współrzędnych Kinect. Zakładając, że Kinect znajduje się na górze ekranu, to + y wskazuje w dół, a urządzenie, z którego korzystasz, to milimetry, spodziewam się, że współrzędne te będą równe (+ -300, 200, 0), (+ -300, 500, 0).

Istnieją teraz dwie możliwości dla dalekiego samolotu. Możesz wybrać użycie stałej odległości od kamery do dalekiej płaszczyzny. Oznaczałoby to, że daleki samolot cofnąłby się, gdyby użytkownik cofnął się, prawdopodobnie przycinając obiekty, które chciałbyś narysować.Lub możesz utrzymać daleką płaszczyznę w ustalonej pozycji w WCS, jak pokazano na obrazku. Uważam, że ten drugi jest bardziej przydatny. Jeśli chodzi o najbliższy samolot, myślę, że ustalona odległość od kamery jest w porządku.

Wejścia są położenia 3D ekranu wcsPtTopLeftScreen i wcsPtBottomRightScreen, śledzonym położenie głowicy wcsPtHead, wartość Z z drugiej płaszczyźnie wcsZFar (wszystko w WCS), i wartość Rz od najbliższej powierzchni camZNear (we współrzędnych kamery). Musimy obliczyć parametry punktu ściętego we współrzędnych kamery.

camPtTopLeftScreen = wcsPtTopLeftScreen - wcsPtHead; 
camPtTopLeftNear = camPtTopLeftScreen/camPtTopLeftScreen.z * camZNear; 

i tym samym z dolnym prawym punktem. Też:

camZFar = wcsZFar - wcsPtHead.z 

enter image description here

Teraz jedynym problemem jest to, że Kinect i OpenGL stosować różne układy współrzędnych. W modelu Kinect CS, + y punktów w dół, + punktów z użytkownika w kierunku urządzenia Kinect. W OpenGL + y wskazuje w górę, + z wskazuje na widza. Oznacza to, że musimy pomnożyć przez Y i Z -1:

glFrustum(camPtTopLeftNear.x, camPtBottomRightNear.x, 
    -camPtBottomRightNear.y, -camPtTopLeftNear.y, camZNear, camZFar); 

Jeśli chcesz lepsze wyjaśnienie, które obejmuje również stereoskopii, sprawdź this video, znalazłem to wnikliwe i dobrze zrobione.

Szybkie demo, konieczne może być dostosowanie wcsWidth, pxWidth i wcsPtHead.z.

#include <glm/glm.hpp> 
#include <glm/ext.hpp> 
#include <glut.h> 
#include <functional> 

float heightFromWidth; 
glm::vec3 camPtTopLeftNear, camPtBottomRightNear; 
float camZNear, camZFar; 
glm::vec3 wcsPtHead(0, 0, -700); 

void moveCameraXY(int pxPosX, int pxPosY) 
{ 
    // Width of the screen in mm and in pixels. 
    float wcsWidth = 520.0; 
    float pxWidth = 1920.0f; 

    float wcsHeight = heightFromWidth * wcsWidth; 
    float pxHeight = heightFromWidth * pxWidth; 
    float wcsFromPx = wcsWidth/pxWidth; 

    glm::vec3 wcsPtTopLeftScreen(-wcsWidth/2.f, -wcsHeight/2.f, 0); 
    glm::vec3 wcsPtBottomRightScreen(wcsWidth/2.f, wcsHeight/2.f, 0); 
    wcsPtHead = glm::vec3(wcsFromPx * float(pxPosX - pxWidth/2), wcsFromPx * float(pxPosY - pxHeight * 0.5f), wcsPtHead.z); 
    camZNear = 1.0; 
    float wcsZFar = 500; 

    glm::vec3 camPtTopLeftScreen = wcsPtTopLeftScreen - wcsPtHead; 
    camPtTopLeftNear = camZNear/camPtTopLeftScreen.z * camPtTopLeftScreen; 
    glm::vec3 camPtBottomRightScreen = wcsPtBottomRightScreen - wcsPtHead; 
    camPtBottomRightNear = camPtBottomRightScreen/camPtBottomRightScreen.z * camZNear; 
    camZFar = wcsZFar - wcsPtHead.z; 

    glutPostRedisplay(); 
} 

void moveCameraZ(int button, int state, int x, int y) 
{ 
    // No mouse wheel in GLUT. :(
    if ((button == 0) || (button == 2)) 
    { 
    if (state == GLUT_DOWN) 
     return; 
    wcsPtHead.z += (button == 0 ? -1 : 1) * 100; 
    glutPostRedisplay(); 
    } 
} 

void reshape(int w, int h) 
{ 
    heightFromWidth = float(h)/float(w); 
    glViewport(0, 0, w, h); 
} 

void drawObject(std::function<void(GLdouble)> drawSolid, std::function<void(GLdouble)> drawWireframe, GLdouble size) 
{ 
    glPushAttrib(GL_ALL_ATTRIB_BITS); 
    glEnable(GL_COLOR); 
    glDisable(GL_LIGHTING); 
    glColor4f(1, 1, 1, 1); 
    drawSolid(size); 
    glColor4f(0.8, 0.8, 0.8, 1); 
    glDisable(GL_DEPTH_TEST); 
    glLineWidth(1); 
    drawWireframe(size); 

    glColor4f(0, 0, 0, 1); 
    glEnable(GL_DEPTH_TEST); 
    glLineWidth(3); 
    drawWireframe(size); 
    glPopAttrib(); 
} 

void display(void) 
{ 
    glPushAttrib(GL_ALL_ATTRIB_BITS); 
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); 
    glEnable(GL_DEPTH_TEST); 

    // In the Kinect CS, +y points down, +z points from the user towards the Kinect. 
    // In OpenGL, +y points up, +z points towards the viewer. 
    glm::mat4 mvpCube; 
    mvpCube = glm::frustum(camPtTopLeftNear.x, camPtBottomRightNear.x, 
    -camPtBottomRightNear.y, -camPtTopLeftNear.y, camZNear, camZFar); 
    mvpCube = glm::scale(mvpCube, glm::vec3(1, -1, -1)); 
    mvpCube = glm::translate(mvpCube, -wcsPtHead); 
    glMatrixMode(GL_MODELVIEW); glLoadMatrixf(glm::value_ptr(mvpCube)); 

    drawObject(glutSolidCube, glutWireCube, 140); 

    glm::mat4 mvpTeapot = glm::translate(mvpCube, glm::vec3(100, 0, 200)); 
    mvpTeapot = glm::scale(mvpTeapot, glm::vec3(1, -1, -1)); // teapots are in OpenGL coordinates 
    glLoadMatrixf(glm::value_ptr(mvpTeapot)); 
    glColor4f(1, 1, 1, 1); 
    drawObject(glutSolidTeapot, glutWireTeapot, 50); 

    glFlush(); 
    glPopAttrib(); 
} 

void leave(unsigned char, int, int) 
{ 
    exit(0); 
} 

int main(int argc, char **argv) 
{ 
    glutInit(&argc, argv); 
    glutCreateWindow("glut test"); 
    glutDisplayFunc(display); 
    glutReshapeFunc(reshape); 
    moveCameraXY(0,0); 
    glutPassiveMotionFunc(moveCameraXY); 
    glutMouseFunc(moveCameraZ); 
    glutKeyboardFunc(leave); 
    glutFullScreen(); 
    glutMainLoop(); 
    return 0; 
} 

następujących obrazów powinno być oglądany z odległości równej 135% ich szerokość na ekranie (70 cm na 52 cm mi szerokości w pełnym ekranie). enter image description here enter image description here

+0

To po prostu zmienia całą koncepcję, którą miałem w głowie. Mam zamiar spróbować, ale to, co już wypróbowałem, było oparte na tym [LINK] (http://kode80.com/2012/04/09/holotoy-perspective-in-webgl/), dla którego [źródło jest tutaj] (http://kode80.com/holowebgl/main.js) i używa 'gluLookAt'. Moje rozumienie "glFrustum" polegało na tym, że wprowadzane parametry ustanowiłyby objętość obcinania, a zastosowanie "gluLookAt" ma miejsce wtedy, gdy występuje zniekształcenie. Tymczasem dam zastrzyk do tej metody, ale możesz spojrzeć na link o lookAt nie jest właściwym sposobem, aby tam przejść, ponieważ wydaje się, że jest poprawny? – user1240679

+0

[Oto inny link] (http://doc-ok.org/?p=77), który opisuje, dlaczego nie chcesz się obracać. Chociaż kontekstem jest stereoskopia, obowiązują te same zasady, co w przypadku śledzenia głowy. –

+0

Próbowałem uzyskać zniekształcenie perspektywy używając samego "glFrustum" i nie używając "gluLookAt", ale nie mogłem uzyskać efektu. Dodałem jeszcze jedno pytanie o uzyskanie zniekształcenia z samym 'glFrustum', aby to sprawdzić. Ten sam, z którego byłeś tutaj powiązany. [Inny link do pytania o projekcję poza osią] (http://stackoverflow.com/questions/16446092/off-axis-projection-with-glfrustum?rq=1).Używając 'gluLookAt', robię tylko transformację oglądania i przenoszę 'kamerę' do innej pozycji, ale nie obracam jej. Chciałem ponownie sprawdzić to tutaj. – user1240679

2

Najlepszym wyjaśnieniem, w jaki sposób korzystać z glFrustum dla aplikacji śledzenia głowy można znaleźć w tym artykule Roberta Kooima nazwie uogólnione projekcja perspektywa:

http://csc.lsu.edu/~kooima/pdfs/gen-perspective.pdf

Pozwala także po prostu korzystaj z projekcji stereo, musisz tylko przełączać się między lewą a prawą kamerą!

+0

Powinienem był wcześniej ugryźć kulę i przejrzałem ten artykuł. Kod był bardzo łatwy do wdrożenia. Dzięki za udostępnienie! –