Linux系統下的OpenGL圖形程式架構
說到圖形界面程式設計,想到的大多是Windows下的多種多樣的應用程式。Linux是一個基于指令行的作業系統,在Linux下的工作大多是在指令行裡完成的,而并非像Windows那樣。圖形界面雖然并不是Linux的一部分,我們通過一些工具,依然可以編寫一些在Linux下的圖形應用程式。
OpenGL是一個工業标準的三維計算機圖形軟體接口,它由SGI公司釋出并廣泛應用于Unix、OS/2、Windows/NT等多種平台,這其中當然也包括Linux。
X11也叫做X Window系統,X Window系統 (X11或X)是一種位圖顯示的 視窗系統 。它是在 Unix 和 類Unix作業系統,以及 OpenVMS 上建立圖形使用者界面的标準工具包和協定。Linux就是一個類Unix的作業系統。
有了這兩個工具,我們便能在Linux下進行圖形界面程式設計了。
1.建立視窗
由于X Window系統的協定和架構 X基于 用戶端-伺服器 模型,首先建立一個連接配接,連接配接到X伺服器。
dpy = XOpenDisplay(NULL);
if (dpy == NULL)
fatalError("could not open display");
對于X視窗系統來說,它所使用的OpenGL擴充是作為OpenGL的一個附件提供的,是以還需要檢測X伺服器是否是支援OpenGL擴充。
if(!glXQueryExtension(dpy, &dummy, &dummy))
fatalError("X server has no OpenGL GLX extension");
接下來就是給視窗找到一個合适的視覺樣式。比如有些支援雙緩沖,有些不支援,etc。
vi = glXChooseVisual(dpy, DefaultScreen(dpy), dblBuf);
if (vi == NULL)
{
vi = glXChooseVisual(dpy, DefaultScreen(dpy), snglBuf);
if (vi == NULL) fatalError("no RGB visual with depth buffer");
glf_DoubleBuffer = GL_FALSE;
}
if(vi->class != TrueColor)
fatalError("TrueColor visual required for this program");
接着建立一個OpenGL狀态資訊,等一下建立視窗會用到。
cx = glXCreateContext(dpy, vi, None, GL_TRUE);
if (cx == NULL)
fatalError("could not create rendering context");
調用XCreateWindow函數,使用剛剛指定的視覺樣式建立視窗。
cmap = XCreateColormap(dpy, RootWindow(dpy, vi->screen), vi->visual, AllocNone);
swa.colormap = cmap;
swa.border_pixel = 0;
swa.event_mask = KeyPressMask | ExposureMask | ButtonPressMask | StructureNotifyMask;
win = XCreateWindow(dpy, RootWindow(dpy, vi->screen), 0, 0,
glf_WinWidth, glf_WinHeight, 0, vi->depth, InputOutput, vi->visual,
CWBorderPixel | CWColormap | CWEventMask, &swa);
XSetStandardProperties(dpy, win, "main", "main", None, argv, argc, NULL);
再将剛剛建立的OpenGL狀态資訊,綁定到視窗。
glXMakeCurrent(dpy, win, cx);
最後,調用XMapWindow函數将視窗顯示在桌面上就完成了。
XMapWindow(dpy, win);
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnLwAzN18VMxATOxIDO1MTMvwVNx8CXxAzMxAjMvw1ckF2bsBXdvwFdl5mLuR2cj5Set1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
2.OpenGL初始化
編寫一個glfInit()函數用來進行對OpenGL的初始化,如:調整投影變換,設定視口,etc.這個函數定義在glFrame.h中。
void glfInit()
{
glViewport(0, 0, glf_WinWidth, glf_WinHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glf_left=0.0f;
glf_right=50.0f;
glf_bottom=0.0f;
glf_top=50.0f;
glf_near=-1.0f;
glf_far=1.0f;
glOrtho(glf_left,glf_right,glf_bottom,glf_top,glf_near,glf_far);
glMatrixMode(GL_MODELVIEW);
}
3.接受和處理XEvent事件
寫一個while循環,調用XNextEvent函數來接收滑鼠和鍵盤事件。XEvent的定義在Xlib.h中找到。在XEvent中有一個變量type,辨別了事件的種類。是以我們可以寫一個switch語句來,對不同的事件進行不同的處理。 對于鍵盤事件,編寫一個glfProcessKeyboard函數來對其進行處理,參數就是按鍵的所代表的值。如:XK_Escape,etc。這些宏都定義在keysymdef.h中。 對于滑鼠事件,編寫一個glfProcessMousePress函數來對滑鼠按鍵事件進行處理,參數是滑鼠按鍵時的位置x和y,左上方為原點。 在處理了這些事件後,修改布爾變量needUpdate的值,告訴glFrame是否需要進行更新操作。
if (needUpdate)
{
glfUpdate(); //glFrame更新
needUpdate = GL_FALSE;
needDraw = GL_TRUE;
}
if (needDraw)
{
glfDraw(); //glFrame繪制
needDraw = GL_FALSE;
}
4.glfFrame.h
在前面的内容裡,一個基于OpenGL的Linux圖形程式架構以及基本搭建完成了。這個時候,編譯運作可以看到:
可以注意到,所有和OpenGL相關的編碼,都被放到了glfFrame.h中,于是我們的工作隻需要修改glfFrame裡面的内容就能夠自由的進行繪制工作,并且響應滑鼠鍵盤事件。 為了能夠實作更多的功能,我為這個架構實作了按鈕和标簽。在font.h,定義了26個英文大寫字母的位圖矩形數組。使用glBitmap函數以及顯示清單機制使我們能夠很友善地使用光栅字型。 列印字母的具體做法:
glRasterPos2i(label.posx,label.posy);
printString(label.str);
當然在使用光栅字型的時候,你需要先調用makeRasterFont() 函數來生成這26個顯示清單。做法來源于紅書。 于是,有了這個顯示字型的工具,我們可以定義标簽Label:
struct Label
{
GLboolean isShow; //是否顯示
char str[100]; //顯示的内容
GLint posx; //光栅位置x
GLint posy; //光栅位置y
GLfloat color[4]; //顔色
};
以及繪制标簽的函數GLboolean glfDrawLabel(Label label)
GLboolean glfDrawLabel(Label label)
{
if (label.isShow == GL_FALSE)
return GL_FALSE;
glPushAttrib(GL_CURRENT_BIT);
glColor3fv(label.color);
glRasterPos2i(label.posx,label.posy);
printString(label.str);
glPopAttrib();
return GL_TRUE;
}
對于按鈕來說,當一個頁面裡面有很多按鈕時,glFrame需要知道是那一個按鈕被按下了,進而進行對應的操作。于是定義一個全局變量
typedef char ButtonEvent;
ButtonEvent glf_buttonevent[10]; 當檢測到按鈕被摁下時,就把按鈕的event資訊儲存到glf_buttonevent裡面,按鈕的event資訊需要事先設定好。 于是對按鈕的結構體有定義:
struct Button
{
GLboolean isAround;
GLfloat aroundColor[4];
GLint aroundLineWidth;
GLboolean isShow;
Label text;
Point2D leftDown;
Point2D leftUp;
Point2D rightDown;
Point2D rightUp;
GLfloat color[4];
ButtonEvent event[10];
};
以及按鈕的摁下檢測函數
GLboolean glfCheckButtonPress(int ix,int iy,Button button)
{
double x,y;
iy=glf_WinHeight-iy;
//将滑鼠的位置坐标轉化為OpenGL坐标系中
x=(double)ix/(double)glf_WinWidth*(glf_right-glf_left); //把坐标轉化到視景體中
y=(double)iy/(double)glf_WinHeight*(glf_right-glf_left); //把坐标轉化到視景體中
if (x <= button.rightDown.p[0] && x >= button.leftDown.p[0]
&& y <= button.rightUp.p[1] && y >= button.rightDown.p[1])
{
strcpy(glf_buttonevent,button.event);
return GL_TRUE;
}
return GL_FALSE;
}
最後就是按鈕的繪制函數
GLboolean glfDrawButton(Button button)
{
if (button.isShow == GL_FALSE)
return GL_FALSE;
glPushAttrib(GL_CURRENT_BIT);
glColor3fv(button.color);
glBegin(GL_QUADS);
glVertex2fv(button.leftDown.p);
glVertex2fv(button.leftUp.p);
glVertex2fv(button.rightUp.p);
glVertex2fv(button.rightDown.p);
glEnd();
glfDrawLabel(button.text);
if (button.isAround == GL_TRUE)
{
glColor3fv(button.aroundColor);
glLineWidth(button.aroundLineWidth);
glBegin(GL_LINES);
glVertex2fv(button.rightDown.p);
glVertex2fv(button.leftDown.p);
glEnd();
glBegin(GL_LINES);
glVertex2fv(button.leftDown.p);
glVertex2fv(button.leftUp.p);
glEnd();
glBegin(GL_LINES);
glVertex2fv(button.leftUp.p);
glVertex2fv(button.rightUp.p);
glEnd();
glBegin(GL_LINES);
glVertex2fv(button.rightUp.p);
glVertex2fv(button.rightDown.p);
glEnd();
}
glPopAttrib();
return GL_TRUE;
}
最終的效果圖:
本文的全部代碼以及上傳到了Great-Code上,請猛擊此處。 附glFrame.h代碼:
#ifndef GLFRAME_H_INCLUDED
#define GLFRAME_H_INCLUDED
#include "global.h"
#include "font.h"
#include "Label.h"
#include "Button.h"
void glfPrint(GLboolean doubleBuffer);
void glfInit();
void glfProcessKeyboard(KeySym keysym);
void glfProcessMouseClick(int x,int y);
void glfprocessButtonPress();
void glfUpdate();
void glfReshape(int width,int height);
void glfDraw();
Label label;
Button button;
Button b_exit;
void glfPrint(GLboolean doubleBuffer)
{
if (doubleBuffer)
glXSwapBuffers(dpy, win);
else
glFlush();
}
void glfInit()
{
glViewport(0, 0, glf_WinWidth, glf_WinHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glf_left=0.0f;
glf_right=50.0f;
glf_bottom=0.0f;
glf_top=50.0f;
glf_near=-1.0f;
glf_far=1.0f;
glOrtho(glf_left,glf_right,glf_bottom,glf_top,glf_near,glf_far);
glMatrixMode(GL_MODELVIEW);
makeRasterFont();
label.isShow=GL_TRUE;
label.color[0]=1.0f;
label.color[1]=1.0f;
label.color[2]=0.0f;
label.color[3]=0.0f;
label.posx=15;
label.posy=40;
strcpy(label.str,"HELLO SSSOGS");
button.isShow=GL_TRUE;
button.isAround=GL_TRUE;
button.aroundLineWidth=3;
button.aroundColor[0]=0.0f;
button.aroundColor[1]=1.0f;
button.aroundColor[2]=0.0f;
button.aroundColor[3]=0.0f;
button.leftDown.p[0]=4;
button.leftDown.p[1]=20;
button.leftUp.p[0]=4;
button.leftUp.p[1]=30;
button.rightDown.p[0]=24;
button.rightDown.p[1]=20;
button.rightUp.p[0]=24;
button.rightUp.p[1]=30;
button.color[0]=0.9f;
button.color[1]=0.5f;
button.color[2]=0.9f;
button.color[3]=0.0f;
button.text.isShow=GL_TRUE;
button.text.color[0]=0.0f;
button.text.color[1]=0.0f;
button.text.color[2]=0.0f;
button.text.color[3]=0.0f;
button.text.posx=6;
button.text.posy=25;
strcpy(button.text.str,"HIDE WORDS");
strcpy(button.event,"Test");
b_exit.isShow=GL_TRUE;
b_exit.isAround=GL_TRUE;
b_exit.aroundLineWidth=3;
b_exit.aroundColor[0]=0.0f;
b_exit.aroundColor[1]=1.0f;
b_exit.aroundColor[2]=0.0f;
b_exit.aroundColor[3]=0.0f;
b_exit.leftDown.p[0]=26;
b_exit.leftDown.p[1]=20;
b_exit.leftUp.p[0]=26;
b_exit.leftUp.p[1]=30;
b_exit.rightDown.p[0]=46;
b_exit.rightDown.p[1]=20;
b_exit.rightUp.p[0]=46;
b_exit.rightUp.p[1]=30;
b_exit.color[0]=0.9f;
b_exit.color[1]=0.5f;
b_exit.color[2]=0.9f;
b_exit.color[3]=0.0f;
b_exit.text.isShow=GL_TRUE;
b_exit.text.color[0]=0.0f;
b_exit.text.color[1]=0.0f;
b_exit.text.color[2]=0.0f;
b_exit.text.color[3]=0.0f;
b_exit.text.posx=28;
b_exit.text.posy=25;
strcpy(b_exit.text.str,"EXIT");
strcpy(b_exit.event,"exit");
}
void glfProcessKeyboard(KeySym keysym)
{
if (keysym == (KeySym)XK_Escape)
exit(0);
}
void glfProcessMousePress(int x,int y)
{
if (glfCheckButtonPress(x,y,button) == GL_TRUE)
{
glfprocessButtonPress();
}
if (glfCheckButtonPress(x,y,b_exit) == GL_TRUE)
{
glfprocessButtonPress();
}
}
void glfprocessButtonPress()
{
if (glf_buttonevent == NULL)
return ;
if (strcmp(glf_buttonevent,"Test") == 0)
{
label.isShow=!label.isShow;
if (label.isShow)
{
strcpy(button.text.str,"HIDE WORDS");
}
else
{
strcpy(button.text.str,"SHOW WORDS");
}
}
if (strcmp(glf_buttonevent,"exit") == 0)
{
exit(0);
}
}
void glfUpdate()
{
}
void glfReshape(int width,int height)
{
glf_WinWidth=width;
glf_WinHeight=height;
glViewport(0,0,glf_WinWidth,glf_WinHeight);
}
void glfDraw()
{
glClearColor(0.0,0.0,0.0,0.0);
glClear(GL_COLOR_BUFFER_BIT);
glfDrawLabel(label);
glfDrawButton(button);
glfDrawButton(b_exit);
glfPrint(glf_DoubleBuffer);
}
#endif // GLFRAME_H_INCLUDED