GLSL显示地球仪

33 篇文章 4 订阅
28 篇文章 3 订阅
在博客 不使用gluSphere显示地球仪

中,我介绍了一种opengl显示地球的办法。那种办法的短处是,大量的计算由CPU完成。假如我们在做一个功能很强、CPU负担较重的界面,较为明智的办法是尽量让GPU分担CPU的工作。本篇博客演示的就是通过GLSL,让GPU分担CPU的一部分工作。

先分析《不使用gluSphere显示地球仪》一文中哪些地方可以交给GPU处理:CPU要计算每个Vertex在世界坐标系的位置

void MainWindow::vInitVerticesTextures(int iVerticalAngleStep, int iHorizontalAngleStep, int iR,
                                     GLfloat * & pVertData, GLfloat * & pTexData,
                                     int & iVerticalPntQuantity, int & iHorizontalPntQuantity)
{
    iVerticalPntQuantity = 180 / iVerticalAngleStep;
    iHorizontalPntQuantity = 360 / iHorizontalAngleStep;
    pVertData = new GLfloat[iVerticalPntQuantity * iHorizontalPntQuantity * 18];
    pTexData = new GLfloat[iVerticalPntQuantity * iHorizontalPntQuantity * 12];

    int iVertexNodeCount = 0, iTexNodeCount = 0;
    for(int vAngle = 0; vAngle < 180; vAngle = vAngle + iVerticalAngleStep)// 垂直方向angleSpan度一份
    {
        for(int hAngle = 0; hAngle < 360; hAngle = hAngle + iHorizontalAngleStep)// 水平方向angleSpan度一份
        {
            // 纵向横向各到一个角度后计算对应的此点在球面上的坐标
            float x0 = (float) (iR * sin(vAngle * DEG2RAD) * cos(hAngle * DEG2RAD));
            float y0 = (float) (iR * sin(vAngle * DEG2RAD) * sin(hAngle * DEG2RAD));
            float z0 = (float) (iR * cos(vAngle * DEG2RAD));
                    // Log.w("x0 y0 z0","" + x0 + "  "+y0+ "  " +z0);

            float x1 = (float) (iR * sin(vAngle * DEG2RAD) * cos((hAngle + iHorizontalAngleStep) * DEG2RAD));
            float y1 = (float) (iR * sin(vAngle * DEG2RAD) * sin((hAngle + iHorizontalAngleStep) * DEG2RAD));
            float z1 = (float) (iR * cos(vAngle * DEG2RAD));

            float x2 = (float) (iR * sin((vAngle + iVerticalAngleStep) * DEG2RAD) *
                                cos((hAngle + iHorizontalAngleStep) * DEG2RAD));
            float y2 = (float) (iR * sin((vAngle + iVerticalAngleStep) * DEG2RAD) *
                                sin((hAngle + iHorizontalAngleStep) * DEG2RAD));
            float z2 = (float) (iR * cos((vAngle + iVerticalAngleStep) * DEG2RAD));
                    // Log.w("x2 y2 z2","" + x2 + "  "+y2+ "  " +z2);
            float x3 = (float) (iR * sin((vAngle + iVerticalAngleStep) * DEG2RAD) * cos(hAngle * DEG2RAD));
            float y3 = (float) (iR * sin((vAngle + iVerticalAngleStep) * DEG2RAD) * sin(hAngle * DEG2RAD));
            float z3 = (float) (iR * cos((vAngle + iVerticalAngleStep) * DEG2RAD));
                    // Log.w("x3 y3 z3","" + x3 + "  "+y3+ "  " +z3);
                    // 将计算出来的XYZ坐标加入存放顶点坐标的ArrayList
            pVertData[iVertexNodeCount++] = x1;
                    //alVertix.add(x1);
            pVertData[iVertexNodeCount++] = y1;
                    //alVertix.add(y1);
            pVertData[iVertexNodeCount++] = z1;
                    //alVertix.add(z1);
            pVertData[iVertexNodeCount++] = x3;
                    //alVertix.add(x3);
            pVertData[iVertexNodeCount++] = y3;
                    //alVertix.add(y3);
            pVertData[iVertexNodeCount++] = z3;
                    //alVertix.add(z3);
            pVertData[iVertexNodeCount++] = x0;
                    //alVertix.add(x0);
            pVertData[iVertexNodeCount++] = y0;
                    //alVertix.add(y0);
            pVertData[iVertexNodeCount++] = z0;
                    //alVertix.add(z0);

                    // *****************************************************************
            float s0 = hAngle / 360.0f;
            float s1 = (hAngle + iHorizontalAngleStep)/360.0f ;
            float t0 = 1 - vAngle / 180.0f;
            float t1 = 1 - (vAngle + iVerticalAngleStep) / 180.0f;

            /*t0 = t0 - 0.1;
            t1 = t1 - 0.1;
            if(t0 < 0)
                t0 = t0 + 1;
            if(t1 < 0)
                t1 = t1 + 1;*/

            pTexData[iTexNodeCount++] = s1;
                    //textureVertix.add(s1);// x1 y1对应纹理坐标
            pTexData[iTexNodeCount++] = t0;
                    //textureVertix.add(t0);
            pTexData[iTexNodeCount++] = s0;
                    //textureVertix.add(s0);// x3 y3对应纹理坐标
            pTexData[iTexNodeCount++] = t1;
                    //textureVertix.add(t1);
            pTexData[iTexNodeCount++] = s0;
                    //textureVertix.add(s0);// x0 y0对应纹理坐标
            pTexData[iTexNodeCount++] = t0;
                    //textureVertix.add(t0);

                    // *****************************************************************
            pVertData[iVertexNodeCount++] = x1;
                    //alVertix.add(x1);
            pVertData[iVertexNodeCount++] = y1;
                    //alVertix.add(y1);
            pVertData[iVertexNodeCount++] = z1;
                    //alVertix.add(z1);
            pVertData[iVertexNodeCount++] = x2;
                    //alVertix.add(x2);
            pVertData[iVertexNodeCount++] = y2;
                    //alVertix.add(y2);
            pVertData[iVertexNodeCount++] = z2;
                    //alVertix.add(z2);
            pVertData[iVertexNodeCount++] = x3;
                    //alVertix.add(x3);
            pVertData[iVertexNodeCount++] = y3;
                    //alVertix.add(y3);
            pVertData[iVertexNodeCount++] = z3;
                    //alVertix.add(z3);

                    // *****************************************************************
            pTexData[iTexNodeCount++] = s1;
                    //textureVertix.add(s1);// x1 y1对应纹理坐标
            pTexData[iTexNodeCount++] = t0;
                    //textureVertix.add(t0);
            pTexData[iTexNodeCount++] = s1;
                    //textureVertix.add(s1);// x2 y3对应纹理坐标
            pTexData[iTexNodeCount++] = t1;
                    //textureVertix.add(t1);
            pTexData[iTexNodeCount++] = s0;
                    //textureVertix.add(s0);// x3 y3对应纹理坐标
            pTexData[iTexNodeCount++] = t1;
                    //textureVertix.add(t1);
                    // *****************************************************************
        }
    }
}

