Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Surface-based lighting

http://www.fx-craft.com/

Открывшиеся перспективы и возрожденные надежды

Для начала

В одна тысяча девятьсот девяносто седьмом году ко мне в руки попала небезызвестная игра под названием Quake. Долго радовался я, долгими ночами наяривая в нее, как по сетке, так и в сингл. В очередной раз зорко подметив в титрах такие имена как John Carmak и Michael Abrash, я подивился и порадовался за людей, создающих такую красоту прямо на экране электронного болвана. Все было красиво в этой игре - и alias models, и частицы и взрывы и Скорость. Но самое главное - я (да и никто не мог до первых Абрашевских статей) никак не мог понять как им удалось сделать такое красивое освещение, - вокруг факелов световое пятно ярче, дальше от факела - ближе к темным углам - оно плавно тускнеет, переходя в красивую тень с размытыми границами. Оно было таким реальным, таким, как его воспринимает человеческий глаз, а не как оно просчитывается математическими методами. Позже, я узнал, что, придумывая эту модель освещения, программисты из id именно создали иллюзию светового пятна, в угоду глазу, нарисовав его на текстуре. Назвали они ее surface-based lighting, что в дословном переводе звучит как "поверхностное освещение", хотя было бы правильнее назвать его "освещение поверхностями". Или "освещение, основаное на построении поверхностей". Но я ограничусь тем, что буду называть эту модель как "метод карт освещенности" или "метод лайтмэпов (lightmaps)".

Вот об этой модели и пойдет речь ниже.

О методе Гуро и его недостатках

Традиционным способом создания освещения в полигональных моделях являлся метод Гуро (Gouraud shading). Метод Гуро вычисляет значения освещенности в каждой вершине полигона, и затем, получает значения освещенности по каждому горизонтальному отрезку линейно интерполируя значения вдоль ребер. Для текстурированного полигона для каждого пикселя вычисляется отвечающий ему тексель, затем на него “накладывается” значение освещенности, и, в итоге, мы получаем т. н. lit pixel (освещенный пиксель). Значение освещенности может получаться как просто из интенсивности, так и из трех отдельных компонент RGB. RGB-освещение дает более интересные возможности, например, цветные источники света, но вычисления для этого более сложные и лучше всего подходят для полноцветных RGB-моделей. В 96 году, во время создания движка первой Кваки, время 256-цветных режимов и палитр, все естественно использовали значения интенсивности.

Итак, Метод Гуро сочетает приличное качество картинки с относительно маленьким количеством вычислений и компактным набором данных, который суть есть простое расширение вашей основной полигональной модели. К тому же все-все-все Кому Не Лень зашивают метод Гуро в свои графические библиотеки. Казалось бы – бери и пользуй, сейчас уже даже писать ничего не надо. Однако, имеется несколько важных недостатков.

Качество освещения с использованием метода Гуро сильно зависит от размера рисуемых многоугольников. Используется линейная интерполяция, так что световые пятна, такие, какими мы их представляем, появляются только около вершин, а закраска является монотонной вдоль каждой стороны полигона. Выглядит ужасно. А теперь учтите то, как, например, в Quake мы гоняемся за минимизацией количества полигонов в BSP-дереве. Наиболее громко об этом кричит утилитка под названием Vis. То есть, ни Кармака ни Абраша не устраивал метод Гуро, во-первых, потому, что он не позволяет создавать высокоточные или театральные эффекты освещения. А в изначальном Кваковском движке ИСПОЛЬЗОВАЛСЯ МЕТОД ГУРО! Первая их идея состояла в том, чтобы, спроецировав точку, в которой находится источник света (а в Кваке они все точечные), на полигон, разбить его на несколько. Т. е. в той точке, куда падает свет, мы получаем достаточно красивое световое пятно. Посмотрите на рисунок:

Классная идея. Воплощу в ближайшее время. Но в то время это также показалось неприемлемым. Почему? Возникали проблемы с перекрывающимися световыми пятнами и с тенями, где требуются дополнительные полигоны, чтобы картинка была усвояемой без рвотных позывов. Вообще говоря красивые тени требуют не пару больших полигонов, а много много маленьких, потому что тогда не заметен уродливый переход от "светлой" вершины к "темной".

Еще одна проблема - метод Гуро не является корректным с точки зрения перспективы, т. е. в некоторых случаях можно получить результат зависящий от положения наблюдателя. Если полигон является квадратом, то это очень хорошо заметно из такого рисунка:

