четверг, 8 марта 2012 г.

Основы FBO в OpenGL. Часть третья, буфер глубины

Часть первая, немного теории
Часть вторая, простой пример
Часть третья, буфер глубины
Часть четвертая, MRT
Часть пятая, чтение
Часть шестая, R2VB
Ссылки по теме     В предыдущей части статьи мы научились делать простой рендеринг в текстуру, но для реализации большинства эффектов нам еще может потребоваться буфер глубины. В этой части статьи мы с вами разберем как прикрепить буфер глубины к буферу кадра и сумеем отобразить наш буфер глубины в виде текстуры.



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

Доработаем процедуру инициализации, добавив туда создание буфера рендеринга и присоединив к нему буфер глубины:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
procedure InitFBO(Width, Height: integer);
var Status: GLUInt;
begin
  //Для начала создадим текстуру куда все это будет рендериться
  textureId:=CreateTextureRGBA8(Width, Height, true);

  //Создадим RBO
  glGenRenderbuffersEXT(1, @rboId);
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rboId);
  glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, Width, Height);
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);

  //Создадим буфер FBO
  glGenFramebuffersEXT(1, @fboId);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);

  //Буферы созданы, осталось лишь присоеденить текстуру и буфер рендеринга к FBO
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
                            GL_TEXTURE_2D, textureId, 0);
  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
                            GL_RENDERBUFFER_EXT, rboId);

  //Проверим статус, чтоб убедиться что нет никаких ошибок
  Status:=glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
  assert(status=GL_FRAMEBUFFER_COMPLETE_EXT, 'FBOError '+inttostr(Status));
  //Ну и не забудем деактивировать буфер, иначе весь последующий рендеринг
  //будет осуществляться в него
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
end;

Здесь мы (строки 8-11) создали новый буфер рендеринга и при помощи команды glRenderbufferStorageEXT указали что тип хранимых данных будет соответствовать компонентам глубины. После чего, в строке 20, мы прикрепили его к нашему буферу кадра и указали что в этом буфере рендеринга будет сохраняться глубина сцены.

Полный исходный код примера вы найдете в FBO_DB.

В данном примере буфер глубины хоть и присутствовал, но мы его «не видели» и вам пришлось доверять мне наслово, давайте исправим эту ситуацию. Для того чтобы визуализировать логический буфер глубины его нужно присоединить к буферу FBO в качестве текстуры со специальным внутренним форматом GL_DEPTH_COMPONENT, для этого немного доработаем процедуру инициализации FBOInit:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
procedure InitFBO(Width, Height: integer);
var Status: GLUInt;
begin
  textureId:=CreateDepthTexture(Width, Height);
  //Создадим буфер FBO
  glGenFramebuffersEXT(1, @fboId);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
  //Буферы созданы, осталось лишь присоеденить текстуру к FBO
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, textureId, 0);
  //запретить запись/чтение в этот буфер
  glDrawBuffer(GL_NONE);  glReadBuffer(GL_NONE);
  //Проверим статус, чтоб убедиться что нет никаких ошибок
  Status:=glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
  assert(status=GL_FRAMEBUFFER_COMPLETE_EXT, 'FBOError '+inttostr(Status));
  //Ну и не забудем деактивировать буфер, иначе весь последующий рендеринг
  //будет осуществляться в него
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
  fboReady:=true;
end;

Во-первых мы полностью удалили создание буфера рендеринга, так как его наличие для данной задачи не обязательно, во-вторых - в 9 строке, вместо записи буфера цвета в текстуру мы сделали запись буфера глубины, просто заменив тип буфера на
GL_DEPTH_ATTACHMENT_EXT. Ну и естественно мы создали новую текстуру с форматом пикселя GL_DEPTH_COMPONENT, в которую можно было бы поместить наш буфер глубины. Для этого, в строке 4, мы воспользовались функцией CreateDepthTexture.
В этом примере мы не используем буфер цвета, потому, чтоб не было ошибок, мы должны запретить чтение/запись в буфер цвета, это мы и делаем в строке 11.

Результат работы этой программы вы можете увидеть на Рис.2.
Рис.2. Запись буфера глубины в текстуру.
Здесь белый цвет соответствует пустоте (бесконечности), чем пиксель темнее, тем он ближе к камере.
Исходный код примера вы найдете в FBO_DT.

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

Для этого, в первую очередь объявим еще одну глобальную переменную, в которой будет храниться идентификатор нашего буфера цвета, назовем его ColorTexId, теперь немного изменим InitFBO, добавив туда создание еще одной целочисленной текстуры (строка 4) и присоединение ее в качестве буфера цвета (строка 11):
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
procedure InitFBO(Width, Height: integer);
var Status: GLUInt;
begin 
  ColorTexId:=CreateTextureRGBA8(Width, Height, true);
  textureId:=CreateDepthTexture(Width, Height);
  // Создадим буфер FBO
  glGenFramebuffersEXT(1, @fboId);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
  //Буферы созданы, осталось лишь присоединить текстуру к FBO
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, textureId, 0);
  glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, ColorTexId, 0);
  //Проверим статус, чтоб убедиться что нет никаких ошибок
  Status:=glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
  assert(status=GL_FRAMEBUFFER_COMPLETE_EXT, 'FBOError '+inttostr(Status));
  //Ну и не забудем деактивировать буфер, иначе весь последующий рендеринг
  //будет осуществляться в него
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
end;

