Fun Trick: Draw OpenGL on StaticImage
Posted: Wed Mar 12, 2014 17:30
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
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
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
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.
Now all we need is our Update function to draw the stock value graph by calling the DrawOnTexture function.
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.
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.