ПрограммированиеСтатьиГрафика

Построение теней в OpenGL при помощи теневых буферов

Автор:

Метод теневых буферов — это image-based метод получения реалистичных теней. Для него нужны расширения ARB_shadow (или SGIX_shadow), ARB_depth_texture (или SGIX_depth_texture) и, опционально, ARB_shadow_ambient.

Достоинства и недостатки:

Достоинства:
- самозатенение
- простота (не нуждается в дополнительной обработке геометрии, как shadow volumes)
- аппаратная поддержка
- так как метод image-based, карты теней не зависят от исходной геометрии (можно использовать alpha-culling, N-Patch'и (TruForm) и т.д.

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

Как это работает

Алгоритм:
1. Рендерим сцену в пространстве источника света в текстуру специального формата (depth map);
2. Устанавливаем генерацию текстурных координат и параметры фильтрации;
3. Рендерим сцену в пространстве камеры;

Реализация:

Сначала зададим некоторые константы и функции:

var
  DepthTex: GLuint; 

const
  DepthTexSize = 512; // размер shadow-map текстуры
  WinXSize = 640; WinYSize = 480; // размер окна

procedure SetLightProjection; // проекционная матрица источника света
begin
  gluPerspective(90, DepthTexSize/DepthTexSize, 1, 500);
end;

procedure SetLightModelview; // модельная матрица источника света
begin
  glTranslate(0, 0, -10);
end;

procedure SetCameraProjection; // проекционная матрица камеры
begin
  gluPerspective(90, WinXSize/WinYSize, 1, 500);
end;

procedure SetCameraModelview; // модельная матрица камеры
begin
  glTranslatef(0, -10, -10);
  glRotatef(60, 1, 0, 0);
end;

procedure DoRenderScene; // фактический рендеринг геометрии
begin
  // к примеру, glutTeapot(2);
end;

Теперь нужно создать текстуру, в которой будет содержаться информация о глубине сцены (depth map). Для этого в OpenGL существует расширение ARB_depth_texture. В нем вводятся новые внутренние форматы текстуры GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT24_ARB,
GL_DEPTH_COMPONENT32_ARB.

glGenTextures(1, @DepthTex);
glCopyTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 0, 0,
                  DepthTexSize, DepthTexSize, 0);

Ставим фильтрацию и клампинг:

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_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

Если depth-текстуру мы будем использовать как обычную, пусть данные трактуются как яркость:

glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE_ARB, GL_LUMINANCE);

Устанавливаем режим текстуры для сравнения по Z. Z из текстуры сравнивается
с текстурной координатой R (Z фрагмента в базисе источника света) - для этого нужно наличие расширения GL_ARB_shadow. Если указанное сравнение (GL_LEQUAL в нашем случае) не проходит, результат будет GL_TEXTURE_COMPARE_FAIL_VALUE, иначе 1:

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);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FAIL_VALUE_ARB, 0.5);

Если расширение GL_ARB_texture_ambient не поддерживается, то, когда сравнение не проходит, результатом будет 0.

Затем устанавливаем генерацию текстурных координат:

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

Устанавливаем z-bias:

glPolygonOffset(4, 4);

Зачем нужен z-bias? Так как точность z-буфера не бесконечна, могут возникать ошибки сравнения, из-за этого будут затеняться точки, которые быть затененными не должны. Смещением глубины полигонов в первом проходе мы как бы сдвигаем точки ближе к наблюдателю, чтобы избежать артефактов z-fighting'а.

Все, переходим к рендерингу сцены:

За первый проход мы рендерим сцену с позиции источника света, и заносим depth map в текстуру. При рендеринге запрещаем запись цвета, наложение текстур, так как они нам не нужны:

procedure Pass1;
begin
  glViewport(DepthTexSize, DepthTexSize);

  // устанавливаем матрицы

  glMatrixMode(GL_PROJECTION);
  LoadIdentity;
  SetLightProjection;
  glMatrixMode(GL_MODELVIEW);
  LoadIdentity;
  SetLightModelview;

  // подготовка и рендеринг сцены

  glDisable(GL_TEXTURE_2D);
  glColorMask(GL_FALSE, GL_FALSE, GL_FALSE); // запрещаем запись цвета
  glEnable(GL_POLYGON_OFFSET_FILL); // разрешаем z-bias
  DoRenderScene;
  glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, DepthTexSize, DepthTexSize);
  glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); // разрешаем запись цвета
  glDisable(GL_POLYGON_OFFSET_FILL);
end;

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

procedure Pass2;
begin
  // устанавливаем матрицы
  glViewport(WinXSize, WinYSize);
  glMatrixMode(GL_PROJECTION);
  LoadIdentity;
  SetCameraProjection;
  glMatrixMode(GL_MODELVIEW);
  LoadIdentity;
  SetCameraModelview;

  // подготовка и рендеринг сцены

  glEnable(GL_TEXTURE_2D);
  // Устанавливаем плоскости для генерации текстурных координат
  glTexGenfv(GL_S, GL_EYE_PLANE, @PS);
  glTexGenfv(GL_T, GL_EYE_PLANE, @PT);
  glTexGenfv(GL_R, GL_EYE_PLANE, @PR);
  glTexGenfv(GL_Q, GL_EYE_PLANE, @PQ);

  // Устанавливаем текстурную матрицу
  glMatrixMode(GL_TEXTURE);
    LoadIdentity;
    // Ставим texture bias, т.е. [-1, 1] -> [0, 1]
    glTranslatef(0.5, 0.5, 0.5);
    glScalef(0.5, 0.5, 0.5);
    // Положение и проекция источника света
    SetLightProjection;
    SetLightModelview;
  glMatrixMode(GL_MODELVIEW);

  // Рендерим геометрию
  DoRenderScene;
end;

И последнее, at last but not at least :)

procedure RenderScene;
begin
  Pass1;
  Pass2;
end;

P.S. Существует еще один image-based способ получения теней, так называемый projective shadow mapping. Суть его заключается в том, что в первом проходе мы рендерим в текстуру некий объект (occluder), от которого будем отбрасывать тень, черным цветом на белом фоне, а во втором проходе просто проективно накладываем полученную текстуру на сцену. Понятно, что в таком случае мы
не получим самозатенения, и будем ограничены только одним объектом, отбрасывающим тень.

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

Изображение

Демку и исходный код на Delphi можно скачать отсюда: 20031019.zip (115 kB).

Статьи по теме:
[1] Cass Everitt, Ashu Rage, Cem Cebenoyan. Hardware Shadow Mapping. Whitepaper:
(см. на http://developer.nvidia.com)
[2] Cass Everitt, Projective Texture Mapping. Whitepaper:
(см. на http://developer.nvidia.com)
[3] Mark Kilgard. Shadow Mapping with Today's Hardware. Technical presentation:
(см. на http://developer.nvidia.com)
[4] OpenGL Extension Registry.
http://oss.sgi.com/projects/ogl-sample/registry/

#буферы, #Delphi, #OpenGL, #тени

19 октября 2003 (Обновление: 15 июня 2009)

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