Построение 3D-чертежей
(принято к публикации 2014-10-24)
Довольно часто возникают ситуации, когда требуется сделать некий чертеж на веб-странице. Обычно для этих целей хватает возможностей SVG. Но иногда появляется необходимость запечатлеть нечто трехмерное. Вовсе необязательно при этом ожидать реалистичной графики - например, для иллюстрации задач по стереометрии используются схематичные "каркасные" рисунки, состоящие из прямых линий или несложных кривых.
Столкнувшись с такой ситуацией мы быстро выяснили, что нам лень самим придумывать, как будет выглядеть тот или иной параллелепипед на чертеже, поэтому решили приложить некоторые усилия и научиться задавать и отображать средствами SVG + Javascript несложную трехмерную графику.
В этой ситуации не требуется заботиться о скорости работы алгоритмов - сцены предполагаются статичными. Таким образом, решение поставленной задачи оказывается под силу даже таким дилетантам, как мы.
Сначала немного теории из проективной геометрии и почти забытого курса компьютерной графики. Физически процесс наблюдения трехмерной схемы выглядит следующим образом:
Теперь нужно записать эти действия строгим языком математики. Итак, для точек сцены введем четырехмерные векторы (x, y, z, 1), где x, y, z - обычные трехмерные координаты точки в системе XYZ, а четвертая единица - дань проективной геометрии, о которой было сказано выше и будет сказано дальше.
Кроме того, пусть задана координатная система, связанная с экраном и имеющая в системе XYZ следующие направляющие векторы осей:
0'Y': (ay, by, cy, 1)T
0'Z': (az, bz, cz, 1)T
Преобразование, переводящее сцену в координаты, связанные с экраном, является линейным и может быть представлено в виде произведения двух матриц:
ax | bx | cx | 0 |
ay | by | cy | 0 |
az | bz | cz | 0 |
0 | 0 | 0 | 1 |
1 | 0 | 0 | -x0 |
0 | 1 | 0 | -y0 |
0 | 0 | 1 | -z0 |
0 | 0 | 0 | 1 |
Проективное же преобразование, которое оставляет 0 на месте, а точку с координатами (0, yн, 0, 1) перегоняет в минус бесконечность, имеет вид:
1 | 0 | 0 | 0 |
0 | 1 | 0 | 0 |
0 | 0 | 1 | 0 |
0 | -1/yн | 0 | 1 |
Таким образом получаем окончательную формулу преобразования вектора (x, y, z, 1) в координаты, связанные с экраном:
xэ |
yэ |
zэ |
tэ |
1 | 0 | 0 | 0 |
0 | 1 | 0 | 0 |
0 | 0 | 1 | 0 |
0 | -1/yн | 0 | 1 |
ax | bx | cx | 0 |
ay | by | cy | 0 |
az | bz | cz | 0 |
0 | 0 | 0 | 1 |
1 | 0 | 0 | -x0 |
0 | 1 | 0 | -y0 |
0 | 0 | 1 | -z0 |
0 | 0 | 0 | 1 |
x |
y |
z |
1 |
Конечно, координата yэ/tэ в реальности тоже эффективно используется - она имеет непосредственное отношение к глубине, по ней могут быть упорядочены элементы сцены при отрисовке, чтобы дальние элементы не заслоняли ближние.
Теперь уже можно, будучи вооруженными вышеизложенной теорией, приступить к реализации своих амбициозных планов. Вот, например, простейшая сцена:
Как видим, необязательно ограничивать себя каркасными моделями. Действительно, перейти от отрезков к многоугольникам-граням достаточно просто. Во-первых, следует упорядочить грани, скажем, по координате Y их центров (т.е., по среднему, 1/n ∑ (yэ(i)/tэ(i)), Y-координат их вершин), тогда последовательная отрисовка граней от дальней к ближней даст примерно верную картину перекрытия их друг другом. Во-вторых, закрашивание граней может быть сделано ориентируясь на абсолютную величину косинуса угла между вектором нормали к грани и вектором 0'Y' - направлением взгляда наблюдателя. Чем меньше этот самый модуль косинуса, тем "темнее" должна быть нарисована грань. Вектор же нормали легко определить, посчитав векторное произведение любых двух смежных рёбер. В результате мы получаем возможность строить нечто более красивое: