среда, 22 июня 2011 г.

Создание сложной процедурной поверхности средствами GPU (Перевод) Часть 2

 

Советы по выбору октав
Ценно рассмотреть несколько вещей, связанных с выбором октав. Во-первых, мировые координаты могут быть использованы без изменений для 7-8 высокочастотных октав. Однако, для одной или двух низкочастотных октав, мировые координаты должны быть сначала немного повернуты (с помощью матрицы вращения 3x3) во избежание повторения выступающих элементов ландшафта. Также разумно переиспользовать 3 или 4 текстуры шума девятью октавами для увеличения когерентности кеша; переиспользование не будет заметно на сильно рознящихся частотах.
В итоге, точность может понижаться, когда мы работаем с очень низкочастотным шумом. Ошибки проявляются как (нежелательный) высокочастотный шум. Для борьбы с этим, мы вручную применили трилинейную интерполяцию при работе с низкочастотной октавой (или двумя), используя полную точность дробного числа. Узнать об этом подробнее можно, посмотрев комментарии в коде shaders\density.h.
Использование множество октав шума делаем рельеф изотропным (одинаковым во всех направлениях), что выглядит слишком искусственно. Одним из способов борьбы с такой однородностью является искривление мировых координат с помощью другого (низкочастотного) шума перед использованием этих координат девятью октавами. На средней частоте и средней амплитуде, искривление создает нереальный, плохой, неестественный рельеф, как показано на Рисунке 1-12. На низкой частоте и высокой амплитуде, искривление увеличит количество гор, пещер и арок. Эффекты можно комбинировать, суммируя две октавы.
  1. // Сделайте это перед использование 'ws' для девяти октав!
  2. float3 warp = noiseVol2.Sample( TrilinearRepeat, ws*0.004 ).xyz;
  3. ws += warp * 8;  
// Do this before using 'ws' to sample the nine octaves!
float3 warp = noiseVol2.Sample( TrilinearRepeat, ws*0.004 ).xyz;
ws += warp * 8;
clip_image001
Рисунок 1-12 Искривление мировых координат для получения сюрреалистических картин
Мы также введем жесткий "пол" в сцене, используя очень быстрый ростом функции плотности ниже определенной координаты y. Это подражает явлению в природе, когда размытая порода стекает вниз. Визуально, это делает сцену менее «водянистой» и более похожей на землю, как видно на Рисунке1-13.
  1. float hard_floor_y = -13;
  2. density += saturate((hard_floor_y - ws_orig.y)*3)*40; 
float hard_floor_y = -13;
density += saturate((hard_floor_y - ws_orig.y)*3)*40;
clip_image002
Figure 1-13 Представленный уровень пола
Множество других эффектов может быть получено изменением значения функции плотности при помощи мировой координаты y. Мы можем делать изменение используя предискривление или постискривление мировых координат. При использовании постискривления, эффекты, которые выглядят, как шлейфы или террасы начнут мяться и таять, вдоль края земли. Рисунки 1-14 и 1-15 показывают два примера.
clip_image003
Рисунок 1-14 построение шельфов
clip_image004
Рисунок 1-15 повторяющиеся террасы
Это базовые приемы. Конечно, возможно множество эффектов, которые получаются простыми изменениями в файле shaders\density.h.
1.3.4 Выбор поверхности
Все рассмотренные приемы направлены на создание хорошего, натурально выглядящего ландшафта. Однако, на практике, мы должны уметь задавать форму поверхности. Для этого есть много способов.
Использование созданной вручную 2D текстуры
Растягивание нарисованной вручную 2D текстуры на очень большую область. Функция плотности может выбирать 2D текстуру с помощью ws.xz и используя результат для применения восьми октав шума. Красный канал может быть использован для изменения ws.y перед его использованием, в результате шум будет либо сплющен в одних областях, либо вытянут в других. Зеленый канал может отвечать за амплитуду высокочастотных октав, что делает землю либо холмистой, либо равнинной. А синий канал может отвечать за эффект искривления, описанный ранее, что сделает землю более "чужеродной" в некоторых областях.
Добавление воздействий вручную
Также возможно задавать вручную воздействие на плотность. Так, если игровому уровню нужна ровная площадка для посадки корабля, как на Рисунке 1-16, вы можете передать данные пиксельному шейдуру, описав их в буфере констант. Код шейдера будет выглядеть примерно следующим образом:
  1. float distance_from_flat_spot = length(ws.xz – flat_spot_xz_coord);
  2. float flatten_amount = saturate(outer_radius – distance_from_flat_spot)/
  3. (outer_radius – inner_radius) ) * 0.9;
  4. density = lerp(density, ws.y – flat_spot_height, flatten_amount); 
