Как рендерить вне экрана в OpenGL?

Моя цель - визуализировать сцену OpenGL без окна, прямо в файл. Сцена может быть больше, чем разрешение моего экрана.

Как я могу это сделать?

Я хочу иметь возможность выбирать любой размер области рендеринга, например 10000x10000, если это возможно?


person Rookie    schedule 28.08.2012    source источник


Ответы (5)


Все начинается с glReadPixels, который вы будете использовать для передачи пикселей, хранящихся в конкретный буфер на GPU в основную память (RAM). Как вы заметите в документации, нет аргументов для выбора буфера. Как обычно с OpenGL, текущий буфер для чтения - это состояние, которое вы можете установить с помощью glReadBuffer.

Итак, очень простой метод закадрового рендеринга будет примерно таким. Я использую псевдокод C ++, поэтому он, скорее всего, будет содержать ошибки, но должен прояснить общий поток:

//Before swapping
std::vector<std::uint8_t> data(width*height*4);
glReadBuffer(GL_BACK);
glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,&data[0]);

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

У этого есть несколько недостатков. Во-первых, мы действительно не делаем закадровый рендеринг. Рендерим в экранные буферы и читаем из них. Мы можем эмулировать закадровый рендеринг, никогда не меняя задний буфер местами, но это кажется неправильным. Кроме того, передний и задний буферы оптимизированы для отображения пикселей, а не для их чтения. Вот где в игру вступают объекты Framebuffer.

По сути, FBO позволяет вам создавать нестандартный буфер кадра (например, буферы FRONT и BACK), которые позволяют рисовать в буфер памяти вместо экранных буферов. На практике вы можете рисовать либо в текстуре, либо в буфере рендеринга. Первый оптимален, когда вы хотите повторно использовать пиксели в самом OpenGL в качестве текстуры (например, наивная «камера безопасности» в игре), второй - если вы просто хотите визуализировать / считывать. При этом приведенный выше код стал бы чем-то вроде этого, снова псевдокодом, так что не убивайте меня, если я ошибся или забыл некоторые инструкции.

//Somewhere at initialization
GLuint fbo, render_buf;
glGenFramebuffers(1,&fbo);
glGenRenderbuffers(1,&render_buf);
glBindRenderbuffer(render_buf);
glRenderbufferStorage(GL_RENDERBUFFER, GL_BGRA8, width, height);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER​,fbo);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, render_buf);

//At deinit:
glDeleteFramebuffers(1,&fbo);
glDeleteRenderbuffers(1,&render_buf);

//Before drawing
glBindFramebuffer(GL_DRAW_FRAMEBUFFER​,fbo);
//after drawing
std::vector<std::uint8_t> data(width*height*4);
glReadBuffer(GL_COLOR_ATTACHMENT0);
glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,&data[0]);
// Return to onscreen rendering:
glBindFramebuffer(GL_DRAW_FRAMEBUFFER​,0);

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

Наконец, вы можете использовать объекты пиксельного буфера, чтобы сделать считанные пиксели асинхронными. Проблема в том, что glReadPixels блокируется до тех пор, пока данные пикселей не будут полностью переданы, что может привести к остановке вашего процессора. С PBO реализация может немедленно вернуться, поскольку она все равно контролирует буфер. Только при отображении буфера конвейер будет блокировать. Однако PBO можно оптимизировать для буферизации данных исключительно в ОЗУ, поэтому этот блок может занять намного меньше времени. Код считываемых пикселей будет примерно таким:

//Init:
GLuint pbo;
glGenBuffers(1,&pbo);
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glBufferData(GL_PIXEL_PACK_BUFFER, width*height*4, NULL, GL_DYNAMIC_READ);

//Deinit:
glDeleteBuffers(1,&pbo);

//Reading:
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
glReadPixels(0,0,width,height,GL_BGRA,GL_UNSIGNED_BYTE,0); // 0 instead of a pointer, it is now an offset in the buffer.
//DO SOME OTHER STUFF (otherwise this is a waste of your time)
glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo); //Might not be necessary...
pixel_data = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);

Деталь в кепках обязательна. Если вы просто отправите glReadPixels в PBO, а затем glMapBuffer этого PBO, вы не получите ничего, кроме большого количества кода. Конечно, glReadPixels может вернуться немедленно, но теперь glMapBuffer остановится, потому что он должен безопасно отобразить данные из буфера чтения в PBO и в блок памяти в основной RAM.

