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

Реализация карт теней с использованием GLSL шейдеров

Автор:

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

Многие статьи про карты теней на OpenGL уже устарели, и интерес представляют только для любителей программировать на «антиквариате». Данная статья попытается восполнить этот небольшой пробел.

Начало
Инициализация
  Инициализация для варианта #1
  Инициализация для варианта #2
Рендер в текстуру
  Шейдер записи глубины в текстуру
Рендер сцены с тенями
  Матрица источника света
  Шейдер сцены с тенью
  Режим сравнения текстуры
Заключение

Начало

Принцип построения теней достаточно прост. Всё, что видно с позиции источника света, то освещено, остальное же находится в тени.

Принцип работы можно разделить на два этапа:
1. Сохранение в текстуру глубины сцены с позиции источника света. Такую текстуру называют shadow map.
2. Рендер сцены с тенями, с использованием данных, полученных на первом этапе.

В примере статьи эти два этапа вынесены в отдельные функции RenderToShadowMap() и RenderShadowedScene().

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

Инициализация

Инициализация шейдеров и модели тривиальна, вы можете в них разобраться самостоятельно, поэтому описывать её не буду. А вот инициализацию текстур стоит описать подробнее.

Начнём с рассмотрения обоих вариантов рендеринга теней:

Вариант #1: Прикрепить текстуру к FBO как буфер цвета. Для теста глубины используем рендер-буфер.
В этом случае нам придётся производить запись глубины с помощью шейдера. Плюс заключается в том, что мы можем записывать глубину так, как нам захочется. Можем сохранять что-то ещё, помимо глубины, в остальные компоненты текстуры, если она у нас не однокомпонентная.

Вариант #2: Прикрепить текстуру к FBO как буфер глубины. Буфер цвета не используется.
Здесь нам уже не нужен шейдер, чтобы сохранить глубину. Просто рендерим сцену.

Оба варианта имеют как свои плюсы, так и минусы.

Инициализация для варианта #1

Начнём с инициализации для варианта #1. Создадим текстуру, которая будет прикреплена как буфер цвета:

    glGenTextures(1, &texDepth);
    glBindTexture(GL_TEXTURE_2D, texDepth);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    if (glewIsSupported("GL_ARB_texture_rg"))
    {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_R16, texDepthSizeX, texDepthSizeY, 
                      0, GL_RED, GL_UNSIGNED_SHORT, 0);
    }
    else
    {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16, texDepthSizeX, texDepthSizeY,
                      0, GL_RGBA, GL_UNSIGNED_SHORT, 0);
    }
    glBindTexture(GL_TEXTURE_2D, 0);

Я думаю, некоторых пояснений требует используемый формат текстуры.

Начнём с битности. Чем ниже битность, тем ниже точность. Поэтому, при малых битах, больше внимания стоит уделить плоскостям отсечения, нужно ближнюю и дальнюю плоскости расположить как можно ближе к сцене, особенно ближнюю. Иначе могут появится неприятные артефакты из-за нехватки точности. В данном примере я буду использовать 16 бит. Хотя успехов можно добиться и с 8 битной текстурой.

Для хранения глубины нам нужна однокомпонентная текстура. Такие форматы текстур нам даёт поддержка расширения GL_ARB_texture_rg. Если поддержка есть, то используем GL_R16. Можно так же использовать аналог с плавающей запятой GL_R16F. Если же поддержки такого расширения нет, то будем использовать GL_RGBA16. Некоторые видеокарты старого поколения, например, такие как GeForce 6, не поддерживают 16 битных целочисленных текстур, поэтому драйвер будет использовать 8 битные, что может привести к артефактам из-за нехватки точности. Вы можете поэкспериментировать с разными форматами, и выбрать наиболее подходящий.

Предпоследний аргумент type, у функции glTexImage2D(), не имеет в данном случае никакого значения, и типы расставлены для красоты.

Теперь создадим рендер-буфер, он нам нужен для теста глубины:

  glGenRenderbuffersEXT(1, &rbDepth);
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rbDepth);
  glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, texDepthSizeX, texDepthSizeY);
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);

И последнее, создаём фрейм-буфер:

  glGenFramebuffersEXT(1, &fbDepth);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbDepth);
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texDepth, 0);
  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, rbDepth);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

Как видно в качестве буфера цвета мы прикрепляем текстуру, а в качестве буфера глубины прикрепляем рендер-буфер.

Инициализация для варианта #2

Как и в варианте #1 мы создаём текстуру, но её формат будет иным:

  glGenTextures(1, &texDepth2);
  glBindTexture(GL_TEXTURE_2D, texDepth2);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, texDepthSizeX, texDepthSizeY,
                 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
  glBindTexture(GL_TEXTURE_2D, 0);

Используется специальный формат текстуры для хранения глубины.

Создаём фрейм-буфер:

  glGenFramebuffersEXT(1, &fbDepth2);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbDepth2);
  glDrawBuffer(GL_NONE);
  glReadBuffer(GL_NONE);
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, texDepth2, 0);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);

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

  glDrawBuffer(GL_NONE);
  glReadBuffer(GL_NONE);
Страницы: 1 2 3 4 Следующая »

#GLSL, #OpenGL, #shader, #тени

1 февраля 2009 (Обновление: 14 сен 2009)

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