1. 程式人生 > >qt opengl 2d紋理到3d球面

qt opengl 2d紋理到3d球面

   根據前面的實踐,已能通過程式碼生成球面的頂點,要將2維圖貼到球表面;只要能計算出每個頂點對應的紋理座標就可以了;關於2維圖貼到球面的公式如下:

         float pi = 3.14159265;

    U = arcsin(z / R)/pi + 0.5;
    V = arctan(y/x)/2/pi;

        x、y、z分別為頂點的三個座標。由此我們便可以貼圖了,另外我們還可以利用shader函式幫我們計算,以減少cpu消耗。

  我在貼圖的同時,加入了光照效果;其渲染器實現如下:

#ifndef BALLTEXTURERENDER_H
#define BALLTEXTURERENDER_H

#include <QOpenGLExtraFunctions>
#include <QOpenGLTexture>
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QImage>
#define PI 3.14159265f

class BallTextureRender
{
public:
    BallTextureRender() = default;
    ~BallTextureRender();
    void initsize(float r,QImage &img); //根據圖片,生成紋理資料
    void render(QOpenGLExtraFunctions *f,QMatrix4x4 &pMatrix,QMatrix4x4 &vMatrix,QMatrix4x4 &mMatrix,QVector3D &lightLocation,QVector3D &camera); //渲染

private:
    QOpenGLShaderProgram program_;
    QOpenGLTexture *texture_{nullptr};
    QOpenGLBuffer vbo_;
    float r_ = 0.0f;
    QVector<GLfloat> points_;
};

#endif // BALLTEXTURERENDER_H
#include "balltexturerender.h"

BallTextureRender::~BallTextureRender()
{
    if(texture_){
        texture_->destroy();
        delete texture_;
    }
}

void BallTextureRender::initsize(float r, QImage &img)
{
    program_.addCacheableShaderFromSourceFile(QOpenGLShader::Vertex,"vsrc.vert");
    program_.addCacheableShaderFromSourceFile(QOpenGLShader::Fragment,"fsrc.frag");
    program_.link();

    r_ = r; //生成球面頂點
    int angleSpan = 10; //弧度 = 角度 * PI / 180
    for(int vAngle = -90; vAngle < 90; vAngle = vAngle + angleSpan){ //生成球面頂點
        for(int hAngle = 0; hAngle <= 360; hAngle = hAngle + angleSpan){
            float x0 = r * ::cos(vAngle * PI / 180) * ::cos(hAngle * PI / 180);
            float y0 = r * ::cos(vAngle * PI / 180) * ::sin(hAngle * PI / 180);
            float z0 = r * ::sin(vAngle * PI / 180);

            float x1 = r * ::cos(vAngle * PI / 180) * ::cos((hAngle + angleSpan) * PI / 180);
            float y1 = r * ::cos(vAngle * PI / 180) * ::sin((hAngle + angleSpan) * PI / 180);
            float z1 = r * ::sin(vAngle * PI / 180);

            float x2 = r * ::cos((vAngle + angleSpan) * PI / 180) * ::cos((hAngle + angleSpan) * PI / 180);
            float y2 = r * ::cos((vAngle + angleSpan) * PI / 180) * ::sin((hAngle + angleSpan) * PI / 180);
            float z2 = r * ::sin((vAngle + angleSpan) * PI / 180);

            float x3 = r * ::cos((vAngle + angleSpan) * PI / 180) * ::cos(hAngle * PI / 180);
            float y3 = r * ::cos((vAngle + angleSpan) * PI / 180) * ::sin(hAngle * PI / 180);
            float z3 = r * ::sin((vAngle + angleSpan) * PI / 180);

            points_ << x1 << y1 << z1 << x3 << y3 << z3
                   << x0 << y0 << z0 << x1 << y1 << z1
                   << x2 << y2 << z2 << x3 << y3 << z3;
        }
    }

    texture_ = new QOpenGLTexture(img); //生成紋理資料
    vbo_.create();
    vbo_.bind();
    vbo_.allocate(points_.constData(),points_.count() * sizeof GLfloat);
}

void BallTextureRender::render(QOpenGLExtraFunctions *f, QMatrix4x4 &pMatrix, QMatrix4x4 &vMatrix, QMatrix4x4 &mMatrix, QVector3D &lightLocation, QVector3D &camera)
{
    f->glEnable(GL_DEPTH_TEST);
    f->glEnable(GL_CULL_FACE);
    program_.bind();
    vbo_.bind();
    f->glActiveTexture(GL_TEXTURE0 + 0); //使用0號紋理
    program_.setUniformValue("uPMatrix",pMatrix);
    program_.setUniformValue("uVMatrix",vMatrix);
    program_.setUniformValue("uMMatrix",mMatrix);
    program_.setUniformValue("uLightLocation",lightLocation);
    program_.setUniformValue("uCamera",camera);
    program_.setUniformValue("uR",r_);
    program_.setUniformValue("sTexture",0);
    program_.enableAttributeArray(0);
    program_.enableAttributeArray(1);

    texture_->bind(0); //球面上的頂點,同時也是法向量
    program_.setAttributeBuffer(0,GL_FLOAT,0,3,0);
    program_.setAttributeBuffer(1,GL_FLOAT,0,3,0);
    f->glDrawArrays(GL_TRIANGLES,0,points_.count() / 3);

    program_.disableAttributeArray(0);
    program_.disableAttributeArray(1);
    texture_->release();
    vbo_.release();
    program_.release();
    f->glDisable(GL_DEPTH_TEST);
    f->glDisable(GL_CULL_FACE);
}

   其shader如下,光照部分採用的混合光,和前面的一樣。