Цифрами обозначены значения освещенности в вершинах. Возможное решение - использовать только треугольники. Но треугольников нужно существенно больше чем прямоугольников. Они освещаются независимо от ориентации, но все равно неправильно, если не хуже. Одни расстройства.

Еще проблема - идея добавления новой вершины подходит только для статического освещения, а как быть с динамическим? Если движется полигон или источник света или оба вместе? Т. к. метод проецирования источника света на грань и последующего разбиения годится только для статического освещения, в динамике мы получаем все тот же "голый" Гуро с ужасными несуразицами, о которых я писал выше.

Итог: злой дядька Гуро заставляет нас строить наши виртуальные миры при помощи зверского количества малых треугольников, когда детализация опупительна сама по себе и огрехи этого метода не видны. Это собственно и есть основная моя идея сейчас - почему я отказался от лайтмэпов см. ниже. Но Кармак - не я. И Абраш тоже, в общем-то, не я. И я не являюсь ими. А им в 96 году было не до кучи треугольников, им бы красиво осветить пару здоровенных ПРЯМОугольников. По этому они держали военный совет, на котором постановили - использовать Гуро только на малых движущихся брашах (подъемники и пр.). Кстати, в Descent Гуро доведен до совершенства - и они это признают. Но для Кваки им было все мало - ни приемлемого качества ни, толком, скорости.

И они начали искать. Существует еще много различных методов освещения - на Гуро мир клином не сошелся - и куча из них дает более высокое качество, начиная с закрашивания по Фонгу, когда интерполируются не значения освещенности в вершинах, а нормаль к поверхности по всему полигону, и заканчивая полнейшей трассировкой лучей (ray tracing), заключающееся в полном просчете всех световых лучей, их отражений и преломлений для каждого источника света и КАЖДОГО (!) пикселя. Рехнуться можно. Все это дико медленно и, опять же, нескромным запросам программистов из id не удовлетворяло. И так продолжалось до тех пор, пока Кармак не пришел в офис и не сказал свой великий перл: "You know, I have an idea...".

Великая идея мастера

Идея Кармака состояла в том, что он хотел рассматривать и грань и световое пятно, как одну поверхность (surface) - это будет выглядеть получше и рисоваться побыстрее. И тогда он разбил освещение и, собственно, рендеринг на два отдельных шага. Это сейчас, после этой моей фразы можете посмотреть на меня как на идиота, а тогда это было революционное решение. Ведь сцены с освещением по Гуро рендерились так: как бы "препроцессорный" шаг вначале, когда строится world database и считается освещение в вершинах. Дальше, уже в реальном времени, значения освещенности меняются если есть динамический источник света, и только затем уже рисуются все полигоны.

В Quake они предложили о, что сейчас все знают (или не знают) как просто лайтмэпы, а тогда они это назвали surface-based lighting. Оно добавляет еще один шаг препроцессора сцены (т.е выполняемый при создании/компиляции этой самой сцены), при котором для каждого полигона выполняется построение т. н. карты освещенности (lightmap) с шагом в 16 текселей как по вертикали, так и по горизонтали, которая суть есть прямоугольная матрица с определенным образом просчитанными значениями освещенности в узлах. Значения эти мы получаем, вычисляя освещение от всех "близких" источников света ("близкие" могут быть разные: от попадающий в некий куб с заданной стороной до текущего портала (если кто не знает, что это такое, то обратитесь к докам по оптимизациям BSP-деревьев; я же могу сказать, что это просто определенным образом полученная часть уровня); для динамических лайтмэпов удобнее использовать для этих целей PVS (potentially visible set - опять же см. пред. комментарий)) для каждого узла матрицы, наложенной на полигон (как текстура - здесь есть некие сложности с просчетом текстурных координат, и это хорошо, так как в сложных моментах можно всегда придумать малЕнькие оптимизации). Как это сделать? Можно, выбрав на полигоне некие точки, испускать лучи из этих точек до всех источников света. Можно, представляя источник света как frustum, отсекать полигоны не попадающие в него или попадающие частично, сортировать их по удаленности от передней отсекающей плоскости этого frustum-а, и рисовать предыдущий прямо на лайтмэпе следующего. И т.д. Дальше идет фильтрация значений, для того чтобы теневые области не имели ступенчатые границы (техника, впервые предложенная Billy Zelsnack). И, наконец, можно провести какие-нибудь дополнительные расчеты, например закрасить все это Фонгом, чтобы поверхности были гладенькими (но это опять же "дорого", поэтому этот предложенный Абрашом аспект так никогда и не использовался). Уже в реальном времени для каждого полигона выполняется следующее: его текстура тайлится в некий буфер и каждый тексель высветляется, используя значения интенсивности четырех ближайших узлов карты освещенности, как показано на рисунке. При динамическом освещении лайтмэп пересчитывается до того как буфер (буфер в данном случае - это полигон с текстурой плюс "наложенная" на нее (текстуру) карта освещенности; он и называется поверхностью) строится. Полигон рисуется с перспективным текстурированием, которое на входе, в качестве текстуры получает поверхность, построенную на предыдущем шаге. И НИКАКОГО ОСВЕЩЕНИЯ НЕ ПРОИЗВОДИТСЯ.