float distance_from_flat_spot = length(ws.xz – flat_spot_xz_coord);
float flatten_amount = saturate(outer_radius – distance_from_flat_spot)/
                       (outer_radius – inner_radius) ) * 0.9;
density = lerp(density, ws.y – flat_spot_height, flatten_amount);
clip_image005
Рисунок1-16 Вручную созданная площадка для посадки космического корабля
Здесь функция плотности будет на 90% заменена функцией «площадки» внутри inner_radius радиуса от центра в мировых координатах; Однако, дистанцией outer_radius, влияние сводится к нулю (saturate() смыкает значения из диапазона 0..1). В дополнении, много таких площадок могут быть использованы на поверхности—на разных высотах, широтах, и с разным радиусом. Если нужна больше, чем одна площадка на блок, можно использовать динамические циклы. Приложение отвечает за обновление буфера констант с информацией о месте создания блоков.
Добавление различных эффектов
Эффекты можно создавать комбинированием предыдущих приемов—от пещер до уступов, до нарисованных карт рек. Если вы это представляете, то можете попробовать написать код. Что за сферическая планета на Рисунке 1-17? Вместо использования координаты y в качестве уровня земли, попробуйте это (плюс шум):
  1. float rad = 80;
  2. float density = rad - length(ws – float3(0, -rad, 0)); 
float rad = 80;
float density = rad - length(ws – float3(0, -rad, 0));
clip_image006
Рисунок 1-17 сферическая планета
Для бесконечной 3D сети пещер, попробуйте это (плюс шум):
  1. //Этот положительно начинающийся наклон дает больше гор, чем на открытом месте.
  2. float density = 12;  
//This positive starting bias gives us a little more rock than open space.
float density = 12;
Функция плотности, используемая в этом демо может быть модифицирована в файле shaders\density.h, который используется в некоторых других шейдерах. Если вы хотите производить изменения в реальном времени, сделайте следующее: запустите демо в оконном режиме (смотри bin\args.txt), изменить функция плотности оп вашему усмотрению, и нажмите F9 (перезагрузить шейдеры), после чего - "Z" (Пересоздать землю).

1.4 Создание полигонов внутри блока поверхности

Существует множество способов решение задачи создания блоков поверхности средствами GPU. В первом приближении, мы создаем значение плотности в 3D текстуре (представление всех углов вокселя в блоке) за один проход. На втором проходе мы смотрим все воксели в заданном объеме и используем GS для создания (и вывода в буфер вершин) от 0 до 15 вершин в каждом вокселе. Вершины интерпретируются как список треугольников, то есть каждые три вершины образуют треугольник.
Сейчас посмотрим, что нам надо для создания всего одной вершины. Вот те данные, которые нам нудно знать и хранить для каждой вершины:
  • Мировые координаты
  • Мировой вектор нормали (для освещения)
  • Значение освещения "ambient occlusion" (мера загражденности)
Эти данные могут быть представлены в виде 7 дробных чисел. Отметим, что значение ambient occlusion упаковано в канал .w первого вектора float4.
  1. struct rock_vertex {
  2.    float4 wsCoordAmbo;
  3.    float3 wsNormal;
  4. };  
struct rock_vertex {
   float4 wsCoordAmbo;
   float3 wsNormal;
};
Нормаль может быть получена, взятием градиента функции плотности (частная производная, или независимая мера изменения, в направлениях x, y и z) и нормализацией результата. Это достигается выборкой плотности шесть раз. Для определения величины изменений вдоль x, мы берем следующий текстель вдоль направления +x, далее выбираем текстель в направлении -x, и подсчитывают разницу; это и есть величина изменения вдоль x. Делаем то же самое для направлений y и z, всего получаем 6 выборок. Три результата объединяют в float3, и нормализуют, получая качественную поверхность нормали, которая в дальнейшем может быть использована для освещения. Программа 1-1 показывают код шейдера.
Пример 1-1. Подсчет нормали через градиент
  1. float d = 1.0/(float)voxels_per_block;
  2. float3 grad;
  3. grad.x = density_vol.Sample(TrilinearClamp, uvw + float3( d, 0, 0)) -
  4.          density_vol.Sample(TrilinearClamp, uvw + float3(-d, 0, 0));
  5. grad.y = density_vol.Sample(TrilinearClamp, uvw + float3( 0, d, 0)) -
  6.          density_vol.Sample(TrilinearClamp, uvw + float3( 0,-d, 0));
  7. grad.z = density_vol.Sample(TrilinearClamp, uvw + float3( 0, 0, d)) -
  8.          density_vol.Sample(TrilinearClamp, uvw + float3( 0, 0,-d));
  9. output.wsNormal = -normalize(grad);  
