QT+Opengl ES2.0顯示立方體

好嘞,這一次是一個不錯的突破。前幾次的文章中已經將點雲用QT結合opengl顯示出來了,但是所用的opengl爲固定管線繪製方式。這種方式效率低下,比方說你有一塊100W的點雲,那麼每次paint的時候都要循環繪製100W個點在屏幕上,這也就是爲什麼傳統opengl顯示800W的點雲就開始卡B了。
    筆者寄希望於QT中的OpenglES2.0,這種採用頂點緩存和索引緩存的繪製方式大大提升了效率。
    無奈的是,OpenglES2.0的繪製管線本來就十分複雜,再加上QT這個虛架子,網上的例子少而又少,導致很多人望而卻步。
    筆者研究了QT Creator自帶的所有的Opengl例程,這些例程無一例外地使用了OpenglES2.0的可編程管線,但是卻沒有一個例子符合我的要求,它們全部都進行了紋理加載和顯示,因此着色器也顯得相當複雜,而在這之前作者一點着色器的編程經驗也沒有。我們知道繪製點雲是不需要紋理座標的,那麼就有必要官方例子進行改進。
    筆者這才決定,先把官方的紋理立方體修改爲彩色立方體,然後再從彩色立方體過渡到點雲。實際上修改爲彩色立方體之後,再修改爲顯示點雲已經是小菜一碟了。
    但這時我發現噩夢纔剛剛開始。我必須先須學習glsl語言,然後重新定義頂點緩存和索引緩存進行繪製,只要有微小的錯誤都有可能導致錯誤的結果。
    爲了這點事情,作者不得不每天下班坐一個多小時的車回寢室然後開始研究。本人查遍了網上幾乎所有相關博文,但是一篇能用的都沒有。作者在這一點上是很失望的。
    還好本人有着打不死的小強精神,經過線索的拼湊,這一問題終於在第四個夜晚——今天告破了。
    QT+opengl ES 2.0的彩色立方體終於被本人無情地顯示出來了。同時也感嘆求學不易,少年仍需努力。
    下面我就把代碼全部分享出來,打上必要的註釋。

    老規矩,我們先上效果。


這個GIF是不是顯示有點問題啊。。。不過不管了。關鍵的地方我用紅色標註了。
上代碼。

首先是一個點雲發動機,我們在這裏面進行索引和緩存的定義,它繼承自QOpenGLFunctions

class qScarlet_GLCloudEngin_ES2 : protected QOpenGLFunctions
{
public:
    qScarlet_GLCloudEngin_ES2();
    virtual ~qScarlet_GLCloudEngin_ES2();

    void drawCubeGeometry(QOpenGLShaderProgram *program);

private:
    void initCubeGeometry();

    QOpenGLBuffer arrayBuf;
    QOpenGLBuffer indexBuf;
};
它的CPP對應如下:

qScarlet_GLCloudEngin_ES2::qScarlet_GLCloudEngin_ES2()
    : indexBuf(QOpenGLBuffer::IndexBuffer)
{
    initializeOpenGLFunctions();

    // Generate 2 VBOs
    arrayBuf.create();
    indexBuf.create();

    // Initializes cube geometry and transfers it to VBOs
    initCubeGeometry();
}
qScarlet_GLCloudEngin_ES2::~qScarlet_GLCloudEngin_ES2()
{
    arrayBuf.destroy();
    indexBuf.destroy();
}
void qScarlet_GLCloudEngin_ES2::initCubeGeometry()
{
    // For cube we would need only 8 vertices but we have to
    // duplicate vertex for each face because texture coordinate
    // is different.
//這裏是八個頂點的存儲位置
    CubeVertexData vertices[] = {
        // Vertex data for face 0
        {QVector3D(1.0f,  1.0f,  1.0f), QVector3D(0.0f, 0.0f,1.0f)},  // v0
        {QVector3D(-1.0f, 1.0f,  1.0f), QVector3D(1.0f, 0.0f,1.0f)}, // v1
        {QVector3D(-1.0f, -1.0f,  1.0f), QVector3D(0.0f, 1.0f,1.0f)},  // v2
        {QVector3D( 1.0f, -1.0f,  1.0f), QVector3D(1.0f, 1.0f,1.0f)}, // v3

        // Vertex data for face 1
        {QVector3D( 1.0f, 1.0f,  -1.0f), QVector3D( 1.0f, 0.0f,0.0f)}, // v4
        {QVector3D( -1.0f, 1.0f,  -1.0f), QVector3D(0.0f,1.0f, 0.0f)}, // v5
        {QVector3D( -1.0f,  -1.0f,  -1.0f), QVector3D(1.0f, 1.0f,0.0f)},  // v6
        {QVector3D( 1.0f,  -1.0f,  -1.0f), QVector3D(0.33f, 0.30f,0.60f)}, // v7
    };
//它們的索引緩存。
    GLushort indices[] = {
        0,1,2,0,2,3,
        4,0,3,4,3,7,
        5,4,7,5,7,6,
        1,5,6,1,6,2,
        5,1,0,5,0,4,
        6,7,3,6,3,2
    };
    // Transfer vertex data to VBO 0
    arrayBuf.bind();
    arrayBuf.allocate(vertices, 8 * sizeof(CubeVertexData));//這裏放置頂點緩存的個數,立方體一共有八個頂點,而不是六個。。。

    // Transfer index data to VBO 1
    indexBuf.bind();
    indexBuf.allocate(indices, 36 * sizeof(GLushort));//面片有3*2*6一共36個頂點。
}
 
