Автор: Дмитрий Трифонов «DikobrAz»

Опубликовано: 11.04.2010

Изменено: 11.04.2010

Постоянная ссылка

Комментарии [24]

Визуализация водной поверхности. Быстрое преобразование Фурье на GPU


В статье рассмотрен алгоритм быстрого преобразования Фурье на GPU, а также технология создания водной поверхности с использованием продвинутой статистической модели.



Страницы: 1 2 3 4

Содержание

1. Введение
2. Плоская поверхность воды
    2.1 Рельефное текстурирование
    2.2 Добавление локальных отражений и преломлений
    2.3 Комбинирование отражений и преломлений
    2.4 Модель освещения
3. Быстрое преобразование Фурье на GPU
    3.1 Дискретное преобразование Фурье
    4.2 Быстрое преобразование Фурье
    4.3 Реализация с использованием шейдеров
    4.4 Производительность
4. Статистическая модель синтеза поверхности океана
    4.1 Основные положения. Примеры использования
    4.2 Спектр Филлипса
    4.3 Нормали и “острые” волны
5. Визуализация поверхности океана
    5.1 Проекционная сетка или сетка привязанная к камере
    5.2 Пересечение с пирамидой видимости
    5.3 Распределение детализации
    5.4 Коррекция по краям
    5.5 Фильтрация
    5.6 Отражения и преломления
    5.7 Океанский шейдер
    5.8 Производительность и оптимизация
6. Заключение
7. Источники
8. Демо и исходный код

1. Введение

Реалистичная визуализация водной поверхности один из самых эффективных способов сделать 3D приложение привлекательным. Но многие алгоритмы синтеза поверхности, как правило, сложны в реализации и требовательны к аппаратуре, поэтому к вопросу выбора алгоритма стоит подойти с особым вниманием.

Ранние методы визуализации воды в реальном времени, основывались на предположении о том, что водная поверхность плоская. Иллюзия волн создавалась за счет рельефного текстурирования с использованием заранее сгенерированных карт для нормалей и высот. Такой подход до сих пор часто используется. Он отлично подходит для визуализации небольших и спокойных поверхностей воды, например озер, прудов и луж. Кроме того, плоская поверхность воды сильно упрощает визуализацию отражений, преломлений, физическую модель, требует минимального колличества полигонов и позволяет легко добавлять спецэффекты, например, волны от объектов.

К сожалению такой подход очень плохо справляется с визуализаций больших открытых водоемов, а именно тех, где ожидается увидить хоть сколько-нибудь крупные волны. Как только у 3D ускорителей появилась возможность изменять геометрию на лету, без серьезного падения производительности, рельефное текстурирование было вытеснено анимацией вершин. Но получаемые поверхности все равно приходится делать достаточно плоскими, потому что для реалистичного синтеза больших волн требуются более изощренные подходы, нежели анимация за счет заранее сгенерированных карт нормалей и высот. Таким подходом, например, является параметрическая модель "Gernster Waves", но хотя модель применялась на практике, широкого распространения не получила. Существует гораздо более реалистичная статистическая модель, которой будет посвящена основная часть статьи.


Рисунок 1. Поверхность, синтезированная моделью "Gernster Waves".

2. Плоская поверхность воды

    2.1 Рельефное текстурирование

Самым простым методом визуализации водной поверхности, как уже было сказано, является рельефное текстурирование плоскости на основе заранее сгенерированных карт нормалей или высот. Обычно используется периодическая текстура с фрактальным шумом, возможно использование объемной текстуры.

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

    
Формула 1. Функция турбулентности.
P – позиция точки, t - время. Источник
     Рисунок 2. Статическая карта нормалей,
используемая для анимации водной поверхности

    2.2 Добавление локальных отражений и преломлений

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