float d = 1.0/(float)voxels_per_block;
float3 grad;
grad.x = density_vol.Sample(TrilinearClamp, uvw + float3( d, 0, 0)) -
         density_vol.Sample(TrilinearClamp, uvw + float3(-d, 0, 0));
grad.y = density_vol.Sample(TrilinearClamp, uvw + float3( 0, d, 0)) -
         density_vol.Sample(TrilinearClamp, uvw + float3( 0,-d, 0));
grad.z = density_vol.Sample(TrilinearClamp, uvw + float3( 0, 0, d)) -
         density_vol.Sample(TrilinearClamp, uvw + float3( 0, 0,-d));
output.wsNormal = -normalize(grad);
Значение ambient occlusion показывает, в общем, сколько света дойдет до вершины, в зависимости от окружающей геометрии. Значение отвечает за затенение вершин, которые лежат с углах, щелях и канавах, куда проникает меньше света. В общем, мы можем получить это значение расположением большой сферы окружающего света, который освещает вершину. Далее мы трассируем внутренние лучи, чтобы увидеть от какой части вершин лучи могут достичь данную вершину без соударения с другими частями поверхности, или мы можем испустить множество лучей из вершины и смотреть какая часть лучей сможет пройти определенное расстояние без соприкосновения с поверхностью. Наш пример использует последний метод.
Для подсчета значения ambient occlusion в точке, мы испускаем 32 луча. Константа Пуассона распределения точек на поверхности сферы хорошо подходит для этого. Мы храним эти точки в буфере констант. Мы можем—и будем—переиспользовать тот же набор лучей для каждой вершины, для которой хотим получить значение ambient occlusion. (Замечание: Мы можем использовать наше распределения Пуассона вместо создания своего; поищите "g_ray_dirs_32" в models\tables.nma на прилагаемом DVD.) Для каждого луча, мы берем 16 выборок значение плотности вдоль хода луча—опять, выборкой функции плотности. Если среди выборок есть положительное значение, мы считаем, что луч пересекся с землей и дальше не пойдет. Когда все 32 луча рассчитаны, часть заблокированных из них—обычно от 0.5 до 1—становятся значением ambient occlusion. (Несколько вершин имею значение ambient occlusion менее 0.5, потому что большинство лучей в полушарии по направлению к земле быстро находят преграду.)
Позже, когда рельеф отображен, освещение рассчитывается как обычно, но окончательное значение освещенности (рассеянное и отраженное) будет применено с учетом этого значения. Мы рекомендуем умножать освещение на saturate(1 – 2*ambient_occlusion), что переведет значение occlusion равное 0.5 в световой множитель 1, а значение occlusion равное 1 в световой множитель 0. Множитель также может быть пропущен через функцию pow() для красивого эффекта затухания
1.4.1 Граничные данные
Нужно иметь ввиду, что некоторые лучи выходят за пределы блока известных значений плотности, что дает неверные данные. В таком случае на границе блоков будут образовываться световые артефакты. Однако, это легко решается незначительным расширением объема известной плотности и использованием дополнительного места для хранения значений функции плотности за пределами нашего блока. Блок разделен на 323 вокселя для тесселяции, но мы должны иметь, например, 443 значений плотности, где дополнительные "поля" вокселей отображают функцию плотности за пределами блока 323. Теперь мы можем испускать лучи за пределы нашего объема и получать более точные результаты. Результат все еще не превосходный, но на практике, соотношение (32 вокселей против 6 вокселей на границе для каждой грани) дает хороший результаты без видимых артефактов. Стоить иметь ввиду, что эти размерности отражают количество вокселей в блоке; массив плотности (который согласуется с угловыми вокселями) будет содержать еще по одному элементу в каждом направлении.
К сожалению, работа с короткими лучами не подходит для больших, низкочастотных элементов поверхности, например для затемнения внутри пещер. Для таких низкочастотных элементов, мы сделаем несколько выборок реальной функции плотности вдоль каждого луча, но на больших расстояниях—умышленно за пределами текущего блока. Взятие реальной функции плотности намного более трудоемко, но, к счастью, для хороших результатов нам достаточно сделать около четырех выборок для каждого луча. Для облегчения обработки, мы можем также использовать "легковесную" функцию плотности. Она игнорирует высокочастотные октавы, потому что они не важны на больших расстояниях. На практике, при наличии восьми октав шума, безопасно отбрасывать три наиболее высокочастотных.
Блок псевдокода в Программе 1-2 показывает, как получить ambient occlusion для вершины.

Комментариев нет:

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