#version 330
uniform mat4 uPMatrix,uVMatrix,uMMatrix; //投影,檢視,轉換矩陣
uniform vec3 uLightLocation,uCamera; //光源位置,攝像機位置
uniform float uR; //球半徑
layout (location = 0) in vec3 aPosition;
layout (location = 1) in vec3 aNormal;
smooth out vec2 vTextureCood; //每個頂點對應的紋理座標
smooth out vec4 vAmbient; //環境光強度,傳遞到片元著色器
smooth out vec4 vDiffuse; //散射光強度,傳遞到片元著色器
smooth out vec4 vSpecular; //調光強度,傳遞到片元著色器

void pointLight(in vec3 normal,inout vec4 ambient,inout vec4 diffuse,inout vec4 specular,in vec4 lightAmbient,in vec4 lightDiffuse,in vec4 lightSpecular,in float shininess){
    ambient = lightAmbient;

    vec3 normalTarget = aPosition + normal;
    vec3 newNormal = normalize((uMMatrix * vec4(normalTarget,1)).xyz - (uMMatrix * vec4(aPosition,1)).xyz);
    vec3 eye = normalize(uCamera - (uMMatrix * vec4(aPosition,1)).xyz);
    vec3 vp = normalize(uLightLocation - (uMMatrix * vec4(aPosition,1)).xyz);
    vec3 halfVector = normalize(eye + vp);

    float nDotViewPotision = max(0.0,dot(newNormal,vp));
    diffuse = lightDiffuse * nDotViewPotision;

    float nDotViewHalfVector = dot(newNormal,halfVector);
    float powerFactor = max(0.0,pow(nDotViewHalfVector,shininess));
    specular = lightSpecular * powerFactor;
}

void main(void)
{
    gl_Position = uPMatrix * uVMatrix * uMMatrix * vec4(aPosition,1);
    vec4 ambient = vec4(0.0,0.0,0.0,0.0),diffuse = vec4(0.0,0.0,0.0,0.0),specular = vec4(0.0,0.0,0.0,0.0);
    pointLight(aNormal,ambient,diffuse,specular,vec4(0.15,0.15,0.15,1.0),vec4(0.8,0.8,0.8,1.0),vec4(0.7,0.7,0.7,1),50);
    vec2 textureCood = vec2(0.0,0.0);
    float pi = 3.14159265;
    textureCood.x = asin(aPosition.z / uR)/pi + 0.5; //根據公式計算u座標
    textureCood.y = atan(aPosition.y/aPosition.x)/2/pi; //根據公式計算v座標
    vTextureCood = textureCood;
    vAmbient = ambient;
    vDiffuse = diffuse;
    vSpecular = specular;
}

    通過以上程式碼渲染器就完成了,接下來是使用。我讓球面在自轉的同時也執行了公轉。實現如下

#ifndef WIDGET_H
#define WIDGET_H

#include <QOpenGLWidget>
#include <QTimer>
#include "balltexturerender.h"
class Widget : public QOpenGLWidget
{
    Q_OBJECT

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

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

private:
    BallTextureRender render_; //渲染器
    QVector3D lightLocation_,camera_; //光源位置和攝像機位置
    QMatrix4x4 pMatrix; //投影矩陣
    QTimer tm_; 
    float angle_ = 0; //旋轉角度

private slots:
    void slotTimeout();
};

#endif // WIDGET_H
#include "widget.h"

Widget::Widget(QWidget *parent)
    : QOpenGLWidget(parent)
{
    connect(&tm_,SIGNAL(timeout()),this,SLOT(slotTimeout()));
    tm_.start(30);
}

Widget::~Widget()
{

}

void Widget::initializeGL()
{
    render_.initsize(1.0,QImage("test.jpg")); //生成頂點和紋理資料
    lightLocation_ = QVector3D(50,10,0);
    camera_ = QVector3D(0,0,6);
}

void Widget::paintGL()
{
    QOpenGLExtraFunctions *f = QOpenGLContext::currentContext()->extraFunctions();
    f->glClearColor(0.0,0.0,0.0,1.0);
    f->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    QMatrix4x4 vMatrix;
    vMatrix.lookAt(camera_,QVector3D(0,0,-1),QVector3D(0,1,0));

    QMatrix4x4 mMatrix;
    //先x軸旋轉30度,然後再y軸旋轉一個遞增角度,最後平移,實現公轉和自轉
    mMatrix.rotate(30,1,0,0); 
    mMatrix.rotate(angle_,0,1,0);
    mMatrix.translate(1.5,0,0);

    render_.render(f,pMatrix,vMatrix,mMatrix,lightLocation_,camera_);
    angle_ += 5;
}

void Widget::resizeGL(int w, int h)
{
    pMatrix.setToIdentity();
    pMatrix.perspective(45,float(w)/h,0.01f,100.0f);
}

void Widget::slotTimeout()
{
    update();
}