Как визуализировать за кадром на OpenGL?

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

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

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

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 и в блок памяти в основной оперативной памяти.

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

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

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

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

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

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

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

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

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

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

Framebuffer Objects

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

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

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

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

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

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

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

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

Самый простой способ - использовать что-то, называемое Frame Buffer Objects (FBO). Вам все равно придется создать окно для создания контекста opengl (но это окно может быть скрыто).

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

Другие вопросы по тегам