Page 1 of 1

Fun Trick: Draw OpenGL on StaticImage

Posted: Wed Mar 12, 2014 17:30
by KishukuOni
I figured out how to do something pretty cool last night and I wanted to post it.
I would love to hear any corrections, criticisms, or suggestions.
I wanted to render something in OpenGL and then take a snapshot of it and put that inside a StaticImage. Parts of my program are running entirely in OpenGL, updated every frame, but imagine a mini-map. I want it embedded inside a Frame Window. I don't need to update it unless something major changes (new building, nuclear explosion, etc.). I already have all of the framework in place to draw all of the ground textures and landscape features in OpenGL. How do I get that to an image I can put inside a CEGUI element? It turns out, this is pretty simple to do, yay CEGUI.
For reference, I'm running CEGUI 0.8.3 and the Open GL Renderer.
Also, some of the sample code is using C++11, but that should be easy to rewrite in C++98 with functors or the like if that's your thing.

In my example I will be creating a stock tracker that draws the history of a stock in a graph. It will update each game day (in the game stocks change once a day). The image will be shown in a StaticImage in the stock tracker FrameWindow.

Setup Step 1 - Create an Empty Open GL Texture

Code: Select all

GLuint GenerateEmptyTexture(int width, int height) {
   GLuint tex;
   vector<unsigned int> data(width * height * 4);
   glGenTextures(1, &tex);
   glBindTexture(GL_TEXTURE_2D, tex);
   glTexImage2D(GL_TEXTURE_2D, 0, 4, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   glBindTexture(GL_TEXTURE_2D, 0);
   return tex;
}


Pretty straight forward so far. This just makes an empty texture of the desired dimensions . (Change 4 to 3 and GL_RGBA to GL_RGB if you don't need an Alpha channel).

Next I want to grab my CEGUI OpenGL renderer and create a CEGUI Texture based on this OpenGL texture.

Setup Step 2 - Create CEGUI Texture

Code: Select all

static const int HISTORY_TEXTURE_SIZE = 128;
OpenGLRenderer* renderer = dynamic_cast<OpenGLRenderer*>(System::getSingleton().getRenderer());
GLuint historyTex = GenerateEmptyTexture(HISTORY_TEXTURE_SIZE, HISTORY_TEXTURE_SIZE);   
Texture& texture = renderer->createTexture("MarketHistoryTex", historyTex, Sizef(HISTORY_TEXTURE_SIZE, HISTORY_TEXTURE_SIZE));


Now I want to create a CEGUI Image that I can set as the Image Property of my StaticImage.

Setup Step 3 - Create CEGUI Image

Code: Select all

BasicImage& image = static_cast<BasicImage&>(ImageManager::getSingleton().create("BasicImage", "MarketHistory"));
image.setTexture(&texture);
image.setArea(Rectf(0, 0, HISTORY_TEXTURE_SIZE, HISTORY_TEXTURE_SIZE));
image.setAutoScaled(AutoScaledMode::ASM_Both);
root->getChild("History")->setProperty("Image", "MarketHistory");


At this point your StaticImage will just show a blank white 128x128 image, if you want to test the setup portion.

Next I need a function that draws the OpenGL and copies it onto my OpenGL texture.
Now most people will tell you to use Frame Buffer Objects but I'm going OpenGL 1.0 on this. (Mainly because the rest of the OpenGL in the game is written OpenGL 1.0 style). If you are interested in using more modern techniques, the web has lots of examples.
Because I will want to reuse this function with more than just my stock market texture, I will pass the specific OpenGL drawing routine in as a function pointer.

Code: Select all

void DrawOnTexture(GLuint tex, int width, int height, std::function<void(void)> draw) {
   int viewport[4];
   glGetIntegerv(GL_VIEWPORT, viewport);
   glViewport(0, 0, width, height); // change viewport to 128x128
   glClear(GL_COLOR_BUFFER_BIT); // and clear depth bit, if required
   draw();
   glBindTexture(GL_TEXTURE_2D, tex);
   glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 0, 0, width, height, 0); // copy view into my texture
   glBindTexture(GL_TEXTURE_2D, 0);
   glViewport(viewport[0], viewport[1], viewport[2], viewport[3]);    // restore viewport
}


Now all we need is our Update function to draw the stock value graph by calling the DrawOnTexture function.

Code: Select all

DrawOnTexture(historyTex, HISTORY_TEXTURE_SIZE, HISTORY_TEXTURE_SIZE, [=, &stock]() {
   float scale = stock.MaxPrice() - stock.MinPrice();
   glColor3f(1.0f, 0.05f, 0.05f);
   glLineWidth(2.0f);
   glScale(window_width, window_height, 0.0f);
   glBegin(GL_LINES);
   for (size_t i(1); i < stock.History().size(); ++i) {
      glVertex2f(static_cast<float>(x-1) / (stock.History().size()-1),
            (stock.History()[x-1] - stock.MinPrice()) / scale);
      glVertex2f(static_cast<float>(x) / (stock.History().size()-1),
            (stock.History()[x] - stock.MinPrice()) / scale);
   }
   glEnd();
});


Done. Everything else magically works. Now I will have a graph in my StaticImage.
The OpenGL texture is referenced in the CEGUI Texture which is referenced in the StaticImage. I love how this is all neatly tied together. CEGUI is awesome.
Have Fun,
K.

Re: Fun Trick: Draw OpenGL on StaticImage

Posted: Wed Mar 12, 2014 17:55
by Ident
EDIT2: Okay i just saw you wanna support OpenGL 1.X. In that case the following message mostly applies to anyone who will support higher OpenGL versions, especially 3.X+, both mipmapping and FBOs are handled differently (better) in 3.X+.


A little hint. You can use mipmapping for your texture minification. If you render a large texture on a polygon or CEGUI Window that appears much smaller on the screen it will increase the quality of the rendered image a lot. There is no real downside and it is supported on pretty much all hardware.

Here is how to adjust the texture parameters, i also added the wrapping parameters which you forgot to set btw.:

Code: Select all

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);


You will need to generate mipmaps everytime you change the top level texture, by using glGenerateMipmap(...):

Code: Select all

 glGenerateMipmap(GL_TEXTURE_2D);

Also call this after your calls to glTexImage2D, to allocate the memory for the mipmaps!



Thanks for your code snippets, I am sure someone will be glad about this!