Наклонная усеченная/внеосевая проекция для отслеживания головы в OpenGL

Я пытаюсь сделать внеосевую проекцию в своем приложении и пытаюсь изменить перспективу сцены в соответствии с положением головы пользователя. Обычно, учитывая, что мне нужно было нарисовать блок на экране, я рисовал блок на экране следующим образом:

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

Чтобы сделать здесь внеосевую проекцию, я знаю, что мне придется изменить перспективную проекцию следующим образом:

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);

Для симметричной усеченной пирамиды в приведенном выше случае (где headX и headY равны нулю) параметры left, right получаются равными -0.33, параметры 0.33 и bottom, top получаются равными -0.25, 0.25 и устанавливают объем отсечения по этим координатам. Я попытался смоделировать отклонение от оси с помощью мыши для теста и сделал следующее:

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

Тем не менее, я намерен использовать Kinect, который дает мне координаты головы порядка (200, 400, 1000), (-250, 600, 1400), (400, 100, 1400) и т. д., и я не могу понять, как изменить параметры усеченного конуса, когда у меня есть эти положения головы . Например: если считать, что 0 находится в центре Kinect, если пользователь переместится так, что его позиция будет (200, 400, 1000), то как здесь изменятся параметры усеченной пирамиды?
Как должны будут отрисовываться объекты, когда z-distance будет получено от Kinect тоже надо будет учитывать? Объекты должны уменьшаться в размере по мере увеличения z, и это может произойти с помощью вызова glTrasnlate() внутри приведенного выше внеосевого кода, но две шкалы систем координат различны (теперь glFrustum устанавливает громкость отсечения от [-0,25,0,33] до [ 0,25,-0,33], где Kinect порядка сотен (400,200,1000)). Как тогда применить значения z к glFrustum/gluLookAt?


person user1240679    schedule 23.05.2013    source источник
comment
Интересный проект. Интересно, каковы шансы, что в конечном итоге дети будут сидеть в дюйме от телевизора, чтобы получить максимальное представление о мире? ;)   -  person Samuel Harmer    schedule 30.05.2013
comment
Вы заставили его работать с Kinect? Видео с точки зрения пользователя было бы действительно круто.   -  person Andreas Haferburg    schedule 31.05.2013


Ответы (2)


Во-первых, вы не хотите использовать gluLookAt. gluLookAt поворачивает камеру, но физический экран, на который смотрит пользователь, не вращается. gluLookAt будет работать только в том случае, если экран будет вращаться так, что нормаль экрана будет продолжать указывать на пользователя. Перспективное искажение внеосевой проекции позаботится обо всем необходимом нам вращении.

Что вам нужно учитывать в вашей модели, так это положение экрана внутри усеченного конуса. Рассмотрим следующее изображение. Красные точки — это границы экрана. Чего вам нужно добиться, так это того, чтобы эти положения оставались постоянными в 3D WCS, поскольку физический экран в реальном мире также (надеюсь) не двигается. Я думаю, что это ключевое понимание виртуальной реальности и стереоскопии. Экран — это что-то вроде окна в виртуальную реальность, и чтобы совместить реальный мир с виртуальной реальностью, вам нужно совместить усеченную пирамиду с этим окном.

Отличные навыки MSPaint