Также обратите внимание, что я использую GL_BGRA повсюду, потому что многие видеокарты внутренне используют его как оптимальный формат рендеринга (или версию GL_BGR без альфа). Это должен быть самый быстрый формат для такой передачи пикселей. Я попытаюсь найти статью nvidia, которую прочитал об этом несколько месяцев назад.

При использовании OpenGL ES 2.0 GL_DRAW_FRAMEBUFFER может быть недоступен, вы должны просто использовать GL_FRAMEBUFFER в этом случае.

person KillianDS    schedule 28.08.2012
comment
как использовать FBO, не открывая окно? если я не вызываю glutCreateWindow(argv[0]);, моя программа просто не запускается. - person Ciro Santilli 新疆再教育营六四事件ۍ 15.01.2013
comment
К сожалению, вы не можете (в общем). OpenGL не позволяет создавать контекст вне экрана. Есть некоторые уловки с использованием окна / контекста из другого приложения, но большинство людей просто создают окно 1x1 и сразу же скрывают его. - person KillianDS; 15.01.2013
comment
Пожалуйста, не редактируйте сообщение с дополнительными проблемами. Ответ заключается в том, как визуализировать вне экрана, а не в том, как настроить контекст OpenGL без окна (связанная, но технически совершенно другая тема). - person KillianDS; 16.01.2013
comment
на Android opengles 2.x нет GL_DRAW_, просто удали _DRAW и все нормально. Также перед рендерингом следует вызвать glViewport (0, 0, width, height). Вы захотите вернуть его к предыдущим значениям после завершения рендеринга. - person over_optimistic; 18.04.2013
comment
@over_optimistic: насколько мне известно, и спецификация OpenGL ES, и спецификация OpenGL определяют конкретную константу буфера отрисовки, вы уверены, что Android не поддерживает ее или вы просто используете немедленный режим? Область просмотра действительно должна быть установлена, как и любой другой элемент, необходимый для рендеринга вашей сцены, но это не существенная часть закадрового рендеринга. Область просмотра - это элемент того, как вы визуализируете сцену, а не то, где вы ее визуализируете. - person KillianDS; 18.04.2013
comment
Очевидно, это не поддерживается в OpenGL ES 2.0, добавлю это. - person KillianDS; 18.04.2013
comment
Что ж, Android GLES20 не предоставляет glReadBuffer - person ılǝ; 16.07.2014
comment
Просто из любопытства ... @KillianDS, вы упомянули наивную «камеру безопасности» ... как бы вы реализовали не-наивную камеру безопасности? - person Ryan Kennedy; 09.03.2015
comment
@RyanMuller: наивный может быть плохим выбором слов;), «возможный компонент камеры безопасности» могло бы быть лучшим выражением. Возможно, вы захотите использовать разные грани отбраковки для своих камер безопасности, вы также можете отключить некоторые эффекты для своей камеры (например, тени) просто для повышения производительности. Возможно, вы захотите пост-обработку изображения, чтобы вставить статическое изображение и т. Д. - person KillianDS; 09.03.2015
comment
@KillianDS А, в этом есть смысл. Я подумал, возможно, вы намекаете на какое-то более темное волшебство ;-) - person Ryan Kennedy; 09.03.2015
comment
В вопросе четко указано без окна, поэтому я действительно ожидал здесь ответа на эту побочную тему. Для меня это более интересный аспект вопроса, поскольку по умолчанию любой рендеринг в OpenGL происходит буквально вне экрана. - person bluenote10; 22.07.2015
comment
Привет. Я пробовал этот код (первый пример). Я визуализирую трехмерный мир в окне и хочу создать снимок более высокого разрешения на диск, поэтому я пытаюсь создать renderBuffer. Я пробовал ваш код, но получаю пустой выходной снимок, и мой трехмерный мир зависает даже после того, как я вызвал glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0). IKs есть более обширное объяснение этого? - person manatttta; 23.07.2015
comment
glBindFramebuffer(GL_READ_FRAMEBUFFER​,fbo); отсутствует перед вызовом glReadPixels - person buburs; 21.09.2015
comment
Это хороший ответ, но обязательно прочтите документацию по каждой из упомянутых функций. Функции очень придирчивы к спецификаторам формата. - person parker.sikand; 16.10.2016

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

