Отрисовка за пределами экрана (с FBO и RenderBuffer) и передача цвета, глубины, трафарета в пикселях

Я должен отрисовать за пределами экрана в 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;
}

2

Решение

Глубина .. Я ожидаю белое изображение с градиентом серого треугольника ..

Ваш код не предполагает этого.

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-битный компонент целое число формат изображения. Итак, вы интерпретируете floats как 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 бит — трафаретом.

3

Другие решения

Хорошо, картина глубины выглядит так, как можно ожидать из этого кода:

 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.

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

… Ну, приходит ответ Николя Боласа, который прав так же, как я написал. Плюс он указал на утечку памяти.

2

По вопросам рекламы [email protected]