Постобработка при помощи FBO Автор: Тимур Гафаров aka Gecko Версия Xtreme3D: 3.2 Дата: 27.10.2016 В Xtreme3D 3.2 появилась поддержка фреймбуферных объектов (Framebuffer Objects, сокращенно FBO). Это современная технология внеэкранного рендеринга - то есть, проще говоря, рендеринга в текстуру. С ее помощью можно написать абсолютно любой эффект постобработки - то есть, пропустить отрендеренное изображение через какой-нибудь фильтр. В качестве примеров постобработки можно назвать Motion Blur, Bloom, SSAO, Depth of Field и т.д. Некоторые популярные эффекты уже реализованы на Xtreme3D - вы можете найти соответствующий пример на странице http://xtreme3d.narod.ru/examples.htm В Xtreme3D и раньше можно было рендерить в текстуру (ViewerCopyToTexture в Xtreme3D 2.0), а в Xtreme3D 3.0 появилась поддержка MemoryViewer - обертки над p-буферами, которые были распространены до появления FBO. Преимуществом FBO перед этими методами является значительно более высокая производительность - в отличие от MemoryViewer, тут не используется переключение контекстов, и, в отличие от ViewerCopyToTexture, не происходит копирование участков видеопамяти. Фактически, FBO позволяет просто создать в видеопамяти новый буфер кадра и рендерить в него. Затем этот буфер используется в качестве текстуры. На этом уроке я покажу, как использовать FBO для реализации простейшего эффекта постобработки - инвертирования. Общая логика нашей программы будет выглядеть следующим образом: 1. Создаем FBO размером с наш Viewer. 2. Создаем материал и шейдер, осуществляющий инвертирование. К шейдеру прикрепляем текстуру с буфером из FBO. 3. Создаем экранный спрайт для эффекта инвертирования, покрывающий весь Viewer целиком. К нему будет применен материал с шейдером. Рендеринг осуществляется в таком порядке: 1. Вызываем Update 2. Рендерим 3D-сцену в FBO (не рендерим 2D-объекты!) 3. Рендерим экранный спрайт 4. Рендерим 2D-объекты, если они нужны (текст, HUD и т.д.) Для такого "нестандартного" порядка отрисовки потребуется задать особую иерархию корневых Dummycube'ов: global.backAndScene = DummycubeCreate(0); global.back = DummycubeCreate(global.backAndScene); global.scene = DummycubeCreate(global.backAndScene); global.front = DummycubeCreate(0); global.fboFront = DummycubeCreate(0); Обратите внимание, что мы сгруппировали global.back и global.scene под общим родителем global.backAndScene, чтобы их можно было отрендерить вместе. Также мы создали global.fboFront - это будет отдельный родитель для экранного спрайта с нашим эффектом. Теперь можно спокойно создавать любую 3D-сцену, а также 2D-объекты, используя для этого привычные global.back, global.scene и global.front. Затем создаем FBO (учитывая, что вы уже создали Viewer и камеру): fbo = FBOCreate(window_get_width(), window_get_height(), view); FBOSetCamera(fbo, camera); Создаем шейдер и передаем ему в параметр текстуру из FBO: vp = TextRead('shaders/invert-vp.glsl'); fp = TextRead('shaders/invert-fp.glsl'); invertShader = GLSLShaderCreate(vp, fp); paramTexture = GLSLShaderCreateParameter(invertShader, 'texture'); GLSLShaderSetParameterFBOColorTexture(paramTexture, fbo, 0); Шейдеры у нас будут такие: Вершинный шейдер - файл "shaders/invert-vp.glsl": void main() { gl_Position = ftransform(); gl_TexCoord[0] = gl_MultiTexCoord0; } Фрагментный шейдер - файл "shaders/invert-fp.glsl": uniform sampler2D texture; void main() { vec4 color = texture2D(texture, gl_TexCoord[0].xy); gl_FragColor = 1.0 - color; gl_FragColor.a = 1.0; } Создаем материал, использующий этот шейдер, и экранный спрайт с данным материалом: MaterialCreate('mInvert', ''); MaterialSetOptions('mInvert', 1, 1); MaterialSetShader('mInvert', invertShader); fboSprite = HUDSpriteCreate('mInvert', window_get_width(), window_get_height(), global.fboFront); ObjectSetPosition(fboSprite, window_get_width()/2.0, window_get_height()/2.0, 0); Спрайт мы помещаем в потомки к global.fboFront. Теперь в событии Step: Update(1.0 / room_speed); FBORenderObject(fbo, global.backAndScene); ViewerRenderEx(view1, global.fboFront, true, false, false); ViewerRenderEx(view1, global.front, false, true, true); Смысл этого кода стоит объяснить подробнее. Сначала мы, как обычно, вызываем Update, обновляя внутренние состояния объектов. Затем, вместо того, чтобы вызывать ViewerRender, мы рендерим FBO, передавая в соответствующую функцию наш корневой Dummycube для объектов заднего и сценического плана - global.backAndScene. Тем самым мы рисуем эти объекты в текстуру, которая используется шейдером инвертирования. Затем нам нужно отрендерить во Viewer только экранный спрайт с эффектом инвертирования, игнорируя остальные объекты. Это невозможно сделать при помощи ViewerRender, поскольку она отрендерит все корневые Dummycube'ы. В Xtreme3D 3.2 появилась функция ViewerRenderEx, с помощью которой можно рендерить отдельные объекты и иерархии - мы вызываем ее для global.fboFront. Параметры true, false, false задают следующие опции рендеринга: очистить Viewer фоновым цветом, не делать переключение заднего и переднего буферов (мы это сделаем следующим вызовом) и не обновлять счетчик FPS (мы тоже сделаем это дальше). Вторым вызовом ViewerRenderEx мы рендерим global.front и, соответственно, все двумерные объекты переднего плана - то есть, экранный текст и HUD-объекты, которые не должны попадать под эффект инвертирования. В этот раз мы не хотим очищать Viewer, зато хотим переключить буферы и обновить счетчик кадровой частоты. Вы можете вызывать ViewerRenderEx сколько хотите, для любых объектов и Dummycube'ов, но помните, что переключать буферы и обновлять счетчик можно только про последнем вызове этой функции в шаге - в противном случае движок будет работать неправильно. Как видите, постобработка в Xtreme3D 3.2 требует несколько дополнительных действий по сравнению с обычным прямым рендерингом во Viewer, однако структура сцены особо не меняется - можно адаптировать эту технологию к любой игре. Кроме того, можно создать несколько FBO и рендерить в них разные группы объектов, применять к полученным картинкам разные шейдеры и составлять целые конвейеры фильтров, где одна картинка поступает в другой FBO для дополнительной обработки - современные видеокарты позволяют делать подобные вещи очень быстро. Возможности поистине безграничны!