Главная -
Статьи -
Проекты -
Ссылки -
Скачать -
Из гельминтов -
Юмор, приколы -
Почитать -
Обо мне -
Мысли -
Гостевая -

Построение теней при помощи теневых карт (shadow maps)

Кроме метода теневых объемов (требующего предварительной обработки геометрии и работы в трехмерном пространстве) есть еще один довольно простой метод для получения теней - использование так называемых теневых карт (shadow maps).

Данный метод работает не в исходном трехмерном пространстве, а в картинной плоскости (и поэтому ему свойственны ошибки дискретизации - aliasing artifacts), но зато он крайне прост, не требует специальной обработки геометрии сцены и может использовать альфа-канал текстуры для создания "дырок".

В основе этого метода лежит крайне простая идея, что освещенные фрагменты - это те фрагменты, которые видны из положения источника света.

Поэтому, если мы расположим камеру в положении источника света и отрендерим сцену при помощи этой камеры, то мы получим все освещенные фрагменты.

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

Рис 1. Рендеринг из положения источника света.

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

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

Тогда для определения видимости источником света данного фрагмента достаточно просто сравнить з начение глубины depthMap(s,t) и r. Если первая величина больше или равна второй, то данный фрагмент проходит тест глубины для источника света и, следовательно, освещен.

При этом текстурные координаты (s,t,r) фактически являются координатами фрагмента в системе координат камеры, использованной для рендеринга из положения источника света (с точностью до отображения куба [-1,1]3 в [0,1]3).

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

Рис 2. Координаты в относительно источника света.

Рассмотрим, что необходимо для практической реализации данного метода.

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

Во-вторых, необходим быстрый способ вычисления depthMap(s,t)<=r (хотя это моежт быть легко реализовано при помощи фрагментной программы).

Первая возможность предоставляется расширением ARB_depth_texture (или SGIX_depth_texture, расширением которого оно является).

Данное расширение вводит новый формат текстуры (для использования в командах glGetTexImage, glTexImage1D, glTexImage2D, glTexSubImage1D и glTexSubImage2D) - GL_DEPTH_COMPONENT.

Также данное расширение вводит несколько внутренних форматов для функций glTexImage1D, glTexImage2D, glCopyTexImage1D и glCopyTexImage2D - GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT16_ARB, GL_DEPTH_COMPONENT24_ARB и GL_DEPTH_COMPONENT32_ARB.

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

Если к такой текстуре происходит обращение, подразумевающее получение цветового значения, то текстура трактуется как текстура типа GL_LUMINANCE.

Вторая возможность предоставляется расширением ARB_shadow (или SGIX_shadow).

Данное расширение вводит новые значения для переметра pname функций glTexParamerteri, glTexParameterf, glTexParameteriv и glTexParameterfv - GL_TEXTURE_COMPARE_MODE_ARB и GL_TEXTURE_COMPARE_FUNC_ARB.

Также вводится константа GL_COMPARE_R_TO_TEXTURE_ARB, которое может быть использовано в качестве параметра param у функций glTexParamerteri и glTexParameterf при значении pname равном GL_TEXTURE_COMPARE_MODE_ARB.

Команда

    glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB,
                      GL_COMPARE_R_TO_TEXTURE_ARB );

вводит фактически новый режим текстурирования - в качестве результата обращения к текстуре будет возвращаться результат сравнения r-компоненты текстурных координат со значением текстуры по индексу (s,t).

При выполнении неравенства возвращается единица, в противном случае - ноль.

Возможно использование всего двух возможных функций сравнения - GL_LEQUAL и GL_GEQUAL, устанавливаемые при помощи следующей команды:

    glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, func );

Вернуться к нормальному реэиму текстурирования можно при помощи следующей команды:

    glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );

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

В результате мы приходим к следующему способу получения построения теней:

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

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

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

Также для теневой карты задается режим текстурирования, возвращающий нулевое значение, если r>shadowMap(s,t), и единицу в противном случае.

Удобно результат текстурирования для теневой карты использовать для модулирования значения освещенности в точке, что обеспечивает корректное вычисление освещенности с учетом теней.

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

Первый шаг метода может быть легко представлен в виде следующего фрагмента кода:

