Я должен отрисовать за пределами экрана в OpenGL, а затем передать изображение в QImage. Плюс, просто для тренировки я хотел бы перенести в процессор также глубину и буфер трафарета.
Для рисования вне экрана я использовал Frame Buffer Object с Render Buffer (а не с текстурой, потому что мне не нужна текстура).
Пиксельная передача с цветными буферами (фактическое необработанное изображение) работает .. Я вижу то, что я ожидаю. Но глубина и трафарет не работают .. странное изображение для глубины и ничего для трафарета.
Во-первых, самая легкая часть того, что я на самом деле рисую:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslatef(-1.5f,0.0f,-6.0f);
glBegin(GL_TRIANGLES);
glColor3f(1.0f,0.0f,0.0f);
glVertex3f( 0.0f, 1.0f, 0.0f);
glColor3f(0.0f,1.0f,0.0f);
glVertex3f(-1.0f,-1.0f, -1.0f);
glColor3f(0.0f,0.0f,1.0f);
glVertex3f( 1.0f,-1.0f, 0.0f);
glEnd();
Здесь инициализация FBO и буфера рендеринга 3:
// frame buffer object
glGenFramebuffers(1, &fboId);
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
// render buffer as color buffer
glGenRenderbuffers(1, &colorBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, colorBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
// attach render buffer to the fbo as color buffer
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorBuffer);
// render buffer as depth buffer
glGenRenderbuffers(1, &depthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, depthBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
// attach render buffer to the fbo as depth buffer
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthBuffer);
glGenRenderbuffers(1, &stencilBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, stencilBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
// attach render buffer to the fbo as stencil buffer
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_BUFFER, GL_RENDERBUFFER, stencilBuffer);
И, наконец, здесь 3 метода для передачи пикселей:
QImage FBO::getImage()
{
// this is called Pixel Transfer operation: http://www.opengl.org/wiki/Pixel_Transfer
uchar *pixels;
pixels = new uchar[width * height * 4];
for(int i=0; i < (width * height * 4) ; i++ ) {
pixels[i] = 0;
}
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
glReadPixels( 0,0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
QImage qi = QImage(pixels, width, height, QImage::Format_ARGB32);
qi = qi.rgbSwapped();
return qi;
}
QImage FBO::getDepth()
{
// this is called Pixel Transfer operation: http://www.opengl.org/wiki/Pixel_Transfer
uchar *pixels;
pixels = new uchar[width * height * 4];
for(int i=0; i < (width * height * 4) ; i++ ) {
pixels[i] = 0;
}
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
glBindRenderbuffer(GL_RENDERBUFFER, depthBuffer);
glReadPixels( 0,0, width, height, GL_DEPTH_COMPONENT, GL_FLOAT, pixels);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
QImage qi = QImage(pixels, width, height, QImage::Format_ARGB32);
qi = qi.rgbSwapped();
return qi;
}
QImage FBO::getStencil()
{
// this is called Pixel Transfer operation: http://www.opengl.org/wiki/Pixel_Transfer
uchar *pixels;
pixels = new uchar[width * height * 4];
for(int i=0; i < (width * height * 4) ; i++ ) {
pixels[i] = 0;
}
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
glBindRenderbuffer(GL_RENDERBUFFER, stencilBuffer);
glReadPixels( 0,0, width, height, GL_STENCIL_INDEX, GL_FLOAT, pixels);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
QImage qi = QImage(pixels, width, height, QImage::Format_ARGB32);
qi = qi.rgbSwapped();
return qi;
}
Вот скриншот 2 (цвет и глубина, с трафаретом я получаю пустой QImage):
Цветной это именно то, что я рисую (перевернул, но я думаю, что это нормально). Глубина .. Я ожидаю белое изображение с градиентом серого треугольника .. Наверняка я делаю какую-то ошибку в формате изображения (GL_FLOAT
?) но я попробовал некоторые комбинации, и это лучший результат … фиолетовый фон с блестящими цветами внутри него … и с трафаретным буфером … я ожидаю контур белого треугольника на черном фоне, но .. понятия не имею, почему я ничего не вижу ..
Хорошо, никогда не используйте только трафаретный буфер, поэтому я немного изменил рефакторинг.
при объявлении FBO:
// render buffer for both depth and stencil buffer
glGenRenderbuffers(1, &depthStencilBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, depthStencilBuffer);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
// attach render buffer to the fbo as depth buffer
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencilBuffer);
пиксельная передача глубины:
QImage FBO::getDepth()
{
std::vector<uchar> pixels;
pixels.reserve(width * height*4);
for(int i=0; i < (width * height*4) ; i++ ) {
pixels.push_back(0);
}
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
glBindRenderbuffer(GL_RENDERBUFFER, depthStencilBuffer);
glReadPixels( 0,0, width, height, GL_DEPTH24_STENCIL8, GL_UNSIGNED_BYTE, pixels.data());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
std::vector<uchar> _pixels;
_pixels.reserve(width * height*4);
for(int i=0; i < (width * height*4) ; i++ ) {
uchar p_red = pixels[i];
uchar p_green = pixels[i+1];
uchar p_blue = pixels[i+2];
uchar p_stencil = pixels[i+3];
_pixels.push_back(p_red);
_pixels.push_back(p_green);
_pixels.push_back(p_blue);
_pixels.push_back(255); // alpha
}
QImage qi = QImage(_pixels.data(), width, height, QImage::Format_ARGB32);
//qi = qi.rgbSwapped();
return qi;
}
трафарет похож, но с использованием p_stencil
с компонентом RGB
.. Полученное изображение является черным изображением как для глубины, так и для трафарета.
благодаря ответу Николаса мне удалось использовать буфер рендеринга для буфера глубины и трафарета и извлечь компонент глубины, QImage::Format_ARGB32
с этим кодом:
QImage FBO1::getDepth()
{
// sizeof( GLuint ) = 4 byte
// sizeof( uchar ) = 1 byte
std::vector<GLuint> pixels(width * height);
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
glBindRenderbuffer(GL_RENDERBUFFER, depthStencilBuffer);
glReadPixels( 0,0, width, height, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, pixels.data());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
std::vector<uchar> _pixels;
for(int i=0; i < (width * height) ; i++ ) {
GLuint color = pixels[i];
float temp = (color & 0xFFFFFF00) >> 8; // 24 bit of depth component
// remap temp that goes from 0 to 0xFFFFFF (24 bits) to
// _temp that goes from 0 to 0xFF (8 bits)
float _temp = (temp / 0xFFFFFF) * 0xFF;
_pixels.push_back((uchar)_temp);
_pixels.push_back((uchar)_temp);
_pixels.push_back((uchar)_temp);
_pixels.push_back(255);
}
QImage qi = QImage(_pixels.data(), width, height, QImage::Format_ARGB32);
qi = qi.rgbSwapped();
return qi;
}
.. Еще некоторые проблемы с компонентом трафарета (следующий код не работает: он производит глюки изображения):
QImage FBO1::getStencil()
{
// sizeof( GLuint ) = 4 byte
// sizeof( uchar ) = 1 byte
std::vector<GLuint> pixels(width * height);
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
glBindRenderbuffer(GL_RENDERBUFFER, depthStencilBuffer);
glReadPixels( 0,0, width, height, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, pixels.data());
glBindFramebuffer(GL_FRAMEBUFFER, 0);
std::vector<uchar> _pixels;
for(int i=0; i < (width * height); i++ ) {
GLuint color = pixels[i];
uchar temp = (color & 0x000000FF); // last 8 bit of depth component
_pixels.push_back(temp);
_pixels.push_back(temp);
_pixels.push_back(temp);
_pixels.push_back(255);
}
QImage qi = QImage(_pixels.data(), width, height, QImage::Format_ARGB32);
qi = qi.rgbSwapped();
return qi;
}
Глубина .. Я ожидаю белое изображение с градиентом серого треугольника ..
Ваш код не предполагает этого.
uchar *pixels;
pixels = new uchar[width * height * 4];
Это создает массив целые. Это также утечка памяти; использование std::vector<uchar>
вместо.
glReadPixels( 0,0, width, height, GL_DEPTH_COMPONENT, GL_FLOAT, pixels);
Это говорит OpenGL написать поплавки к вашему массиву целые. Так что OpenGL будет лечить pixels
как массив GLfloat
с при написании.
QImage qi = QImage(pixels, width, height, QImage::Format_ARGB32);
Я предполагаю, что это будет интерпретировать это как некий 8-битный компонент целое число формат изображения. Итак, вы интерпретируете float
s как 8-битные целые числа. Нет никаких оснований ожидать, что это будет вести себя рационально.
Вы должны либо читать буфер глубины как плавающие и преобразовывать его в цвета пикселей более разумным способом, либо читать буфер глубины как целочисленное значение, позволяя OpenGL выполнить преобразование в оттенках серого за вас.
Кроме того, вы должны всегда использовать комбинированное изображение глубины / трафарета, не два отдельных рендербуфера.
glReadPixels( 0,0, width, height, GL_DEPTH24_STENCIL8, GL_UNSIGNED_BYTE, pixels.data());
Вы, кажется, не понимаете, как передача пикселей Работа.
Во-первых, формат передачи пикселей указывает, какие компоненты вы читаете. Оно делает не уточняйте их размеры. GL_DEPTH24_STENCIL8
является формат изображения, не формат передачи пикселей. Если вы хотите прочитать глубину и трафарет с изображения, вы используете GL_DEPTH_STENCIL
, Форматы передачи пикселей не имеют размеров.
Так что эта функция просто дает вам ошибку OpenGL.
Размер приходит от второго параметра, тип передачи пикселей. В этом случае GL_UNSIGNED_BYTE
означает, что он будет считывать глубину и трафарет, преобразовывать каждое в 8-разрядное значение без знака и сохранять два из них на пиксель в pixels.data()
,
Буферы глубины хранят только 1 значение на пиксель. Глубина / трафарет только магазин 2. Вы не могу скопируйте их в 4-компонентный формат на пиксель с помощью OpenGL. Поэтому, как бы вы ни строили свой QImage позже, должен Мне какой-то метод, который принимает 1 или 2 значения на пиксель.
Вообще говоря, если вы хотите прочитать буфер глубины и хотите, чтобы данные буфера глубины действительно имели смысл, вы делаете это:
std::vector<GLuint> pixels(width * height); //std::vector value-initializes its elements.
glBindFramebuffer(GL_FRAMEBUFFER, fboId);
glReadPixels( 0,0, width, height, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, pixels.data());
Как вы помещаете их в QImage для визуализации, зависит от вас. Но это дает вам массив беззнаковых целых, где старшие 24 бита являются компонентом глубины, а младшие 8 бит — трафаретом.
Хорошо, картина глубины выглядит так, как можно ожидать из этого кода:
glReadPixels( 0,0, width, height, GL_DEPTH_COMPONENT, GL_FLOAT, pixels);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
QImage qi = QImage(pixels, width, height, QImage::Format_ARGB32);
QImage не знает, как обращаться с плавающими числами, поэтому он интерпретирует каждую группу из 32 битов (в пределах числа с плавающей точкой), как если бы они были компонентами ARGB, по 8 бит каждая. У поплавка есть 25-битная мантисса в нижних битах, которая прекрасно отображается в этих компонентах. Те полосы, которые вы видите, — просто увеличивающаяся мантисса, обрезанная до некоторого количества битов.
QImage действительно очень ограниченная вещь (она даже не может иметь дело с конфигурациями HDR, например, 16 бит на канал, что немного расстраивает). В любом случае, лучше всего конвертировать значения с плавающей точкой в диапазон 0… 255 и передавать их в виде изображения в градациях серого в QImage.
Также отдельный трафарет глубины имеет большое значение для производительности. Всегда используйте комбинированный формат, где это возможно.
… Ну, приходит ответ Николя Боласа, который прав так же, как я написал. Плюс он указал на утечку памяти.