Search code examples
c++qtopenglopeninventor

Convert Coin3D SoOffscreenRenderer to QImage and render with OpenGL


I'm trying to display a Coin3D/Open Inventor scene with QT in a QGLWidget, by using the SoOffscreenRenderer and I need help converting it to a QImage

What I tried so far, is render the scene into SoOffscreenRenderer and get the buffer like this:

unsigned char * getCoinCubeImgBuffer(){
  // [...] create the scene, add lightning and camera

  SoOffscreenRenderer offscreenRenderer(vpRegion);
  offscreenRenderer.setComponents(
                      SoOffscreenRenderer::Components::RGB_TRANSPARENCY
                    );
  SbBool ok = offscreenRenderer.render(root);

  // to be sure that something is actually rendered
  // save the buffer content to a file
  SbBool ok = offscreenRenderer.render(root);
  qDebug() << "SbBool ok?" << ok;
  qDebug() << "wasFileWrittenRGB" << 
    offscreenRenderer.writeToRGB("C:/test-gl.rgb");
  qDebug() << "wasFileWrittenPS" << 
    offscreenRenderer.writeToPostScript("C:/test-gl.ps");


  unsigned char * imgbuffer = offscreenRenderer.getBuffer();
  return imgbuffer;
}

and then create a QImage from the buffer data:

QImage convertImgBuffer(){
  unsigned char *const imgBuffer = getCoinCubeImgBuffer();
  QImage img(imgBuffer, windowWidth, windowHeight, QImage::Format_ARGB32);

  // Important!
  img = img.rgbSwapped();

  QImage imgGL = convertToGLFormat(img);
  return imgGL;
}

Would this be the correct way to do it?

As described in this question about drawing a QImage, I'm able to draw it if the source is a picture.

e: To make sure that my buffer actually contains a scene, I write the buffer content to two files. You can view .rgb and .ps files for example with IrfanView plus its plugins.

e2: Just figured out, that I have to use img.rgbSwapped(). Now it's showing the scene black&white and without lightning. I will investigate further.

e3: With the code like this, you need to adapt the OpenGL call in this way to render in color

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tex.width(), 
    tex.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, tex.bits());

First format is GL_RGB, second GL_RGBA. The cube is still completely black though.

e4: It was an error in my scene, you have to add a light before you add the rest and especially before you add the camera.


So now I can either use `QGLWidget`s functions like `bindTexture()` or use direct OpenGL calls, but I'm not sure how exactly. It would be great if someone could push me in the right direction. Can't I just use OpenGL like this? glEnable(GL_TEXTURE_2D); glGenTextures(1, &offscreenBufferTexture); glBindTexture(GL_TEXTURE_2D, offscreenBufferTexture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, imgGL.width(), imgGL.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, imgGL.bits()); Or maybe `glDrawPixels()`? glDrawPixels(imgGL.width(), imgGL.height(), GL_RGBA, GL_UNSIGNED_BYTE, imgGL.bits());


I figured out how to draw a QImage with OpenGL, see this thread. So there seems to be a problem with the buffer or the conversion of it.


Solution

  • Here's the resulting code which renders the scene correctly.

    First create a valid scene

    void loadCoinScene(){
        // Init Coin
        SoDB::init();
        // The root node
        root = new SoSeparator;
        root->ref();
    
        // Add the light _before_ you add the camera
        SoDirectionalLight * light = new SoDirectionalLight;
        root->addChild(light);
    
        vpRegion.setViewportPixels(0, 0, coinSceneWidth, coinSceneHeight);
    
        SoPerspectiveCamera *perscam = new SoPerspectiveCamera();
        root->addChild(perscam);
    
        SbRotation cameraRotation = SbRotation::identity();
        cameraRotation *= SbRotation(SbVec3f(0, 1, 0), 0.4f);
        perscam->orientation = cameraRotation;
    
        SoCube * cube = new SoCube;
        root->addChild(cube);
        // make sure that the cube is visible
        perscam->viewAll(root, vpRegion);
    }
    

    Then render the scene into the Offscreen Buffer and convert it to a QImage:

    QImage getCoinCubeImgBuffer(){
        SoOffscreenRenderer offscreenRenderer(vpRegion);
        offscreenRenderer.setComponents(
          SoOffscreenRenderer::Components::RGB_TRANSPARENCY
        );
        offscreenRenderer.render(root);
    
        QImage img(offscreenRenderer.getBuffer(), coinSceneWidth, 
            coinSceneHeight, QImage::Format_ARGB32);
    
        // Important!
        return img.rgbSwapped();
    }
    

    If you now want to render the QImage with OpenGL, use my solution from my Render QImage with OpenGL question and change the loadTexture2() method to this:

    QImage loadTexture2(GLuint &textureID){
        glEnable(GL_TEXTURE_2D); // Enable texturing
    
        glGenTextures(1, &textureID); // Obtain an id for the texture
        glBindTexture(GL_TEXTURE_2D, textureID); // Set as the current texture
    
        QImage im = getCoinCubeImgBuffer();
        // Convert to OpenGLs unnamed format
        // The resulting GL format is GL_RGBA
        QImage tex = QGLWidget::convertToGLFormat(im);
    
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, tex.width(), tex.height(), 0, 
            GL_RGBA, GL_UNSIGNED_BYTE, tex.bits());
    
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
    
        glDisable(GL_TEXTURE_2D);
    
        return tex;
    }