其中涉及大量三角函数运算。而三角函数运算是比较耗时的。这部分是我们的优化目标。

在着手优化之前,不妨先分析一下绘制地球仪的原理。

1)地球仪是一个圆球,但是opengl是无法绘制理想的球形的。它所能做的是用多面体来近似完美球形。在本例子里,我把地球按照经度每10度一格,纬度每10度一格来分割。因此地球表面被划分为180 / 10 * 360 / 10 个格子。



2)地球表面的形貌记录在一个矩形 的图片里。这就是我们要用的纹理。


假如把这张图也用网格分割,那么纵向的线对应经线,而横向线代表纬线。这与球面的网格构成对应关系。


我们要做的就是把分割的纹理填充到对应的球面的网格里去。

这通过GLSL的texture()函数实现。

不难看出,纹理的横纵坐标对应地球仪的经纬度。但是GLSL对纹理的坐标取值有规定:0-1.左边=0,最右边=1;最上=1,最下=0。因此在vertex shader里有这一句:

texCoord = vec2(pos.x / 360.0, 1.0 - pos.y / 180.0);

opengl通过setAttributeArray()函数把vertex的坐标传入GPU。shader的数目与vertex的数目相等,每一个shader对应一个vertex。在《不使用gluSphere显示地球仪》一文中,我们让CPU计算了各个vertex的世界坐标。为了减少CPU的负担,在本例中,传入vertex shader的坐标不是各个vertex在世界坐标系的坐标,而是vertex 在球面的经纬度坐标。

