Depth Peel или разложение сцены по слоям

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

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

depth peeling - layers

Рис 1. Разложение изображения на слои .

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

Примерами, где возникает подобная необходимость, являются вывод полупрозрачных объектов (далеко не всегда можно правильно упорядочить выводимые грани) и CGS (Constructive Solid Geometry) - построение сложных объектов путем применения операций объединения, пересечения и вычитания к более простым объектам.

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

Рассмотрим как в этот подход работает.

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

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

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

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

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

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

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

shadow map

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

Операция чтения из теневой карты фактически осуществляет сравнение глубин передаваемой точки и значения глубины из карты.

Таким образом мы приходим к следующему алгоритму:

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

После выполнения этих четырех шагов мы получаем изображение второго слоя. Путем повторения шагов 2-4 мы можем получить любой требуемый слой.

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

//
// Vertex shader for depth peeling
//

varying vec4 pos;
varying vec3 pt;

void	main ()
{
    gl_Position     = ftransform ();
    gl_TexCoord [0] = gl_MultiTexCoord0;
    pos             = gl_Position;
    pt              = (pos.xyz / pos.w + vec3 ( 1.0 )) * 0.5;
}

//
// Fragment shader for depth peeling
//

varying vec4 pos;
varying vec3 pt;

uniform sampler2D       decalMap;
uniform sampler2DShadow depthMap;

void main (void)
{
    const vec3 eps = vec3 ( 0, 0, -0.001 );
	
                                            // get decal color
    vec4 decalColor = texture2D ( decalMap, gl_TexCoord [0].xy );
    vec4 depth      = shadow2D  ( depthMap, pt + eps );

    if ( depth.x > 0.5 )                    // reject previously visible fragment
        discard;

    gl_FragColor = decalColor;
}

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

Ниже приводится фрагмент кода на С++ для вывода второго слоя - обратите внимание, здесь в зависимости от значения булевской переменной drawDepth выводится либо соответствующий слой, либо используемая теневая карта.

void	renderLayer1 ()
{
    renderScene ();	
                                        // copy depth map into texture
    glActiveTextureARB ( GL_TEXTURE0_ARB );
    glBindTexture      ( GL_TEXTURE_2D, 0 );

    glActiveTextureARB ( GL_TEXTURE1_ARB );
    glBindTexture      ( GL_TEXTURE_2D, shadowMap );
    glCopyTexImage2D   ( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 0, 0, 
                         shadowMapSize, shadowMapSize, 0 );
    glBindTexture      ( GL_TEXTURE_2D, 0 );

    if ( drawDepth )
    {
        glBindTexture      ( GL_TEXTURE_2D,  0 );
	    glActiveTextureARB ( GL_TEXTURE0_ARB );
        glBindTexture      ( GL_TEXTURE_2D, shadowMap );
        glEnable           ( GL_TEXTURE_2D );
        glTexParameteri    ( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, GL_NONE );

        glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
        startOrtho ();

        glBegin ( GL_QUADS );

            glTexCoord2f ( 0, 0 );
            glVertex2f   ( 0, 0 );

            glTexCoord2f ( 1,   0 );
            glVertex2f   ( 511, 0 );

            glTexCoord2f ( 1, 1 );
            glVertex2f   ( 511, 511 );

            glTexCoord2f ( 0, 1 );
            glVertex2f   ( 0, 511 );
	    glEnd   ();

        glBindTexture ( GL_TEXTURE_2D, 0 );
        endOrtho ();
    }
    else
    {
        glActiveTextureARB ( GL_TEXTURE0_ARB );
        glBindTexture      ( GL_TEXTURE_2D, diffuseMap );

        glActiveTextureARB ( GL_TEXTURE1_ARB );
        glBindTexture      ( GL_TEXTURE_2D, shadowMap );
        glTexParameteri    ( GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_ARB, 
                             GL_COMPARE_R_TO_TEXTURE_ARB );

        program.bind ();

        renderScene ();        // render scene culling first layer with old depth buffer

        program.unbind ();

        glActiveTextureARB ( GL_TEXTURE0_ARB );
        glBindTexture      ( GL_TEXTURE_2D, 0 );
        glActiveTextureARB ( GL_TEXTURE1_ARB );
        glBindTexture      ( GL_TEXTURE_2D, 0 );
    }
}

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

При написании данной стать использовались материалы с сайта компании NVIDIA и статья Касса Эверитта "Interactive Order-Independent Transparency".

Valid HTML 4.01 Transitional

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