Пост babarun про 3d-движок на js вызвал творческий порыв добавить тонирование Гуро для пущей реалистичности. Вот, что получилось (а сейчас ещё и с зеркальностью). По сравнению с обычным (flat) тонированием нормали требуется иметь не для граней, а для вершин (то есть для треугольной грани три нормали). По готовым нормалям граней нормали вершин вычисляются просто усреднением нормалей всех граней, включающих данную вершину; это делается один раз перед рендерингом. Освещённость вершин высчитывается в начале каждого кадра с учётом изменения положения камеры.
Основная сложность заключалась в том, как залить треугольник градиентом.
Попиксельный доступ к буферу изображения отметаем, как медленный. В canvas есть линейный градиент, нам же необходимо заливать треугольник так, чтобы углы оказались заданных цветов. Вообще говоря, эта задача к линейному градиенту в общем виде не сводится. Простой пример: три вершины треугольника имеют цвета красный, зелёный и синий. Однако в том случае, если цветовые вектора вершин линейно зависимы, как в примере babarun, задачу можно свести к линейному градиенту. Вопрос только в том, как его задать.
Итак, у нас есть три вершины (x1,y1), (x2,y2), (x3,y3) с цветами c1, c2, c3. Для задания линейного градиента в canvas надо указать начальную точку, конечную точку и их цвета. Отсортируем вершины так, чтобы цвета шли по возрастанию и совместим начальную точку линейного градиента с самой тёмной вершиной (x1, y1). Теперь нам надо выбрать такое направление линейного градиента, чтобы длины проекций векторов (x2,y2)-(x1,y1) и (x3,y3)-(x1,y1) на это направление были пропорциональны разностям цветов (c2-c1) и (c3-c1). Мы можем зафиксировать цвет конечной точки линейного градиента в самый яркий из нужных (c3), то есть проекция (x3,y3) должна точно попадать в конечную точку. Это условие и условие пропорциональности проекций разностям цветов дают два уравнения на координаты конечной точки линейного градиента. Уравнения легко решаются в вашем любимом солвере (я использовал Maple), скрипт babarun модифицируется и выкладывается на сервер.
Весь исходный текст в html-файле. Вот новые функции:
calcGradientPos — вычисляет позицию конца вектора линейного градиента с началом в (0,0) (собственно, решение системы уравнений)
calcFullGradientPos — вызывает calcGradientPos, смещая первую вершину в (0,0), а затем возвращая назад.
getGradient — создаёт объект CanvasGradient для заданного треугольника.
paintGradientTriangle — рисует треугольник в canvas. Вершины заданы двойным массивом [3][3], первый индекс — номер вершины, второй индекс задаёт координаты x, y и цвет (от 0 до 1); в конце цвет умножается на параметры (r,g,b) — общий цвет фигуры.
calcVerticesNormals — вычисляет нормали вершин по нормалям граней.
Ещё ряд изменений в функции draw_mesh, в частности, в самом начале цикл, в котором освещаются вершины.
FPS упал вдвое, но возможности для оптимизаций не исчерпаны (они и в исходной версии были). Кто хочет, может попытаться улучшить результат. Я сделал только версию с 1920 полигонов, желающие могут легко добавить остальные.
Upd: была ошибка с нормировкой нормалей, поэтому не получалось сделать зеркальность. Сейчас зеркальность добавлена (спасибо lsdima за то, что подтолкнул разобраться с этим).