"    float x = pos[2] * sin(pos[1] * DEG2RAD) * cos(pos[0] * DEG2RAD);\n"
                        "    float y = pos[2] * sin(pos[1] * DEG2RAD) * sin(pos[0] * DEG2RAD);\n"
                        "    float z = pos[2] * cos(pos[1] * DEG2RAD);\n"
                        "    gl_Position = mat4MVP * vec4(x, y, z, 1.0);\n"

经纬度转世界坐标的工作在vertex shader里完成。最后用mat4MVP * vec4(x,y,z,1.0)来完成世界坐标到观察者坐标的变换。

texCoord随后传入fragment shader。利用texture(Tex, texCoord)获取对应的gl_Position的颜色。相邻的两个vertex之间的点的颜色按如下方法得出:texCoord 与 texCoord之间进行插值。插值的结果也带入texture(Tex, texCoord),得出该点颜色。这一步不需要程序员显式的写入代码,GLSL内部已经做了这样的处理。

最后给出代码:

h文件:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLTexture>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>

class MainWindow : public QOpenGLWidget, protected QOpenGLFunctions
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = 0);
    ~MainWindow();

    GLuint                          m_uiVertLoc;
    GLuint                          m_uiTexNum;
    QOpenGLTexture          *       m_pTextures;
    QOpenGLShaderProgram    *       m_pProgram;
    GLfloat                 *       m_pVertices;
    int                             m_iCount;

protected:
    void        initializeGL();
    void        paintGL();
    void        resizeGL(int w, int h);
};

#endif // MAINWINDOW_H


cpp文件:

#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QOpenGLWidget(parent)
{
}

MainWindow::~MainWindow()
{
    m_pTextures->release();
    delete m_pTextures;
    delete m_pProgram;
    delete [] m_pVertices;

}

