您当前的位置: 首页 >  qt

龚建波

暂无认证

  • 3浏览

    0关注

    313博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

OpenGL with QtWidgets:练习之绘制2D环形进度条

龚建波 发布时间:2020-05-10 22:04:33 ,浏览量:3

1.实现思路

这里主要涉及几个点:绘制圆环,绘制文字,动画,抗锯齿。

绘制圆环网上有些人是计算好圆边的顶点后传入的,我这里直接在片段着色器里根据距离圆心的距离来渲染的圆环。

void main()
{
    float len = abs(sqrt(pow(thePos.x,2)+pow(thePos.y,2)));
    float alpha = abs(len-0.75);
    alpha = (alpha>0.15)?0.0:1.0;
    FragColor = vec4(0.4,0.1,0.6,alpha);
}

绘制文字的话,网上有的人是先绘制到纹理上再渲染问题,不过 Qt 的 QOpenGLWidget 类可以配合 QPianter 使用。因为是 2D,我直接使用 QPianter 绘制的文字。这里面也遇到问题,就是 QPainter 绘制的文字和在别的画布上呈现的效果不一样,最后选择的微软雅黑效果才好点。

void CircleProgressBar::paintGL()
{
    QPainter painter(this);
    painter.setPen(Qt::white);
    painter.setFont(QFont("Microsoft YaHei",16));
    const QString text_val=QString::number(progress*100,'f',2)+" %";
    const int text_x=width()/2-painter.fontMetrics().width(text_val)/2;
    const int text_y=height()/2+painter.fontMetrics().height()/2;
    painter.drawText(text_x,text_y,text_val);
}

动画我使用的属性动画而不是 QTimer,让 Qt 来决定刷新的时机。

抗锯齿我参照了网上的一些方式,比如多重采样什么的都没效果,最后用的 smoothstep 函数来实现的圆环部分的抗锯齿。

(参照:Shader smoothstep使用_冠位仓鼠--慕白-CSDN博客_smoothstep)

void main()
{
    float len = abs(sqrt(pow(thePos.x,2)+pow(thePos.y,2)));
    //float alpha = 1.0-smoothstep(0.15,0.15+aSmoothWidth,abs(len-0.75));
    float alpha = smoothstep(0.15+aSmoothWidth,0.15,abs(len-0.75));
    FragColor = vec4(0.4,0.1,0.6,alpha);
}

色条上的锯齿用的 smoothstep 配合 mix 消除。

(题外话,我开始以为 GLSL 没有 atan2 函数,哪成想他的 atan 有个重载版本就是 atan2 的功能;此外,OpenGL 和 OpenGL ES  的 GLSL 在语法上有一些小的区别,有点坑) 

(2021-5-4)修改了进度值为 0 时因为 smoothstep 导致还有一条横线的 bug,增加了进度大于零的判断。

2.实现代码