Для этого вам нужно определить положение экрана в системе координат 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
comment
Это просто меняет всю концепцию, которая у меня была на уме. Я собираюсь попробовать, но то, что я уже пробовал, было основано на этом ССЫЛКА, для которой исходник находится здесь и использует gluLookAt. Мое понимание glFrustum заключалось в том, что параметры, которые входят, будут устанавливать громкость отсечения, а при применении gluLookAt происходит искажение. Между тем, я попробую этот метод, но не могли бы вы взглянуть на ссылку о том, что lookAt - это неправильный способ перейти туда, потому что он кажется правильным? - person user1240679; 26.05.2013
comment
Вот еще одна ссылка, описывающая, почему вы не хотите выполнять ротацию. Хотя контекст стереоскопический, применяются те же принципы, что и при отслеживании головы. - person Andreas Haferburg; 26.05.2013
comment
Я попытался получить искажение перспективы, используя только glFrustum и не используя gluLookAt, но не смог добиться эффекта. Я добавил еще один вопрос об искажении только с помощью glFrustum, чтобы проверить это. Тот самый, с которого вы связались здесь. Еще одна ссылка на вопрос о внеосевой проекции . Используя gluLookAt, я просто выполняю преобразование просмотра и перемещаю «камеру» в другое положение, но не поворачиваю ее. Хотел проверить еще раз с вами здесь. - person user1240679; 27.05.2013
comment
О, это был ты. :) Трудно отличить анонимных пользователей. Когда вы ранее разместили эту ссылку на Holotoy, я больше не был уверен, поэтому добавил пример реализации в свой ответ. Вы пробовали это? - person Andreas Haferburg; 27.05.2013
comment
Я попробовал тот же тест здесь. Вот скриншот, когда глаз камеры находится далеко от модели. Когда я перемещаю глаз ближе к модели и влево, вот скриншот Верхний правый угол куба находится на той же высоте, что и верхний левый угол. Значит ли это, что трансформация просмотра здесь правильная? Перевод сцены или перемещение камеры вместо этого приводят к одному и тому же здесь, в этом дело? - person user1240679; 28.05.2013
comment
Нет, это не тот тест. Если направление просмотра center - eye кратно (0,0,-1), то gluLookAt не будет вращаться, а только сдвинется на -eye. Но вы хотите, чтобы куб оставался в центре экрана, поэтому параметр center должен оставаться на уровне (0,0,0). Если вы затем переместите глаза, gluLookAt создаст вращение. И да, перемещение камеры по -глазу - это то же самое, что перемещение сцены по +глазу. - person Andreas Haferburg; 28.05.2013
comment
Согласно приведенным выше расчетам, camTopLeftNear и camBottomRightNear становятся меньше, когда голова пользователя перемещается дальше. Это уменьшает объем отсечения, и нарисованный объект кажется больше по размеру. Однако обычно, когда мы двигаемся дальше, размер любого объекта уменьшается. Не так ли? Я немного запутался, понимая это. P.S. Я скоро загружу видео и сообщу вам здесь. Спасибо еще раз - person user1240679; 02.06.2013
comment
У меня также были определенные сомнения относительно того, зачем нам вычислять «camTopLeftNear» и «camBottomRightNear». Поскольку мы уже вычисляем 'camTopLeftScreen' и 'camBottomRightScreen', почему расчеты не могут быть в этих координатах? Зачем нам нужно умножать это на (1/camTopLeftScreen.z * camZNear). «camZnear» в моем случае — 0,1. - person user1240679; 02.06.2013
comment
Что касается вашего первого вопроса, объект на экране не должен уменьшаться. Когда пользователь отходит от экрана, экран в реальном мире уже становится меньше в реальном поле зрения пользователя, а вместе с ним и объект кажется меньше, хотя его размер на экране остается почти таким же . - person Andreas Haferburg; 02.06.2013
comment
Второй вопрос: glFrustum ожидает, что верхний, левый, нижний и правый параметры будут на ближней плоскости. camTopLeft/BottomRightScreen — это угловые точки экрана, поэтому расстояние по оси z от глаз равно camTopLeftScreen.z. А чтобы вычислить параметры на ближней плоскости, нам нужно расстояние по оси camZNear, поэтому мы должны умножить эти точки на camZNear/camTopLeftScreen.z. Это умножение означает, что мы масштабируем угловые точки в направлении z системы координат камеры. Посмотрим, смогу ли я добавить это к рисунку. - person Andreas Haferburg; 02.06.2013
comment
Извините, под camTopLeft/BottomRightScreen я имел в виду camTopLeftScreen или camBottomRightScreen, это не имеет отношения к делению. - person Andreas Haferburg; 02.06.2013
comment
Спасибо за точное объяснение по второму вопросу. Насчет первого, я не совсем уверен, что до сих пор получу его, поэтому возьму пример со своей стороны. Предположим, что для начального положения пользователя A параметры «левый/правый» пирамиды усечения получаются равными -0,25/0,25. Однако, когда пользователь перемещается дальше, левое/правое, как в приведенном выше случае, деленное на Z, становится -0,15/0,15. Таким образом, поле, которое раньше занимало меньшую часть, будет занимать больше места и увеличиваться на экране, когда пользователь отойдет назад. - person user1240679; 02.06.2013
comment
В отличие от сценария реального мира, когда размер объектов уменьшается, когда пользователь возвращается назад, кажется, что я что-то упускаю. Почему объекты на экране должны увеличиваться в размерах на экране с увеличением Z? - person user1240679; 02.06.2013
comment
давайте продолжим это обсуждение в чате - person Andreas Haferburg; 02.06.2013
comment
@AndreasHaferburg Я пытаюсь реализовать ваш код в вершинном шейдере в Quartz Composer. Я использую некоторые константы, основанные на пикселях на миллиметр моего экрана. Я пропускаю этап отрицательного масштабирования, специфичный для Kinect. Должен ли я заканчивать с gl_Position = mvpCube * gl_Vertex; - person David Braun; 12.10.2014
comment
@DavidBraun Звучит разумно, это заменит glLoadMatrix. Если необходимо, возможно, вы могли бы открыть отдельный вопрос и связать его здесь. - person Andreas Haferburg; 12.10.2014
comment
Хорошие новости: мне удалось реализовать код Kooima в Quartz. Он строит матрицу произведения усеченного конуса, поворота и перевода. Я называю эту матрицу m. Я завершаю свой шейдер Quartz Vertex с помощью gl_Position = m * gl_ModelViewMatrix * gl_Vertex; - person David Braun; 12.10.2014

Лучшее объяснение того, как использовать glFrustum для приложений слежения за головой, вы можете найти в этой статье Роберта Куимы, которая называется обобщенной перспективной проекцией:

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

Это также позволяет вам просто использовать стереопроекции, вам просто нужно переключаться между левой и правой камерами!

person linello    schedule 03.07.2013
comment
Я должен был стиснуть зубы раньше и проработать этот документ. На самом деле код было очень легко реализовать. Спасибо, что поделился! - person David Braun; 12.10.2014