Программирование игр, создание игрового движка, OpenGL, DirectX, физика, форум
GameDev.ru / Программирование / Статьи / Создание реалистичной поверхности воды с использованием GLSL

Создание реалистичной поверхности воды с использованием GLSL

Автор:

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

Сейчас мы вместе будем делать красивую, а главное реалистичную воду. Небольшое ограничение, которое я накладываю на воду это то, что вода находится  в плоскости X0Z, то еесть имеет нулевую Y координату и нигде не изгибается. Это ограничение больше связано с отсутствием необходимости изменять уровень воды, а не с техническими проблемами.

Итак, наша задача – добавить в сцену поверхность воды. Вот сценка без неё:

Сцена без воды | Создание реалистичной поверхности воды с использованием GLSL

Мы же попытаемся изобразить нечто такое:

Сцена с водой на GLSL | Создание реалистичной поверхности воды с использованием GLSL

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

Вот такие текстуры нам потребуются:
1) карта предрассчитанных нормалей
2) текстура, содержащая отражения
3) текстура, содержащая всю сцену (либо только то, что находиться под водой) – текстура преломления
4) текстура, содержащая глубину сцены (либо только того, что находиться под водой)
5) текстура, содержащая глубину сцены, отрисованной из позиции источника света

Параметры, которые нам потребуются:
1) время (для анимации волн)
2) значение непрозрачности (density) воды
3) цвет воды
4) положение источника света
5) положение наблюдателя
6) матрица для теней (используется технология shadow maps)
7) цвет отраженного света («specular» источника света)

Где взять эти текстуры?
Я пишу эту статью, полагая, что читатель немного знаком с трехмерной графикой, знает что такое «проективное наложение текстуры» и как работать с рендером в текстуру (для этого можно использовать любую технологию – от glCopyTexImage2D до Framebuffer Object).

Итак, текстуры:
1) карты нормалей: можно скачать здесь: http://ricks.pisem.su/files/waternormals.zip, выковырять из какой-нибудь игрушки или сгенерировать
2) текстура с отражениями: необходимо отрисовать в текстуру перевернутую сцену. Это можно сделать примерно таким образом:

  gluLookAt( view_pos.x, view_pos.y, view_pos.z, view_center.x, 
             view_center.y, view_center.z, 0.0, 1.0, 0.0);
  glScalef(1.0, -1.0, 1.0);

Только необходимо учесть такой момент: рисовать то, что находится под водой нам не нужно. Для этого включим плоскость отсечения:

const
  REFLECTION_CLIPPLANE : array [0..3] of double = (0.0, 1.0, 0.0, 0.1); 
  // последним параметром можно поиграться,
  // чтобы получить наилучший результат
  // ...
  glClipPlane(GL_CLIP_PLANE0, @REFLECTION_CLIPPLANE);
  glEnable(GL_CLIP_PLANE0);
  RenderScene;
  glDisable(GL_CLIP_PLANE0);

3) текстура преломлений: можно еще раз отрисовать всю сцену в отдельную текстуру, а можно сэкономить и скопировать в текстуру содержимое текущего буфера.
4) глубина сцены: можно использовать текущую глубину сцены (если используется Framebuffer object) либо так же скопировать глубину текущего буфера
5) если у вас в сцене уже есть тени, с использованием Shadow maps – это очень хорошо – вам не придется еще раз отрисовавать сцену с позиции источника света :)

Примерная последовательность отрисовки:
1) рисуем сцену в текстуру с позиции источника света
2) рисуем перевернутую сцену в текстуру отражений
3) рисуем сцену в текущий буфер
4) копируем содержимое буфера в текстуру преломлений
5) отключаем запись глубины (чтобы не было конфликтов с чтением/записью глубины)
6) рисуем воду

Итак, мы начинаем!

Главное в отрисовке воды – это шейдер. Для начала сделаем заготовку для вершинного и фрагментного шейдеров:

Вершинный:

uniform mat4 shadow_matrix;

varying vec4 vertex;
varying vec4 shadow_proj_coords;
varying vec4 proj_coords;

void main()
{ 
 vertex = gl_Vertex;
 shadow_proj_coords = shadow_matrix * vertex;

 gl_TexCoord[0] = gl_MultiTexCoord0;
 gl_Position = gl_ModelViewProjectionMatrix * vertex;
 proj_coords = gl_Position;
}

Фрагментный:

uniform sampler2D normal_texture; // карта нормалей
uniform sampler2D refract_texture; // карта преломлений (сцены под водой)
uniform sampler2D reflect_texture; // карта отражений (сцены над водой)
uniform sampler2D depth_texture; // карта глубины сцены
uniform sampler2DShadow shadow_texture; // карта глубины сцены с позиции источника света

uniform vec4 time_density_clipplane;
uniform vec3 light_source;
uniform vec3 view_position;
uniform vec4 water_color;
uniform vec3 specular_color;

varying vec4 vertex;
varying vec4 shadow_proj_coords;
varying vec4 proj_coords;

const float fNormalScale = 6.0; 

float SampleShadow(vec3 shadow_tc)
{
  return shadow2D(shadow_texture, shadow_tc).x;
}

float calculate_linear_depth(float value)
{
  return time_density_clipplane.w * time_density_clipplane.z / 
    ( time_density_clipplane.w - value * 
    (time_density_clipplane.w - time_density_clipplane.z) );
}

void main()
{
  gl_FragColor = vec4(1.0);
}

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

Теперь подробнее рассмотрим фрагментный шейдер. Как видно в него должны передаваться пять перечисленных ранее текстур, и помимо этого еще ряд параметров.
vec4 time_density_clipplane содержит в себе время, степень непрозрачности воды и значения для ближней и дальней плоскостей отсечения. Они нужны нам для расчета линейной глубины сцены. Для этого служит функция calculate_linear_depth.
vec3 light_source и view_position – это соответственно положение источника света и наблюдателя в мировой системе координат.
vec4 water_color – содержит в первых трех компонентах непосредственно цвет воды, а в 4-ой компоненте масштабный коэффициент для HDR картинки. Его можно получить как сумму яркостей diffuse и ambient составляющих источника света.
vec3 specular_color – значение отраженного света источника.
Функция SampleShadow – вычисляет затененность текущей точки. Думаю с этим тоже понятно.

Страницы: 1 2 Следующая »

12 июня 2009

#GLSL, #NextGen, #OpenGL, #вода


Обновление: 22 июня 2009

2001—2018 © GameDev.ru — Разработка игр