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

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

Рис 1. Отражение с использованием отраженной камеры.

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

Рис 2. Рябь на воде, отражающей сцену.

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

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

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

void reshape ( int w, int h )
{
   glViewport     ( 0, 0, (GLsizei)w, (GLsizei)h );
   glMatrixMode   ( GL_PROJECTION );
   glLoadIdentity ();
   gluPerspective ( 60.0, (GLfloat)w/(GLfloat)h, 1.0, 60.0 );
   glMatrixMode   ( GL_MODELVIEW );
   glLoadIdentity ();
   gluLookAt      ( eye.x, eye.y, eye.z,    // eye
                    3, 3, 1,                // center
                    0, 0, 1 );              // up
}

Тогда для рендеринга отражения следует использовать модифицированный вариант этой функции:

void reshapeMirrored ( int w, int h )
{
   glViewport     ( 0, 0, (GLsizei)w, (GLsizei)h );
   glMatrixMode   ( GL_PROJECTION );
   glLoadIdentity ();
   gluPerspective ( 60.0, (GLfloat)w/(GLfloat)h, 1.0, 60.0 );
   glMatrixMode   ( GL_MODELVIEW );
   glLoadIdentity ();
   glScalef       ( 1, -1, 1 );             // change handedness of the coordinate system
   gluLookAt      ( eye.x, eye.y, -eye.z,   // eye
                    3, 3, -1,               // center
                    0, 0, -1 );             // up
}

В приведенной функции считается, что отражение происходит относительно плоскости z=0. Обратите внимание, что поскольку отражение меняет ориентацию системы координат (левостороннюю переводит в правостороннюю и наоборот), то необходимо не только поменять соответствующие параметры у функции gluLookAt, но поменять ориентацию системы при помощи вызова функции glScale.

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

Рис 3.

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

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

Представим себе, что в положении наблюдателя (и с его ориентацией) расположен проектор, проектирующий текстуру с отражением сцены.

Рис 4.

Подобное проектирование текстуры как бы инвертирует перспективное преобразование и в результате своего применения дает полностью корректное изображение отраженной сцены.

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

Тогда каждой точке на текстуре будет соответствовать правильная точка на грани, за исключением одного момента. Как известно произведение модельной и проектирующей матриц в OpenGL переводит всю сцену в куб [-1,1]3.

Однако текстурные координаты задаются на отрезке [0,1]2. Поэтому необходимо дополнительное преобразование, обеспечивающее отображение отрезка [-1,1] в отрезок [0,1].

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

    glMatrixMode  ( GL_TEXTURE );
    glPushMatrix  ();

    glLoadIdentity ();
    glTranslatef   ( 0.5, 0.5, 0 );     // remap from [-1,1]^2 to [0,1]^2
    glScalef       ( 0.5, 0.5, 1 );
    glMultMatrixf  ( pr );
    glMultMatrixf  ( mv );

В приведенном фрагменте кода считается, что величины mv и pr содержат матрицы модельного преобразования (modelview) и проектирования (projection), использованные при рендеринге отражения в текстуру.

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

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

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

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

Однако обратите внимание, что в случае использования шейдеров необходимо не только явное умножение переданных текстурных координат на матрицу их преобразования, но осуществление либо их деления на w-компоненту, либо обращение к функции доступа к текстуре, которая выполняет это деление сама (при использовании языка GLSL такой функцией является texture2DProj).

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

//
// Ripple reflection fragment shader
//

varying vec4      pos;
uniform float     time;
uniform sampler3D noiseMap;
uniform sampler2D reflectionMap;

void main (void)
{
    vec3 ncoord    = vec3 ( pos.x, pos.y + time, pos.z ) * 1.1;
    vec3 noise     = texture3D ( noiseMap, ncoord ).rgb;
    vec4 tex       = gl_TextureMatrix [0] * pos + vec4 ( noise * 0.05, 0.0 );
    vec3 reflColor = texture2DProj ( reflectionMap, tex ) * 0.8;

    gl_FragColor = vec4 ( reflColor, 1.0 );
}

Изображение, приведенное на рис. 2, было построено именно при помощи этого шейдера.

Несложно изменить данный шейдер для использования альфа-канала дополнительной текстуры для управления формой отражающей поверхности. Также возможно использование альфа-канала для обеспечение полупрозрачности отражающей поверхности (если такой поверхностью является например вода).

По этой ссылке можно скачать весь исходный код к этой статье. Также доступны для скачивания откомпилированные версии для M$ Windows, Linux и Mac OS X.

Valid HTML 4.01 Transitional

Напиши мне
Используются технологии uCoz