Для создания реалистичной воды нам, конечно же, потребуется много текстур и переменных (параметров). Но мы постараемся минимизировать их количество.
Вот такие текстуры нам потребуются:
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 Следующая »
#GLSL, #NextGen, #OpenGL, #вода
12 июня 2009
(Обновление: 22 июня 2009)
Комментарии [480]