Curious slowdown in opengl when using instanced rendering - performance
I've run into a quite odd performance issue. So far I've reduced the problem to this: I'm rendering 20x20x20 cubes in a grid, using glDrawElementsInstanced, which works fine as long as my camera is far away from the origin, however when it gets closer to the origin, it starts grinding to a halt.
I'm defining my model view projection matrix through:
float distance=3.8;
Projection = glm::perspective(65.0f, (float)(width)/height, 0.1f, 300.0f);
View = glm::lookAt( glm::vec3(0,0,-distance),
glm::vec3(0,0,10),
glm::vec3(0,1,0));
Model = glm::rotate(glm::mat4(1.0f), 0.0f, glm::vec3(0.25f, 1.0f,0.75f));
With distance at 40, there's no problems, but when distance decreases to about 3.8 and lower, everything grinds to a halt.
The actual call to rendering is carried out through:
glBindVertexArray(cubeVAO);
glDrawElementsInstanced(GL_TRIANGLES, indices.size(),GL_UNSIGNED_INT,(GLvoid*)(0),latticePoints.size());
While putting all the vertices in a single buffer and rendering by calling:
glBindVertexArray(nonInstancedVAO);
glDrawArrays(GL_TRIANGLES, 0,vertices.size() );
Completely removes the behavior. Anyone who's experienced similar behavior who can point me in the direction of a solution? If failing that, anyone who's got an idea of how to track down something like this? I hoped I would be able to determine what was causing the slowdown using gDEBugger, however that just reconfirms that there aren't any other opengl calls, and doesn't really help figuring out what's taking up all the processing time.
Another note is that glDrawArraysInstanced also shows the same slowdown, and that splitting the call into 4 separate calls with a quarter of the geometry each also stops the slowdown.
Update
Here's an attempt at a minimal illustration of the problem.
//Minimal reproduction of problem
#include <stdio.h>
#include <string>
#include <fstream>
#include <stdlib.h>
#include <string.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
// Include GLM
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <vector>
#include <iostream>
#include <stdio.h>
//Set to true to use instanced rendering (glDrawElementsInstanced), false to render a generated grid instead (glDrawElements)
#define Instanced true
//Translation from origin. Problme is pressent at 0 distance, but disapears at ex. 40.
const float distanceFromOrigin=0;
// Function to load shaders
GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path);
int main(){
int width, height;
bool running = true;
// Initialise GLFW
glfwInit();
glfwWindowHint(GLFW_SAMPLES,1);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT,GL_TRUE);
glfwWindowHint(GLFW_VERSION_MAJOR, 4);
GLFWwindow* windowRef = glfwCreateWindow( 512, 512, "",0,0);
glfwMakeContextCurrent(windowRef);
glewInit();
//Load Shader
GLuint programID = LoadShaders( "Simple.vs.c", "Simple.fs.c" );
GLuint MatrixID = glGetUniformLocation(programID, "MVP");
glUseProgram(programID);
glm::mat4 Model,Projection,MVP,View,checkMVP;
std::vector<GLuint> sqIndice = {3,2,1,1,0,3,4,5,6,6,7,4,0,4,7,7,3,0,0,1,5,5,4,0,2,3,7,7,6,2,6,5,1,1,2,6,0,4,7,7,3,0};
std::vector<GLfloat> sqVertex = {-1, 1, -1, -1, 1, 1, -1, -1, 1, -1, -1, -1, 1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, -1};
std::vector<GLfloat> sqColor = {0.2472,0.24,0.6,0.6,0.24,0.442893,0.6,0.547014,0.24,0.24,0.6,0.33692,0.24,0.353173,0.6,0.6,0.24,0.563266,0.6,0.426641,0.24,0.263452,0.6,0.24};
const float lattice = 5;
const int mxn = 10;
std::vector<GLfloat> v1 = {lattice,-1,0};
std::vector<GLfloat> v2 = {1,lattice,0};
std::vector<GLfloat> v3 = {0,0,lattice};
std::vector<GLfloat> offset = {0,0,-distanceFromOrigin};
std::vector<GLfloat> latticePoints,sqVertexGrid,sqColorGrid;// = {0,0,0};
std::vector<GLuint> sqIndiceGrid;
// Looping stuff to generate the full grid of "instances" to render in a single call.
int instanceCount=0;
//Generate Lattice vectors, aswell as a vector containing the full grids of indices,vertexes and colors
for(int x=-mxn;x<mxn;++x){
for(int y=-mxn;y<mxn;++y){
for(int z=-mxn;z<mxn;++z){
for(int n=0;n<3;++n){
latticePoints.push_back( x*v1[n]+y*v2[n]+z*v3[n]+offset[n] );
};
for(int elm=0;elm<sqVertex.size();elm+=3){
for(int n=0;n<3;++n){
sqVertexGrid.push_back(sqVertex[elm+n]+x*v1[n]+y*v2[n]+z*v3[n]+offset[n]);
sqColorGrid.push_back(sqColor[elm+n]);
};
};
for(int elm=0;elm<sqIndice.size();++elm){
sqIndiceGrid.push_back(sqIndice[elm]+instanceCount*sqVertex.size()/3);
};
++instanceCount;glewInit
};
};
};
#if Instanced==true
//Initialize and fill vertex,color and indice buffers with the relevant data.
GLuint cubeVAO;
glGenVertexArrays(1, &cubeVAO);
glBindVertexArray(cubeVAO);
glEnable(GL_DEPTH_TEST);
//Vertex buffer
GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sqVertex.size()*sizeof(GLfloat), &sqVertex[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,(void*)0);
//Color buffer
GLuint colorBuffer;
glGenBuffers(1, &colorBuffer);
glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
glBufferData(GL_ARRAY_BUFFER, sqColor.size()*sizeof(GLfloat), &sqColor[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,0,(void*)0);
// Indice buffer
GLuint indicesBuffer;
glGenBuffers(1, &indicesBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sqIndice.size()*sizeof(GLuint), &sqIndice[0], GL_STATIC_DRAW);
//Lattice point buffer
GLuint latticePointBuffer;
glGenBuffers(1, &latticePointBuffer);
glBindBuffer(GL_ARRAY_BUFFER, latticePointBuffer);
glBufferData(GL_ARRAY_BUFFER, latticePoints.size()*sizeof(GLfloat), &latticePoints[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2,3,GL_FLOAT,GL_FALSE,0,(void*)0);
glVertexAttribDivisor(2,1);
glBindVertexArray(0);
#elif Instanced==false
GLuint cubeGridVAO;
glGenVertexArrays(1, &cubeGridVAO);
glBindVertexArray(cubeGridVAO);
glEnable(GL_DEPTH_TEST);
//Vertex buffer
GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
glBufferData(GL_ARRAY_BUFFER, sqVertexGrid.size()*sizeof(GLfloat), &sqVertexGrid[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,(void*)0);
//Color buffer
GLuint colorBuffer;
glGenBuffers(1, &colorBuffer);
glBindBuffer(GL_ARRAY_BUFFER, colorBuffer);
glBufferData(GL_ARRAY_BUFFER, sqColorGrid.size()*sizeof(GLfloat), &sqColorGrid[0], GL_STATIC_DRAW);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1,3,GL_FLOAT,GL_FALSE,0,(void*)0);
// Indice buffer
GLuint indicesBuffer;
glGenBuffers(1, &indicesBuffer);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sqIndiceGrid.size()*sizeof(GLuint), &sqIndiceGrid[0], GL_STATIC_DRAW);
glBindVertexArray(0);
#endif
while(running)
{
glfwGetFramebufferSize(windowRef, &width, &height);
height = height > 0 ? height : 1;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Projection = glm::perspective(65.0f, (float)(width)/height, 0.1f, 300.0f);
View = glm::lookAt( glm::vec3(0.0f,0.0f,-(distanceFromOrigin+3.8f)),
glm::vec3(0.0f,0.0f,100.0f),
glm::vec3(0.0f,1.0f,0.0f));
Model = glm::rotate(glm::mat4(1.0f), 0.0f, glm::vec3(0.25f, 1.0f,0.75f));
MVP = Projection*View*Model;
glUniformMatrix4fv(MatrixID, 1, GL_FALSE, glm::value_ptr(MVP));
#if Instanced==true
glBindVertexArray(cubeVAO);
glDrawElementsInstanced(GL_TRIANGLES, sqIndice.size(),GL_UNSIGNED_INT,(GLvoid*)(0),latticePoints.size());
#elif Instanced==false
glBindVertexArray(cubeGridVAO);
glDrawElements(GL_TRIANGLES, sqIndiceGrid.size(),GL_UNSIGNED_INT,(GLvoid*)(0));
#endif
glfwPollEvents();
glfwSwapBuffers(windowRef);
std::cout<<".\n";
running = !glfwGetKey(windowRef,GLFW_KEY_ESCAPE) && !glfwWindowShouldClose(windowRef);
}
glfwDestroyWindow(windowRef);
glfwTerminate();
return 0;
};
GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path){
// Create the shaders
GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
// Read the Vertex Shader code from the file
std::string VertexShaderCode;
std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
if(VertexShaderStream.is_open()){
std::string Line = "";
while(getline(VertexShaderStream, Line))
VertexShaderCode += "\n" + Line;
VertexShaderStream.close();
}else{
printf("Impossible to open %s. Are you in the right directory?\n", vertex_file_path);
return 0;
}
// Read the Fragment Shader code from the file
std::string FragmentShaderCode;
std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in);
if(FragmentShaderStream.is_open()){
std::string Line = "";
while(getline(FragmentShaderStream, Line))
FragmentShaderCode += "\n" + Line;
FragmentShaderStream.close();
}
GLint Result = GL_FALSE;
int InfoLogLength;
// Compile Vertex Shader
printf("Compiling shader : %s\n", vertex_file_path);
char const * VertexSourcePointer = VertexShaderCode.c_str();
glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
glCompileShader(VertexShaderID);
// Check Vertex Shader
glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> VertexShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
printf("%s\n", &VertexShaderErrorMessage[0]);
}
// Compile Fragment Shader
printf("Compiling shader : %s\n", fragment_file_path);
char const * FragmentSourcePointer = FragmentShaderCode.c_str();
glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
glCompileShader(FragmentShaderID);
// Check Fragment Shader
glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1);
glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
printf("%s\n", &FragmentShaderErrorMessage[0]);
}
// Link the program
printf("Linking program\n");
GLuint ProgramID = glCreateProgram();
glAttachShader(ProgramID, VertexShaderID);
glAttachShader(ProgramID, FragmentShaderID);
glLinkProgram(ProgramID);
// Check the program
glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
if ( InfoLogLength > 0 ){
std::vector<char> ProgramErrorMessage(InfoLogLength+1);
glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
printf("%s\n", &ProgramErrorMessage[0]);
}
glDeleteShader(VertexShaderID);
glDeleteShader(FragmentShaderID);
return ProgramID;
}
Ok, take a deep breath and take a seat: your problem is graphic card memory speed.
But you could make it easier for the GPU by fixing this bug:
glDrawElementsInstanced(GL_TRIANGLES, sqIndice.size(),GL_UNSIGNED_INT,(GLvoid*)(0),latticePoints.size());
glDrawElementsInstanced expects the number of instances to draw as the last parameter. But you pass the number of elements in latticePoints. That's 3 times the number of instances. This results in a zero lettice point inside the shader (because of prevented out of bounds access). So 16000 cubes are not translated and are painted at the same position. This results in painting the front face of the cubes 16000 times. The depth buffer doesn't prevent this because the faces do not conceal each other, they are on the same spot.
So when your distanceFromOrigin decreases the 16000 center cubes get bigger and bigger. OpenGL has to draw more and more pixels. A lot more, to be exact. It has to draw so much that it hits the speed limit of the graphic card memory.
Read Diagnose OpenGl Performance Problems for the whole story.
Related
Vertex buffers in open gl es 1.X
I am teaching myself about open gl es and vertex buffer (VBO) and I have written code and it is supposed to draw one red triangle but instead it colours the screen black: - (void)drawRect:(CGRect)rect { // Draw a red triangle in the middle of the screen: glColor4f(1.0f, 0.0f, 0.0f, 1.0f); // Setup the vertex data: typedef struct { float x; float y; } Vertex; const Vertex vertices[] = {{50,50}, {50,150}, {150,50}}; const short indices[3] = {0,1,2}; glGenBuffers(1, &vertexBuffer); glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); NSLog(#"drawrect"); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(3, GL_FLOAT, 0, 0); // The following line does the actual drawing to the render buffer: glDrawElements(GL_TRIANGLE_STRIP, 3, GL_UNSIGNED_SHORT, indices); glBindRenderbufferOES(GL_RENDERBUFFER_OES, framebuffer); [eAGLcontext presentRenderbuffer:GL_RENDERBUFFER_OES]; } Here vertexBuffer is of type GLuint. What is going wrong? Thanks for your help.
Your vertices dont have a Z component, try {{50,50,-100}, {50,150,-100}, {150,50,-100}}; (your camera by default looks down the Z axis so putting it in the -Z should put it on screen) if you cant see it still try smaller numbers, im not sure what your near and far draw cutoff distance is, and if its not even set i dont know what the default is. This might not be the only issue but its the only one i can see by just looking quickly at it.
You need to add glViewport(0, 0, 320, 480); where you create the frame buffer and set up the context. And replace your call to glDrawElements with glDrawArrays(GL_TRIANGLE_STRIP, ...);
thread 1 :program received signal exc_bad_access?
currently i am working in simple game app, using GL_TRIANGLE_STRIP to draw a ball image (Moving left to right) and using GL_LINES to draw a line in Touch began Delegate, when i touch the screen app will quit like program received signal exc_bad_access, i spend 14 hours but i cannot fix this, please help me any one. Thanks in Advance I tried the source code: -(void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { Test =YES; [self.effect prepareToDraw]; const GLfloat line[] = { 0, 2, 0,-3, }; GLuint bufferObjectNameArray; glGenBuffers(1, &bufferObjectNameArray); glBindBuffer(GL_ARRAY_BUFFER, bufferObjectNameArray); glBufferData( GL_ARRAY_BUFFER, // the target buffer sizeof(line), // the number of bytes to put into the buffer line, // a pointer to the data being copied GL_STATIC_DRAW); // the usage pattern of the data glEnableVertexAttribArray(GLKVertexAttribPosition); glVertexAttribPointer( GLKVertexAttribPosition, // the currently bound buffer holds the data 2, // number of coordinates per vertex GL_FLOAT, // the data type of each component GL_FALSE, // can the data be scaled 2*4, // how many bytes per vertex (2 floats per vertex) NULL); // offset to the first coordinate, in this case 0 glDrawArrays(GL_Lines,0,3); } - (void)glkView:(GLKView *)view drawInRect:(CGRect)rect { glClearColor(1, 1, 1, 1); glClear(GL_COLOR_BUFFER_BIT); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); NSLog(#"Children count:%d",[self.children count]); // Store ball image in NSMutable array for (SGGSprite * sprite in self.children) { self.effect.texture2d0.name = self.textureInfo.name; self.effect.texture2d0.enabled = YES; GLKMatrix4 modelMatrix = GLKMatrix4Identity; modelMatrix = GLKMatrix4Translate(modelMatrix, -self.position.x, -self.position.y, 0); modelMatrix = GLKMatrix4Translate(modelMatrix, self.contentSize.width, self.contentSize.height+140, 0); return modelMatrix; [self.effect prepareToDraw]; long offset = (long)&_quad; glEnableVertexAttribArray(GLKVertexAttribPosition); glEnableVertexAttribArray(GLKVertexAttribTexCoord0); NSLog(#"TexturedVertex:%ld",offset); glVertexAttribPointer(GLKVertexAttribPosition, 2, GL_FLOAT, GL_FALSE, sizeof(TexturedVertex), (void *) (offset + offsetof(TexturedVertex, geometryVertex))); glVertexAttribPointer(GLKVertexAttribTexCoord0, 2, GL_FLOAT, GL_FALSE, sizeof(TexturedVertex), (void *) (offset + offsetof(TexturedVertex, textureVertex))); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); // When i tab the screen, the app will quit in this line like thread 1 :program received signal exc_bad_access. } }
Open GLES 1.1 - GLColorPointer creates a rainbow of colours when I just want red
I'm rendering an object like this: for (int i = 0; i < COLOR_ARRAY_SIZE; i += 4) { colors[i] = 1.0f; colors[i + 1] = 0.0f; colors[i + 2] = 0.0f; colors[i + 3] = 1.0f; } // Clear color and depth buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Set GL11 flags: glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glEnable(GL_DEPTH_TEST); // make sure nothing messes with the colour glDisable(GL_BLEND); glDisable(GL_DITHER); glDisable(GL_FOG); glDisable(GL_LIGHTING); glDisable(GL_TEXTURE_2D); glShadeModel(GL_FLAT); // Load projection matrix: glMatrixMode(GL_PROJECTION); glLoadMatrixf(projectionMatrix); // Load model view matrix and scale appropriately int kObjectScale = 300f; glMatrixMode(GL_MODELVIEW); glLoadMatrixf(modelViewMatrix); glTranslatef(0.5f, 0.5f, 0.5f); glScalef(kObjectScale, kObjectScale, kObjectScale); // Draw object glVertexPointer(3, GL_FLOAT, 0, (const GLvoid*) &vertexPositions[0]); glNormalPointer(GL_FLOAT, 0, (const GLvoid*) &vertexNormals[0]); glColorPointer(4, GL_FLOAT, 0, (const GLvoid*) &colors[0]); glDrawElements(GL_TRIANGLES, 11733, GL_UNSIGNED_SHORT, (const GLvoid*) &indices[0]); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_COLOR_ARRAY); I would expect that this would render my object all in red, but instead it's a rainbow of different colours. Does anyone know why? I would assume that there is something wrong with my "colors" array buffer but I can't for the life of me see what it is. The actual vertices seem to rendered just fine.
Your for loop is very confused. You're incrementing your value of i by 4 each time. What's more is that you're indexing with an offset of 1, 2 and 3 in lines 3-5. I presume that your define of COLOR_ARRAY_SIZE is 4? Try initializing your color array as follows: float colors[] = {1.0f, 0.0f, 0.0f, 1.0f}; And then calling glColorPointer as follows: glColorPointer(4, GL_FLOAT, 0, colors); Notice that I've set the stride to be 0. If your color array only contains colors then I don't see any reason why you should be using a stride (stride is used to jump over interwoven information in an array).
Rendering 2D sprites in a 3D world?
How do I render 2D sprites in OpenGL given that I have a png of the sprite? See images as an example of the effect I'd like to achieve. Also I would like to overlay weapons on the screen like the rifle in the bottom image. Does anyone know how I would achieve the two effects? Any help is greatly appreciated.
In 3D terms, this is called a "billboard". A billboard is completely flat 2D plane with a texture on it and it always faces the camera. See here for a pure OpenGL implementation: http://nehe.gamedev.net/data/articles/article.asp?article=19 Just about any 3D engine should be able to do them by default. Ogre3D can do it, for instance.
a) For the first case: That's not really 2D sprites. Those men seem to be rendered as single quads with a texture with some kind of transparency (either alpha test or alpha blending). Anyway, even a single quad can still be considered a 3D object, so for such situation you might want to treat it as one: track its translation and rotation and render it in the same way as any other 3D object. b) For the second case: If you want the gun (a 2D picture, I pressume) to be rendered in the same place without any perspective transformation, then you can use the same technique one uses for drawing the GUI (etc). Have a look at my post here: 2D overlay on a 3D scene
For the overlaying of the 2D weapon, you can use glOrtho for the camera view.
You create a 3d quad and map the .png-based texture to it. You can make the quad face whatever direction you want, as in the first picture, or make it always facing the camera (like a billboard, mentioned by Svenstaro) as in your second picture. Though, to be fair, I am sure that second picture just blitted the image (with some scaling) directly in the software-created framebuffer (that looks like Wolf3d tech, software rendering).
Take a look at OpenGL Point Sprites: http://www.informit.com/articles/article.aspx?p=770639&seqNum=7 Especially useful for partical systems but may do the trick for your purposes.
Check this tutorial about billboards. I think you'll find useful. http://www.lighthouse3d.com/opengl/billboarding/
opengl-tutorial has: a tutorial http://www.opengl-tutorial.org/intermediate-tutorials/billboards-particles/billboards/ focused on energy bars OpenGL 3.3+ WTF licensed code that just works: https://github.com/opengl-tutorials/ogl/blob/71cad106cefef671907ba7791b28b19fa2cc034d/tutorial18_billboards_and_particles/tutorial18_billboards.cpp Screenshot: Code: #include <stdio.h> #include <stdlib.h> #include <vector> #include <algorithm> #include <GL/glew.h> #include <glfw3.h> GLFWwindow* window; #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> #include <glm/gtx/norm.hpp> using namespace glm; #include <common/shader.hpp> #include <common/texture.hpp> #include <common/controls.hpp> #define DRAW_CUBE // Comment or uncomment this to simplify the code int main( void ) { // Initialise GLFW if( !glfwInit() ) { fprintf( stderr, "Failed to initialize GLFW\n" ); getchar(); return -1; } glfwWindowHint(GLFW_SAMPLES, 4); glfwWindowHint(GLFW_RESIZABLE,GL_FALSE); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Open a window and create its OpenGL context window = glfwCreateWindow( 1024, 768, "Tutorial 18 - Billboards", NULL, NULL); if( window == NULL ){ fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" ); getchar(); glfwTerminate(); return -1; } glfwMakeContextCurrent(window); // Initialize GLEW glewExperimental = true; // Needed for core profile if (glewInit() != GLEW_OK) { fprintf(stderr, "Failed to initialize GLEW\n"); getchar(); glfwTerminate(); return -1; } // Ensure we can capture the escape key being pressed below glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE); // Hide the mouse and enable unlimited mouvement glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // Set the mouse at the center of the screen glfwPollEvents(); glfwSetCursorPos(window, 1024/2, 768/2); // Dark blue background glClearColor(0.0f, 0.0f, 0.4f, 0.0f); // Enable depth test glEnable(GL_DEPTH_TEST); // Accept fragment if it closer to the camera than the former one glDepthFunc(GL_LESS); GLuint VertexArrayID; glGenVertexArrays(1, &VertexArrayID); glBindVertexArray(VertexArrayID); // Create and compile our GLSL program from the shaders GLuint programID = LoadShaders( "Billboard.vertexshader", "Billboard.fragmentshader" ); // Vertex shader GLuint CameraRight_worldspace_ID = glGetUniformLocation(programID, "CameraRight_worldspace"); GLuint CameraUp_worldspace_ID = glGetUniformLocation(programID, "CameraUp_worldspace"); GLuint ViewProjMatrixID = glGetUniformLocation(programID, "VP"); GLuint BillboardPosID = glGetUniformLocation(programID, "BillboardPos"); GLuint BillboardSizeID = glGetUniformLocation(programID, "BillboardSize"); GLuint LifeLevelID = glGetUniformLocation(programID, "LifeLevel"); GLuint TextureID = glGetUniformLocation(programID, "myTextureSampler"); GLuint Texture = loadDDS("ExampleBillboard.DDS"); // The VBO containing the 4 vertices of the particles. static const GLfloat g_vertex_buffer_data[] = { -0.5f, -0.5f, 0.0f, 0.5f, -0.5f, 0.0f, -0.5f, 0.5f, 0.0f, 0.5f, 0.5f, 0.0f, }; GLuint billboard_vertex_buffer; glGenBuffers(1, &billboard_vertex_buffer); glBindBuffer(GL_ARRAY_BUFFER, billboard_vertex_buffer); glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_DYNAMIC_DRAW); #ifdef DRAW_CUBE // Everything here comes from Tutorial 4 GLuint cubeProgramID = LoadShaders( "../tutorial04_colored_cube/TransformVertexShader.vertexshader", "../tutorial04_colored_cube/ColorFragmentShader.fragmentshader" ); GLuint cubeMatrixID = glGetUniformLocation(cubeProgramID, "MVP"); static const GLfloat g_cube_vertex_buffer_data[] = { -1.0f,-1.0f,-1.0f,-1.0f,-1.0f, 1.0f,-1.0f, 1.0f, 1.0f,1.0f, 1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f, 1.0f,-1.0f,1.0f,-1.0f, 1.0f,-1.0f,-1.0f,-1.0f,1.0f,-1.0f,-1.0f,1.0f, 1.0f,-1.0f,1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f,-1.0f, 1.0f, 1.0f,-1.0f, 1.0f,-1.0f,1.0f,-1.0f, 1.0f,-1.0f,-1.0f, 1.0f,-1.0f,-1.0f,-1.0f,-1.0f, 1.0f, 1.0f,-1.0f,-1.0f, 1.0f,1.0f,-1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f,-1.0f,-1.0f,1.0f, 1.0f,-1.0f,1.0f,-1.0f,-1.0f,1.0f, 1.0f, 1.0f,1.0f,-1.0f, 1.0f,1.0f, 1.0f, 1.0f,1.0f, 1.0f,-1.0f,-1.0f, 1.0f,-1.0f,1.0f, 1.0f, 1.0f,-1.0f, 1.0f,-1.0f,-1.0f, 1.0f, 1.0f,1.0f, 1.0f, 1.0f,-1.0f, 1.0f, 1.0f,1.0f,-1.0f, 1.0f}; static const GLfloat g_cube_color_buffer_data[] = { 0.583f, 0.771f, 0.014f,0.609f, 0.115f, 0.436f,0.327f, 0.483f, 0.844f,0.822f, 0.569f, 0.201f,0.435f, 0.602f, 0.223f,0.310f, 0.747f, 0.185f,0.597f, 0.770f, 0.761f,0.559f, 0.436f, 0.730f,0.359f, 0.583f, 0.152f,0.483f, 0.596f, 0.789f,0.559f, 0.861f, 0.639f,0.195f, 0.548f, 0.859f,0.014f, 0.184f, 0.576f,0.771f, 0.328f, 0.970f,0.406f, 0.615f, 0.116f,0.676f, 0.977f, 0.133f,0.971f, 0.572f, 0.833f,0.140f, 0.616f, 0.489f,0.997f, 0.513f, 0.064f,0.945f, 0.719f, 0.592f,0.543f, 0.021f, 0.978f,0.279f, 0.317f, 0.505f,0.167f, 0.620f, 0.077f,0.347f, 0.857f, 0.137f,0.055f, 0.953f, 0.042f,0.714f, 0.505f, 0.345f,0.783f, 0.290f, 0.734f,0.722f, 0.645f, 0.174f,0.302f, 0.455f, 0.848f,0.225f, 0.587f, 0.040f,0.517f, 0.713f, 0.338f,0.053f, 0.959f, 0.120f,0.393f, 0.621f, 0.362f,0.673f, 0.211f, 0.457f,0.820f, 0.883f, 0.371f,0.982f, 0.099f, 0.879f}; GLuint cubevertexbuffer; glGenBuffers(1, &cubevertexbuffer); glBindBuffer(GL_ARRAY_BUFFER, cubevertexbuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(g_cube_vertex_buffer_data), g_cube_vertex_buffer_data, GL_DYNAMIC_DRAW); GLuint cubecolorbuffer; glGenBuffers(1, &cubecolorbuffer); glBindBuffer(GL_ARRAY_BUFFER, cubecolorbuffer); glBufferData(GL_ARRAY_BUFFER, sizeof(g_cube_color_buffer_data), g_cube_color_buffer_data, GL_DYNAMIC_DRAW); #endif double lastTime = glfwGetTime(); do { // Clear the screen glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); double currentTime = glfwGetTime(); double delta = currentTime - lastTime; lastTime = currentTime; computeMatricesFromInputs(); glm::mat4 ProjectionMatrix = getProjectionMatrix(); glm::mat4 ViewMatrix = getViewMatrix(); #ifdef DRAW_CUBE // Again : this is just Tutorial 4 ! glDisable(GL_BLEND); glUseProgram(cubeProgramID); glm::mat4 cubeModelMatrix(1.0f); cubeModelMatrix = glm::scale(cubeModelMatrix, glm::vec3(0.2f, 0.2f, 0.2f)); glm::mat4 cubeMVP = ProjectionMatrix * ViewMatrix * cubeModelMatrix; glUniformMatrix4fv(cubeMatrixID, 1, GL_FALSE, &cubeMVP[0][0]); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, cubevertexbuffer); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0 ); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, cubecolorbuffer); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, (void*)0 ); glDrawArrays(GL_TRIANGLES, 0, 12*3); glDisableVertexAttribArray(0); glDisableVertexAttribArray(1); #endif // We will need the camera's position in order to sort the particles // w.r.t the camera's distance. // There should be a getCameraPosition() function in common/controls.cpp, // but this works too. glm::vec3 CameraPosition(glm::inverse(ViewMatrix)[3]); glm::mat4 ViewProjectionMatrix = ProjectionMatrix * ViewMatrix; glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Use our shader glUseProgram(programID); // Bind our texture in Texture Unit 0 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, Texture); // Set our "myTextureSampler" sampler to user Texture Unit 0 glUniform1i(TextureID, 0); // This is the only interesting part of the tutorial. // This is equivalent to mlutiplying (1,0,0) and (0,1,0) by inverse(ViewMatrix). // ViewMatrix is orthogonal (it was made this way), // so its inverse is also its transpose, // and transposing a matrix is "free" (inversing is slooow) glUniform3f(CameraRight_worldspace_ID, ViewMatrix[0][0], ViewMatrix[1][0], ViewMatrix[2][0]); glUniform3f(CameraUp_worldspace_ID , ViewMatrix[0][1], ViewMatrix[1][1], ViewMatrix[2][1]); glUniform3f(BillboardPosID, 0.0f, 0.5f, 0.0f); // The billboard will be just above the cube glUniform2f(BillboardSizeID, 1.0f, 0.125f); // and 1m*12cm, because it matches its 256*32 resolution =) // Generate some fake life level and send it to glsl float LifeLevel = sin(currentTime)*0.1f + 0.7f; glUniform1f(LifeLevelID, LifeLevel); glUniformMatrix4fv(ViewProjMatrixID, 1, GL_FALSE, &ViewProjectionMatrix[0][0]); // 1rst attribute buffer : vertices glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, billboard_vertex_buffer); glVertexAttribPointer( 0, // attribute. No particular reason for 0, but must match the layout in the shader. 3, // size GL_FLOAT, // type GL_FALSE, // normalized? 0, // stride (void*)0 // array buffer offset ); // Draw the billboard ! // This draws a triangle_strip which looks like a quad. glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray(0); // Swap buffers glfwSwapBuffers(window); glfwPollEvents(); } // Check if the ESC key was pressed or the window was closed while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS && glfwWindowShouldClose(window) == 0 ); // Cleanup VBO and shader glDeleteBuffers(1, &billboard_vertex_buffer); glDeleteProgram(programID); glDeleteTextures(1, &TextureID); glDeleteVertexArrays(1, &VertexArrayID); #ifdef DRAW_CUBE glDeleteProgram(cubeProgramID); glDeleteVertexArrays(1, &cubevertexbuffer); glDeleteVertexArrays(1, &cubecolorbuffer); #endif // Close OpenGL window and terminate GLFW glfwTerminate(); return 0; } Tested on Ubuntu 15.10. Axis oriented version of this question: https://gamedev.stackexchange.com/questions/35946/how-do-i-implement-camera-axis-aligned-billboards Here we have done a viewpoint oriented billboard.
How to display unicode text in OpenGL?
Is there a good way for displaying unicode text in opengl under Windows? For example, when you have to deal with different languages. The most common approach like #define FONTLISTRANGE 128 GLuint list; list = glGenLists(FONTLISTRANGE); wglUseFontBitmapsW(hDC, 0, FONTLISTRANGE, list); just won't do because you can't create enough lists for all unicode characters.
You should also check out the FTGL library. FTGL is a free cross-platform Open Source C++ library that uses Freetype2 to simplify rendering fonts in OpenGL applications. FTGL supports bitmaps, pixmaps, texture maps, outlines, polygon mesh, and extruded polygon rendering modes. This project was dormant for awhile, but is recently back under development. I haven't updated my project to use the latest version, but you should check it out. It allows for using any True Type Font via the FreeType font library.
I recommend reading this OpenGL font tutorial. It's for the D programming language but it's a nice introduction to various issues involved in implementing a glyph caching system for rendering text with OpenGL. The tutorial covers Unicode compliance, antialiasing, and kerning techniques. D is pretty comprehensible to anyone who knows C++ and most of the article is about the general techniques, not the implementation language.
Id recommend FTGL as already recommended above, however I have implemented a freetype/OpenGL renderer myself and thought you might find the code handy if you want reinvent this wheel yourself. I'd really recommend FTGL though, its a lot less hassle to use. :) * glTextRender class by Semi Essessi * * FreeType2 empowered text renderer * */ #include "glTextRender.h" #include "jEngine.h" #include "glSystem.h" #include "jMath.h" #include "jProfiler.h" #include "log.h" #include <windows.h> FT_Library glTextRender::ftLib = 0; //TODO::maybe fix this so it use wchar_t for the filename glTextRender::glTextRender(jEngine* j, const char* fontName, int size = 12) { #ifdef _DEBUG jProfiler profiler = jProfiler(L"glTextRender::glTextRender"); #endif char fontName2[1024]; memset(fontName2,0,sizeof(char)*1024); sprintf(fontName2,"fonts\\%s",fontName); if(!ftLib) { #ifdef _DEBUG wchar_t fn[128]; mbstowcs(fn,fontName,strlen(fontName)+1); LogWriteLine(L"\x25CB\x25CB\x25CF Font: %s was requested before FreeType was initialised", fn); #endif return; } // constructor code for glTextRender e=j; gl = j->gl; red=green=blue=alpha=1.0f; face = 0; // remember that for some weird reason below font size 7 everything gets scrambled up height = max(6,(int)floorf((float)size*((float)gl->getHeight())*0.001666667f)); aHeight = ((float)height)/((float)gl->getHeight()); setPosition(0.0f,0.0f); // look in base fonts dir if(FT_New_Face(ftLib, fontName2, 0, &face )) { // if we dont have it look in windows fonts dir char buf[1024]; GetWindowsDirectoryA(buf,1024); strcat(buf, "\\fonts\\"); strcat(buf, fontName); if(FT_New_Face(ftLib, buf, 0, &face )) { //TODO::check in mod fonts directory #ifdef _DEBUG wchar_t fn[128]; mbstowcs(fn,fontName,strlen(fontName)+1); LogWriteLine(L"\x25CB\x25CB\x25CF Request for font: %s has failed", fn); #endif face = 0; return; } } // FreeType uses 64x size and 72dpi for default // doubling size for ms FT_Set_Char_Size(face, mulPow2(height,7), mulPow2(height,7), 96, 96); // set up cache table and then generate the first 256 chars and the console prompt character for(int i=0;i<65536;i++) { cached[i]=false; width[i]=0.0f; } for(unsigned short i = 0; i < 256; i++) getChar((wchar_t)i); getChar(CHAR_PROMPT); #ifdef _DEBUG wchar_t fn[128]; mbstowcs(fn,fontName,strlen(fontName)+1); LogWriteLine(L"\x25CB\x25CB\x25CF Font: %s loaded OK", fn); #endif } glTextRender::~glTextRender() { // destructor code for glTextRender for(int i=0;i<65536;i++) { if(cached[i]) { glDeleteLists(listID[i],1); glDeleteTextures(1,&(texID[i])); } } // TODO:: work out stupid freetype crashz0rs try { static int foo = 0; if(face && foo < 1) { foo++; FT_Done_Face(face); face = 0; } } catch(...) { face = 0; } } // return true if init works, or if already initialised bool glTextRender::initFreeType() { if(!ftLib) { if(!FT_Init_FreeType(&ftLib)) return true; else return false; } else return true; } void glTextRender::shutdownFreeType() { if(ftLib) { FT_Done_FreeType(ftLib); ftLib = 0; } } void glTextRender::print(const wchar_t* str) { // store old stuff to set start position glPushAttrib(GL_TRANSFORM_BIT); // get viewport size GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); gluOrtho2D(viewport[0],viewport[2],viewport[1],viewport[3]); glPopAttrib(); float color[4]; glGetFloatv(GL_CURRENT_COLOR, color); glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT | GL_TRANSFORM_BIT); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glEnable(GL_TEXTURE_2D); //glDisable(GL_DEPTH_TEST); // set blending for AA glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glTranslatef(xPos,yPos,0.0f); glColor4f(red,green,blue,alpha); // call display lists to render text glListBase(0u); for(unsigned int i=0;i<wcslen(str);i++) glCallList(getChar(str[i])); // restore old states glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPopAttrib(); glColor4fv(color); glPushAttrib(GL_TRANSFORM_BIT); glMatrixMode(GL_PROJECTION); glPopMatrix(); glPopAttrib(); } void glTextRender::printf(const wchar_t* str, ...) { if(!str) return; wchar_t* buf = 0; va_list parg; va_start(parg, str); // allocate buffer int len = (_vscwprintf(str, parg)+1); buf = new wchar_t[len]; if(!buf) return; vswprintf(buf, str, parg); va_end(parg); print(buf); delete[] buf; } GLuint glTextRender::getChar(const wchar_t c) { int i = (int)c; if(cached[i]) return listID[i]; // load glyph and get bitmap if(FT_Load_Glyph(face, FT_Get_Char_Index(face, i), FT_LOAD_DEFAULT )) return 0; FT_Glyph glyph; if(FT_Get_Glyph(face->glyph, &glyph)) return 0; FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1); FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph)glyph; FT_Bitmap& bitmap = bitmapGlyph->bitmap; int w = roundPow2(bitmap.width); int h = roundPow2(bitmap.rows); // convert to texture in memory GLubyte* texture = new GLubyte[2*w*h]; for(int j=0;j<h;j++) { bool cond = j>=bitmap.rows; for(int k=0;k<w;k++) { texture[2*(k+j*w)] = 0xFFu; texture[2*(k+j*w)+1] = ((k>=bitmap.width)||cond) ? 0x0u : bitmap.buffer[k+bitmap.width*j]; } } // store char width and adjust max height // note .5f float ih = 1.0f/((float)gl->getHeight()); width[i] = ((float)divPow2(face->glyph->advance.x, 7))*ih; aHeight = max(aHeight,(.5f*(float)bitmap.rows)*ih); glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT | GL_TRANSFORM_BIT); // create gl texture glGenTextures(1, &(texID[i])); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, texID[i]); 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); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, texture); glPopAttrib(); delete[] texture; // create display list listID[i] = glGenLists(1); glNewList(listID[i], GL_COMPILE); glBindTexture(GL_TEXTURE_2D, texID[i]); glMatrixMode(GL_MODELVIEW); glPushMatrix(); // adjust position to account for texture padding glTranslatef(.5f*(float)bitmapGlyph->left, 0.0f, 0.0f); glTranslatef(0.0f, .5f*(float)(bitmapGlyph->top-bitmap.rows), 0.0f); // work out texcoords float tx=((float)bitmap.width)/((float)w); float ty=((float)bitmap.rows)/((float)h); // render // note .5f glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0f, .5f*(float)bitmap.rows); glTexCoord2f(0.0f, ty); glVertex2f(0.0f, 0.0f); glTexCoord2f(tx, ty); glVertex2f(.5f*(float)bitmap.width, 0.0f); glTexCoord2f(tx, 0.0f); glVertex2f(.5f*(float)bitmap.width, .5f*(float)bitmap.rows); glEnd(); glPopMatrix(); // move position for the next character // note extra div 2 glTranslatef((float)divPow2(face->glyph->advance.x, 7), 0.0f, 0.0f); glEndList(); // char is succesfully cached for next time cached[i] = true; return listID[i]; } void glTextRender::setPosition(float x, float y) { float fac = ((float)gl->getHeight()); xPos = fac*x+FONT_BORDER_PIXELS; yPos = fac*(1-y)-(float)height-FONT_BORDER_PIXELS; } float glTextRender::getAdjustedWidth(const wchar_t* str) { float w = 0.0f; for(unsigned int i=0;i<wcslen(str);i++) { if(cached[str[i]]) w+=width[str[i]]; else { getChar(str[i]); w+=width[str[i]]; } } return w; }
You may have to generate you own "glyph cache" in texture memory as you go, potentially with some sort of LRU policy to avoid destroying all of the texture memory. Not nearly as easy as your current method, but may be the only way given the number of unicode chars
You should consider using an Unicode rendering library (eg. Pango) to render the stuff into a bitmap and put that bitmap on the screen or into a texture. Rendering unicode text is not simple. So you cannot simply load 64K rectangular glyphs and use it. Characters may overlap. Eg in this smiley: ( ͡° ͜ʖ ͡°) Some code points stack accents on the previous character. Consider this excerpt from this notable post: ...he com̡e̶s, ̕h̵is un̨ho͞ly radiańcé destro҉ying all enli̍̈́̂̈́ghtenment, HTML tags lea͠ki̧n͘g fr̶ǫm ̡yo͟ur eye͢s̸ ̛l̕ik͏e liquid pain, the song of re̸gular expression parsing will extinguish the voices of mortal man from the sphere I can see it can you see ̲͚̖͔̙î̩́t̲͎̩̱͔́̋̀ it is beautiful the final snuffing of the lies of Man ALL IS LOŚ͖̩͇̗̪̏̈́T ALL IS LOST the pon̷y he comes he c̶̮omes he comes the ichor permeates all MY FACE MY FACE ᵒh god no NO NOO̼OO NΘ stop the an*̶͑̾̾̅ͫ͏̙̤g͇̫͛͆̾ͫ̑͆l͖͉̗̩̳̟̍ͫͥͨe̠̅s ͎a̧͈͖r̽̾̈́͒͑e not rè̑ͧ̌aͨl̘̝̙̃ͤ͂̾̆ ZA̡͊͠͝LGΌ ISͮ̂҉̯͈͕̹̘̱ TO͇̹̺ͅƝ̴ȳ̳ TH̘Ë͖́̉ ͠P̯͍̭O̚N̐Y̡ H̸̡̪̯ͨ͊̽̅̾̎Ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̱̹̭̯ͧ̾ͬC̷̙̲̝͖ͭ̏ͥͮ͟Oͮ͏̮̪̝͍M̲̖͊̒ͪͩͬ̚̚͜Ȇ̴̟̟͙̞ͩ͌͝S̨̥̫͎̭ͯ̿̔̀ͅ If you truly want to render Unicode correctly you should be able to render this one correctly too. UPDATE: Looked at this Pango engine, and it's the case of banana, the gorilla, and the entire jungle. First it depends on the Glib because it used GObjects, second it cannot render directly into a byte buffer. It has Cario and FreeType backends, so you must use one of them to render the text and export it into bitmaps eventually. That's doesn't look good so far. In addition to that, if you want to store the result in a texture, use pango_layout_get_pixel_extents after setting the text to get the sizes of rectangles to render the text to. Ink rectangle is the rectangle to contain the entire text, it's left-top position is the position relative to the left-top of the logical rectangle. (The bottom line of the logical rectangle is the baseline). Hope this helps.
Queso GLC is great for this, I've used it to render Chinese and Cyrillic characters in 3D. http://quesoglc.sourceforge.net/ The Unicode text sample it comes with should get you started.
You could also group the characters by language. Load each language table as needed, and when you need to switch languages, unload the previous language table and load the new one.
Unicode is supported in the title bar. I have just tried this on a Mac, and it ought to work elsewhere too. If you have (say) some imported data including text labels, and some of the labels just might contain unicode, you could add a tool that echoes the label in the title bar. It's not a great solution, but it is very easy to do.