Часть первая, немного теории
Часть вторая, простой пример
Часть третья, буфер глубины
Часть четвертая, MRT
Часть пятая, чтение
Часть шестая, R2VB
Ссылки по теме
В предыдущих частях мы с вами научились создавать буфер FBO, прикреплять к нему буферы рендеринга и текстуры, а так же попробовали разные комбинации этих буферов, но этим технология FBO не ограничивается. Одной из ключевых возможностей FBO является возможность прикрепить к нему несколько буферов цвета и одновременно рисовать в каждый из них. Данная технология носит название MRT (Multiple Render Target). Разберем эту технологию на примере демо, осуществляющего вывод каждого канала цвета в отдельную текстуру.
Запись данных в эти буферы возможна только из фрагментного шейдера, посредством команды GLSL gl_FragData[n], где n – номер буфера цвета.
Для реализации этого демо нам потребуется написать простейший шейдер, создать несколько текстурных юнитов и прикрепить их к FBO в качестве буферов цвета, этим мы сейчас и займемся.
Для начала напишем шейдеры, вершинный:
И фрагментный:
С вершинным шейдером все понятно, остановимся на фрагментном. Здесь в первых 3 текстурных юнита будут помещены цветовые компоненты (красная, зеленая и синя соответственно), а в 4-й буфер будет помещена нормированная координата z фрагмента, переведенная в серый цвет.
На этом с шейдерами закончили, переходим к процедуре инициализации FBO. Итак, нам потребуется создать 4 текстурных юнита, задать для каждого параметры и форматы, активировать генерацию мип-уровней и присоединить каждый из них к буферу FBO. Дабы сократить объем кода оформим это все в виде массивов и циклов. Для начала объявим глобальный массив:
TexturesId: array[0..3] of GLUInt;
в котором и будут храниться идентификаторы наших текстур. Теперь перепишем процедуру инициализации:
Как видите? практически ничего не изменилось, за исключением того, что мы теперь указываем какая текстура к какому буферу цвета будет прикрепляться. Буфер рендеринга мы здесь не используем.
Более существенные изменения коснутся процедуры FBORender, вот ее полный код:
С генерацией мип-уровней думаю все понятно(строки 22-26), давайте разберем что же такое этот glDrawBuffers (строка 13) и зачем мы его используем. В предыдущих примерах мы использовали лишь один буфер цвета, в который и происходило рисование (или же вообще отключали его, как в примере с буфером глубины). Сейчас же нам нужно произвести рисование сразу в 4 буфера, об этом мы и должны как-то сообщить OpenGL, для этих целей служит функция glDrawBuffers, первым параметром которой идет количество цветовых буферов в которые мы будем осуществлять рендеринг, а вторым – указатель на список идентификаторов буферов, в нашем случае массив buffers(строки 4-7).
После завершения рендеринга в текстуру желательно восстановить буферы вывода, это делается в строке 20.
Кроме этого нам еще нужно применить шейдер к рисованию нашего кубика (строка 15) и отменить шейдер после завершения рисования (строка 17).
Для создания шейдера воспользуемся функцией CreateShader из модуля DemoTools, в качестве параметров этой функции используются имена файлов содержащие код вершинного и фрагментного шейдеров. Результат сохраняется в глобальной переменной Shader: TShader, тип TShader так же определен в нашем вспомогательном модуле.
Если вы все сделали правильно то у вас должно получиться примерно как на Рис.3.
Полный код примера можно найти в FBO_MRT.
Присоединение буфера глубины и буфера трафарета происходит аналогичным образом, потому останавливаться на этом не будем.
Одно важное замечание – формат, тип и размер всех буферов цвета должен совпадать.
Это ограничение можно обойти, создав несколько буферов FBO и сделав часть буферов разделяемыми (к примеру буфер глубины будет общим для всех буферов кадра), но в этом случае рендеринг будет многопроходным – вначале биндим буфер кадра с присоединенным к нему буфером глубины и осуществляем рендеринг в буфер глубины, потом присоединяем буфер кадра с присоединенными текстурой первого типа (к примеру Float) и буфером глубины (не забываем что он у нас общий для всех буферов кадра) и осуществляем второй проход рендеринга, после чего биндим третий буфер кадра и осуществляем 3-й проход рендеринга. Громоздко, но иногда это необходимо.
Попробуем это реализовать на примере, для этого в очередной раз перепишем процедуры инициализации и рендеринга:
Изменений очень много, потому давайте разберем все по порядку.
Начнем с имен переменных – здесь мы используем две текстуры – целочисленную и вещественную (float) текстуру с именами fixedColor и floatColor соответственно. Так же нам потребуется идентификатор для буфера глубины, мы его обозвали depth. Так же в нашем примере используется три буфера кадра, потому мы оформили их в виде массива:
framebuffers: array[0..2] of GLUInt, который заполняется в строке 11.
Далее в строках 10-23 мы создаем три различных комбинации буферов – только глубина (14-15), глубина + целочисленная текстура(17-19), глубина + флоат-текстура(21-23).
Так как у нас 3 буфера кадра, то в идеале нам нужно осуществлять проверку статуса после заполнения каждого из них, но для экономии места мы эту часть пропустим.
Ну и процедура рендеринга:
Основное отличие от предыдущих примеров – нам необходимо трижды отрисовать наш куб в 3 буфера кадров, что мы и делаем в строках 7-10, потом в строках 12-15, и наконец в 17-20.
Результат здесь не очень эффектный, но нам важно чтоб оно вообще рисовало без ошибок:
Полные исходные коды примера вы найдете в FBO_3T.
Иногда бывает необходимость не только произвести рендеринг в текстуру, но и осуществить чтение из нее, в следующей части мы с вами рассмотрим как можно произвести на стороне CPU чтение из буфера или текстуры, а так же познакомимся с понятием PBO.
Часть вторая, простой пример
Часть третья, буфер глубины
Часть четвертая, MRT
Часть пятая, чтение
Часть шестая, R2VB
Ссылки по теме
В предыдущих частях мы с вами научились создавать буфер FBO, прикреплять к нему буферы рендеринга и текстуры, а так же попробовали разные комбинации этих буферов, но этим технология FBO не ограничивается. Одной из ключевых возможностей FBO является возможность прикрепить к нему несколько буферов цвета и одновременно рисовать в каждый из них. Данная технология носит название MRT (Multiple Render Target). Разберем эту технологию на примере демо, осуществляющего вывод каждого канала цвета в отдельную текстуру.
Запись данных в эти буферы возможна только из фрагментного шейдера, посредством команды GLSL gl_FragData[n], где n – номер буфера цвета.
Для реализации этого демо нам потребуется написать простейший шейдер, создать несколько текстурных юнитов и прикрепить их к FBO в качестве буферов цвета, этим мы сейчас и займемся.
Для начала напишем шейдеры, вершинный:
1 2 3 4 | void main ()
{
gl_Position = ftransform();
}
|
И фрагментный:
1 2 3 4 5 6 7 | void main(void) { gl_FragData[0] = vec4(1.0,0.0,0.0,1.0); gl_FragData[1] = vec4(0.0,1.0,0.0,1.0); gl_FragData[2] = vec4(0.0,0.0,1.0,1.0); gl_FragData[3] = vec4(gl_FragCoord.zzz,1.0); } |
С вершинным шейдером все понятно, остановимся на фрагментном. Здесь в первых 3 текстурных юнита будут помещены цветовые компоненты (красная, зеленая и синя соответственно), а в 4-й буфер будет помещена нормированная координата z фрагмента, переведенная в серый цвет.
На этом с шейдерами закончили, переходим к процедуре инициализации FBO. Итак, нам потребуется создать 4 текстурных юнита, задать для каждого параметры и форматы, активировать генерацию мип-уровней и присоединить каждый из них к буферу FBO. Дабы сократить объем кода оформим это все в виде массивов и циклов. Для начала объявим глобальный массив:
TexturesId: array[0..3] of GLUInt;
в котором и будут храниться идентификаторы наших текстур. Теперь перепишем процедуру инициализации:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | procedure InitFBO(Width, Height: integer); var Status: GLUInt; i: integer; begin //Создадим 4 целочисленные текстуры for i:=0 to 3 do TexturesId[i]:= CreateTextureRGBA8(Width, Height, true); // Создадим буфер FBO glGenFramebuffersEXT(1, @fboId); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId); //Буферы созданы, осталось лишь присоединить текстуры к FBO for i:=0 to 3 do glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT+i, GL_TEXTURE_2D, TexturesId[i], 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 20 21 22 23 24 25 26 27 28 29 30 | procedure FBORender; var viewport: array[0..3] of integer; i: integer; const buffers: array [0..3] of GLenum = (GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT, GL_COLOR_ATTACHMENT2_EXT, GL_COLOR_ATTACHMENT3_EXT); begin glGetIntegerv(GL_VIEWPORT, @viewport); glViewport(0,0,texWidth,texHeight); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboId); //Активируем FBO //Указываем в какие буферы будет происходить рисование glDrawBuffers (4, @buffers[0]); glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);//Очищаем буферы цвета и глубины glUseProgram(Shader.pId); //Активируем шейдер DrawCube; //Рендерим объект/сцену glUseProgram(0); // Деактивируем шейдер glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);//Деактивируем FBO //Восстанавливаем рисование в фоновый буфер glReadBuffer(GL_BACK); glDrawBuffer(GL_BACK); glViewport(viewport[0],viewport[1],viewport[2],viewport[3]); //Сгенерируем для полученных текстур мип-уровни for i:=0 to 3 do begin glBindTexture(GL_TEXTURE_2D, texturesId[i]); glGenerateMipmapEXT(GL_TEXTURE_2D); end; //Нарисуем четыре квада с разными текстурами drawScreenQuad(2,2,[texturesId[0], texturesId[1], texturesId[2], texturesId[3]]); glBindTexture(GL_TEXTURE_2D, 0);//Деактивируем текстуру end; |
С генерацией мип-уровней думаю все понятно(строки 22-26), давайте разберем что же такое этот glDrawBuffers (строка 13) и зачем мы его используем. В предыдущих примерах мы использовали лишь один буфер цвета, в который и происходило рисование (или же вообще отключали его, как в примере с буфером глубины). Сейчас же нам нужно произвести рисование сразу в 4 буфера, об этом мы и должны как-то сообщить OpenGL, для этих целей служит функция glDrawBuffers, первым параметром которой идет количество цветовых буферов в которые мы будем осуществлять рендеринг, а вторым – указатель на список идентификаторов буферов, в нашем случае массив buffers(строки 4-7).
После завершения рендеринга в текстуру желательно восстановить буферы вывода, это делается в строке 20.
Кроме этого нам еще нужно применить шейдер к рисованию нашего кубика (строка 15) и отменить шейдер после завершения рисования (строка 17).
Для создания шейдера воспользуемся функцией CreateShader из модуля DemoTools, в качестве параметров этой функции используются имена файлов содержащие код вершинного и фрагментного шейдеров. Результат сохраняется в глобальной переменной Shader: TShader, тип TShader так же определен в нашем вспомогательном модуле.
Если вы все сделали правильно то у вас должно получиться примерно как на Рис.3.
Рис.3. Рендеринг в 4 буфера цвета. |
Полный код примера можно найти в FBO_MRT.
Присоединение буфера глубины и буфера трафарета происходит аналогичным образом, потому останавливаться на этом не будем.
Одно важное замечание – формат, тип и размер всех буферов цвета должен совпадать.
Это ограничение можно обойти, создав несколько буферов FBO и сделав часть буферов разделяемыми (к примеру буфер глубины будет общим для всех буферов кадра), но в этом случае рендеринг будет многопроходным – вначале биндим буфер кадра с присоединенным к нему буфером глубины и осуществляем рендеринг в буфер глубины, потом присоединяем буфер кадра с присоединенными текстурой первого типа (к примеру Float) и буфером глубины (не забываем что он у нас общий для всех буферов кадра) и осуществляем второй проход рендеринга, после чего биндим третий буфер кадра и осуществляем 3-й проход рендеринга. Громоздко, но иногда это необходимо.
Попробуем это реализовать на примере, для этого в очередной раз перепишем процедуры инициализации и рендеринга:
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 | procedure InitFBO(Width, Height: integer); begin fixedColor:=CreateTextureRGBA8(Width, Height, true); floatColor:=CreateTextureRGBA16F(Width, Height, true); //Создаём буфер глубины. glGenRenderbuffersEXT(1, @depth); glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depth); glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, width, height); glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, 0); //Создадим три буфера кадра glGenFramebuffersEXT(3, @framebuffers); //Буферы созданы, осталось лишь присоединить текстуры к FBO, при этом //буфер глубины общий для всех glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffers[0]); glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depth); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffers[1]); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, fixedColor, 0); glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depth); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffers[2]); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, floatColor, 0); glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depth); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); end; |
Изменений очень много, потому давайте разберем все по порядку.
Начнем с имен переменных – здесь мы используем две текстуры – целочисленную и вещественную (float) текстуру с именами fixedColor и floatColor соответственно. Так же нам потребуется идентификатор для буфера глубины, мы его обозвали depth. Так же в нашем примере используется три буфера кадра, потому мы оформили их в виде массива:
framebuffers: array[0..2] of GLUInt, который заполняется в строке 11.
Далее в строках 10-23 мы создаем три различных комбинации буферов – только глубина (14-15), глубина + целочисленная текстура(17-19), глубина + флоат-текстура(21-23).
Так как у нас 3 буфера кадра, то в идеале нам нужно осуществлять проверку статуса после заполнения каждого из них, но для экономии места мы эту часть пропустим.
Ну и процедура рендеринга:
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 30 31 32 33 | procedure FBORender; var viewport:array[0..3] of integer; begin glGetIntegerv(GL_VIEWPORT, @viewport); glViewport(0,0,texWidth,texHeight); //Рисуем в буфер глубины glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffers[0]); glDrawBuffer(GL_NONE); glClear(GL_DEPTH_BUFFER_BIT);//Очищаем буфер глубины DrawCube; //Рисуем в целочисленную текстуру glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffers[1]); glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT); glClear ( GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); DrawCube; //Рисуем в float-текстуру glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffers[2]); glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT); glClear ( GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); DrawCube; // Возврат к оконному контексту. glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);//Деактивируем FBO glDrawBuffer(GL_BACK); glViewport(viewport[0],viewport[1],viewport[2],viewport[3]); //генерируем мип-уровни glBindTexture(GL_TEXTURE_2D, fixedColor); glGenerateMipmapEXT(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, floatColor); glGenerateMipmapEXT(GL_TEXTURE_2D); //Нарисуем два квада с разными текстурами drawScreenQuad(1,2,[ fixedColor, floatColor]); glBindTexture(GL_TEXTURE_2D, 0);//Деактивируем текстуру end; |
Основное отличие от предыдущих примеров – нам необходимо трижды отрисовать наш куб в 3 буфера кадров, что мы и делаем в строках 7-10, потом в строках 12-15, и наконец в 17-20.
Результат здесь не очень эффектный, но нам важно чтоб оно вообще рисовало без ошибок:
Рис.4. Многопроходный рендеринг с разделяемым буфером глубины. |
Иногда бывает необходимость не только произвести рендеринг в текстуру, но и осуществить чтение из нее, в следующей части мы с вами рассмотрим как можно произвести на стороне CPU чтение из буфера или текстуры, а так же познакомимся с понятием PBO.
Комментариев нет:
Отправить комментарий