Сфера в трехмерной графике обычно состоит из сотни-другой треугольников, при этом половина из них не видна человеку, поскольку их отсекает face culling и/или zbuffer.
Сфера всегда кажется наблюдателю кругом. Поэтому можно схитрить и вместо сферы нарисовать плоскость, которая всегда повернута к наблюдателю одной стороной. А уже на этой плоскости с помощью математики и какой-то матери изобразить текстуру, освещение и блик.
Нам нужно вычислить три главных параметра: позиция плоскости, её размер и трехмерные координаты сферы, спроецированной на эту плоскость. Получив эти параметры можно приступить к обману нашего наблюдателя.
С позицией плоскости все относительно просто: если вы рисуете ближнюю часть сферы, то это сдвиг на радиус от центра воображаемой сферы к камере.
Вычисление размера плоскости
Размер плоскости будем вычислять используя школьную геометрию, в частности, уроки про теорему Пифагора, касательные к окружности и определения подобных треугольников. Итак, взгляните на эти картинки (наблюдатель слева, сфера справа, вид сбоку, синим обозначена гипотенуза):
Поскольку L — это касательная к окружности, треугольник LDR является прямоугольным, а высота H проведенная из прямого угла делит этот треугольник на два ему подобных. Нас интересует треугольник hCl.
Нам известен радиус нашей сферы R и дистанция до камеры D. Обладая знаниями восьмого класса о теореме Пифагора, найдем катет L прямоугольного треугольника LDR:
L=sqrt{D^2 - R^2}Также нам известен катет C треугольника hCl:
C = D - RПоскольку треугольники подобны, то соотношения сторон у них одинаковые:
\frac{h}{C}=\frac{R}{L}Значит искомый катет h равен:
h=\frac{R*(D-R)}{L}Итак, мы получили размер нашей плоскости в зависимости от расстояния до камеры и радиуса нашей сферы — с чем я нас и поздравляю!
Вычисление нормали для сферы спроецированной на плоскость
Осталась сущая мелочь — получить нормаль сферы в каждой точке нашей плоскости. Дальше пойдет магия, так что не удивляйтесь. Идея математически верная, но, возможно, есть способ гораздо проще. Если у вы его знаете — пишите в комментариях, не стесняйтесь.
Фактически, наша плоскость представляет собой экран для проецирования сферы. Поэтому построим матрицу перспективной проекции, чтобы знать трехмерные координаты каждой точки на этой сфере относительно камеры. Матрица проекции строится по четырем параметрам:
- угол обзора (fov) — в нашем случае это угол между $L$ и $D$ умноженный на два, то есть $2.0 * acos(L/D)$
- соотношение сторон экрана (aspect ratio) — наша плоскость квадратная, поэтому единичка
- ближняя плоскость отсечения (near plane) — расстояние от камеры до ближней точки сферы ($D — R$)
- дальняя плоскость отсечения (far plane) — расстояние от камеры до дальней точки сферы ($D + R$)
Напрямую эта матрица проекции нам не подходит, поскольку выполняет операцию приведения координат из пространства камеры в плоскость экрана. Для нашей задачи ее необходимо сперва инвертировать.
Умножив полученную матрицу на любой вектор (X, Y, -1) мы получим точку на ближней к наблюдателю плоскости в пространстве камеры. Затем мы построим луч к этой точке из камеры и найдем ближайшее пересечение луча со сферой. После этого можно найти нормаль, вычислив вектор от центра сферы до полученной точки пересечения.
Лирическое отступление — умножив инвертированную матрицу проекции на вектор (0, 1, -1), мы получим вектор, у которого значение Y будет равно размеру нашей плоскости, то есть h. Но мы ведь не ищем легких путей, правда? (:
Результат и исходники
Тестовая схема сделана при помощи библиотеки three.js. На одной половине экрана рисуется честная сфера, на другой имитация. Попробуйте догадаться без подглядывания в исходники — где истинная сфера, а где ложная. Подсказка: ложная сфера выглядит лучше истинной. Освещение сделано практически один в один с предыдущей заметкой об освещении куба.
Исходный код выложен на GitHub.