void qScarlet_GLCloudEngin_ES2::drawCubeGeometry(QOpenGLShaderProgram *program)
{
    // Tell OpenGL which VBOs to use
    arrayBuf.bind();
    indexBuf.bind();

    // Offset for position
    quintptr offset = 0;

    // Tell OpenGL programmable pipeline how to locate vertex position data
    int vertexLocation = program->attributeLocation("a_position");//這個名字一定要和glsl對應好,下面的colAttr也是。,他們是作爲輸入參數在glsl中被存儲的。
    program->enableAttributeArray(vertexLocation);
    program->setAttributeBuffer(vertexLocation, GL_FLOAT, offset, 3, sizeof(CubeVertexData));

    // Offset for texture coordinate
    offset += sizeof(QVector3D);

    // Tell OpenGL programmable pipeline how to locate vertex texture coordinate data
    int texcoordLocation = program->attributeLocation("colAttr");
    program->enableAttributeArray(texcoordLocation);
    program->setAttributeBuffer(texcoordLocation, GL_FLOAT, offset,3, sizeof(CubeVertexData));

    // Draw cube geometry using indices from VBO 1
    //GL_TRIANGLE_FAN  GL_TRIANGLE_STRIP GL_QUADS  GL_TRIANGLE_STRIP GL_POINTS
    glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_SHORT, 0);
}
然後是一個用於顯示和控制旋轉縮放的Wildget,這個借鑑了官方例子的相機,因爲我覺得它的控制做得滿足我的要求。
class qScarlet_GLWidget_ES2 : public QOpenGLWidget, protected QOpenGLFunctions
{
    Q_OBJECT

public:
    explicit qScarlet_GLWidget_ES2(QWidget *parent = 0);
    ~qScarlet_GLWidget_ES2();

protected:
    void mousePressEvent(QMouseEvent *e) Q_DECL_OVERRIDE;
    void mouseReleaseEvent(QMouseEvent *e) Q_DECL_OVERRIDE;
    void mouseMoveEvent(QMouseEvent *e) Q_DECL_OVERRIDE;
    void timerEvent(QTimerEvent *e) Q_DECL_OVERRIDE;
    void wheelEvent(QWheelEvent *e);

    void initializeGL() Q_DECL_OVERRIDE;
    void resizeGL(int w, int h) Q_DECL_OVERRIDE;
    void paintGL() Q_DECL_OVERRIDE;

    void initShaders();
    void initTextures();

private:
    QBasicTimer timer;
    QOpenGLShaderProgram program;
    qScarlet_GLCloudEngin_ES2 *geometries;
    QOpenGLTexture *texture;
    //we don't use texture but we leave it along.
    QMatrix4x4 projection;
    QVector2D mousePressPosition;
    QVector3D rotationAxis;
    qreal angularSpeed;
    QQuaternion rotation;
    GLfloat zoom;
};
它的cpp文件對應如下: 

qScarlet_GLWidget_ES2::qScarlet_GLWidget_ES2(QWidget *parent) :
    QOpenGLWidget(parent),
    geometries(0),
    texture(0),
    angularSpeed(0)
{
    //resourcescarlet2.qrc
    //也不知道這句話是不是有用
    //Q_INIT_RESOURCE(resourcescarlet2);
}