Чем же сия модель освещения так хороша? Первое и самое главное преимущество - оно предоставляет возможность получать корректное с точки зрения перспективной проекции освещение, исключая все шероховатости с ориентацией полигона, его проецированием на экран или отсечением (clipping), потому что оно производится в координатах, связанных с самой поверхностью, а не в экранных. Освещая в поверхностных координатах, мы назначаем интенсивность текселям инвариантно, если говорить об этих всех подводных камнях, а затем все это попадает через перспективный текстуризатор (не пугайтесь - это всего лишь perspective texture mapper) в ваш конвейер отрисовки и на выходе они ("интенсивности" как узлы лайтмэпа) получаются полностью подогнанными к текселям. Также такая модель освещения поддерживает хорошее (но не совершенное) качество и " правильность" перекрывающихся источников света или теней. Они постарались, чтобы матрица 16x16 в реальном пространстве имела бы сторону в два фута и тогда этого было бы достаточно для того, чтобы после фильтрации, производящейся когда лайтмэп строится, получать сложные теневые конструкции со сглаженными границами. В общем, она идеально подходила ко всем нескромным квейковским запросам к визуализации, оставляя только один вопрос: "А насколько быстро это будет работать?"

Как получилось, скорость surface-based lighting достаточно высока. Дополнительный шаг нужен для создания поверхности, и они переместили его в отдельный цикл, а не делали в основном цикле текстурирования, и, в связи с этим, появилась возможность засунуть почти все переменные регистры (для крестьян - речь идет об ассемблере). Внутренний цикл построения поверхности достаточно эффективен, т. к. он большей частью состоял в интерполировании карты освещенности и сопоставление каждого ее узла текселю, изменении цвета этого текселя, чтобы он выглядел "освещенным" и сохранении результата для последующей визуализации в виде двойного слова для каждых четырех текселей. Все равно дорого. Но. В итоге, как можете сами убедиться, Квейк в среднем дает около сорока fps в разрешении 640x480 на Pentium-100. Такой высокий результат был достигнут благодаря кэшированию поверхностей.

Кэширование

Когда Кармак придумал эту модель, он мгновенно понял, что построение поверхности будет достаточно дорогостоящей операцией (на самом деле даже дороже, чем оказалось после всех ассемблерных оптимизаций). Поэтому, решил он, если поверхность видима и в следующем кадре (а вероятность этого очень велика), то она может быть отрисована без построения заново. Для этого, сказал он, поверхности мы будем кэшировать.

В связи с редко возникающей необходимостью строить поверхность заново, скорость отрисовки в Quake - это, в общем, скорость внутреннего цикла перспективного текстурирования, т. к. "освещение" как таковое не производится вообще. И большие полигоны освещаются правильно и красиво. В худшем случае, когда в кадре все поверхности необходимо построить заново, скорость surface-based освещения немногим меньше, чем применение метода Гуро для данной сцены, но в общем случае получается примерно равной (или даже выше!). Все вроде бы отлично, но на горизонте появляется новая проблема - память.

Поначалу, размер памяти, нужной для кэширования поверхностей, приводил всех в ужас. Размер поверхности сильно зависит от размера текстуры (тайленной или нет) т.к. каждый тексель на каждой поверхности уникален. Поверхности, связанные с частично скрытыми полигонами, также должны быть построены и кэшированы, и если все полигоны будем рисуются back-to-front, то как быть с полигонами не видными вообще? Строить и для них тоже? И кэшировать? В общем, это все означает, что сам кэш по разумению, получался очень большим, несколько мегабайт, даже на 320x200, а это слишком много для игры, предполагавшей работать на машине с восемью мегабайтами оперативной памяти.