void MainWindow::initializeGL()
{
    initializeOpenGLFunctions();
    m_iCount = 0;
    m_uiTexNum = 0;
    QImage img(QString("earth.bmp"));
    //给顶点赋值
    int iAngleStep = 10, iR = 1;
    m_pVertices = new GLfloat[180 / iAngleStep * 360 / iAngleStep * 6 * 3];
    for(int iLongitude = 0; iLongitude < 360; iLongitude += iAngleStep)
    {
        for(int iLatitude = 0; iLatitude < 180; iLatitude += iAngleStep)
        {
            m_pVertices[m_iCount++] = iLongitude;
            m_pVertices[m_iCount++] = iLatitude;
            m_pVertices[m_iCount++] = iR;

            m_pVertices[m_iCount++] = iLongitude;
            m_pVertices[m_iCount++] = iLatitude + iAngleStep;
            m_pVertices[m_iCount++] = iR;

            m_pVertices[m_iCount++] = iLongitude + iAngleStep;
            m_pVertices[m_iCount++] = iLatitude + iAngleStep;
            m_pVertices[m_iCount++] = iR;

            m_pVertices[m_iCount++] = iLongitude + iAngleStep;
            m_pVertices[m_iCount++] = iLatitude + iAngleStep;
            m_pVertices[m_iCount++] = iR;

            m_pVertices[m_iCount++] = iLongitude + iAngleStep;
            m_pVertices[m_iCount++] = iLatitude;
            m_pVertices[m_iCount++] = iR;

            m_pVertices[m_iCount++] = iLongitude;
            m_pVertices[m_iCount++] = iLatitude;
            m_pVertices[m_iCount++] = iR;
        }
    }

    QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);
    const char *vsrc =
                        "#version 330\n"
                        "in vec3 pos;\n"
                        "out vec2 texCoord;\n"
                        "uniform mat4 mat4MVP;\n"
                        "void main()\n"
                        "{\n"
                        "    float DEG2RAD = 3.1415926 / 180.0;\n"
                        "    float x = pos[2] * sin(pos[1] * DEG2RAD) * cos(pos[0] * DEG2RAD);\n"
                        "    float y = pos[2] * sin(pos[1] * DEG2RAD) * sin(pos[0] * DEG2RAD);\n"
                        "    float z = pos[2] * cos(pos[1] * DEG2RAD);\n"
                        "    gl_Position = mat4MVP * vec4(x, y, z, 1.0);\n"
                        "    texCoord = vec2(pos.x / 360.0, 1.0 - pos.y / 180.0);\n"//纹理的Y方向是从下向上的,而pos.y的正方向是从上向下,所以是1.0 - pos.y / 180.0
                        "}\n";
    vshader->compileSourceCode(vsrc);

    QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);
    const char *fsrc =
                        "#version 330\n"
                        "out vec4 color;\n"
                        "in vec2 texCoord;\n"
                        "uniform sampler2D Tex\n;"
                        "void main()\n"
                        "{\n"
                        "    color = texture(Tex, texCoord);\n"//注意,texCoord的值域在0-1之间
                        "}\n";
    fshader->compileSourceCode(fsrc);

    m_pProgram = new QOpenGLShaderProgram;
    m_pProgram->addShader(vshader);
    m_pProgram->addShader(fshader);
    m_pProgram->link();
    m_pProgram->bind();

    m_uiVertLoc = m_pProgram->attributeLocation("pos");
    m_pProgram->enableAttributeArray(m_uiVertLoc);
    m_pProgram->setAttributeArray(m_uiVertLoc, m_pVertices, 3, 0);


    m_pTextures = new QOpenGLTexture(img.mirrored());
    m_pTextures->setMinificationFilter(QOpenGLTexture::Nearest);
    m_pTextures->setMagnificationFilter(QOpenGLTexture::Linear);
    m_pTextures->setWrapMode(QOpenGLTexture::Repeat);
    m_pProgram->setUniformValue("Tex", m_uiTexNum);

    qDebug()<<m_iCount;

    glEnable(GL_DEPTH_TEST);
    glClearColor(0,0,0,1);
}

void MainWindow::paintGL()
{
    //QMatrix4x4在声明时被默认为单位矩阵
    QMatrix4x4 m1, m2, m3, m;

    m1.ortho(-1.0f, +1.0f, -1.0f, 1.0f, -50.0f, 50.0f);//right//generate projection matrix
    m2.lookAt(QVector3D(2,0,0), QVector3D(0,0,0), QVector3D(0,0,10));//generate view matrix, right
    qDebug()<<m2;
    //m3.translate(0,-0.707,0.0);//right, generate model matrices
    //m3.rotate(45, 0.0f, 0.0f, 1.0f);//right, generate model matrices

    m = m1 * m2 * m3;

    m_pProgram->setUniformValue("mat4MVP", m);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    m_pTextures->bind(m_uiTexNum);
    glDrawArrays(GL_TRIANGLES, 0, m_iCount / 3);
    m_pTextures->release();
}

void MainWindow::resizeGL(int w, int h)
{
    glViewport(0,0,w,h);
}

效果:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值