(项目 git 链接:https://github.com/gongjianbo/EasyOpenGL2D)

实现效果(GIF):

主要实现代码:

#ifndef CIRCLEPROGRESSBAR_H
#define CIRCLEPROGRESSBAR_H

#include 
#include 
#include 
#include 
#include 

#include 

//龚建波:环形进度条
class CircleProgressBar : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
{
    Q_OBJECT
    Q_PROPERTY(double drawValue READ getDrawValue WRITE setDrawValue)
public:
    explicit CircleProgressBar(QWidget *parent = nullptr);
    ~CircleProgressBar();

    void setRange(double min,double max);
    void setValue(double value);

    double getDrawValue() const;
    void setDrawValue(double value);

protected:
    //设置OpenGL资源和状态。在第一次调用resizeGL或paintGL之前被调用一次
    void initializeGL() override;
    //渲染OpenGL场景,每当需要更新小部件时使用
    void paintGL() override;
    //设置OpenGL视口、投影等,每当尺寸大小改变时调用
    void resizeGL(int width, int height) override;

private:
    //着色器程序
    QOpenGLShaderProgram shaderProgram;
    //顶点数组对象
    QOpenGLVertexArrayObject vao;
    //顶点缓冲
    QOpenGLBuffer vbo;
    //属性动画
    QPropertyAnimation *animation;
    //进度值
    double progressMin=0;
    double progressMax=100;
    double progressValue=0; //设置的值
    double progressDraw=0; //绘制临时值
};

#endif // CIRCLEPROGRESSBAR_H
#include "CircleProgressBar.h"

#include 
#include 

CircleProgressBar::CircleProgressBar(QWidget *parent)
    : QOpenGLWidget(parent)
{
    animation=new QPropertyAnimation(this,"drawValue");
    animation->setDuration(2000); //动画持续时间
    animation->setEasingCurve(QEasingCurve::OutQuart); //先快后慢
}

CircleProgressBar::~CircleProgressBar()
{
    makeCurrent();
    vbo.destroy();
    vao.destroy();
    doneCurrent();
}

void CircleProgressBar::setRange(double min, double max)
{
    if(progressMaxsetStartValue(progressDraw);
    animation->setEndValue(progressValue);
    animation->start();
}

double CircleProgressBar::getDrawValue() const
{
    return progressDraw;
}

void CircleProgressBar::setDrawValue(double value)
{
    progressDraw=value;
    update();
}

void CircleProgressBar::initializeGL()
{
    //为当前上下文初始化OpenGL函数解析
    initializeOpenGLFunctions();

    //着色器代码
    //in输入,out输出,uniform从cpu向gpu发送
    //[aPos]两个三角的顶点数据
    //[thePos]表示当前像素点
    const char *vertex_str=R"(#version 330 core
                           layout (location = 0) in vec2 aPos;
                           out vec2 thePos;
                           void main()
                           {
                             gl_Position = vec4(aPos, 0.0, 1.0);
                             thePos = aPos;
                           })";
    //GLSL的atan2也叫atan,不过参数不同,我们封装一个0-360度的归一化值[0,1]的版本
    //[FragColor]该点输出颜色,gl_FragColor在3移除了,自己声明一个
    //[aValue]进度值
    //[aSmoothWidth]用来计算平滑所需宽度,根据绘制区域大小来计算
    //[len]坐标点距离圆心的距离,[0,1],勾股定理
    //[alpha]使用smoothstep平滑函数取0.75±0.15的圆圈透明度为1
    //[angle]thePos像素点对应的角度值,用于调节渐变,归一化到[0,1]
    //[angle_smooth]进度值那条斜线取平滑
    //[ret smoothstep(a,b,x)]可以用来生成0-1的平滑过渡,达到抗锯齿效果
    //返回0: xb
    //返回1: xa
    //返回n: 根据x在ab间位置,返回[0,1]过度值
    const char *fragment_str=R"(#version 330 core
                             #define PI 3.14159265
                             uniform float aValue;
                             uniform float aSmoothWidth;
                             in vec2 thePos;
                             out vec4 FragColor;

                             float myatan2(float y,float x)
                             {
                               float ret_val = 0.0;
                               if(x != 0.0){
                                 ret_val = atan(y,x);
                                 if(ret_val < 0.0){
                                   ret_val += 2.0*PI;
                                 }
                               }else{
                                 ret_val = y>0 ? PI*0.5 : PI*1.5;
                               }
                               return ret_val/(2.0*PI);
                             }

                             void main()
                             {
                             float len = abs(sqrt(pow(thePos.x,2.0)+pow(thePos.y,2.0)));
                             float alpha = smoothstep(0.15+aSmoothWidth,0.15,abs(len-0.75));
                             float angle = myatan2(thePos.y,thePos.x);
                             float angle_smooth = smoothstep(aValue+aSmoothWidth/3.0,aValue,angle);

                             if(angle_smooth>0.0 && aValue>0.0){
                               if(angle_smooth>=1.0){
                                 FragColor = vec4(1.0,0.1,(1.0-angle),alpha);
                               }else{
                                 FragColor = vec4(mix(vec3(1.0,0.1,(1.0-angle)),vec3(0.4,0.1,0.6),1.0-angle_smooth),alpha);
                               }
                             }else{
                               FragColor = vec4(0.4,0.1,0.6,alpha);
                             }
                             })";


    //将source编译为指定类型的着色器,并添加到此着色器程序
    if(!shaderProgram.addCacheableShaderFromSourceCode(
                QOpenGLShader::Vertex,vertex_str)){
        qDebug()            
关注
打赏
1655829268
查看更多评论
0.0370s