МИНОБРНАУКИ РОССИИ ТОМСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ Факультет информатики Кафедра теоретических основ информатики УДК 004.925.3 ДОПУСТИТЬ К ЗАЩИТЕ В ГАК Зав. кафедрой, доц., к.т.н. ____________ А.Л.Фукс «_____»__________2012 г. БАКАЛАВРСКАЯ РАБОТА МЕТОД ДИНАМИЧЕСКОЙ ЗАГРУЗКИ ДАННЫХ ДЛЯ ТРАССИРОВКИ ЛУЧЕЙ В РЕАЛЬНОМ ВРЕМЕНИ по основной образовательной программе подготовки бакалавров 010400 - Информационные технологии Колупаев Михаил Владимирович Руководитель ВКР, доцент кафедры теоретических основ информатики, к.т.н. ____________А.В.Приступа подпись «_____»__________2012 г. Автор работы студент группы № 1481 _____________М.В.Колупаев подпись Электронная версия бакалаврской работы помещена в электронную библиотеку. Файл Томск – 2012 Реферат Дипломная работа 40 с., 18 рис., 3 табл., 15 источников. ТРАССИРОВКА ЛУЧЕЙ, ОКТАРНОЕ ДЕРЕВО, ВОКСЕЛЬ, ВИЗУАЛИЗАЦИЯ В РЕАЛЬНОМ ВРЕМЕНИ, ДИНАМИЧЕСКАЯ ЗАГРУЗКА ДАННЫХ Объект разработки – система визуализации трехмерной сцены в реальном времени. Цель работы – разработка эффективного метода хранения трехмерной сцены и динамической загрузки видимых частей сцены в реальном времени для использования при трассировке лучей с помощью видеокарты. Метод исследования – экспериментальный на ЭВМ. Результаты работы – разработаны методы разбиения октарного дерева на блоки постоянного размера, измерена их эффективность на тестовых данных. Разработан метод динамической загрузки нужных блоков на основе обратной связи от трассировщика лучей. Реализована система визуализации, использующая эти методы. Область применения – разработанные методы могут быть использованы для ускорения процесса визуализации трехмерных сцен в системах, использующих обратную трассировку лучей. 2 Содержание Введение ............................................................................................................................ 5 1 Распространенные методы визуализации ................................................................... 8 1.1 Основные требования к системе визуализации................................................... 8 1.2 Растеризация ........................................................................................................... 8 1.3 Трассировка лучей (ray tracing) ........................................................................... 10 1.3.1 Список примитивов ....................................................................................... 10 1.3.2 Равномерная сетка ......................................................................................... 10 1.3.3 Дерево ............................................................................................................. 10 1.3.4 Преимущества трассировки лучей .............................................................. 12 1.3.5 Недостатки трассировки лучей .................................................................... 12 1.4 Трассировка путей (path tracing) ......................................................................... 12 2 Обзор существующих реализаций трассировки лучей в реальном времени с динамической загрузкой данных .......................................................................................... 14 3 Предлагаемая система визуализации ........................................................................ 18 3.1 Краткое описание ................................................................................................. 18 3.2 Представление дерева .......................................................................................... 18 3.2.1 Основные наблюдения .................................................................................. 18 3.2.2 Данные в вершине ......................................................................................... 18 3.2.3 Блоки............................................................................................................... 19 3.2.3.1 Мотивация ............................................................................................... 19 3.2.3.2 Критерии оценки способа разбиения на блоки ................................... 20 3.2.3.3 Первый вариант метода ......................................................................... 20 3.2.3.4 Второй вариант метода .......................................................................... 22 3.2.3.5 Третий вариант метода .......................................................................... 24 3.2.3.5 Четвертый вариант метода .................................................................... 24 3.2.4 Хранение дерева в постоянной памяти ....................................................... 26 3.2.4.1 Общая структура .................................................................................... 26 3.2.4.2 Структура вершинных ссылок .............................................................. 26 3.2.5 Хранение кеша дерева в видеопамяти ......................................................... 26 3.2.6 Загрузка блоков в кеш и выгрузка из кеша ................................................. 26 3.3 Визуализация и обратная связь ........................................................................... 27 3.3.1 Трассировка луча ........................................................................................... 27 3.3.2 Многопроходная визуализация с запоминанием состояния ..................... 28 3 3.3.2.1 Визуализация .......................................................................................... 28 3.3.2.2 Извлечение данных обратной связи ..................................................... 30 3.4 Поддержка кеша дерева ....................................................................................... 31 3.4.1 Стратегия кеширования ................................................................................ 31 3.4.2 Предотвращение выгрузки родителей ........................................................ 31 3.4.3 Предотвращение выгрузки заведомо используемых блоков .................... 31 3.4.4 Обнаружение недостаточного размера кеша .............................................. 31 4 Результаты .................................................................................................................... 32 4.1 Производительность ............................................................................................ 32 4.2 Гибкость ................................................................................................................ 37 Заключение ..................................................................................................................... 38 Список использованных источников ........................................................................... 39 4 Введение Представление и визуализация трехмерных сцен - одна из основных задач компьютерной графики. За последние десятилетия для решения этой задачи предложено множество подходов, подходящих для различных применений. Некоторые подходы развиты лучше других. Развитие некоторых подходов в большой степени сдерживается ограниченной производительностью современных вычислительных машин, особенно это касается визуализации в реальном времени. В последние 10-20 лет для визуализации в реальном времени в подавляющем большинстве случаев используется растеризация треугольников. В конце 1990-х годов этот метод получил аппаратную поддержку (в лице видеокарты) и с тех пор является стандартом де-факто. Но прогресс не стоит на месте, и современные видеокарты обладают достаточной производительностью, чтобы использовать другие методы визуализации, более эффективные в некоторых ситуациях. Одному из недооцененных перспективных способов визуализации - трассировке лучей - и посвящена данная работа. Обзор других современных методов визуализации приведен в главе 1. Выделяют прямую и обратную трассировку лучей. Прямая трассировка лучей (трассировка фотонов) моделирует движение частиц света от источников света до попадания в виртуальную камеру. В чистом виде этот метод крайне неэффективен, так как лишь небольшая доля лучей света попадают в камеру, и большая часть работы делается впустую. Прямая трассировка лучей, как правило, применяется для предварительного расчета освещенности частей поверхностей при фиксированном положении сцены и источников света. В таком случае путь фотонов прерывается не при попадании в камеру, а после определенного количества отражений или преломлений. При каждом отражении или преломлении фотон обновляет информацию об освещенности той точки поверхности, в которой он отразился или преломился. В дальнейшем эта информация сохраняется в картах освещенности или непосредственно в текстурах поверхностей и используется при визуализации любыми методами. Трассировка фотонов позволяет моделировать глобальное освещение, включая такие явления как каустика (освещение отраженным или преломленным светом, рисунок 1) и затенение рассеянного света. 5 Рисунок 1 – каустика (стол освещается преломленным светом) Примечание – Изображение взято из [11]. Обратная трассировка лучей (далее называемая просто трассировкой лучей) моделирует движение фотона, попавшего в камеру, в обратном направлении. Иными словами, из каждого пиксела матрицы виртуальной камеры выпускается луч в пространство. Моделируется путь луча (включая не рассеянные отражения и преломления) до столкновения с непрозрачной не отражающей поверхностью. Цвет пиксела выбирается в соответствии с задетыми лучом точками поверхностей. Обратная трассировка лучей обладает следующими преимуществами: Позволяет строить реалистичные изображения, учитывающие отражения и преломления (рисунок 2). Не требует отсечения невидимых поверхностей. Время не тратится на обработку не видимой в данный момент геометрии (при использовании подходящих структур данных). Эффективно реализуется динамический уровень детализации (при использовании подходящих структур данных). Время не тратится на прорисовку слишком мелких (меньше пиксела) деталей. Эффективно независимы. реализуется параллельная 6 обработка, так как лучи При параллельной обработке лучей эффективно используется разделенный между потоками кеш, так как близкие лучи проходят по близким траекториям. Рисунок 2 – фотореалистичное изображение, построенное с помощью трассировки лучей Примечание - Изображение взято из [12]. Производительность современных видеокарт позволяет использовать обратную трассировку лучей с приемлемой производительностью. Обзор существующих систем визуализации, использующих трассировку лучей, представлен в главе 2. Для многих практических применений (например, в играх) необходима возможность визуализировать в реальном времени большие детализированные сцены. Оперативная память и видеопамять не в состоянии вместить такие сцены целиком, поэтому нужна система динамической загрузки видимых частей сцены в нужной детализации. Данная работа состоит в разработке такой системы. 7 1 Распространенные методы визуализации 1.1 Основные требования к системе визуализации Адаптивный уровень детализации (LOD - level of detail). Не нужно тратить ресурсы на обработку трудно различимых или неразличимых деталей сцены. Например, далекие объекты могут иметь меньшую детализацию без потери визуального качества изображения. Отсечение невидимых частей сцены (culling). Не нужно тратить ресурсы на обработку не попавших в кадр частей сцены. Например, объекты, целиком загороженные стеной, можно не рисовать. Загрузка сцены по частям (out-of-core storage). Не нужно держать в оперативной памяти или видеопамяти всю сцену разом, достаточно загружать видимые части. Например, при перемещении камеры в другое помещение можно выгрузить большую часть покинутого помещения и загрузить большую часть посещенного. Возможно обобщение этого подхода: использовать несколько видов памяти как несколько уровней кеша - каждый следующий кеш меньше и быстрее предыдущего. Например: сетевое хранилище, жесткий диск, оперативная память, видеопамять. 1.2 Растеризация Наиболее распространенный и простой метод. Повсеместно используется для визуализации в реальном времени. Сцена представляется в виде набора треугольников, на которые “натянуты” различные карты: цвéта (текстуры), рельефа, прозрачности и чего угодно еще. Для визуализации треугольники проецируются на экран и закрашиваются с учетом освещения, свойств поверхности и чего угодно еще. Современные видеокарты используют конвейер, изображенный на рисунке 3. 8 Рисунок 3 – Графический конвейер, используемый современными видеокартами Примечание – Изображение взято из [7]. 9 Базовый алгоритм достаточно прост. Но требования, поставленные в предыдущей, фактически добавляют три новые задачи, требующие нетривиальных решений. 1.3 Трассировка лучей (ray tracing) Идейно простой и красивый метод [15]). Часто используется для визуализации не в реальном времени. Каждому пикселу изображения ставится в соответствие луч в пространстве. Ищется первое пересечение луча со сценой. Цвет пиксела выбирается в соответствии с освещением и свойствами поверхности в точке пересечения с лучом. Также пересечение может порождать вторичные лучи для создания эффектов отражения (луч отражается от поверхности), преломления (луч преломляется поверхностью) и теней (пускается луч в источник света для проверки видимости). Этот процесс поиска пересечений и порождения дополнительных лучей называется трассировкой луча. Нужно хранить сцену в структуре данных, позволяющей быстро находить первое пересечение с лучом. Рассмотрим основные возможные структуры. 1.3.1 Список примитивов Как и при растеризации, хранить сцену в виде списка треугольников или других примитивных фигур. Для проверки пересечения с лучом обходить весь список. Метод эффективен только при малом числе примитивов, так как время визуализации пропорционально произведению количества примитивов и разрешения изображения. 1.3.2 Равномерная сетка Пространство разбивается на параллелепипеды, образующие равномерную сетку. В каждом параллелепипеде хранится какая-нибудь информация о его части сцены, например: воксель, один примитив или список примитивов. Для поиска пересечения с лучом можно использовать алгоритм Брезенхема, обходя ячейки вдоль луча, пока не найдется пересечение. Выбор шага сетки - отдельная оптимизационная задача. При маленьком шаге требуется много памяти для хранения сетки, а также много времени для обхода; при большом шаге либо детализация мала (при хранении вокселя или примитива в параллелепипеде), либо в ячейках много примитивов, и проверка пересечений с ними занимает много времени (при хранении списка примитивов). Метод эффективен на небольших сценах с простой геометрией. 1.3.3 Дерево Пространство рекурсивно разбивается на части. Части образуют дерево: каждая часть либо объявляется листом, либо разбивается на несколько частей-детей. Процесс разбиения прекращается, когда часть оказывается пустой или слишком маленькой, и поэтому объявляется листом. Для поиска пересечения с лучом используется алгоритм, похожий на алгоритм Брезенхема, с обходом по порядку листьев, через которые проходит луч. Такой обход асимптотически более эффективен, чем обход регулярной 10 сетки, потому что он пропускает большие объемы пустого пространства за один шаг (из-за того, что даже большие пустые вершины не делятся на части). Некоторые способы разбиения: kd-дерево (kd-tree, [9]). Часть - параллелепипед. Часть делится на две части некоторой плоскостью, перпендикулярной одной из трех осей координат. Плоскость выбирается так, чтобы минимизировать глубину дерева. Октарное дерево (octree, [1]). Часть - куб. Куб равномерно делится на 8 кубов-детей. В листьях дерева хранится информация о геометрии сцены внутри соответствующей части пространства. Возможные способы представления геометрии в листе дерева: Воксель. Лист либо полностью заполнен материей, либо полностью пуст. o Преимущества Простота. o Недостатки Воксели размером хотя бы 3 пиксела заметны. Одна плоскость. o Преимущества Компактность (3 числа с небольшой точностью). Выглядит намного лучше, чем воксель. o Недостатки Разрывы поверхности на стыке листьев. Две параллельные плоскости. o Преимущества Компактность (4 числа с небольшой точностью). Выглядит намного лучше, чем воксель. Нет разрывов. Изоповерхность (или две изоповерхности) трилинейной функции ([14]). Хранятся значения функции в вершинах параллелепипеда. Внутри параллелепипеда значения функции получаются трилинейной интерполяцией. o Преимущества Гладкость. Выглядит немного лучше, чем плоскость. o Недостатки Громоздкость (8 чисел с небольшой точностью). Разрывы (в случае одной поверхности) или изломы (в случае двух поверхностей) на стыке вершин разных уровней. 11 Список примитивов. o Преимущества Если исходная сцена представлена списком примитивов, данные не теряются. Можно обходиться меньшим количеством уровней. Можно выбирать глубину дерева, глубиной и размером списков. балансируя между o Недостатки Разный размер данных в вершинах. Кроме геометрии в листе можно хранить информацию о поверхности и материале, такую как цвет, нормаль, коэффициенты отражения и преломления, прозрачность. Во внутренних вершинах (не листьях) можно хранить усредненные значения величин в листьях. Это позволяет при трассировке луча не всегда спускаться до листьев, а останавливаться на уровне, обеспечивающем достаточную детализацию (например, всегда останавливать спуск по дереву, если размер вершины в проекции стал меньше размера пиксела). Таким образом, два из трех требований выполнены. Автоматический уровень детализации достигается за счет остановки спуска по дереву на достаточной глубине. Отсечение невидимых частей достигается естественным образом: лучи доходят только до видимых поверхностей. Тем не менее, динамическая загрузка данных требует нетривиальных решений. 1.3.4 Преимущества трассировки лучей Однородное хранение данных. Не нужны текстуры и прочие карты. Следовательно, не нужно динамически загружать карты по частям. Естественный динамический уровень детализации. Естественные отражения и преломления. Простые в реализации жесткие тени. С ростом детализации трудоемкость растет примерно логарифмически. 1.3.5 Недостатки трассировки лучей Статичная геометрия. Отсутствие аппаратной поддержки При низкой детализации медленнее растеризации. Дерево занимает много памяти. 1.4 Трассировка путей (path tracing) Как и при трассировке лучей, из камеры выпускаются лучи. Но при столкновении луча с рассеивающей свет поверхностью, луч физически корректно отражается в случайном направлении, так же, как отражается в хаотическом направлении луч света 12 ([5]). Луч останавливается только когда попадает в источник света или накапливает слишком много отражений. Каждый пиксел трассируется много раз, и результаты усредняются. Из-за случайности, на изображении присутствует шум. Чем больше проходов трассировки совершено, тем меньше остается шума. Этот способ визуализации используется для создания изображений не в реальном времени. Для создания видеоряда (даже не в реальном времени) используется редко изза низкой производительности. Существуют экспериментальные реализации в реальном времени, но они пока не достигли приемлемой производительности и качества изображения. 13 2 Обзор существующих реализаций трассировки лучей в реальном времени с динамической загрузкой данных В [13] представлена самая быстрая на сегодняшний день реализация трассировки лучей в реальном времени с использованием воксельных октарных деревьев. Предлагается компактная структура данных для хранения октарного дерева и эффективный алгоритм трассировки луча. Геометрия в вершине дерева представляется парой параллельных плоскостей, ограничивающих материю. Область между этими плоскостями названа контуром. Используется красивая идея кооперации между контурами вершин разных уровней: геометрия материи внутри вершины определяется как пересечение контуров всех вершин на пути от этой вершины до корня. Таким образом, геометрия листа представляет собой не просто пару плоскостей, а целый многогранник. Это существенно улучшает точность аппроксимации острых углов (рисунок 4), а также ускоряет (!) трассировку лучей, так как проверку пересечения с контуром нужно делать на каждом уровне, а значит часто пустое пространство будет пропускаться на более высоком уровне (чем если бы проверка пересечения с контуром происходила только в листьях). Для построения контуров используется алгоритм, жадно выбирающий лучшее направление плоскостей на каждом уровне. Также в вершине хранятся нормаль и цвет. 14 Рисунок 4 – сравнение качества аппроксимации поверхности с помощью вокселей (сверху) и контуров (снизу). Примечание – Также виден эффект кооперации контуров разных уровней. Изображение взято из [13]. Структура ссылок на детей изображена на рисунке 5. Ссылка занимает 4 байта, но не хранится для листьев. Учитывая, что листьев в среднем в три раза больше, чем остальных вершин, структура дерева занимает в среднем 1 байт на воксель. Также вместе со ссылкой на детей хранится 4-байтная ссылка на контуры детей. Сам контур занимает тоже 4 байта. Контуры хранятся не для всех вершин, а только для тех, где они существенно улучшают точность аппроксимации геометрии. Так как контуры нужны в небольшой доле вершин, контуры в среднем занимают чуть больше одного байта на вершину. Рисунок 5 – структура ссылок на детей в [13] Примечание – Изображение взято из [13]. Для хранения цветов и нормалей вершин используется блочное сжатие с потерями, позволяющее достичь потребления памяти в среднем 1 байт для цвета и 2 байта для нормали. 15 Таким образом, потребление памяти составляет около 5 байт на воксель, что сравнимо с представлением сцены треугольниками со сжатыми текстурами и картами высот. Помимо способа хранения дерева, в [13] представлен оптимизированный алгоритм поиска пересечения луча с октарным деревом. Этот алгоритм используется в данной работе, и более подробное его описание содержится в главе 3.3.1. Для устранения алиасинга и артефактов недостаточного разрешения сцены предлагается пост-обработка изображения размывающим фильтром переменного радиуса, зависящего от размера вокселя, определившего цвет пиксела. Для ускорения рендеринга кадра предлагается метод beam optimization. Изображение разбивается на квадраты размера 4x4 или 8x8 пикселов равномерной сеткой. Через узлы сетки пускаются лучи, и ищется их первое пересечение с деревом. При этом запрещается спускаться в дереве в вершины, размер которых в проекции меньше размера квадратов, на которые разбито изображение. В результате для каждого квадрата находится нижняя оценка расстояния до сцены, и трассировку луча в каждом пикселе можно начинать сразу с этой глубины. Такая оптимизация улучшает производительность на 5-20%. Для динамической загрузки данных дерево разбивается на так называемые slices (здесь далее называемые фрагментами). Фрагмент – набор всех вершин одного уровня, находящихся в некотором кубе. Фрагменты имеют переменный размер и образуют дерево. Список всех фрагментов постоянно хранится в видеопамяти, позволяя при обходе дерева быстро узнавать адрес указанного фрагмента, если он загружен. Таким образом, при загрузке нового фрагмента не нужно обновлять ссылки в родителе. Недостаток такого метода состоит в том, что при трассировке луча происходят дополнительные обращения в память для получения адресов фрагментов. Для выделения и освобождения видеопамяти для фрагментов используется свой менеджер памяти, использующий заранее выделенный большой буфер для размещения фрагментов. При многократном выделении и освобождении фрагментов память подвергается фрагментации, поэтому время от времени менеджер памяти перемещает фрагменты для освобождения достаточно длинных отрезков памяти. Загружаемые и выгружаемые фрагменты выбираются в зависимости от положения камеры, минимизируя максимальный видимый размер вокселя. Таким образом, видимость частей сцены оценивается лишь приблизительно, исходя из положения камеры. Например, близкие объекты, загороженные стеной, все равно будут загружены. Предположительно, это приводит к загрузке большого количества бесполезных данных, например, при визуализации помещений. Atomontage ([2]) – многообещающий проприетарный игровой движок, использующий комбинацию из полигональных и воксельных данных. Для хранения воксельных данных используется октарное дерево. Особенностью этого движка являются изменяемость воксельной геометрии в реальном времени, включая физически корректную разрушаемость. Также в планах заявлена реализация моделирования других физических явлений: термодинамики, огня, смены агрегатных состояний, движения жидкости, даже электричества и химических реакций. Тем не менее, по всей видимости, динамическая загрузка данных пока не реализована. Также эффективной динамической загрузке данных препятствует их изменяемость. В [14] представлен метод визуализации данных, представленных облаком точек. Предлагается на основе облака точек строить октарное дерево, содержащее в каждой 16 вершине 8 значений – значения трилинейной (в пределах куба этой вершины) функции в вершинах куба вершины. Поверхность задается как множество точек, где значение функции равно нулю. Для проверки пересечения луча с содержимым куба используется следующий приближенный метод. Вычисляется значение функции в нескольких точках, равномерно распределенных вдоль луча внутри куба. Если между какими-то соседними точками функция меняет знак, приблизительная точка пересечения находится как взвешенное среднее этих двух точек с весами, равными абсолютным значениям функции. Несмотря на то, что находить точное место пересечение можно за O(1), решая квадратное уравнение, такой приближенный метод обладает большей производительностью при пренебрежимо маленькой потере качества изображения. При визуализации данных такого дерева могут возникать видимые разрывы поверхности на стыках вершин разного уровня. Для устранения таких разрывов используется два приема. Во-первых, устраняются стыки между листьями, уровень которых различается более чем на единицу. Если такой стык находится, менее глубокий лист разбивается на 8 листьев следующего уровня. Во-вторых, поверхностям разрешается немного выходить за пределы куба. В результате поверхности соседних листьев немного перекрываются, делая щель между ними незаметно маленькой. В [4] предлагается метод рендеринга изоповерхностей скалярной функции, заданной в узлах равномерной сетки. В отличие от более современных работ, для программирования видеокарты используются шейдеры, что накладывает серьезные ограничения на структуру программы. В частности, невозможна произвольная запись данных, что делает большое число алгоритмов неприменимыми. Несмотря на эти ограничения, реализована динамическая загрузка данных на основе обратной связи от трассировщика лучей. Исходный трехмерный массив значений разбивается на кубические «кирпичи» данных (называемые далее кирпичами). Строится октарное дерево, содержащее кирпичи в вершинах. Загрузка кирпичей происходит отдельно от загрузки вершин. Таким образом, вершина может быть загружена при незагруженном кирпиче. Это может быть полезно для внутренних вершин дерева. В [3] также используются «кирпичи» из массива вокселей. Предлагаемая структура предназначена для визуализации объемных данных с низкой, но не нулевой прозрачностью, таких как облака. Используется достаточно сложная система извлечения данных обратной связи, основанная на запросах видимости и рекурсивном разбиении изображения. Таким образом, существующие реализации динамической загрузки октарного дерева используют структуры данных, замедляющие обращение к данным во время трассировки луча, а также требуют механизм выделения и освобождения видеопамяти блоками произвольного размера. 17 3 Предлагаемая система визуализации 3.1 Краткое описание Используется октарное дерево. В текущей реализации в листьях дерева хранятся воксели, но архитектура позволяет без труда хранить и использовать произвольные дополнительные данные в вершинах. Единственное требование - размер данных должен быть одинаковым для всех вершин. Структура дерева, то есть, фактически, воксельная геометрия, занимают 4 байта на воксель. Для трассировки лучей используется видеокарта (реализация на OpenCL). Дерево загружается и выгружается из видеопамяти блоками. Представленные методы разбиения дерева на блоки постоянного размера представляют новизну. Для выбора загружаемых блоков используется обратная связь от трассировщика лучей. Для выбора выгружаемых блоков используется принцип LRU. 3.2 Представление дерева 3.2.1 Основные наблюдения Эксперименты показали, что на типичных наборах данных: У вершины в среднем около 4 детей. Листьев в дереве в примерно в 3 раза больше, чем внутренних вершин. Примерно половина вершин - пустые листья. Следовательно, хранение всех 8 ссылок на детей для каждой вершины было бы крайне неэффективным. Используемый в данной работе способ хранения ссылок на детей требует 4 байта для каждой внутренней вершины и непустого листа (пустые листья не хранятся). 3.2.2 Данные в вершине Дерево представляется в виде последовательности вершин. С каждой вершиной связан некоторый набор данных. Структура и размер в байтах этих наборов одинаковы для всех вершин в пределах дерева, но может быть произвольной для разных деревьев. Набор данных о вершине состоит из набора полей (частей), называемых каналами дерева. Например, в дереве могут быть каналы цвета, нормали, прозрачности и прочие. Каждый канал характеризуется названием и количеством байт на вершину. Название канала определяет семантику данных канала. Данные каналов могут использоваться трассировщиком лучей при визуализации. Каналы, не поддерживаемые трассировщиком, игнорируются им. Механизм каналов значительно увеличивает гибкость формата представления дерева. В частности, он позволяет не фиксировать жестко способ хранения геометрии в листьях (см. 1.3.3), а добавлять новые способы в существующую систему. Из вышесказанного следует, что данные каждой вершины имеют одинаковый размер (в пределах одного дерева). Это позволяет хранить вершины последовательно и эффективно обращаться к ним по номеру. Для хранения структуры дерева (то есть ссылок на детей) используется специальный канал дерева, называемый каналом 18 ссылок. Это единственный обязательный канал любого дерева: без связей между вершинами не может быть никакого (невырожденного) дерева. Канал ссылок занимает 4 байта на вершину и имеет формат, представленный на рисунке 6. Рисунок 6 – предлагаемая структура ссылок на детей. Биты маски детей соответствуют возможным детям вершины. Установленный бит означает, что этот ребенок существует (не пуст). Ссылка на первого ребенка (при непустой маске детей и отсутствии промаха) указывает номер вершины, являющейся первым ребенком вершины. Остальные дети вершины идут по порядку сразу после первого. Количество детей совпадает с количеством установленных бит в маске детей. Понятия промаха, блока, далекой ссылки и дублирования будут рассмотрены далее. 3.2.3 Блоки 3.2.3.1 Мотивация Множество вершин дерева разбивается на блоки. Преимущества разбиения на блоки (по сравнению с загрузкой и выгрузкой по одной вершине): Загрузка с жесткого диска и передача в видеопамять блоками намного эффективнее, чем по одной вершине. Меньше обновлений ссылок в родителях при загрузке и выгрузке, чем при загрузке по одной вершине. При загрузке одной вершины нужно обновлять ссылку в родителе после каждой вершины. При загрузке блока достаточно обновить несколько ссылок в родителях. Локальность доступа к данным при обходе дерева (больше попаданий в кеш). Если размер блока сравним с размером кеша, при спуске по дереву хотя бы несколько подряд идущих доступов в память будут попадать в кеш. Все блоки имеют одинаковый размер (в пределах одного дерева). Преимущества блоков одинаковоо размера (по сравнению с блоками переменного размера): Простое размещение блоков в видеопамяти и файле. Не нужен динамический аллокатор видеопамяти. 19 Быстрый доступ к блоку в файле по номеру. Быстрое определение номера блока, содержащего вершину, по адресу вершины в видеопамяти. Недостатки блоков одинакового размера: Плохая локальность блока при упаковке нескольких кусков дерева в один блок. Пустоты в блоках при упаковке маленького количества кусков дерева в один блок. Между двумя недостатками можно балансировать, меняя количество кусков дерева, упаковываемых в один блок. 3.2.3.2 Критерии оценки способа разбиения на блоки Способы разбиения на блоки можно сравнивать по следующим критериям: Плотность упаковки. В блоках должно быть как можно меньше неиспользуемого места. Можно рассматривать две величины: средняя заполненность блока и процент увеличения размера (накладные расходы) по сравнению с не разбитым на блоки деревом. Среднюю заполненность блока можно вычислить как . Накладные расходы можно вычислить как . Пространственная локальность блока. Субъективная величина, отражающая, насколько компактный и связный кусок пространства содержит блок. Можно использовать следующую метрику: среднее количество пикселов изображения, попадающих в один и тот же блок (разумеется, усреднение ведется только по блокам, в которые попал хоть один пиксел). 3.2.3.3 Первый вариант метода Рассмотрим вспомогательное дерево, вершины которого соответствуют группам вершин-братьев в исходном дереве (рисунок 7). Ребра вспомогательного дерева соответствуют внутренним вершинам исходного дерева. Вершинам вспомогательного дерева присвоим вес, равный количеству соответствующих вершин исходного дерева. 20 Рисунок 7 – пример вспомогательного дерева. Примечание - Круги – вершины исходного дерева, обведенные наборы кругов – вершины вспомогательного дерева. Пусть блок - связная часть вспомогательного дерева. Следовательно, блоки сами образуют дерево, и в каждом блоке есть вершина-корень блока. Пусть сумма весов вершин блока ограничена сверху некоторой величиной (размером блока). Возникает задача разбиения дерева на минимальное количество блоков. Или, что то же самое, разбиения дерева на блоки максимальной средней заполненности. Для такого разбиения предлагается следующий алгоритм. Обойдем дерево в глубину, начиная от корня, следующей рекурсивной процедурой: 21 функция обход(вершина v): список множеств вершин S = пустой список для всех детей g вершины v: S.добавить(обход(g)) * если суммарный размер множеств в S меньше размера блока: вернуть объединение множеств в S и v иначе записать каждое множество из S в отдельный блок вернуть {v} В конце запишем результат корневого вызова функции в отдельный корневой блок. Докажем, что алгоритм разбивает дерево на минимальное возможное количество блоков. Заметим, что если заменить условие в строке * на недетерминированное ветвление (сделав алгоритм недетерминированным), то любое разбиение дерева может быть получено таким алгоритмом. Также заметим, что количество блоков не увеличится, если заменить условие снова на детерминированное. Теорема доказана. Несмотря на оптимальность разбиения, на практике средняя заполненность блока 25-40%, то есть дерево, упакованное в блоки, занимает на 150-300% больше памяти, чем не разбитое на блоки дерево. Такая плотность упаковки неприемлемо мала. 3.2.3.4 Второй вариант метода Пусть теперь: Блок - набор из не более чем 8 связных частей вспомогательного дерева. Корни этих частей будем называть корнями блока. Все корни блока имеют общего (непосредственного) родителя. Следовательно, блоки образуют дерево, как и в первом варианте метода. Для разбиения на такие блоки предлагается следующий алгоритм. Обойдем вершины вспомогательного дерева следующей рекурсивной процедурой: 22 функция обход(вершина v): список множеств вершин S = пустой список для всех детей g вершины v: S.добавить(обход(g)) если суммарный размер множеств в S меньше размера блока: вернуть объединение множеств в S и v иначе * разбить список S на минимальное количество подсписков так, чтобы объединение множеств в каждом подсписке помещалось в блок; если решений несколько, минимизировать минимальный размер объединения множеств подсписка; множества каждого подсписка объединить; результат поместить в список множеств вершин T записать каждое множество из T, кроме минимального, в отдельный блок вернуть вершиной v минимальное множество из T с добавленной В конце запишем результат корневого вызова функции в отдельный корневой блок. Докажем, что алгоритм разбивает дерево на минимальное возможное количество блоков (при условии, что размер блока больше 8). Функция обход(v) возвращает некоторую часть поддерева v, а все остальные вершины поддерева v распределяет в новые блоки. Для доказательства теоремы достаточно доказать, что функция обход 1. создает минимально возможное количество новых блоков и 2. при минимальном количестве новых блоков возвращает минимально возможную часть дерева. Докажем это по индукции. База индукции: утверждение верно для поддерева размера 1, то есть листа. Действительно, в этом случае функция вернет единственную вершину поддерева и не создаст новых блоков. Индуктивный переход. Пусть утверждение верно для всех размеров поддерева, меньших N. Докажем, что оно верно для поддеревьев размера N. Рассмотрим произвольный вызов функции обход(v) такой, что поддерево v имеет размер N. Этот вызов делает рекурсивные вызовы для детей v. Строка *, фактически, повторяет доказываемое утверждение. Значит, при любом фиксированном разбиении поддеревьев детей, обход(v) находит оптимальный результат. Докажем, что замена рекурсивных вызовов обход(x) на какое-нибудь другое разбиение детей что-то(x) не улучшит результат. Пусть что-то(x) создало столько же новых детей, сколько обход(x), но вернуло большее множество вершин. Ответ не мог улучшиться, потому что все, что можно сделать с бóльшим множеством вершин, можно сделать и с меньшим. Пусть теперь что-то(x) создало больше новых детей, чем обход(x). Ответ не мог 23 улучшиться, потому что множество, возвращенное обход(x) можно выделить в отдельный блок и повторить рассуждения из предыдущего случая, считая, что обход(x) вернул пустое множество. Теорема доказана. При таком определении блока эксперименты показали увеличение размера дерева на 5-20%, что вполне приемлемо. Но такое разбиение чрезвычайно плохо показало себя при реальной динамической загрузке блоков. Такие блоки обладают очень плохой пространственной локальностью (рисунок 8) ввиду следующего эффекта. Заметим, что количество вершин растет экспоненциально с ростом глубины вершины в дереве. Разбиение дерева, построенное снизу вверх, одинаково обращается с вершинами с разных уровней, поэтому в среднем блок содержит немного вершин верхних уровней и много вершин нижних уровней. Когда камера смотрит на далекую поверхность, трассировщику лучей, наоборот, нужно много вершин с верхних уровней и ни одной с нижних. Получается, что для загрузки какого-то количества вершин с верхних уровней приходится загружать во много раз больше вершин с нижних уровней. В результате динамический уровень детализации, фактически, не работает, и части сцены загружаются почти всегда в максимальной детализации, независимо от расстояния до зрителя. Такая пространственная локальность неприемлема. 3.2.3.5 Третий вариант метода На этот раз будем избегать передачи недостроенных блоков снизу вверх. Вместо этого будем наполнять весь блок сразу обходом в ширину. Уберем требование общего родителя корней блока. Теперь блок обладает следующими свойствами: Блок - набор связных частей дерева. Количество частей ограничено небольшой константой (в текущей реализации 8). Корни этих частей попрежнему будем называть корнями блока. Все корни блока лежат в одном и том же блоке. Таким образом, блоки попрежнему образуют дерево. Для построения дерева будем использовать следующую рекурсивную процедуру. Будем обходить поддерево в ширину от корня, пока пройденные вершины помещаются в блок. Если поддерево кончилось, а блок не заполнен, то вернем все поддерево как недостроенный блок. Иначе, рекурсивно запустим процедуру для всех детей, не вошедших в блок, вершин, вошедших в блок. Некоторые вызовы вернут недостроенные блоки. Сгруппируем их в блоки, минимизируя количество блоков. Такой метод показывает очень хорошую пространственную локальность нелистовых блоков. С листовыми блоками возникает проблема: в среднем недостроенные листовые блоки, возвращаемые рекурсивной процедурой, очень малы. Большинство таких блоков имеют размер меньше 10 вершин. В результате, при небольшом ограничении на количество корней блока, большая часть листовых блоков получаются заполненными на 5-20%, и общая средняя наполненность блока составляет порядка 20%, что неприемлемо мало. Для приемлемой наполненности блока требуется количество корней порядка 500. Такое количество корней создало бы большие накладные расходы при хранении и загрузке блоков. 3.2.3.6 Четвертый вариант метода Пусть теперь одна и та же вершина может содержаться более чем в одном блоке. Именно, вершина может содержаться в листовом блоке и в его родителе. Из этого 24 следует, что ссылка в другой блок теперь может вести не только в корень блока, но и в любую произвольную вершину блока. Модифицируем предыдущий метод с целью уменьшить количество корней. Попытаемся группировать много недостроенных блоков, создавая мало корней. Заметим, что часто несколько объединяемых недостроенных блоков имеют общего родителя. Включив копию этого родителя в новый блок, мы можем уменьшить количество корней. Родителей этих копий тоже можно скопировать и включить в новый блок. Такой процесс можно продолжать, пока блок не наполнится. Учитывая, что количество вершин убывает в среднем экспоненциально с убыванием глубины, в среднем количество копий в блоке будет значительно меньше количества уникальных вершин. Более подробно, алгоритм имеет следующий вид. Для построения дерева будем использовать рекурсивную процедуру, похожую на процедуру в предыдущем методе. Будем обходить поддерево в ширину от корня, пока пройденные вершины помещаются в блок. Если поддерево кончилось, а блок не заполнен, то вернем все поддерево как недостроенный блок. Иначе, рекурсивно запустим процедуру для всех детей, не вошедших в блок, вершин, вошедших в блок. Потом обойдем вершины блока в глубину, поддерживая и достраивая снизу вверх блоки-детей, начиная от возвращенных рекурсивными вызовами поддеревьев. Этот последний обход в глубину почти полностью повторяет обход в глубину во втором варанте метода. Копирование вершин позволяет при загрузке блока подменять целое поддерево загруженного блока, подменяя одну ссылку на его корень (заменяя поддерево из блокародителя более детализированным поддеревом из блока-ребенка), а также возвращать все на место при выгрузке блока. Четвертый вариант метода на тестовых данных показывает увеличение размера дерева на 25-35%, а также хорошую простанственную локальность блоков (рисунок 9). Рисунок 8 – разбиение на блоки вторым вариантом метода Рисунок 9 – разбиение на блоки четвертым вариантом метода Примечание – Цвет кодирует номер блока. Примечание – Цвет кодирует номер блока. 25 3.2.4 Хранение дерева в постоянной памяти 3.2.4.1 Общая структура Представление дерева в постоянной памяти состоит из следующих частей: Заголовок дерева o Количество блоков o Количество вершин в каждом блоке o Номер корневого блока o Список каналов дерева Последовательность блоков дерева. Каждый блок представляется в следующем виде: o Заголовок блока Список корней. Для каждого корня хранятся: Номер вершины-корня в этом блоке Номер вершины-родителя в родительском блоке o Данные каналов дерева для вершин блока Постоянный размер блоков и заголовков обеспечивает быстрый доступ к блоку по номеру. Количество вершин в блоках и количество блоков ограничено 221 ввиду 21-битной разрядности вершинных ссылок. 3.2.4.2 Структура вершинных ссылок Структура показана на рисунке 6. Ссылки на вершины других блоков помечаются флагом промаха. Любая вершина, копия которой содержится в родительском блоке, помечается флагом дублирования. Флаг далекой ссылки не используется. 3.2.5 Хранение кеша дерева в видеопамяти Каждый канал дерева хранится в отдельном буфере памяти. Это сделано из соображений производительности обхода дерева: при трассировке луча происходит частый произвольный доступ к каналу ссылок и не используются другие каналы. Нет явного деления на блоки: ссылка помечена как промах если и только если она указывает в еще не загруженный блок. Из-за наличия прямых ссылок в другие блоки, 21-битные номера вершин ограничивают размер кеша 221 вершинами, чего на практике недостаточно. Для решения этой проблемы используются далекие ссылки. Если указываемый номер вершины слишком велик, в ссылке выставляется флаг далекой ссылки, и значение ссылки указывает номер значения в отдельном буфере памяти, содержащем 32-битные значения. 3.2.6 Загрузка блоков в кеш и выгрузка из кеша Процедура загрузки блока в кеш: Предусловие: родительский блок должен быть загружен (если загружается не корень). Скопировать данные каналов в видеопамять. 26 Обновить ссылки в родительском блоке, “подвесив” корни загруженного блока в нужных местах. o Загрузка далеких ссылок при необходимости. Выгрузка блока из кеша состоит в обновлении ссылок в родительском блоке к исходным значениям. Таким образом, загрузка блока требует, в основном, последовательного доступа в память, что положительно сказывается на производительности. Также требуется до 8 произвольных записей 4-байтных значений (ссылок в родителе), что не оказывает существенного влияния на производительность. 3.3 Визуализация и обратная связь 3.3.1 Трассировка луча Для поиска пересечения луча с деревом используется алгоритм, аналогичный представленному в [13]. Алгоритм спускается по дереву в самую глубокую вершинукуб, содержащую начало луча. На каждом шаге спуска номер ребенка, в которого нужно перейти, определяется однозначно. При спуске поддерживается стек вершин, составляющих путь до корня. Если в какой-то момент нужного ребенка не существует, начало луча продвигается вдоль луча до выхода из куба, соответствующего несуществующему ребенку. После продвижения луча, происходит подъем по дереву до самой низкой вершины, содержащей новое начало луча. Поиск глубины такой вершины сводится к поиску старшего бита числа, получающегося как побитовое исключающее ИЛИ координат старого и нового начала луча. При известной глубине, искомая вершина извлекается из стека по соответствующему индексу. Таким образом, подъем по дереву на произвольную высоту (а также поиск необходимой высоты) происходит за O(1). Иллюстрация последовательности посещения вершин приведена на рисунке 10. 27 Рисунок 10 – последовательность посещения вершин при трассировке луча Примечание – Изображение взято из [13]. 3.3.2 Многопроходная визуализация с запоминанием состояния 3.3.2.1 Визуализация Так как дерево не загружено в видеопамять полностью, при трассировке луча может произойти промах, то есть попытка обратиться в незагруженную вершину. В таком случае нужно прервать трассировку этого луча, загрузить нужный блок и продолжить трассировку. Для реализации такого поведения используется многопроходная визуализация. Визуализация кадра состоит из проходов. Проход попытка получить цвет каждого пиксела или хотя бы продвинуться к его получению. После каждого прохода от пикселов собираются данные о том, какие блоки нужно загрузить, и нужные блоки загружаются. Если же загружать больше ничего не нужно (все цвета пикселов успешно найдены), больше проходов не делается. Для каждого пиксела изображения трассировщик лучей хранит следующие данные: Трассируемый луч (возможно, уже преломленный или отраженный). Накопленный цвет и накопленная непрозрачность (альфа-канал). Непрозрачность увеличивается при прохождении луча через поверхности. Например, она всегда становится единицей при попадании луча в 28 непрозрачную неотражающую поверхность. Когда непрозрачность становится достаточно близкой к единице, трассировку луча можно считать законченной (потому что дальнейшая трассировка достаточно мало влияет на результирующий цвет). Количество отражений или преломлений для отсечения слишком долго обрабатывающихся лучей (например, при визуализации двух зеркал напротив друг друга). В результате каждого прохода для каждого пиксела находятся: В случае промаха, номер незагруженного блока, который нужно загрузить, чтоы продолжить трассировку. В случае успешного завершения трассировки, номер самого низкого использованного блока (нужен для поддержания LRU-кеша, подробнее в части 3.4). Если трассировка этого пиксела была завершена еще до этого прохода, ничего. Перед всеми проходами происходит инициализация данных трассировщика. Накопленный цвет, непрозрачность и количество отражений инициализируются нулями. Луч инициализируется лучом, выходящим из виртуальной камеры в направлении, соответствующем положению пикселу на экране. Такой луч можно получить, например, используя матрицу, обратную матрице модели-вида-проекции виртуальной камеры. Каждый проход трассировки состоит в выполнении следующих действий для каждого пиксела: 1. Если накопленная непрозрачность достаточно велика, выйти, ничего не вернув. 2. Найти первое пересечение текущего луча с миром. a. Если пересечения нет (луч вышел из мира, никуда не попав), присваиваем пикселу цвет фона. b. Если пересечение найдено, i. Если поверхность не прозрачная и не отражающая, определить окончательный цвет пиксела в соответствии с освещением, свойствами поверхности и пр. Вернуть самый глубокий использованный блок (определяется при поиске пересечения). ii. Если поверхность прозрачная или отражающая, скорректировать текущий цвет пиксела в соответствии со свойствами поверхности и изменить луч на отраженный или преломленный. При этом может возникнуть ситуация, когда из-за ограниченной точности вычислений новый луч начинается внутри материи (от которой он только что отразился, например), и, следовательно, немедленно с ней пересекается, создавая неправильное отражение. Для предотвращения такого развития событий, отраженный луч следует немного продвинуть вперед. Увеличить счетчик отражений и перейти обратно к шагу 1. iii. Если при поиске пересечения произошел промах, продвинуть начало луча до точки пересечения с незагруженной вершиной 29 и вернуть блок, который нужно загрузить, чтобы продолжить (номер блока содержится в вершинной ссылке, содержащей флаг промаха). 3.3.2.2 Извлечение данных обратной связи В результате прохода некоторые пикселы возвращают номера использованных и требуемых блоков. Эти данные попадают в видеопамять. Для использования их нужно перенести в оперативную память. Пикселов порядка миллиона, каждый из них потенциально может содержать 4 байта данных, в сумме порядка нескольких мегабайт данных нужно несколько раз за кадр перенести из видеопамяти в оперативную память. Простое копирование занимает слишком много времени (время такого копирования сравнимо со временем всей остальной части визуализации), поэтому перед копированием эти данные имеет смысл сжимать программой, выполняемой на видеокарте. Так как данные достаточно разреженные и избыточные, сжимать их можно достаточно эффективно. Для существенного уменьшения размера данных достаточно исключать повторяющиеся значения (так как для загрузки важно только множество номеров блоков). Количество различных использованных блоков не может превышать максимального количества блоков в видеопамяти. Если количество различных использованных и запрошенных блоков в сумме превышают максимальное количество блоков в видеопамяти, то загрузить все запрошенные блоки не получится, поэтому незачем получать все их номера. Таким образом, после сжатия размер данных для копирования ограничен несколькими килобайтами, что вполне приемлемо. Исключать повторяющиеся значения с использованием видеокарты можно следующим алгоритмом. Пусть есть исходная последовательность чисел A, из которой требуется удалить повторяющиеся элементы. Проведем следующие преобразования: 1. Упорядочить последовательность по возрастанию (или убыванию). 2. Получить вспомогательную последовательность нулей и единиц B следующим образом: 3. Построить последовательность C префиксных сумм последовательности B: 4. Построить результирующую последовательность без повторов как Объем копируемых данных можно еще уменьшить, немного ухудшив эффективность LRU-кеша. Заметим, что множество использованных блоков, как правило, незначительно меняется от кадра к кадру. Также заметим, что номера использованных блоков не критичны для визуализации, и используются лишь для лучшего предугадывания использования блоков. Это позволяет не извлекать все номера использованных блоков каждый раз, а извлекать их по частям. Можно после каждого прохода извлекать отрезок данных фиксированной длины. Положение отрезка можно выбирать случайно. Чтобы добиться равномерного распределения вероятности по всем элементам, отрезок следует сделать циклическим, то есть разрешить концу отрезка выходить за конец данных и считать, что после конца данных идет снова начало. 30 3.4 Поддержка кеша дерева 3.4.1 Стратегия кеширования Как было описано выше, для выбора загружаемых блоков используется обратная связь от трассировщика лучей. Когда кеш полон (то есть всегда, кроме периода начального заполнения кеша), перед загрузкой блока нужно выгрузить блок, чтобы освободить место. Для выбора выгружаемого блока используется LRU кеш, то есть выгружается блок, к которому не было обращений дольше всего. Для поиска такого блока используется очередь кандидатов на выгрузку (LRU-очередь). Для получения информации об обращениях к блокам используется обратная связь от трассировщика лучей. При получении данных о использовании блока, блок переносится в конец очереди. 3.4.2 Предотвращение выгрузки родителей Для поддержания целостности дерева, нужно запретить выгружать блоки, имеющие загруженных детей. Есть простой способ это сделать: каждый раз, когда нужно перенести блок в конец очереди, будем сначала переносить в конец очереди всех его предков, начиная от корня. Таким образом, родитель любого блока всегда будет в очереди позднее самого блока, и, следовательно, не сможет быть выгружен раньше самого блока. 3.4.3 Предотвращение выгрузки заведомо используемых блоков После получения номеров использованных блоков и требуемых блоков нужно переместить использованные блоки в конец очереди и загрузить требуемые блоки. Если сделать это в таком порядке, может получиться ситуация, когда при загрузке блока будет выгружен родитель другого требуемого блока. Позже этого родителя придется загружать снова. Чтобы избежать таких бесполезных действий, следует перед загрузкой требуемых блоков переместить всех их родителей в конец очереди. 3.4.4 Обнаружение недостаточного размера кеша Если для визуализации кадра нужно больше блоков, чем помещается в кеш, многопроходный процесс визуализации никогда не завершится: одни и те же блоки будут бесконечно загружаться и выгружаться из кеша. Для обнаружения таких ситуаций достаточно обнаруживать попытку выбросить из кеша блок, который был использован (например, загружен) в этом же кадре. Заметим, что все блоки, использованные в текущем кадре, “прижаты” к концу очереди, то есть находятся ближе к концу очереди, чем все остальные блоки. Это нетрудно доказать: до начала кадра нет ни одного использованного блока, а любое использование переносит блок в конец очереди. С учетом этого, для обнаружения недостаточного размера кеша предлагается следующий метод. Модифицируем LRU-очередь так, чтобы она могла содержать не только номера блоков, но и специальные объекты, называемые маркерами. В любой момент можно создать новый маркер и положить в конец очереди. Также можно узнать, был ли указанный маркер уже выброшен из очереди. В начале кадра будем добавлять маркер в конец очереди. Перед каждой загрузкой блока будем проверять, не вышел ли маркер из очереди. Если вышел, то кеш слишком мал для визуализации текущего кадра. 31 4 Результаты 4.1 Производительность Измерение производительности программы производилось на компьютере с видеокартой ATI Radeon HD 4870 с 512 Мб видеопамяти, процессором Intel Core 2 Quad с тактовой частотой 2.5 ГГц и 4 Гб оперативной памяти. Для тестирования производительности использовались следующие сцены из [10] (таблица 1): Таблица 1 – список тестовых сцен Название Количество треугольников в исходной модели Глубина Размер Количество Изображение построенного октарного вершин в октарного дерева блоке дерева Sibenik 75,284 13 1666 МБ 4096 Рисунки 15-16 Conference 331,179 13 1135 МБ 4096 Рисунки 17-18 Hairball 13 12 ГБ 4096 Рисунки 11-14 1,441,098 Модель hairball особенно сложна для трассировки лучей: большая часть лучей несколько раз проходит рядом с препятствиями, что заставляет алгоритм несколько раз глубоко спускаться и подниматься по дереву. Эта модель также сложна для растеризации треугольников: модель состоит из 1,441,098 треугольников, и большая часть площади изображения покрыта сотнями слоев треугольников. Визуализация производилась в разрешении 512x512 пикселов. Спуск по дереву прекращался, когда размер вокселя в проекции становился меньше размера одного пиксела. Размер кеша дерева в видеопамяти составлял 200 МБ. Полученные частоты кадров представлены в таблице 2. Измерения времени работы отдельных стадий визуализации представлены в таблице 3. Можно заметить, что большая часть времени при движении уходит на загрузку блоков с жесткого диска. Следовательно, наиболее важным направлением дальнейшей работы видится реализация фоновой загрузки блоков, не задерживающей визуализацию, а также переупорядочивания и приоритетизации запросов для более быстрого чтения. 32 Таблица 2 – производительность на тестовых сценах Сцена Положение камеры Среднее количество кадров в секунду Комментарий Рисунок 11 16 Около половины лучей быстро проходят через дерево, никуда не попав. Рисунок 12 10 Рисунок 13 4 Рисунок 14 10 Движение в глубине шара, начиная с положения на рисунке 14 4 Рисунок 15 13 Движение между рисунками 15 и 16 3 Нужные данные не помещаются в кеш. Часть данных загружается и выгружается каждый кадр. Hairball Sibenik Conference Рисунок 17 Движение между рисунками 17 и 18 12 4 33 Таблица 3 – среднее время, затрачиваемое на отдельные стадии визуализации кадра, а также средние значения некоторых величин Графический процессор Шина ЦП ГП Диск Время, мс на кадр Центральный процессор Ресурс Действие Рис. 14 Рис. 14, движение Рис. 13 Рис. 15-16, движение Рис. 17-18, движение Пометить использованные блоки 0.4 0.1 3 0.7 0.6 Запросить блоки 0 0 5 0 0 0.6 8 4 4 Обработать ссылки в блоке Инициализировать кадр 1 1 1 1 1 Проходы трассировки 84 48 111 39 46 Сортировка 9 13 9 13 13 Префиксные суммы 3 5 3 5 5 Завершить кадр 0.5 0.4 0.5 0.4 0.4 Загрузить блоки 0 33 5 213 148 Извлечь промахи 0.1 0.3 0.4 0.4 0.3 Извлечь попадания 0.3 0.4 0.3 0.3 0.3 Загрузить блоки 0 3 33 18 11 Загрузить ссылки 0 2 22 8 6 Обновить далекие ссылки 0 2 22 10 6 Выгрузить ссылки 0 2 24 11 7 Промахи за проход 0 8 6294 56 39 Попадания за проход 413 72 3750 496 434 Загружено блоков за проход 0 8 109 56 39 Обновлено ссылок за проход 0 47 624 250 187 Величины Величина 34 Рисунок 11 – модель hairball снаружи Рисунок 12 – модель hairball около поверхности Примечание – Видны размеры вокселей и треугольников. Рисунок 13 – модель hairball с близкого Рисунок 14 – модель hairball внутри расстояния Примечание – Из-за недостаточного размера кеша виден большой воксель. 35 Рисунок 15 – сцена sibenik, положение 1 Рисунок 16 – сцена sibenik, положение 2 Рисунок 17 – сцена conference, положение 1 Рисунок 18 – сцена conference, положение 2 36 4.2 Гибкость Без изменения архитектуры можно реализовать поддержку следующих возможностей: Более сложная геометрия в листьях. Например, пара плоскостей или изоповерхность ([14]). Первый вариант, согласно [13], ускоряет трассировку лучей в 1.5-2 раза. Более сложная модель освещения и больший набор свойств поверхности. Некоторые способы сжатия с потерями свойств поверхности (таких как цвет и нормаль). Тени, отражения, преломления. С другой стороны, предложенный в [13] способ хранения ссылок и контуров, достигающий размера всего в 2 байта на вершину (по сравнению с 8 байтами при непосредственном хранении), не может быть применен в данной системе без существенного усложнения. 37 Заключение Разработан новый метод динамической загрузки частей октарного дерева, основанный на разбиения дерева на блоки равного размера и обратной связи от трассировщика лучей. Для изучения эффективности метода разработана система визуализации. Предлагаемый метод достигает средней наполненности блока 75-80% при ограничении в 8 компонент связности на блок и низкой глубине блоков. Равный размер блоков и их низкая связанность позволяют существенно упростить механизм загрузки данных по сравнению с существующими методами. Метод обладает хорошей производительностью: время загрузки данных с жесткого диска превышает время их дальнейшей обработки и загрузки в видеопамять. Остальная часть системы визуализации пока уступает аналогам (прежде всего, [13]) по некоторым показателям, однако с использованием методов, перечисленных в работе, может быть беспрепятственно усовершенствована до сравнимого уровня. Кроме того, разработанные методы могут быть использованы в других системах визуализации. Предполагаемые направления дальнейшей работы: Модификация структуры данных таким образом, чтобы она позволяла загружать данные одновременно с трассировкой лучей, тем самым, предположительно, значительно снижая уменьшение производительности во время загрузки данных. Загрузка с жесткого диска, не синхронизированная с кадрами, должна устранить подвисания при резкой смене ракурса. Приоритеты блоков на основе количества пикселов, попадающих в блок. Загрузка в порядке приоритетов, выбор выгружаемого блока с учетом приоритета. Такой подход должен существенно улучшить изображение при недозагруженном дереве. Контуры или изоповерхности в вершинах, сжатие с потерями атрибутов вокселей. Отражения, преломления, полупрозрачность. 38 Список использованных источников и литературы 1. Aaron Knoll. A Survey of Octree Volume Rendering Methods. // URL: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.107.4185&rep=rep1&t ype=pdf (date: 30.05.2012). 2. Atomontage engine - Technology // Atomontage engine. - URL: http://www.atomontage.com/?id=tech_overv (date: 30.05.2012). 3. Cyril Crassin. GigaVoxels: Ray-Guided Streaming for Efficient and Detailed Voxel Rendering. / Fabrice Neyret, Sylvain Lefebvre, Elmar Eisemann. // ACM SIGGRAPH Symposium on Interactive 3D Graphics and Games (I3D). - 2009. – URL: http://maverick.inria.fr/Publications/2009/CNLE09/CNLE09.pdf (date: 30.05.2012). 4. Enrico Gobbett. A single-pass GPU ray casting framework for interactive out-ofcore rendering of massive volumetric datasets / Fabio Marton, Jose Antonio Iglesias Guiti´an // The Visual Computer. – 2008. – pages 797-806. – URL: http://www.crs4.it/vic/cgi-bin/bib-page.cgi?id='Gobbetti:2008:SGR' (date: 30.05.2012). 5. Eric P. Lafortune. Bi-Directional Path Tracing / Yves D. Willems // Proceedings of Compugraphics '93. - Alvor, Portugal, 1993, pages 145-153. – URL: http://www.graphics.cornell.edu/~eric/Portugal.html. 6. Gokul Varadhan. Out-of-Core Rendering of Massive Geometric Environments / Dinesh Manocha // VIS '02 Proceedings of the conference on Visualization '02. Washington DC, USA, 2002. - pages 69-76. – URL: http://gamma.cs.unc.edu/OOC/varadhan.pdf. 7. Graphics pipeline // Wikipedia, the free encyclopedia. - URL: http://en.wikipedia.org/wiki/Graphics_pipeline (date: 30.05.2012). 8. Henrik Wann Jensen, Realistic Image Synthesis Using Photon Mapping / Henrik Wann Jensen. - AK Peters, 2005. 9. Ingo Wald, Vlastimil Havran. On building fast kd-Trees for Ray Tracing, and on doing that in O(N log N) // IEEE Symposium on Interactive Ray Tracing. - 2006. – pages 61-69. – URL: http://dcgi.felk.cvut.cz/home/havran/ARTICLES/ingo06rtKdtree.pdf (date: 30.05.2012). 10. McGuire, Computer Graphics Archive // URL: http://graphics.cs.williams.edu/data/meshes.xml (date: 30.05.2012). 11. Photon mapping // Wikipedia, the free encyclopedia. - URL : http://en.wikipedia.org/wiki/Photon_mapping (date: 30.05.2012). 12. Ray tracing (graphics) // Wikipedia, the free encyclopedia. - URL: http://en.wikipedia.org/wiki/Ray_tracing_(graphics) (date: 30.05.2012). 13. Samuli Laine. Efficient Sparse Voxel Octrees / Tero Karras // Proceedings of the 2010 ACM SIGGRAPH symposium on Interactive 3D Graphics and Games. – 2010. – pages 55-63. – URL: 39 http://www.nvidia.com/docs/IO/88889/laine2010i3d_paper.pdf (date: 30.05.2012). 14. Sriram Kashyap. Implicit Surface Octrees For Ray Tracing Point Models / Rhushabh Goradia, Parag Chaudhuri etc. // Proceedings of the Seventh Indian Conference on Computer Vision, Graphics and Image Processing . - New York, 2010. – URL: http://www.cse.iitb.ac.in/~rhushabh/publications/icvgip10/icvgip10.pdf (date: 30.05.2012). 15. Tomas Nikodym. Ray Tracing Algorithm For Interactive Applications : Bachelor's Thesis / Czech Technical University. – Prague, 2010. – URL: https://dip.felk.cvut.cz/browse/pdfcache/nikodtom_2010bach.pdf (date: 30.05.2012). 40