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 <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>#include <QPropertyAnimation>//龚建波:环形进度条
class CircleProgressBar : public QOpenGLWidget, protected QOpenGLFunctions_3_3_Core
{Q_OBJECTQ_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 <QPainter>
#include <QDebug>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(progressMax<=progressMin)return;progressMin=min;progressMax=max;
}void CircleProgressBar::setValue(double value)
{if(value<progressMin||value>progressMax)return;progressDraw=progressValue;progressValue=value;animation->setStartValue(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 corelayout (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: x<a<b 或者 x>a>b//返回1: x<b<a 或者 x>b>a//返回n: 根据x在ab间位置,返回[0,1]过度值const char *fragment_str=R"(#version 330 core#define PI 3.14159265uniform 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()<<"compiler vertex error"<<shaderProgram.log();}if(!shaderProgram.addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,fragment_str)){qDebug()<<"compiler fragment error"<<shaderProgram.log();}//使用addShader()将添加到该程序的着色器链接在一起。if(!shaderProgram.link()){qDebug()<<"link shaderprogram error"<<shaderProgram.log();}//两个三角拼接的一个矩形const float vertices[] = {-1.0f, -1.0f, //左下角+1.0f, -1.0f, //右下角+1.0f, +1.0f, //右上角+1.0f, +1.0f, //右上角-1.0f, +1.0f, //左上角-1.0f, -1.0f, //左下角};vao.create();vao.bind();vbo=QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);vbo.create();vbo.bind();vbo.allocate(vertices,sizeof(vertices));// position attributeint attr = -1;attr = shaderProgram.attributeLocation("aPos");//setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0)shaderProgram.setAttributeBuffer(attr, GL_FLOAT, 0, 2, sizeof(GLfloat) * 2);shaderProgram.enableAttributeArray(attr);
}void CircleProgressBar::paintGL()
{//以短边为边长,保持比例,Qt这里有个问题,在resize里设置的没用const int item_w=width()>height()?height():width();glViewport((width()-item_w)/2,(height()-item_w)/2,item_w,item_w);glClearColor(0.1f, 0.2f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);glEnable(GL_BLEND);//基于源像素Alpha通道值的半透明混合函数glBlendFunc(GL_SRC_ALPHA, GL_ONE);//开启多重采样抗锯齿,貌似没啥效果//glEnable(GL_MULTISAMPLE);shaderProgram.bind();//把进度[min,max]归一化[0,1]const float progress=(progressDraw-progressMin)/(progressMax-progressMin);qDebug()<<"draw progress"<<progress;shaderProgram.setUniformValue("aValue", progress);//aSmoothWidth用来计算平滑所需宽度,根据不同的大小来计算,这里用N px的宽度shaderProgram.setUniformValue("aSmoothWidth", float(3.0/item_w));vao.bind();glDrawArrays(GL_TRIANGLES, 0, 6);vao.release();shaderProgram.release();//目前文字用QPainter绘制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);
}void CircleProgressBar::resizeGL(int width, int height)
{//以短边为边长,保持比例,Qt这里有个问题,在resize里设置的没用const int item_w=width>height?height:width;glViewport((width-item_w)/2,(height-item_w)/2,item_w,item_w);
}

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

  1. 【WPF】环形进度条

    WPF中自带有长条形的进度条,大部分场景都算适用,但是仍然有一部分空间小的场景不太合适,此时我们想到安卓上常用的环形进度条,美观,又不占空间. 那么WPF中的环形进度条控件在哪呢? 很遗憾,自带组件中 ...

  2. 基于canvas 2D实现微信小程序自定义组件-环形进度条

    基于canvas 2D实现微信小程序自定义组件-环形进度条 最近开发一个小程序项目博闻金榜答题小程序,需要使用到一个可以显示答题倒计时的组件,基于进度条实现,下面就主要介绍基于canvas2D实现一个 ...

  3. 用emWin的2D绘图函数画一个带圆形端点的环形进度条

    先上效果图,首先是顺时针转: 然后是逆时针转: 大概讲一下思路吧,首先讲一下顺时针是怎么弄的,很简单. 画圆弧函数GUI_DrawArc有起始角度a0和终止角度a1两个参数,且a0必须小于a1否则无法 ...

  4. iOS通过CAShapeLayer和UIBezierPath画环形进度条

    UIBezierPath可以绘制矢量路径,而CAShapeLayer是Layer的子类,可以在屏幕进行绘制,本文主要思想是:CAShapeLayer按照UIBezierPath的矢量路径进行绘制. 效 ...

  5. canvas实现半圆环形进度条

    html部分 <canvas id="canvas" width="150" height="150"><p>抱歉, ...

  6. android canvas_Android 自定义View篇(七)实现环形进度条效果

    前言 Android 自定义 View 是高级进阶不可或缺的内容,日常工作中,经常会遇到产品.UI 设计出花里胡哨的界面.当系统自带的控件不能满足开发需求时,就只能自己动手撸一个效果. 本文就带自定义 ...

  7. 仿MIUI音量变化环形进度条实现

    Android中使用环形进度条的业务场景事实上蛮多的,比方下载文件的时候使用环形进度条.会给用户眼前一亮的感觉:再比方我大爱的MIUI系统,它的音量进度条就是使用环形进度条,尽显小米"为发烧 ...

  8. 使用css3制作一个简易的环形进度条

    话不多说,直接上代码: css: section{width:2rem;height:2rem;position: relative;margin:2rem;} .wrap,.circle,.perc ...

  9. 【UE5】自定义环形进度条、方形进度条

    UE5 实现 自定义环形进度条 1.1 新建Material 1.2 设置User Interface 1.3 使用PS绘制图形 (中间为透明,外部为白色的png图片) 方形: 星形: 1.4 图形显 ...

最新文章

  1. 5 个 APP 自动化测试辅助定位工具,你用过几个?
  2. bzoj3791作业*
  3. adb链接手机调试android应用
  4. 联想云:借助云计算助力中国企业数字化转型
  5. docker的安全管理与TLS/LLS加密通信
  6. Spring Boot 面试题
  7. 83998 连接服务器出错_服务端 TCP 连接的 TIME_WAIT 问题分析与解决
  8. 代码调试技巧小集合(C语言描述,但C/C++/Pascal通用)
  9. 项目经理的第二手准备-坚强的挺着(4)
  10. Visual C# 2008+SQL Server 2005 数据库与网络开发――3.1.2 C# 2005和2008之间的关系
  11. 自动控制原理5.1---频率特性
  12. camshift实现目标跟踪
  13. 企业数字化信息化运营管理规划方案
  14. 此Apple ID尚未在iTunes Store Apple Id登录苹果商店(App Store)身份验证提示需要官网技术支持
  15. Android中Device Provisioned引起的问题分析
  16. MOV Cutter使用记
  17. calcite解读专栏前言
  18. 于二〇〇八年十一月二十六日有所思而作
  19. 续篇( 一) TCP 为什么是三次握手,四次挥手?
  20. html5微信录音文件,微信H5录音实现

热门文章

  1. 浙工大计算机专业学物理几年,计算机科学与技术浙工大与杭电哪个强
  2. 华为OD机试用java实现 -【RSA 加密算法】
  3. electron-vue打包win32失败处理
  4. 大功率可编程直流电源的功率和芯片如何选择?
  5. TransFuse:Fusing Transformerd and CNNs for Medical Image Segmentation
  6. K8S(4)DaemonSet
  7. 2021-08-最新正则表达式判断手机号码
  8. 控制台游戏4-打地鼠
  9. 逻辑思考到写作(一)
  10. Symbian--Kill进程