qScarlet_GLWidget_ES2::~qScarlet_GLWidget_ES2()
{
    // Make sure the context is current when deleting the texture
    // and the buffers.
    makeCurrent();
    delete texture;
    delete geometries;
    doneCurrent();
}
void qScarlet_GLWidget_ES2::mousePressEvent(QMouseEvent *e)
{
    // Save mouse press position
    mousePressPosition = QVector2D(e->localPos());
}
void qScarlet_GLWidget_ES2::mouseMoveEvent(QMouseEvent *e)
{
    // Mouse release position - mouse press position
    QVector2D diff = QVector2D(e->localPos()) - mousePressPosition;
    // Rotation axis is perpendicular to the mouse position difference
    // vector
    QVector3D n = QVector3D(diff.y(), diff.x(), 0.0).normalized();
    // Accelerate angular speed relative to the length of the mouse sweep
    qreal acc = diff.length() / 100.0;
    // Calculate new rotation axis as weighted sum
    rotationAxis = (rotationAxis * angularSpeed + n * acc).normalized();

    angularSpeed += acc;
}
void qScarlet_GLWidget_ES2::mouseReleaseEvent(QMouseEvent *e)
{
    angularSpeed=0;
}
void qScarlet_GLWidget_ES2::timerEvent(QTimerEvent *)
{
    // Decrease angular speed (friction)
    angularSpeed *= 0.79;//使用乘法來降低速度666

    // Stop rotation when speed goes below threshold
    if (angularSpeed < 0.01)
    {
        angularSpeed = 0.0;
    }
    else
    {
        // Update rotation
        rotation = QQuaternion::fromAxisAndAngle(rotationAxis, angularSpeed) * rotation;
    }
    update();
}
void qScarlet_GLWidget_ES2::initializeGL()
{
    initializeOpenGLFunctions();
    glClearColor(0, 0, 0, 1);
    initShaders();
    initTextures();
    zoom =-5;
    // Enable depth buffer
    glEnable(GL_DEPTH_TEST);
    // Enable back face culling
    glEnable(GL_CULL_FACE);
    geometries = new qScarlet_GLCloudEngin_ES2();
    // Use QBasicTimer because its faster than QTimer
    timer.start(12, this);
}
void qScarlet_GLWidget_ES2::initShaders()//加載shader,這裏必須注意,新加入glsl文件後必須把QTcreator生成的Release文件夾刪掉然後重新編譯一下才會識別,作者爲這個問題活活憋了一個晚上才知道,原來這是QT的bug。馬丹。。。。
{
    // Compile vertex shader
    if (!program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/glsl/glsl/qScarlet_vshader.glsl"))
        close();
    // Compile fragment shader
   if (!program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/glsl/glsl/qScarlet_fshader.glsl"))
        close();
    // Link shader pipeline
    if (!program.link())
        close();
    // Bind shader pipeline for use
    if (!program.bind())
        close();
}
void qScarlet_GLWidget_ES2::initTextures()
{
    // Load cube.png image
    texture = new QOpenGLTexture(QImage(":/logo/alllogo/OfficialOpenglCube.png").mirrored());

    // Set nearest filtering mode for texture minification
    texture->setMinificationFilter(QOpenGLTexture::Nearest);

    // Set bilinear filtering mode for texture magnification
    texture->setMagnificationFilter(QOpenGLTexture::Linear);

    // Wrap texture coordinates by repeating
    // f.ex. texture coordinate (1.1, 1.2) is same as (0.1, 0.2)
    texture->setWrapMode(QOpenGLTexture::Repeat);
}
void qScarlet_GLWidget_ES2::resizeGL(int w, int h)
{
    // Calculate aspect ratio
    qreal aspect = qreal(w) / qreal(h ? h : 1);

    // Set near plane to 3.0, far plane to 7.0, field of view 45 degrees
    const qreal zNear = 0.001, zFar = 1000.0, fov = 45.0;

    // Reset projection
    projection.setToIdentity();

    // Set perspective projection
    projection.perspective(fov, aspect, zNear, zFar);
}
void qScarlet_GLWidget_ES2::paintGL()
{
    // Clear color and depth buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    texture->bind();

    // Calculate model view transformation
    QMatrix4x4 matrix;
    matrix.translate(0.0, 0.0, -5.0);//zoom
    matrix.translate(0.0, 0.0, zoom);//
    matrix.rotate(rotation);
    // Set modelview-projection matrix
    program.setUniformValue("mvp_matrix", projection * matrix);
    // Use texture unit 0 which contains cube.png
    //program.setUniformValue("texture", 0);
    // Draw cube geometry
    geometries->drawCubeGeometry(&program);
}
void qScarlet_GLWidget_ES2::wheelEvent(QWheelEvent *e)
{
//    當鼠標滑輪在滾動時用於返回滑動的距離,該值等於鼠標旋轉角度的8倍。正數值表示滑輪相對於用戶在向前滑動,
//    相反,負數值表示滑輪相對於用戶是向後滑動的。
    //int mydelta2 = e->delta();
    double ZoomSlowDown =2.5;
    zoom += (double)(e->delta())/8.0/15.0/ZoomSlowDown;
    update();
}
最後把兩個最關鍵的Shader放上來:
頂點着色器:
#ifdef GL_ES
precision mediump int;
precision mediump float;
#endif
uniform mat4 mvp_matrix;
attribute vec4 a_position;  //解釋一下,attribute代表這是一個可由外部傳入的變量
attribute lowp vec4 colAttr;
varying lowp vec4 col;//varying代表opengl的傳出變量,這個變量會由頂點着色器傳出,然後傳入像素着色器。
void main()
{
    gl_Position = mvp_matrix * a_position;//
     col = colAttr;
}
然後下面是像素着色器。簡單一句話就沒了,但是記住這個傳出的col的名字一定要和上面相同才行。
#ifdef GL_ES
precision mediump int;
precision mediump float;
#endif
varying lowp vec4 col;
void main()
{
    gl_FragColor = col;
} 
//至此所有代碼完畢。




發佈了41 篇原創文章 · 獲贊 62 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章