Вот ваши варианты:

Пиксельные буферы

Пиксельный буфер или pbuffer (который не является объектом пиксельного буфера) в первую очередь контекст OpenGL. Обычно вы создаете окно как обычно, а затем выбираете формат пикселей из _1 _ (форматы pbuffer надо брать отсюда). Затем вы вызываете wglCreatePbufferARB, сообщая ему HDC вашего окна и формат буфера пикселей, который вы хотите использовать. Да, и ширина / высота; вы можете запросить максимальную ширину / высоту реализации.

Фреймбуфер по умолчанию для pbuffer не отображается на экране, а максимальная ширина / высота - это то, что аппаратное обеспечение хочет позволить вам использовать. Таким образом, вы можете выполнить рендеринг и использовать glReadPixels для чтения из него.

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

Преимущество здесь заключается в большей поддержке реализации (хотя большинство драйверов, которые не поддерживают альтернативы, также являются старыми драйверами для оборудования, которое больше не поддерживается. Или это оборудование Intel).

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

Объекты Framebuffer

Объекты Framebuffer являются более "правильными" целями визуализации вне экрана, чем pbuffers. FBO находятся в контексте, а pbuffers предназначены для создания новых контекстов.

FBO - это просто контейнер для изображений, в которые вы выполняете рендеринг. Максимальные размеры, которые позволяет реализация, могут быть запрошены; вы можете предположить, что это GL_MAX_VIEWPORT_DIMS (перед проверкой убедитесь, что FBO привязан, поскольку он изменяется в зависимости от того, привязан ли FBO).

Поскольку вы не отбираете из них текстуры (вы просто читаете значения), вам следует использовать буферы рендеринга вместо текстур. Их максимальный размер может быть больше, чем у текстур.

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

Единственный реальный недостаток - это более узкий диапазон оборудования, которое их поддерживает. В общем, все, что AMD или NVIDIA делают и которые они все еще поддерживают (прямо сейчас GeForce 6xxx или лучше [обратите внимание на количество x] и любая карта Radeon HD), будет иметь доступ к ARB_framebuffer_object или OpenGL 3.0+ (где это основная функция ). Старые драйверы могут иметь только поддержку EXT_framebuffer_object (которая имеет несколько отличий). Аппаратное обеспечение Intel - это удача; даже если они заявляют о поддержке 3.x или 4.x, все равно может произойти сбой из-за ошибок драйвера.

person Nicol Bolas    schedule 28.08.2012
comment
похоже, что GL_MAX_VIEWPORT_DIMS не гарантированно возвращает допустимые значения. он дает мне 8192x8192, но когда я создаю такой FBO, он дает ошибку: GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER. меньший размер, например, 8192x4096 работает. Я считаю, что это из-за того, что моя карта gfx имеет только 256 МБ памяти? - person Rookie; 28.08.2012
comment
@Rookie: Максимальный размер области просмотра - это размер области просмотра; можно ли успешно создать буфер рендеринга такого размера, на который этот запрос не отвечает. Вероятно, ваша проблема состоит в том, что glRenderbufferStorage завершился ошибкой GL_OUT_OF_MEMORY, поскольку INCOMPLETE_DRAW_BUFFER предполагает, что у прикрепленного изображения нет хранилища. Конечно, если бы вы использовали 8-битный формат, такой как GL_R8, возможно, это удалось бы. Так что размер остается точным; это не удается по другим причинам. - person Nicol Bolas; 28.08.2012
comment
Framebuffer указан как предпочтительный метод вместе с примерами здесь . В частности, обратитесь к примеру «Отрисовка в буфер». - person parker.sikand; 16.10.2016

Если вам нужно отрендерить что-то, что превышает максимальный размер FBO вашей реализации GL, _ 1_ работает очень хорошо:

Библиотека TR (Tile Rendering) - это служебная библиотека OpenGL для выполнения мозаичного рендеринга. Тайловый рендеринг - это метод создания больших изображений по частям (тайлам).

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

