Voxel-эффект на 1С: портируем демо-эффект из Borland Pascal на платформу 1С
Ко дню программиста продолжаем развивать тему демосцены на 1С. Благодаря ИИ разработки можно браться за более интересные эффекты. На этот раз делаем «Voxel».
Когда-то в DOS-демках и первых 3D-«стрелялках» существовал приём, который позволял рисовать бескрайние гористые ландшафты без полигонов. Сегодня разберём, как этот приём — voxel-рендеринг — устроен изнутри, откуда он взялся, и как его можно портировать на 1С, взяв за основу старое демо на Borland Pascal и уже существующий на платформе эффект «Plasma» (//infostart.ru/1c/tools/1514332/).
Что такое Voxel и с чего всё начиналось
Слово «Voxel» образовано из слов: объёмный (англ. volumetric) и пиксель (англ. pixel)) — элемент объёмного изображения, так же, как «texel» получился из «texture element». Изначально термин пришёл из медицинской визуализации — так называли элементарный кубик объёма в данных КТ- и МРТ-сканирования.
В игровой индустрии voxel-технологию прославила компания NovaLogic: её вице-президент по технологиям Кайл Фриман (Kyle Freeman) разработал движок Voxel Space, вдохновившись как раз опытом работы с воксельными данными медицинских сканеров. Движок дебютировал в 1992 году в игре Comanche: Maximum Overkill — вертолётном симуляторе для MS-DOS.
Важный нюанс: несмотря на название, классический игровой «voxel-рендеринг» — это не отрисовка настоящих трёхмерных кубиков-вокселей (как в Minecraft). Это красивый трюк «2.5D»: ландшафт хранится как две обычные 2D-карты — карта высот (heightmap) и карта цвета (colormap), — а сцена строится путём бросания лучей по каждому столбцу экрана из точки наблюдателя и проекции высоты рельефа на экран с учётом перспективы. Полигонов, честной трёхмерной геометрии и Z-буфера в привычном смысле тут нет — а значит, всё считается в целых числах и работает даже на 486-м процессоре с приемлемой частотой кадров.
Файл VOXEL.PAS (см. макет VOXEL_PAS в обработке), который лёг в основу нашего порта — как раз пример такого демосценового переосмысления идеи: Borland Pascal, видеорежим VGA 320×200×256 (mode 13h), рельеф не загружается из готовых данных, а генерируется на лету фрактальным алгоритмом, а сама отрисовка построена по мотивам той же идеи «карта высот + луч на столбец экрана», но по-своему, без прямого копирования движка Comanche.
Порт на 1С
Экран пришлось эмулировать: у 1С нет ни видеопамяти, ни mem[$A000:0], был использован Табличный документ — он и стал «видеопамятью». Поле табличного документа «Док» разбито на сетку 160×100 ячеек фиксированного размера (ШиринaColonки/ВысотаСтроки выставляются один раз при открытии формы), и каждая ячейка — это один «пиксель»: закрашивается через Док.Область(строка, колонка, строка, колонка).ЦветФона = Color(...).
Генерация карты
Готовых данных о высотах взять неоткуда, поэтому рельеф, как и в оригинале, генерируется процедурно — классическим фрактальным алгоритмом diamond-square (тем же самым, что и procedure plasma в VOXEL.PAS). Идея: берём квадрат, знаем высоту в его 4 углах, вычисляем высоты середин его сторон и центра как среднее соседних углов плюс случайное отклонение — и рекурсивно повторяем для четырёх получившихся квадратов поменьше. Чем меньше квадрат, тем меньше амплитуда случайного отклонения — это и даёт фрактальную, похожую на настоящий рельеф, самоподобную структуру.
Карта хранится не как двумерный массив, а как один плоский Массив размером MapSize*MapSize с ручной индексацией Ind(x,y) = y*MapSize+x — прямой аналог того, как в Паскале адресовался typed-указатель mp^[256*y+x]. Размер карты выбран равным 257 = 2^8+1 — это стандартное требование diamond-square: сторона квадрата должна делиться пополам нацело вплоть до блока 2×2.
Сглаживание
У «сырого» diamond-square есть особенность: случайные отклонения на самом мелком уровне рекурсии могут дать одну аномальную ячейку посреди ровного участка. При отрисовке лучом это превращается в тонкий, ничем не мотивированный «столбик» посреди пейзажа. Чтобы карта выглядела более гладкой, после генерации выполняется один проход простого сглаживания — усреднение каждой ячейки с четырьмя соседями по кресту.
Вес центральной ячейки — 4, каждого из четырёх соседей — 1 (итого делим на 8). Это достаточно мягкий фильтр: общий рельеф не «размывается» в кашу, но одиночные выбросы гасятся.
Рендер сцены
Вместо буквального повторения паскалевской схемы (там область обзора строилась как поворачиваемый ромб с целочисленной тригонометрией cos*256/sin*256) в 1С-версии реализован классический алгоритм Voxel Space: для каждого из 160 столбцов экрана бросается луч под своим углом (dirAngle ± половина угла обзора), и вдоль него, шаг за шагом (ровно на 1 единицу карты за итерацию), берутся высота и цвет рельефа. Высота проецируется на экранную строку по перспективной формуле:
bsl
heightOnScreen = Цел((camHeight - hEff) / z * ScaleHeight) + HorizonRow;
где z — пройденное расстояние. Чем дальше точка, тем меньше её проекция отклоняется от линии горизонта — это и создаёт эффект перспективы. Каждый столбец экрана хранит свою «границу горизонта» (yBuf[col]) — минимальную уже закрашенную строку; когда новая точка рельефа проецируется выше этой границы, между ними закрашивается полоса, а граница сдвигается. Это прямой аналог буфера rng[] из оригинала: он не даёт более далёким и при этом более низким точкам перекрывать уже нарисованные близкие горы.
Что взято из графического движка Plasma
Проект не создавался с нуля — многое сознательно унаследовано от уже существующего на 1С эффекта «Plasma»:
- Сам приём эмуляции экрана. Идея использовать поле табличного документа как framebuffer и красить ячейки через
Область(...).ЦветФона = Color(...), включая настройку сетки фиксированного размера в обработчикеПриОткрытии, — заимствована без изменений. - Стиль генерации палитры.
gen_palette()в «Плазме» строит массивColor-объектов по простым RGB-формулам с использованиемМин/Макс(там — радужный градиент по HSV-подобной схеме).gen_terrain_palette()в Voxel следует той же логике и тому же стилю кода, только подобраны формулы под ландшафтный градиент: вода → трава → земля → скалы → снег. - Работа со случайными числами. Тот же объект
Новый RandomNumberGenerator()и метод.RandomNumber(a, b), что и в «Плазме» — только там он крутит фазы косинусов, а в Voxel — вносит фрактальный шум вncol(). - Механика анимационного цикла. Пара
ПодключитьОбработчикОжидания("Timer", 0.1, Истина)/ОтключитьОбработчикОжидания("Timer"), реквизит-флагDemoIsRun, переключение заголовка кнопки «Остановить» / «Продолжить», подсчётFrame/FPSчерезТекущаяУниверсальнаяДатаВМиллисекундах()— всё это перенесено изПускДемо/Timer«Плазмы» практически без изменений. - Общие соглашения модуля. Всё выполняется
&НаКлиенте(кадр перерисовывается интерактивно, без обращений к серверу), обработчики команд принимают параметрКоманда, стиль именования и смешение русских/английских ключевых слов — по образцу исходного файла.
Использование ИИ разработки
Портирование было выполнено с использованием инструментов ИИ разработки, не важно какой именно. В целом ИИ справился отлично. Код заработал сразу. За 5 итераций удалось пофиксить проблемы рендеринга картинки.
Если есть здравые идеи чего бы ещё такого сделать, пишите в комментах. Для тех, кто хочет написать Qake, пишите, здесь не надо про это.
Проверено на следующих конфигурациях и релизах:
- 1С:Библиотека стандартных подсистем, редакция 3.1, релизы 3.1.12.323
Вступайте в нашу телеграмм-группу Инфостарт