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()
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?