Во-первых, вы не хотите использовать gluLookAt
. gluLookAt
поворачивает камеру, но физический экран, на который смотрит пользователь, не вращается. gluLookAt
будет работать только в том случае, если экран будет вращаться так, что нормаль экрана будет продолжать указывать на пользователя. Перспективное искажение внеосевой проекции позаботится обо всем необходимом нам вращении.
Что вам нужно учитывать в вашей модели, так это положение экрана внутри усеченного конуса. Рассмотрим следующее изображение. Красные точки — это границы экрана. Чего вам нужно добиться, так это того, чтобы эти положения оставались постоянными в 3D WCS, поскольку физический экран в реальном мире также (надеюсь) не двигается. Я думаю, что это ключевое понимание виртуальной реальности и стереоскопии. Экран — это что-то вроде окна в виртуальную реальность, и чтобы совместить реальный мир с виртуальной реальностью, вам нужно совместить усеченную пирамиду с этим окном.
Для этого вам нужно определить положение экрана в системе координат Kinect. Предполагая, что Kinect находится в верхней части экрана, +y указывает вниз и что единица измерения, которую вы используете, — миллиметры, я ожидаю, что эти координаты будут чем-то вроде (+-300, 200, 0), ( +-300, 500, 0).
Теперь есть две возможности для дальней плоскости. Вы можете выбрать фиксированное расстояние от камеры до дальней плоскости. Это означало бы, что дальняя плоскость будет двигаться назад, если пользователь будет двигаться назад, возможно, обрезая объекты, которые вы хотите нарисовать. Или вы можете оставить дальнюю плоскость в фиксированном положении в WCS, как показано на рисунке. Я считаю, что последний более полезен. Для ближней плоскости я думаю, что фиксированное расстояние от камеры нормально.
Входными данными являются 3D-позиции экрана wcsPtTopLeftScreen
и wcsPtBottomRightScreen
, отслеживаемая позиция головы wcsPtHead
, значение z дальней плоскости wcsZFar
(все в WCS) и значение z ближней плоскости camZNear
(в координатах камеры). . Нам нужно вычислить параметры усеченной пирамиды в координатах камеры.
camPtTopLeftScreen = wcsPtTopLeftScreen - wcsPtHead;
camPtTopLeftNear = camPtTopLeftScreen / camPtTopLeftScreen.z * camZNear;
и то же самое с нижней правой точкой. Также:
camZFar = wcsZFar - wcsPtHead.z
Теперь единственная проблема заключается в том, что Kinect и OpenGL используют разные системы координат. В Kinect CS +y указывает вниз, +z указывает от пользователя к Kinect. В OpenGL +y указывает вверх, +z указывает на зрителя. Это означает, что мы должны умножить y и z на -1:
glFrustum(camPtTopLeftNear.x, camPtBottomRightNear.x,
-camPtBottomRightNear.y, -camPtTopLeftNear.y, camZNear, camZFar);
Если вам нужно лучшее объяснение, которое также охватывает стереоскопию, посмотрите это видео, я нашел его познавательно и хорошо сделано.
Быстрая демонстрация, возможно, вам придется настроить wcsWidth
, pxWidth
и 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;
}
Следующие изображения следует просматривать с расстояния, равного 135% их ширины на экране (70 см на моем экране шириной 52 см в полноэкранном режиме).
person
Andreas Haferburg
schedule
26.05.2013