I have the following code to render text in my app, first i get the mouse coordinates in the world, then use those coordinates to place my text in the world, so it will follow my mouse position:
Edit: added buildfont() function in code example:
GLvoid BuildFont(GLvoid) // Build Our Bitmap Font
{
HFONT font; // Windows Font ID
HFONT oldfont; // Used For Good House Keeping
base = glGenLists(96); // Storage For 96 Characters
font = CreateFont( -12, // Height Of Font
0, // Width Of Font
0, // Angle Of Escapement
0, // Orientation Angle
FW_NORMAL, // Font Weight
FALSE, // Italic
FALSE, // Underline
FALSE, // Strikeout
ANSI_CHARSET, // Character Set Identifier
OUT_TT_PRECIS, // Output Precision
CLIP_DEFAULT_PRECIS, // Clipping Precision
ANTIALIASED_QUALITY, // Output Quality
FF_DONTCARE|DEFAULT_PITCH, // Family And Pitch
"Verdana"); // Font Name (if not found, its using some other font)
oldfont = (HFONT)SelectObject(hDC, font); // Selects The Font We Want
wglUseFontBitmaps(hDC, 32, 96, base); // Builds 96 Characters Starting At Character 32
SelectObject(hDC, oldfont); // Selects The Font We Want
DeleteObject(font); // Delete The Font
}
GLvoid glPrint(const char *fmt, ...){
char text[256];
va_list ap;
if (fmt == NULL) return;
va_start(ap, fmt);
vsprintf(text, fmt, ap);
va_end(ap);
glPushAttrib(GL_LIST_BIT);
glListBase(base - 32);
glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);
glPopAttrib();
}
...
glPushMatrix();
glColor4f(0,0,0,1);
// X-1 wont work because these are the world coordinates:
glRasterPos2d(MousePosX-1, MousePosY);
glPrint("TEST");
glColor4f(1,1,0,1);
glRasterPos2d(MousePosX, MousePosY);
glPrint("TEST");
glPopMatrix();
But i want to render multiline texts, or texts with "borders" (like in above code i tried) or text with background (so i could distinguish them better from the background)
So how i do this?
I just need to know how i can move it in pixels, so i could precisely modify the position on my screen WITHOUT using 2d projection view on top of my 3d render projection... i just want to make it as simple as possible.
I tried to draw a quad under the text, but of course it doesnt work since its still using the world coordinates... so when i rotate my camera the text doesnt move along the background of the text... i am afraid the only solution is to create another projection on top of the 3d projection...
Here's a small snippet of code that I use to render some debug text in a small application:
void
renderText(float x, float y, const char* text) {
int viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
glOrtho(viewport[0], viewport[2], viewport[1], viewport[3], -1, 1);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glRasterPos2f(x, viewport[3] - y);
const int length = (int)strlen(text);
for (int i = 0; i < length; ++i) {
glutBitmapCharacter(GLUT_BITMAP_9_BY_15, text[i]);
}
glMatrixMode( GL_PROJECTION );
glPopMatrix();
glMatrixMode( GL_MODELVIEW );
glPopMatrix();
}
xand y is the desired window coordinates of the string. glutBitmapCharacter(GLUT_BITMAP_9_BY_15, ...) is just a utility function from GLUT that renders a 9 x 15 pixels large bitmap character.
Related
This is the code as it is but I'm having trouble making SDL_Rect work or cairo move to / line to. It produces a blank black window. I found out that it is possible for cairo to draw on an SDL2 window but don't know how I would go about making it work. Majority of the code I see uses GTK+.
SDL_Window* mainWindow;
SDL_Renderer* mainRenderer;
SDL_CreateWindowAndRenderer(1280, 960, SDL_WINDOW_SHOWN, &mainWindow, &mainRenderer);
cairo_surface_t* surface;
cairo_t* cr;
surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1200, 900);
cr = cairo_create(surface);
cairo_set_source_rgb(cr, 0.0, 0.0, 0.0);
cairo_set_line_width(cr, 25);
cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
cairo_move_to(cr, 100.0, 100.0);
cairo_line_to(cr, 500, 500);
cairo_stroke(cr);
unsigned char* data;
data = cairo_image_surface_get_data(surface);
SDL_Texture* texture;
SDL_Rect rect = {0, 0, 100, 100};
texture = SDL_CreateTexture(mainRenderer, SDL_PIXELFORMAT_ABGR8888,
SDL_TEXTUREACCESS_STREAMING, 100, 200);
SDL_UpdateTexture(texture, &rect, data, 400);
// Main program loop
while (1)
{
SDL_Event event;
if (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT)
{
SDL_DestroyRenderer(mainRenderer);
SDL_DestroyWindow(mainWindow);
SDL_Quit();
break;
}
}
SDL_RenderClear(mainRenderer);
SDL_RenderCopy(mainRenderer, texture, NULL, NULL);
SDL_RenderPresent(mainRenderer);
}
// Cleanup and quit
cairo_destroy(cr);
cairo_surface_destroy(surface);
Your texture is 100x200 and you're only updating its (0,0)(100,100) rectangle from cairo image data, but with cairo you only started drawing at (100,100), so entire area is black. In addition, your pitch when updating texture is incorrect - it is byte length of source data line; your cairo image have width of 1200 and its format requires 4 bytes per pixel; neglecting padding it is 1200*4, not 400 (note - if format is different, e.g. 3 bytes per pixel, padding may be important - refer to cairo documentation to check if it pads its rows if you're going to use that format). So there are two solutions:
Use cairo to produce full image you want, e.g. don't use (100,100) offset with move_to, or copy entire image to SDL texture. Then only correcting pitch is enough.
Copy part of cairo data to texture,
e.g.
const unsigned int bpp = 4;
const unsigned int pitch = 1200*bpp;
SDL_UpdateTexture(texture, &rect,
data // offset pointer to start at 'first' pixel you want to copy
+ 100*pitch // skip first 100 rows
+ 100*bpp, // and first 100 pixels
pitch // pitch is the same - it refers to source image, not destination
);
I have a QOpenGLWidget in which I draw text with a QPainter. I would like to implement a snapshot feature by rendering the widget content into a QOpenGLFrameBuffer and converting the frame buffer into a QImage.
Unfortunately, if I set the font's point size too high (> 46), the text appears as a black bar in the snapshot, while in the widget it is displayed correctly. See below an example snapshot where the block over the line is supposed to be text with font size > 46.
Here is the simplified code to render the image (it should work, because it is correctly displayed in the QOpenGLWidget):
void renderSomething(const int x, const int y, const QString & str, const int fontSize) {
// 1. draw the line
// (calculate min_x, max_x, y and z values)
glLineWidth(3);
glColor3f(0., 0., 0.);
glBegin(GL_LINES);
glVertex3f(min_x, y, z);
glVertex3f(max_x, y, z);
glEnd();
// 2. draw the text
GLint gl_viewport[4];
glGetIntegerv(GL_VIEWPORT, gl_viewport);
backup_gl_state();
QOpenGLPaintDevice paintDevice(gl_viewport[2], gl_viewport[3]);
QPainter painter(&paintDevice);
painter.setFont(QFont(painter.font().family(), fontSize);
painter.setPen(Qt::black);
painter.drawText(x, y, str);
painter.end();
restore_gl_state();
}
Here is the code for storing a snapshot:
void Viewport::takeSnapshot(const QString & path, const int size) {
glPushAttrib(GL_VIEWPORT_BIT);
glViewport(0, 0, size, size); // since size varies I want the text to scale with it
QOpenGLFramebufferObject fbo(size, size, QOpenGLFramebufferObject::Depth);
fbo.bind();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
renderer.renderSomething(100, 100, "Test", 50); // Here is the render call!
QImage fboImage(fbo.toImage());
QImage image(fboImage.constBits(), fboImage.width(), fboImage.height(), QImage::Format_RGB32);
image.save(path);
glPopAttrib();
fbo.bindDefault();
fbo.release();
}
EDIT
I found a workaround by using QPainter to directly draw the text on the QImage instead of drawing into the FBO. No black bar anymore, but it complicates my code and I would be happy to find a real solution...
The solution was simple: Re-read the documentation on QOpenGLFrameBufferObject in which it says:
Create the QOpenGLFrameBufferObject instance with the CombinedDepthStencil attachment if you want QPainter to render correctly.
I only attached a depth buffer. The correct initialization would be:
QOpenGLFramebufferObject fbo(size, size, QOpenGLFramebufferObject::CombinedDepthStencil);
I'm trying to write bitmap files for every frames that I rendered through OpenGL.
Please notice that I'm not going to read bitmap, I'm gonna WRITE NEW BITMAP files.
Here is part of my C++ code
void COpenGLWnd::ShowinWnd(int ID)
{
if(m_isitStart == 1)
{
m_hDC = ::GetDC(m_hWnd);
SetDCPixelFormat(m_hDC);
m_hRC = wglCreateContext(m_hDC);
VERIFY(wglMakeCurrent(m_hDC, m_hRC));
m_isitStart = 0;
}
GLRender();
CDC* pDC = CDC::FromHandle(m_hDC);
//pDC->FillSolidRect(0, 0, 100, 100, RGB(100, 100, 100));
CRect rcClient;
GetClientRect(&rcClient);
SaveBitmapToDirectFile(pDC, rcClient, _T("a.bmp"));
SwapBuffers(m_hDC);
}
"GLRender" is the function which can render on the MFC window.
"SaveBitmapToDirectFile" is the function that writes a new bitmap image file from the parameter pDC, and I could check that it works well if I erase that double slash on the second line, because only gray box on left top is drawn at "a.bmp"
So where has m_hDC gone? I have no idea why rendered scene wasn't written on "a.bmp".
Here is GLRender codes, but I don't think that this function was the problem, because it can render image and print it out well on window.
void COpenGLWnd::GLFadeinRender()
{
glViewport(0,0, m_WndWidth, m_WndHeight);
glOrtho(0, m_WndWidth, 0, m_WndHeight, 0, 100);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glClear(GL_COLOR_BUFFER_BIT);
glEnable(GL_BLEND);
glBlendFunc(m_BlendingSrc, m_BlendingDest);
glPixelTransferf(GL_ALPHA_SCALE,(GLfloat)(1-m_BlendingAlpha));
glPixelZoom((GLfloat)m_WndWidth/(GLfloat)m_w1, -(GLfloat)m_WndHeight/(GLfloat)m_h1);
glRasterPos2f(0, m_WndHeight);
glDrawPixels((GLsizei)m_w1, (GLsizei)m_h1, GL_BGR_EXT, GL_UNSIGNED_BYTE, m_pImageA);
glPixelTransferf(GL_ALPHA_SCALE,(GLfloat)m_BlendingAlpha);
glPixelZoom((GLfloat)m_WndWidth/(GLfloat)m_w2, -(GLfloat)m_WndHeight/(GLfloat)m_h2);
glRasterPos2f(0, m_WndHeight);
glDrawPixels((GLsizei)m_w2, (GLsizei)m_h2, GL_BGR_EXT, GL_UNSIGNED_BYTE, m_pImageB);
glFlush();
}
I'm guessing you're using MFC or Windows API functions to capture the bitmap from the window. The problem is that you need to use glReadPixels to get the image from a GL context -- winapi isn't able to do that.
I am making a game in OpenGL using GLUT on UNIX. It is a 3D game where the player can move side to side (x-axis) jump (y-axis) and is constantly moving forward and has to avoid oncoming obstacles (for my implementation the player actual stands still while the world constantly moves at the player).
I am having trouble when trying to draw a HUD with bitmap text on it. I have tried creating an orthogonal view and then drawing the text but it always ends up at a random spot on the x-axis and constantly moves towards the player with the world on the z-axis. Once it gets past the player it disappears (which is what happens to all the world objects to cut processing). I want the text in one place and to stay there.
gameSpeed = Accumulator*6;
DrawRobot(); //player
ModelTrans.loadIdentity(); //ModelTrans has helper functions to manipulate
ModelTrans.pushMatrix(); //the current matrix stack
ModelTrans.translate(vec3(0, 0, -gameSpeed)); //move the whole world
...Then I do a bunch of drawing of the game objects...
And here I attempt to do some bitmap fonts. Disabling the depth test helps put the text in front of all the other objects but the other code to create the orthogonal view actually could be commented out and I would still have the same problem.
ModelTrans.popMatrix();
glDisable(GL_DEPTH_TEST);
glMatrixMode(GL_PROJECTION);
ModelTrans.pushMatrix();
glLoadIdentity();
gluOrtho2D(0, WindowWidth, 0, WindowHeight);
glScalef(1, -1, 1);
glTranslatef(0, -WindowHeight, 0);
glMatrixMode(GL_MODELVIEW);
std::string str = "sup";
renderBitmapString(0.5 + xText, 5.0, GLUT_BITMAP_HELVETICA_18, str);
//xText adjusts for the moving left and right of the player
glMatrixMode(GL_PROJECTION);
ModelTrans.popMatrix();
glMatrixMode(GL_MODELVIEW);
glUseProgram(0);
glutSwapBuffers();
glutPostRedisplay();
printOpenGLError();
Here is some other code that may be of use:
void renderBitmapString(float x, float y, void *font, std::string s)
{
glRasterPos2f(x, y);
for (string::iterator i = s.begin(); i != s.end(); ++i)
{
char c = *i;
glutBitmapCharacter(font, c);
}
}
void Timer(int param)
{
Accumulator += StepSize * 0.001f;
glutTimerFunc(StepSize, Timer, 1);
}
void Reshape(int width, int height)
{
WindowWidth = width;
WindowHeight = height;
glViewport(0, 0, width, height);
}
I am making a game in OpenGL using GLUT on UNIX
First of all, you're not doing it on Unix, but most likely using X11. Also I'm pretty sure your OS is a variant of Linux, which is not Unix (a …BSD would be a true Unix).
Anyway, in this code snippet you're adjusting the projection, but not the modelview matrix
glMatrixMode(GL_PROJECTION);
ModelTrans.pushMatrix();
glLoadIdentity();
gluOrtho2D(0, WindowWidth, 0, WindowHeight);
glScalef(1, -1, 1);
glTranslatef(0, -WindowHeight, 0);
glMatrixMode(GL_MODELVIEW);
std::string str = "sup";
renderBitmapString(0.5 + xText, 5.0, GLUT_BITMAP_HELVETICA_18, str);
//xText adjusts for the moving left and right of the player
glMatrixMode(GL_PROJECTION);
ModelTrans.popMatrix();
glMatrixMode(GL_MODELVIEW);
I'm not entirely sure what ModelTrans is, but it has pushMatrix and popMatrix and if I assume, that those are just glPushMatrix and glPopMatrix then your code mises a
glPushMatrix();
glLoadIdentity()
…
glPopMatrix();
block acting on the Modelview matrix. Modelview and Projection matrices have their own stacks in OpenGL and must be pushed/poped individually.
When my window is resized, i don't want the contents to scale but just to increase the view port size. I found this while searching on stackoverflow (http://stackoverflow.com/questions/5894866/resize-viewport-crop-scene) which is pretty much the same as my problem. However I'm confused as to what to set the Zoom to and where, i tried it with 1.0f but then nothing was shown at all :s
This is resize function code at the moment which does scaling:
void GLRenderer::resize() {
RECT rect;
int width, height;
GLfloat aspect;
GetClientRect(hWnd, &rect);
width = rect.right;
height = rect.bottom;
if (height == 0) {
height = 1;
}
aspect = (GLfloat) width / height;
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, aspect, 0.1, 100.0);
glMatrixMode(GL_MODELVIEW);
}
And my function to render a simple triangle:
void GLRenderer::render() {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
glTranslated(0, 0, -20);
glBegin(GL_TRIANGLES);
glColor3d(1, 0, 0);
glVertex3d(0, 1, 0);
glVertex3d(1, -1, 0);
glVertex3d(-1, -1, 0);
glEnd();
SwapBuffers(hDC);
}
You can change the zoom in y (height) with the "field of view" parameter to gluPerspective. The one that is 45 degrees in your code. As it is currently always 45 degrees, you will always get the same view angle (in y). How to change this value as a function of the height of the window is not obvious. A linear relation would fail for big values (180 degrees and up). I would try to use arctan(height/k), where 'k' is something like 500.
Notice also that when you extend the window in x, you will already get what you want (the way your source code currently is). That is, you get a wider field of view. That is because you change the aspect (second argument) to a value depending on the ratio between x and y.
Height and Width is measured in pixels, so a value of 1 is not good.
Notice that you are using deprecated legacy OpenGL. See Legacy OpenGL for more information.