Обычно используют следующий алгоритм:

  1. Камера "отражается" относительно поверхности воды:


    Рисунок 3. Отражение камеры для визуализации локальных отражений

  2. Устанавливается плоскость отсечения, чтобы подводные объекты или части объектов не отрисовывались. Если при рендеринге используются шейдеры, то производить отсечение придется в шейдере. Чтобы этого избежать, можно использовать проекционную матрицу специального вида, которая в качестве ближней плоскости отсечения использует нужную нам плоскость. Техника называется Oblique Near-plane Clipping.
  3. Сцена рендерится с использованием отраженной камеры. Стоит не забыть про отсечение задних граней: если при отражении базис камеры превратился из правостороннего в левосторонний, то следует изменить режим отсечения.


    Рисунок 4. Проецирование сцены в карту отражений.

  4. Устанавливается матрица специального вида, используемая для получения текстурных координат в карте отражений по координате точки поверхности воды. С использованием OpenGL можно вычислить это матрицу следующим образом:
        glLoadIdentity ();
        glTranslatef   ( 0.5, 0.5, 0 );  // из квадрата [-1,1]x[-1,1]
        glScalef       ( 0.5, 0.5, 1 );  // в квадрат [0,1]x[0,1]
        glMultMatrixf  ( projMat );      // перспективная матрица камеры отражений
        glMultMatrixf  ( modelViewMat ); // видовая матрица камеры отражений
        
    Разумеется, это всего лишь пример вычисления матрицы, сам по себе этот код не даст требуемый результат.
  5. Рисуем поверхность воды, используя полученную карту с отражениями и матрицу из предыдущего этапа. Умножая матрицу на позицию точки поверхности, получаем проективную тексурную координату в карте отражений. Для наложения ряби можно сдвигать текстурную координату на величину нормали в точке водной поверхности. В некоторых случаях размытие стоит производить в экранной плоскости или ввести зависимость размытия от расстояния, чтобы поверхность расположенная далеко от наблюдателся не выглядела слишком плоской. Все вышесказанное эквивалентно командам GLSL:
        // Определение текстурных координат через матрицу отражений
        vec4 projTexCoord = reflectionMatrix * waterVertex;
    
        // Размытие в плоскости поверхности воды
        projTexCoord.xy += distortConstant * normal.xy;
        vec4 texel       = texture2DProj(reflectMap, projTexCoord);
    
        // Размытие в экранной плоскости
        vec2 texCoord = projTexCoord.xy / projTexCoord.w + distortConstant * normal.xy;
        vec4 texel    = texture2D(reflectMap, texCoord);
        

    Замечение: Учитывая специфику модельно-видового преобразования, совпадение матриц проекции и то, что водная поверхность представляет собой плоскость, текстурную координату для выборки из текстуры отражений можно определить и не вводя дополнительной матрицы. По сути в качестве текстурной координаты просто используется относительная позиция пикселя на экране. Матрицу для отражений будем получать следующим образом:

          // отражение точки относительно плоскости:
          vec3 reflect(vec3 vec, vec4 plane) {
              return vec - 2 * (dot(vec, plane.xyz) - plane.w) * plane.xyz;
          }
    
          vec3 position  = reflect( masterCamera->getPosition().xyz(), waterPlane );
          vec3 direction = reflect( masterCamera->getDirection(), vec4(waterPlane.xyz, 0.0) );
          vec3 up        = reflect( masterCamera->getUp(), vec4(waterPlane.xyz, 0.0) );
    
          // look_at - создает матрицу такую же как и gluLookAt
          reflectCamera.setViewMatrix( look_at(position, position + direction, up) );  
          
    Тогда текстурная координата в карте отражений будет находиться следующим образом(GLSL):
          vec4 projTexCoord = gl_ModelViewProjectionMatrix * waterVertex;
    
          // После перспективного деления получаются нормализованные координаты, 
          // они в квадрате [-1,1]x[-1,1]. Их надо перевести в текстурные,
          // которые в квадрате [0,1]x[0,1]. В этом и суть операций glTranslatef, glScalef
          // для получения описанной матрицы для получения текстурных координат отражений
          vec2 texCoord = 0.5 * projTexCoord.xy / projTexCoord.w + vec2(0.5);
    
          // В описанном алгоритме получения матрицы для камеры отражения инвертируется
          // ось камеры OY (или up, это и нужно), но вместе с ней и OX (а это уже не нужно), 
          // поэтому надо инвертировать координату x
          texCoord.x = 1.0 - texCoord.x;  
          


    Рисунок 5. Восстановление отражений из текстуры.

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

    2.3 Комбинирование отражений и преломлений

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

