Построение теней в OpenGL при помощи теневых буферов
Автор: Sark7
Метод теневых буферов — это 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)