Qt 內置對OpenGL ES的支持,選用Qt進行OpenGL ES的開發是非常方便的,許多輔助類都已經具備。從Qt 5.0開始增加了一個QWindow類,該類既可以使用OpenGL繪製3D圖形,也可以使用QPainter繪製2D傳統的GDI+圖形,5.0以前的QGLWidget不推薦再使用。在即將到來(官方時間是今年秋天)Qt 5.4會完全廢棄QGLWidget,作爲替代將會新增QOpenGLWidget和QOpenGLWindow類來方便OpenGL的編程。
好了廢話不多說了,今天我會使用OpenGL ES繪製一個彩色立方體,先在桌面平臺編譯運行成功後,再針對Android平臺編譯一次即可,下面是在Android上的運行效果,基於同一個着色器繪製了四個相同的彩色立方體。
爲了使用OpenGL,我從QWindow類和QOpenGLFunctions類派生出了一個OpenGLWindow類,該類聲明如下:
class OpenGLWindow : public QWindow, protected QOpenGLFunctions
{
Q_OBJECT
public:
explicit OpenGLWindow(QWindow *parent = 0);
~OpenGLWindow();
virtual void initialize();
virtual void render(QPainter *painter);
virtual void render(double currentTime = 0.0); // elapsed seconds from program started.
protected:
void exposeEvent(QExposeEvent *event);
private:
QOpenGLPaintDevice *m_device;
QOpenGLContext *m_context;
QTime startTime;
GLuint m_program;
// QOpenGLShaderProgram *m_shaderProgram;
};
由於繼承自QWindow因此可以使用QWindow提供的OpenGL環境,不需要EGL來控制本地窗口顯示圖形。同時由於繼承自QOpenGLFunctions,所以在OpenGLWindow類的成員函數中可以直接使用 gl* 風格的原生的OpenGL API。在Qt中提供了很多封裝好的OpenGL便捷類,如QOpenGLShaderProgram可以很方便的對着色器程序進行操作,但這樣做可能對不熟悉Qt的人不友好,所以這裏我不用Qt提供的便捷類,而直接使用原生的C風格的 OpenGL API進行操作,這完全是可以的(這也是我喜歡Qt的原因之一:提供自身類庫的同時,允許你使用非Qt的類,並提供二者之間的轉換,如Qt中的容器類QVector、QMap、QString可以和C++標準庫中的相應容器相互轉換)。甚至你可以混合使用Qt的OpenGL類和原生的OpenGL API。
下面看看幾個關鍵的函數。首先是initialize()負責着色器的創建、編譯、鏈接等操作,並設置背景色。代碼如下,其中被註釋的部分是使用Qt自帶類庫實現相同的功能。
void OpenGLWindow::initialize()
{
const char *vertexShaderSrc =
"attribute vec4 a_position; \n"
"uniform mat4 u_mvp; \n"
"varying vec4 v_color; \n"
"void main() \n"
"{ \n"
" v_color = a_position*0.7 + 0.5; \n"
" gl_Position = u_mvp * a_position; \n"
"} \n";
const char *fragmentShaderSrc =
"varying vec4 v_color; \n"
"void main() \n"
"{ \n"
" gl_FragColor = v_color; \n"
"} \n";
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSrc, NULL);
glCompileShader(vertexShader);
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSrc, NULL);
glCompileShader(fragmentShader);
m_program = glCreateProgram();
glAttachShader(m_program, vertexShader);
glAttachShader(m_program, fragmentShader);
glLinkProgram(m_program);
// // add vertex shader(compiled internal)
// m_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc);
// // add fragment shader(compiled internal)
// m_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc);
// // link shaders to program
// m_shaderProgram->link();
// set the background clear color.
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}
再來看看QWindow的QExposeEvent事件,當QWindow需要重繪時會調用該事件的處理函數exposeEvent(),這裏我對該事件的處理函數進行了重寫。isExposed()用來判斷當前窗口是否顯示在屏幕上(onScreen or offScreen),只有顯示在屏幕上時才重繪(雖然窗口需要重繪,但是由於沒有顯示在屏幕上,重繪了也沒人看得見,所以加這個判斷可以減少不必要的繪圖操作)。首先創建OpenGL上下文,然後進行調用相應的初始化函數,這兩步只在第一次被執行,以後不會再執行。接下來是該函數的核心部分,調用render()函數在後端緩衝區進行圖形渲染,然後交換前端和後端緩衝區,讓後端緩衝區圖形顯示到界面上。
void OpenGLWindow::exposeEvent(QExposeEvent *event)
{
Q_UNUSED(event)
static bool needInit = true;
// Returns true if this window is exposed in the windowing system.
if (isExposed())
{
if (m_context == nullptr)
{
m_context = new QOpenGLContext(this);
m_context->setFormat(requestedFormat());
m_context->create();
}
m_context->makeCurrent(this);
if (needInit)
{
initializeOpenGLFunctions();
this->initialize();
needInit = false;
}
// calculate elapsed seconds from program started.
double duration = startTime.msecsTo(QTime::currentTime()) / 1000.0;
render(duration);
m_context->swapBuffers(this);
}
}
最後看看render()渲染函數,這個也是學習OpenGL的主要部分。
在render()函數的開始部分創建了一個QOpenGLPaintDevice實例,該示例用於繪製QPainter的繪圖操作,這裏可以忽略,刪掉也可以。接下來就是定義立方體的頂點位置,以及頂點索引。創建2個頂點緩衝區對象(vertex buffer object),通過glBufferData()函數將立方體的頂點位置和頂點所以放到頂點緩衝區對象中,將頂點位置通過glVertexAttribPointer()傳遞給頂點着色器。計算model/view/projection,然後將結果通過glUniformMatrix4fv()傳遞給頂點着色器中的mvp,最後使用glDrawElement()繪製立方體。立方體每個點的顏色由其所在的位置決定,所以不同位置的頂點具有不同顏色。
void OpenGLWindow::render(double currentTime)
{
if (m_device == nullptr)
m_device = new QOpenGLPaintDevice;
m_device->setSize(this->size());
static GLfloat vCubeVertices[] = {
-0.5f, 0.5f, 0.5f, // v0
-0.5f, -0.5f, 0.5f, // v1
0.5f, -0.5f, 0.5f, // v2
0.5f, 0.5f, 0.5f, // v3
0.5f, -0.5f, -0.5f, // v4
0.5f, 0.5f, -0.5f, // v5
-0.5f, 0.5f, -0.5f, // v6
-0.5f, -0.5f, -0.5f, // v7
};
static GLushort vCubeIndices[] = {
0, 1, 2, 0, 2, 3, // front face
5, 6, 7, 4, 5, 7, // back face
0, 1, 7, 0, 6, 7, // left face
2, 3, 4, 3, 4, 5, // right face
0, 3, 5, 0, 5, 6, // top face
1, 2, 4, 1, 4, 7 // bottom face
};
glViewport(0, 0, width(), height());
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(m_program);
GLuint vbos[2];
glGenBuffers(2, vbos);
glBindBuffer(GL_ARRAY_BUFFER, vbos[0]);
glBufferData(GL_ARRAY_BUFFER, sizeof(vCubeVertices), vCubeVertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos[1]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(vCubeIndices), vCubeIndices, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, vbos[0]);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glBindAttribLocation(m_program, 0, "a_position");
static GLfloat angle = 0.0;
GLuint mvpLoc = glGetUniformLocation(m_program, "u_mvp");
QMatrix4x4 model, view, projection, mvp;
model.rotate(angle + 5, QVector3D(1,0,0));
model.rotate(angle - 5, QVector3D(0,1,0));
model.scale(0.5, 0.5, 0.5);
view.translate(0.5, 0.5, 0);
angle += 10;
mvp = projection * view * model;
glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, mvp.constData());
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos[1]);
glDrawElements(GL_TRIANGLES, sizeof(vCubeIndices)/sizeof(GLushort), GL_UNSIGNED_SHORT, 0);
/* draw another cube in different place with the same shader */
view.translate(-1.0, 0, 0);
mvp = projection * view * model;
glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, mvp.constData());
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos[1]);
glDrawElements(GL_TRIANGLES, sizeof(vCubeIndices)/sizeof(GLushort), GL_UNSIGNED_SHORT, 0);
view.translate(0.0, -1.0, 0);
mvp = projection * view * model;
glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, mvp.constData());
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos[1]);
glDrawElements(GL_TRIANGLES, sizeof(vCubeIndices)/sizeof(GLushort), GL_UNSIGNED_SHORT, 0);
view.translate(1.0, 0, 0);
mvp = projection * view * model;
glUniformMatrix4fv(mvpLoc, 1, GL_FALSE, mvp.constData());
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos[1]);
glDrawElements(GL_TRIANGLES, sizeof(vCubeIndices)/sizeof(GLushort), GL_UNSIGNED_SHORT, 0);
QPainter painter(m_device);
render(&painter);
glDeleteBuffers(2, vbos);
}
好了大致就這樣了~