Спасение в мипмэппинге!

Два фактора помогли в решении этой проблемы. Во-первых, для полигонов, не видимых совсем, поверхностей вообще не строятся. Это понятно. Во-вторых, поверхности строятся в четырех mipmap-уровнях, в зависимости от расстояния до наблюдателя, и каждый следующий mipmap-уровень содержит в четыре раза меньше текселей, чем предыдущий. Текущий же mipmap-уровень для данной поверхности выбирается так, чтобы отношение тексель/пиксель было примерно между 1:1 и 1:2 (из чисто эстетических соображений), т. е. более "дальние" поверхности получаются сильно меньше.

В результате, количество текселей на поверхности, нужных для того, чтобы нарисовать сцену в 320x200, резко уменьшилось и теперь варьируется в районе числа 640000. 600 килобайт для кэширования таких поверхностей - это более чем достаточно, даже для сложных сцен, а 640x480 нужно немногим более 1 мегабайта. Mipmap-уровень для поверхности выбирается в зависимости от расстояния от наблюдателя до ближайшей к нему вершины полигона, связанного с этой поверхностью. Плюс они ввели ограничение размера поверхности - 256x256.

Напоследок

Динамическое освещение наносит серьезный удар по ихним мыслям про кэширование поверхностей, потому, что если изменяется освещение, необходимо не только строить поверхность заново, но и перестраивать карту освещенности. В самом худшем случае, когда освещение меняется для каждой видимой поверхности, кэширование исключается и вовсе, а скорость отрисовки состоит из построения и поверхности и текстурирования. Замедление сильно заметно, поэтому они обратились к дизайнерам, с просьбой не вставлять в уровень большое количество динамических источников света. Кстати. Также все дырки от пуль, кровища и пр. - растут именно отсюда. С применением метода поверхностного кэширования, можно реализовывать ЛЮБЫЕ текстурные эффекты. Сопоставление различных техник дает еще более интересные результаты - действуйте.

Лайтмэпы сегодня сильно отличаются от того, о чем я сейчас писал. Во-первых, забудьте про кэширование - большинство современных источников света изменяют свое положение и/или интенсивность и пр. почти каждый кадр. Во-вторых - мультитекстурирование. Шестой D3D поддерживал восемь его уровней, а это значит до семи источников света на полигон. Но это только начиная со второго Вудушника, позволяющего накладывать по два текселя за такт. Если использовать технологию multi-pass (это когда мультитекстурирования нет, и мы, нарисовав текстурированный полигон, просто меняем на нем текстуру на лайтмэп, врубаем альфа блендинг, и рисуем его заново на том же самом месте), то количество светильников на полигон вообще не ограничено. Можно комбинировать эти методы - накладывать по семь-восемь лайтмэпов за раз. И они все будут правильно накладываться друг на друга, пересекаться и пр., не верите - попробуйте сами. Это тоже. Зачем нужно кэширование, когда мы просто накладываем лайтмэп на грань? С мипмэппингом - опять все там (в D3D) нормально. Правильный он там, мипмэппинг. Лепота.

Перспективы. Как я уже писал в новостях, Кармак заявил, что считает лайтмэпы тупиковым направлением и id скорее всего пойдет в сторону per-pixel shading, которое суть есть осуществляемый на аппаратном уровне просчет освещенности для каждого(!) текселя. Т. е. лайтмэпы высочайшего разрешения. Эсли это как-нибудь обломится, то кол-во полигонов может резко спрятать все огрехи метода Гуро, который, к тому же, в большинстве современных карточек прошит железно. И я ожидаю, что вся мировая трехмерно-игро-делающая братия, во главе с вечно кричащем о том, что он идет на шаг впереди Кармака, Тимом (Хрю-Хрю) Свини, покорно как барашки пойдут за idовцами. Но не мы с Ромой.

Чего мы будем делать - мне очень нравится метод RenderToTexture, и я буду прилагать все усилия, чтобы в моей, освещаемой по Гуро сцене с бешеным количеством полигонов на кубический дюйм, были красивые динамические тени с размытыми границами, построенные, как нарисованные этим методом на текстуре полигоны. Потому, что трафаретные (stencil) тени у меня вызывают тошноту.

Вот так.

Sid <ztsid@online.ru>

Оставить комментарий

Комментарий:
можно использовать BB-коды
Максимальная длина комментария - 4000 символов.
 
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог