Ответ 1
Все начинается с glReadPixels
, который вы будете использовать для переноса пикселей, хранящихся в конкретном буфере на графическом процессоре, в основную память (ОЗУ). Как вы заметили в документации, нет аргументов для выбора того, какой буфер. Как обычно в OpenGL, текущий буфер для чтения является состоянием, которое вы можете установить с помощью glReadBuffer
.
Таким образом, очень простой метод рендеринга на экране будет выглядеть следующим образом. Я использую псевдо-код С++, поэтому он, вероятно, будет содержать ошибки, но должен сделать общий поток понятным:
//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 Objects входит в игру.
По сути, FBO позволяет создавать фреймбуфер, отличный от стандартного (например, FRONT и BACK-буферы), которые позволяют рисовать в буфер памяти вместо буферов экрана. На практике вы можете либо нарисовать текстуру, либо renderbuffer. Первый из них является оптимальным, если вы хотите повторно использовать пиксели в 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
блокируется, пока данные пикселя не будут полностью перенесены, что может привести к остановке вашего CPU. С 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
в этом случае.