Радиус проектируемой сферы

Я хочу уточнить предыдущий вопрос:

Как проецировать сферу на экран?

(2) дает простое решение:

приблизительный радиус на экране [ПРОСТРАНСТВО КЛИПА ] = мировой радиус * кроватка (fov / 2) / Z

с:
fov = угол поля зрения
Z   = z расстояние от камеры до сферы

результат находится в пространстве клипа, умножьте на размер области просмотра, чтобы получить размер в пикселях

Теперь моя проблема в том, что у меня нет поля зрения. Известны только матрицы вида и проекции. (И размер области просмотра, если это поможет)

Кто-нибудь знает, как извлечь FOV из проекционной матрицы?

Обновить:

Это приближение работает лучше в моем случае:

float radius = glm::atan(radius/distance);
radius *= glm::max(viewPort.width, viewPort.height) / glm::radians(fov);

4 ответа

Решение

Обновление: см. Ниже.

Поскольку у вас есть матрицы вида и проекции, вот один из способов сделать это, хотя, вероятно, он не самый короткий:

  • преобразовать центр сферы в пространство вида, используя матрицу вида: вызвать точку результата C
  • преобразовать точку на поверхности сферы, например, C+(r, 0, 0) в мировые координаты, где r - мировой радиус сферы, в пространство обзора; назвать точку результата S
  • вычислить rv = расстояние от C до S (в пространстве обзора)
  • пусть точка S1 в координатах вида будет C + (rv, 0, 0) - то есть другой точкой на поверхности сферы в пространстве вида, для которой линия C -> S1 перпендикулярна вектору "взгляда"
  • спроецируйте C и S1 в экранные координаты, используя матрицу проекции как Cs и S1s
  • вычислить радиус экрана = расстояние между Cs и S1s

Но да, как сказал Брандорф, если вы сможете сохранить переменные камеры, такие как FOVy, это будет намного проще.:-)

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

Более того, сделайте то же самое с матрицей вида и превратите усеченную камеру обратно в мировое пространство. Это было бы более эффективно для сравнения многих блоков с; но сложнее понять математику.

Я немного опоздал на эту вечеринку. Но я наткнулся на эту тему, когда смотрел на ту же проблему. Я провел целый день, изучая это, и работал над некоторыми прекрасными статьями, которые я нашел здесь: http://www.antongerdelan.net/opengl/virtualcamera.html

В итоге я начал с матрицы проекции и работал в обратном направлении. Я получил ту же формулу, которую вы упомянули в своем посте выше. (где кроватка (х) = 1/ загар (х))

radius_pixels = (radius_worldspace / {tan(fovy/2) * D}) * (screen_height_pixels / 2)

(где D - расстояние от камеры до ограничивающей сферы цели)

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

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

Если вы возьмете компонент Sy из матрицы проекции, как показано здесь:

Sx  0   0   0
0   Sy  0   0
0   0   Sz  Pz
0   0  -1   0

where Sy = near / range

and where range = tan(fovy/2) x near

(вы можете найти эти определения на странице, на которую я ссылался выше)

если вы подставите диапазон в приведенном выше уравнении, вы получите:

Sy = 1 / tan(fovy/2) = cot(fovy/2)

перестановки:

tan(fovy/2) = 1 / Sy

взяв арктан (обратный загар) с обеих сторон, получим:

fovy/2 = arctan(1/Sy)

так,

fovy = 2 x arctan(1/Sy)

Не уверен, если вы все еще заботитесь - это было какое-то время! - но, возможно, это поможет кому-то еще.

Ответ размещен по вашей ссылке radiusClipSpace = radius * cot(fov / 2) / Zгде fov - угол поля зрения, а Z - расстояние по оси z, определенно работает. Однако имейте в виду, что radiusClipSpace должен быть умножен на ширину области просмотра, чтобы получить меру в пикселях. Значение, измеренное в radiusClipSpace, будет значением от 0 до 1, если объект помещается на экране.

Альтернативным решением может быть использование телесного угла сферы. Сплошной угол, образованный сферой в небе, - это, в основном, область, которую он охватывает, проецируясь на сферу объекта.

Формулы приведены по этой ссылке, но примерно то, что я делаю, это:

if( (!radius && !distance) || fabsf(radius) > fabsf(distance) )
  ; // NAN conditions. do something special.

theta=arcsin( radius/distance )
sphereSolidAngle = ( 1 - cosf( theta ) ) ; // not multiplying by 2PI since below ratio used only
frustumSolidAngle = ( 1 - cosf( fovy / 2 ) ) / M_PI ; // I cheated here. I assumed
// the solid angle of a frustum is (conical), then divided by PI
// to turn it into a square (area unit square=area unit circle/PI)

numPxCovered = 768.f*768.f * sphereSolidAngle / frustumSolidAngle ; // 768x768 screen
radiusEstimate = sqrtf( numPxCovered/M_PI ) ; // area=pi*r*r

Это работает примерно до тех же чисел, что и radius * cot(fov / 2) / Z, Если вам нужна только оценка площади, покрытой проекцией сферы в пикселях, это может быть простой способ.

Я не уверен, что можно легко найти лучшую оценку телесного угла усеченного конуса. Этот метод включает в себя больше соревнований, чем radius * cot(fov / 2) / Z,

FOV не сохраняется непосредственно в матрице проекции, а используется, когда вы вызываете gluPerspective для построения результирующей матрицы.

Наилучшим подходом было бы просто сохранить все переменные вашей камеры в их собственном классе, таком как класс frustum, чьи переменные-члены используются, когда вы вызываете gluPerspective или подобное.

Может быть возможно вернуть FOVy обратно из матрицы, но математика требует ускользнуть от меня.

Другие вопросы по тегам