c++ - tortuga - personajes en opengl
InclinaciĆ³n de tronco fruncido/fuera del eje para el seguimiento de la cabeza en OpenGL (2)
Primero, no quieres usar gluLookAt
. gluLookAt
gira la cámara, pero la pantalla física que mira el usuario no gira. gluLookAt
solo funcionaría si la pantalla girara de forma tal que la pantalla normal siguiera apuntando al usuario. La distorsión de la perspectiva de la proyección fuera del eje se ocupará de toda la rotación que necesitamos.
Lo que debe tener en cuenta en su modelo es la posición de la pantalla dentro del tronco. Considera la siguiente imagen. Los puntos rojos son los bordes de la pantalla. Lo que necesita lograr es que estas posiciones permanezcan constantes en el WCS 3D, ya que la pantalla física en el mundo real también (afortunadamente) no se mueve. Creo que esta es la visión clave de la realidad virtual y la estereoscopía. La pantalla es como una ventana a la realidad virtual, y para alinear el mundo real con la realidad virtual, debe alinear el tronco con esa ventana.
Para hacerlo, debes determinar la posición de la pantalla en el sistema de coordenadas del Kinect. Suponiendo que el Kinect está en la parte superior de la pantalla, que + y apunta hacia abajo, y que la unidad que está utilizando es de milímetros, yo esperaría que estas coordenadas sean algo así como (+ -300, 200, 0), ( + -300, 500, 0).
Ahora hay dos posibilidades para el plano lejano. Puedes elegir usar una distancia fija desde la cámara al plano lejano. Eso significaría que el plano lejano retrocedería si el usuario retrocediera, posiblemente recortando los objetos que le gustaría dibujar. O puede mantener el plano lejano en una posición fija en el WCS, como se muestra en la imagen. Creo que este último es más útil. Para el avión cercano, creo que una distancia fija de la cámara está bien.
Las entradas son las posiciones 3D de la pantalla wcsPtTopLeftScreen
y wcsPtBottomRightScreen
, la posición de seguimiento del encabezado wcsPtHead
, el valor z del plano lejano wcsZFar
(todo en el WCS) y el valor z del plano cercano camZNear
(en las coordenadas de la cámara) . Necesitamos calcular los parámetros del tronco en las coordenadas de la cámara.
camPtTopLeftScreen = wcsPtTopLeftScreen - wcsPtHead;
camPtTopLeftNear = camPtTopLeftScreen / camPtTopLeftScreen.z * camZNear;
y lo mismo con el punto inferior derecho. También:
camZFar = wcsZFar - wcsPtHead.z
Ahora el único problema es que Kinect y OpenGL usan diferentes sistemas de coordenadas. En Kinect CS, + y apunta hacia abajo, + z apunta desde el usuario hacia Kinect. En OpenGL, + y apunta hacia arriba, + z apunta hacia el espectador. Eso significa que tenemos que multiplicar yyz por -1:
glFrustum(camPtTopLeftNear.x, camPtBottomRightNear.x,
-camPtBottomRightNear.y, -camPtTopLeftNear.y, camZNear, camZFar);
Si desea una mejor explicación que también cubra la estereoscopía, vea este video , lo encontré perspicaz y bien hecho.
Demostración rápida, puede que tenga que ajustar wcsWidth
, pxWidth
y 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;
}
Las siguientes imágenes deben verse desde una distancia igual al 135% de su ancho en la pantalla (70 cm en mi pantalla de 52 cm de ancho en pantalla completa).
Intento hacer una proyección fuera del eje en mi aplicación e intentar cambiar la perspectiva de la escena según la posición de la cabeza del usuario. Normalmente, dado que tenía que dibujar un cuadro en la pantalla, dibujaba un cuadro en la pantalla como:
ofBox(350,250,0,50); //ofBox(x, y, z, size); where x, y and z used here are the screen coordinates
Para hacer una proyección fuera del eje aquí, soy consciente de que tendría que cambiar la proyección de perspectiva de la siguiente manera:
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);
Para un tronco truncado simétrico en el caso anterior (donde headX y headY son cero), los parámetros left, right
salen -0.33
, 0.33
y bottom, top
parámetros bottom, top
salen -0.25, 0.25
y establecen mi volumen de recorte a lo largo de esos coordenadas Traté de simular el fuera del eje usando un mouse para una prueba e hice lo siguiente:
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
Sin embargo, tengo la intención de usar Kinect
que me da coordenadas para la cabeza del orden de (200, 400, 1000)
, (-250, 600, 1400)
, (400, 100, 1400) etc. y no puedo hacer cómo cambiar los parámetros del tronco cuando tengo esas posiciones de cabeza. Por ejemplo: Considerando 0
para estar en el centro para el Kinect, si el usuario se mueve de tal manera que su posición es (200, 400, 1000)
, ¿cómo cambiarían los parámetros del tronco truncado aquí?
¿Cómo se tendrán que dibujar los objetos cuando la z-distance
obtenida de Kinect
también deba tenerse en cuenta? Los objetos tienen que glTrasnlate()
tamaño a medida que aumenta z
y eso podría ocurrir mediante la llamada glTrasnlate()
dentro del código fuera del eje, pero las dos escalas de los sistemas de coordenadas son diferentes (glFrustum ahora establece el volumen de recorte en [-0.25,0.33] a [0.25, -0.33] donde Kinect es del orden de cientos (400,200,1000)
). ¿Cómo aplico los valores z a glFrustum
/ gluLookAt
?
La mejor explicación sobre cómo usar glFrustum para las aplicaciones de seguimiento de cabeza que puede encontrar es en este artículo de Robert Kooima llamado proyección de perspectiva generalizada:
http://csc.lsu.edu/~kooima/pdfs/gen-perspective.pdf
¡También le permite simplemente usar proyecciones estéreo, solo tiene que cambiar entre las cámaras izquierda y derecha!