Так как теперь у нас имеется буфер цвета, то мы удалили строки запрещающие рисование в буфер цвета. Теперь внесем небольшие доработки в процедуру FBORender:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
procedure FBORender;
var viewport:array[0..3] of integer;
begin
  glGetIntegerv(GL_VIEWPORT, @viewport);
  glViewport(0,0,texWidth,texHeight);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId); //Активируем FBO
  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);//Очищаем буферы цвета и глубины
  DrawCube; //Рендерим объект/сцену в текстуру
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);//Деактивируем FBO
  glViewport(viewport[0],viewport[1],viewport[2],viewport[3]);
  //Сгенерируем для полученных текстур мип-уровни
  glBindTexture(GL_TEXTURE_2D, textureId);
  glGenerateMipmapEXT(GL_TEXTURE_2D);
  glBindTexture(GL_TEXTURE_2D, ColorTexId);
  glGenerateMipmapEXT(GL_TEXTURE_2D);
  //Нарисуем два квада с разными текстурами
  drawScreenQuad(1,2,[textureId, ColorTexId]);
  glBindTexture(GL_TEXTURE_2D, 0);//Деактивируем текстуру
end;

В этом примере я так же включил генерацию мип-уровней для новой текстуры, делается это в строках 14-15. Так же для рисования скринквада (строка 17) мы воспользовались перегружаемой функцией drawScreenQuad, в качестве параметров которой выступает количество строк, количество столбцов и список текстурных юнитов для каждого из квадов. В результате у вас должно получиться примерно то же что и Рис.3:

Рис.3. Одновременный вывод в текстуры буферов глубины и цвета


Полный код примера смотрите в FBO_DC.

Так же стоит сделать небольшое замечание - в случае если вы захотите одновременно использовать буфер глубины и буфер трафарета, то вместо создания двух буферов рендеринга, можно создать один со специальным типом: GL_DEPTH24_STENCIL8_EXT.
Тогда процедура инициализации сведется к такому виду:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
Procedure InitFBO(Width, Height: integer);
var Status: GLUInt;
Begin 
...
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
  glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rboId);
  glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8_EXT, Width, Height);
  glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, 0 );
  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,GL_DEPTH_ATTACHMENT_EXT,GL_RENDERBUFFER_EXT, rboId);
  glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT,GL_STENCIL_ATTACHMENT_EXT,GL_RENDERBUFFER_EXT, rboId);
  glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
...
end;

Обратите внимание на строки 9-10, мы перенаправляем запись буфера глубины и буфера трафарета в один рендер буфер (rboId). Более подробно этот случай мы разберем в следующих частях.

2 комментария:

  1. Здравствуйте, Ваши статьи очень полезны для начинающих, учился основам VBO. Спасибо.
    Возник вопрос по 3-му примеру, где создается текстура цвета и глубины. В инициализации Вы создаете 2-е текстуры потом создаете RBO объект, который никуда не подключаете, это опечатка или я чегото не понял?
    Код:
    procedure InitFBO(Width, Height: integer);
    var Status: GLUInt;
    begin
    ColorTexId:=CreateTextureRGBA8(Width, Height, true);
    textureId:=CreateDepthTexture(Width, Height);
    //Создадим RBO
    glGenRenderbuffersEXT(1, @rboId);
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, rboId);
    glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT, Width, Height);
    glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0);
    // Создадим буфер FBO
    glGenFramebuffersEXT(1, @fboId);
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId);
    //Буферы созданы, осталось лишь присоединить текстуру к FBO
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, textureId, 0);
    glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, ColorTexId, 0);
    //Проверим статус, чтоб убедиться что нет никаких ошибок
    Status:=glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
    assert(status=GL_FRAMEBUFFER_COMPLETE_EXT, 'FBOError '+inttostr(Status));
    //Ну и не забудем деактивировать буфер, иначе весь последующий рендеринг
    //будет осуществляться в него
    glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
    end;

    ОтветитьУдалить
  2. Здравствуйте, это действительно опечатка (копи-паст зло), в этом случае глубина сохраняется в текстуру и все тесты глубины идут через нее, потому наличие рендербуфера глубины не обязательно. Собственно в предыдущем примере я б этом написал:
    "Во-первых мы полностью удалили создание буфера рендеринга, так как его наличие для данной задачи не обязательно".
    Но так как примеров много, то из-за банального копирования забыл удалить этот кусок.

    Спасибо за ошибку :)

    ОтветитьУдалить