void renderToShadowMap ()
{
    glColorMask     ( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
    glDisable       ( GL_TEXTURE_2D );

    glEnable        ( GL_POLYGON_OFFSET_FILL );
    glPolygonOffset ( 4, 4 );

                                                    // setup projection
    glViewport      ( 0, 0, shadowMapSize, shadowMapSize );
    glClear         ( GL_DEPTH_BUFFER_BIT );

    glMatrixMode    ( GL_PROJECTION );
    glLoadIdentity  ();

    gluPerspective  ( 120, 1, 0.1, 60 );
    gluLookAt       ( light.x, light.y, light.z,     // eye
                      center.x, center.x, center.z,  // center
                      0, 0, 1 );                     // up

    glMatrixMode    ( GL_MODELVIEW );
    glLoadIdentity  ();

                                                    // get modelview and 
                                                    // projections matrices
    glGetFloatv     ( GL_MODELVIEW_MATRIX,  mv );
    glGetFloatv     ( GL_PROJECTION_MATRIX, pr );

                                                    // now render scene from 
                                                    // light position
    renderScene ();

                                                    // copy depth map into texture
    glBindTexture    ( GL_TEXTURE_2D, shadowMap );
    glCopyTexImage2D ( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 0, 0, 
                       shadowMapSize, shadowMapSize, 0 );

                                                    // restore state
    glDisable        ( GL_POLYGON_OFFSET_FILL );
    glColorMask      ( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
    glEnable         ( GL_TEXTURE_2D );
}

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

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

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

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

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

                                       // set correct texcoord transform
    glMatrixMode  ( GL_TEXTURE );
    glPushMatrix  ();

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

Вместо явного задания текстурных координат как пространственных координат вершин, можно воспользоваться режимом автоматического вычисления текстурных координат GL_EYE_PLANE по пространственным координатам:

                                 // put shadow map in texture unit 1
    glActiveTextureARB ( GL_TEXTURE1_ARB );
    glBindTexture      ( GL_TEXTURE_2D, shadowMap );

    glEnable   ( GL_TEXTURE_2D    );
    glEnable   ( GL_TEXTURE_GEN_S );
    glEnable   ( GL_TEXTURE_GEN_T );
    glEnable   ( GL_TEXTURE_GEN_R );
    glEnable   ( GL_TEXTURE_GEN_Q );

    glTexGenfv ( GL_S, GL_EYE_PLANE, Vector4D ( 1, 0, 0, 0 ) );
    glTexGenfv ( GL_T, GL_EYE_PLANE, Vector4D ( 0, 1, 0, 0 ) );
    glTexGenfv ( GL_R, GL_EYE_PLANE, Vector4D ( 0, 0, 1, 0 ) );
    glTexGenfv ( GL_Q, GL_EYE_PLANE, Vector4D ( 0, 0, 0, 1 ) );

    glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

Задание карты теней и соответствующего режима текстурирования для нее осуществляется следующим фрагментом кода, выполняемом всего один раз при запуске программы (данный фргамент создает теневую текстуру и устанавливает ее параметры):

                                    // create depth texture
    glGenTextures   ( 1, &shadowMap );
    glBindTexture   ( GL_TEXTURE_2D, shadowMap );
    glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
    glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
    glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexParameteri ( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );

    glTexParameteri  ( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_COMPARE_R_TO_TEXTURE_ARB );
    glTexParameteri  ( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_ARB, GL_LEQUAL );

    glTexGeni        ( GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR );
    glTexGeni        ( GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR );
    glTexGeni        ( GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR );
    glTexGeni        ( GL_Q, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR );

    glTexImage2D     ( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, shadowMapSize, shadowMapSize, 0,
                       GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL );

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

void display ()
{
    renderToShadowMap ();                 // compute shadow map

                                          // clear buffers
    glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    reshape ( width, height );            // setup modelview and projection

    glMatrixMode   ( GL_MODELVIEW );
    glPushMatrix   ();

    glRotatef    ( rot.x, 1, 0, 0 );
    glRotatef    ( rot.y, 0, 1, 0 );
    glRotatef    ( rot.z, 0, 0, 1 );

                                          // setup shadowing
    glActiveTextureARB ( GL_TEXTURE1_ARB );
    glBindTexture      ( GL_TEXTURE_2D, shadowMap );

    glEnable ( GL_TEXTURE_2D    );
    glEnable ( GL_TEXTURE_GEN_S );
    glEnable ( GL_TEXTURE_GEN_T );
    glEnable ( GL_TEXTURE_GEN_R );
    glEnable ( GL_TEXTURE_GEN_Q );

    glTexGenfv ( GL_S, GL_EYE_PLANE, Vector4D ( 1, 0, 0, 0 ) );
    glTexGenfv ( GL_T, GL_EYE_PLANE, Vector4D ( 0, 1, 0, 0 ) );
    glTexGenfv ( GL_R, GL_EYE_PLANE, Vector4D ( 0, 0, 1, 0 ) );
    glTexGenfv ( GL_Q, GL_EYE_PLANE, Vector4D ( 0, 0, 0, 1 ) );

    glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

                                          // set correct texcoord transform
    glMatrixMode  ( GL_TEXTURE );
    glPushMatrix  ();

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

    glActiveTextureARB ( GL_TEXTURE0_ARB );

    renderScene ();

    glActiveTextureARB ( GL_TEXTURE1_ARB );
    glDisable          ( GL_TEXTURE_2D   );
    glActiveTextureARB ( GL_TEXTURE0_ARB );

                                          // draw the light
    glMatrixMode ( GL_MODELVIEW );
    glPopMatrix  ();
    glPushMatrix ();

    glTranslatef       ( light.x, light.y, light.z );
    glActiveTextureARB ( GL_TEXTURE0_ARB );
    glDisable          ( GL_TEXTURE_2D );
    glutSolidSphere    ( 0.1f, 15, 15 );
    glPopMatrix        ();

    glMatrixMode ( GL_TEXTURE );
    glPopMatrix  ();

    glMatrixMode ( GL_MODELVIEW );

    glutSwapBuffers ();
}

Полный исходный код для этой статья на языке С++ (для платформ M$ Windows и Linux) Вы можете скачать по ссылке в конце статьи.

Рис 3. Изображение, полученное при помощи данного метода.

Рассмотрим теперь основные плюсы и минусы данного метода.

К явным плюсам метода отноится его простота и отсутствие специальных преобразований данных (в отличии от метода тенеывых объемов).

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

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

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

Существует модификация данного метода, так называемые перспективные теневые карты (Perspective Shadow Maps, PSM), которая успешно справляется с оснонвыми недостатками, правда ценой некоторого усложнения метода.

Весь исходный текст к данной статье можно скачать здесь.


Copyright © 2005 Алексей В. Боресков

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