person genpfault    schedule 28.08.2012
comment
Из интереса: этот libtr тоже работает в режиме трехмерной перспективы? (не могу представить, как это возможно без каких-либо швов). - person Rookie; 29.08.2012
comment
Оно делает! Программы OpenGL обычно вызывают glFrustum, glOrtho или gluPerspective для настройки матрицы проекции. В библиотеке TR есть три соответствующие функции. - person genpfault; 29.08.2012
comment
Насколько я помню, libtr довольно хорош. Он вам определенно понадобится для таких огромных разрешений. - person Calvin1602; 31.08.2012

Самый простой способ - использовать так называемые объекты буфера кадра (FBO). Вам все равно придется создать окно для создания контекста opengl (но это окно можно скрыть).

person Andreas Brinck    schedule 28.08.2012
comment
Либо это, либо pbuffers. Тем не менее, размер цели рендеринга ограничен MAX_TEXTURE_SIZE (в случае FBO), поэтому для некоторых очень больших выходных данных может потребоваться разделение на несколько регионов. - person keltar; 28.08.2012
comment
В чем разница с PBO? Какой из них быстрее? - person Rookie; 28.08.2012
comment
Итак, если я использую FBO, я визуализирую текстуру, а затем использую glGetTexImage () для получения данных пикселей текстуры и сохранения в файл? - person Rookie; 28.08.2012
comment
Что-то такое. Однако вам по-прежнему нужен действительный инициализированный контекст GL для выполнения любого рендеринга - неважно, оконный он или закадровый. - person keltar; 28.08.2012
comment
@Rookie: по сути, вы используете PBO для ускорения передачи вашего графического процессора в основную память (RAM), это не метод закадрового рендеринга и может использоваться вместе с FBO. - person KillianDS; 28.08.2012
comment
@keltar, я пробовал с максимальным размером текстуры (8192x8192), но получил ошибку GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER от glCheckFramebufferStatus (). Я думаю, это потому, что моя карта gfx имеет только 256 МБ памяти? Я думаю, изображение размером 8192x8192 занимает 256 МБ ... так что урок таков: не гарантируется работа с MAX_TEXTURE_SIZE. - person Rookie; 28.08.2012
comment
кроме того, можно ли использовать текстуры размера, отличные от степени двойки, с FBO? Если моя карта не поддерживает текстуры, размер которых не зависит от степени двойки, значит ли это, что я не могу сделать это и на FBO? edit: похоже, что он вылетает (в glGetTexImage ()), если я не использую текстуры размера двойки. edit: на самом деле дело не в силе двух, но я думаю, что это как-то связано с размерами, которые делятся на 4. - person Rookie; 28.08.2012
comment
@Rookie: В чем разница с PBO? Он не сказал ОПО; он сказал pbuffers, что совсем другое дело. - person Nicol Bolas; 28.08.2012
comment
@NicolBolas, я знаю, но тот другой парень что-то сказал о PBO (похоже, он удалил свой ответ сейчас). - person Rookie; 28.08.2012
comment
Не так-то просто поверить, что на 256-мегабайтной карте 8192 MAX_TEXTURE_SIZE. Что это за карта? Я имею в виду, что буфер кадра / резервный буфер всегда находится в видеопамяти, поэтому памяти уже недостаточно для размещения текстуры размером 256 МБ. Затем есть текстуры, шейдеры и буферы вершин - все они могут быть освобождены и повторно загружены из ОЗУ при необходимости, но все же должны находиться в видеопамяти, когда вы их рисуете. Итак, я предполагаю, что либо вы не проверили MAX_TEXTURE_SIZE правильно, либо что-то не так с вашим hw / драйвером. - person keltar; 31.08.2012

Самый простой способ достичь вашей цели - использовать FBO для рендеринга вне экрана. И вам не нужно выполнять рендеринг в текстуру, а затем получать тексимаж. Просто выполните рендеринг в буфер и используйте функцию glReadPixels. Эта ссылка будет полезна. См. Примеры объектов Framebuffer

person rtrobin    schedule 29.08.2012
comment
Разве glReadPixels не делает именно то, что делает чтение teximage? или это быстрее? он может ускориться по краям, так как есть некоторые неиспользуемые области пикселей, которые я проигнорирую ... - person Rookie; 29.08.2012
comment
Это должно быть быстрее. glReadPixels напрямую считывает данные из fb в память. Но glGetTexImage сначала требует рендеринга fb в текстуру, а затем чтения из текстуры в память. При правильном использовании fb вы можете просто прочитать те области пикселей, которые вам нужны. - person rtrobin; 29.08.2012