Алгоритм не позволяет физически точно определять куда попадет луч после отражения от водной поверхности. Для наложения ряби, текстурные координаты для выборок из текстуры отражений просто сдвигают на величину проекции нормали на плоскость поверхности воды или на величину какой-либо шумовой функции. Отличить отражения, "размытые" таким образом, от настоящих крайне трудно, но сама водная поверхность выглядит очень плоско. К счастью, этот недостаток можно скомпенсировать. Для этого делаются две выборки: из текстуры отражений и из текстуры окружения. Если первая выборка “пустая”, то нет объекта для локального отражения, - в качестве цвета берётся вторая выборка, из кубической текстуры окружения. На GLSL это может выглядеть более понятно:

vec4 localRefl = texture2D(reflectMap, reflectTexCoord.xy);  // Выборка из текстуры локальных отражений
vec3 gobalRefl = texCUBE(skybox, reflectVec);                // Выборка из текстуры окружения
vec3 color     = mix(gobalRefl, localRefl.rgb, localRefl.a); // Линейная интерполяция между цветами

Стоит не забывать, что при использовании этого комбинированного подхода, во время рендеринга в текстуру отражений, надо отключить отрисовку скайбокса или других объектов глобального окружения. В противном случае теряется весь смысл совмещения глобальных и локальных отражений, потому что все они будут интерпретирваться как локальные.

Рисунок 6. Использование только локальных отражений
Рисунок 7. Использование комбинации локальных и глобальных отражений

Для определения доли отраженной и преломленной энергии служат коэффициенты Френеля. Они определяют какая часть энергии светового пучка отразилось от поверхности в точке раздела двух сред, а какая преломилась.

Формула 2. Коэффициенты Френеля. ηit - коэффициенты преломления сред. θ - угол между нормалью и падающим лучом.

Обычно точная формула не используется, ввиду её вычислительной сложности. Есть множество её приближений, используемых для графических вычислений в реальном времени.

Таблица 1. Различные формулы для приближенного вычисления коэффициентов Френеля. θ - угол между нормалью и падающим лучом.

Используя эти коэффициенты можно вычислить финальный цвет воды в точке поверхности, комбинируя цвета отражения и преломления(или же цвет воды).

    2.4 Модель освещения

Для расчета освещения водной поверхности хорошо подходит модель Блинна-Фонга. При её использовании на поверхности возникают солнечные дорожки, а не овальные блики как при использовании модели Фонга.

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

На практике, часто строят модель освещения исходя из следующих явлений:

  1. В глубоком водоеме цвет водной поверхности меняется от зеленоватого до тёмно синего в соответствии с углом между направлением обзора на точку поверхности и нормалью в ней.
  2. В зависимости от видимой толщи воды можно определять коэффициент для смешивания цвета воды в этой точке и выборки из текстуры преломлений. Для вычисления видимой толщи воды, достаточно буфера глубины, используемого при создании текстуры с преломлениями. Из значения в точке буфера нужно вычесть расстояние до водной поверхности.


    Рисунок 8. Определение видимой толщины воды.

    Такой подход зачастую может оказаться излишним. Редкая игра, в которой художники украшают дно изобилием скважин, рифов и обломков пиратских кораблей. Смешивание цвета из текстуры преломлений и цвета воды с фиксированным коэффициентом или коэффициентом из заранее изготовленной карты прозрачности может дать вполне приемлемый результат. В конце концов прозрачность на основе альфа смешивания тоже может сгодиться, но стоит учесть, что при таком подходе нельзя добавить искажения при преломлении.
     

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

Суммируя все вышеперечисленные допущения и факторы, полную модель освещения можно изобразить в виде схемы:


Рисунок 10. Модель освещения водной поверхности.

  
Рисунок 11. Пример визуализации плоской поверхности воды.
Замечание: Водная поверхность визуализирована с использованием описанных алгоритмов.
Сами презентации разработаны в компании Eligovision, поэтому исходные коды этих приложений,
разумеется, я не имею права предоставлять.

 

Страницы: 1 2 3 4