OpenGL with QtWidgets:练习之绘制2D环形进度条
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环形进度条相关推荐
- 【WPF】环形进度条
WPF中自带有长条形的进度条,大部分场景都算适用,但是仍然有一部分空间小的场景不太合适,此时我们想到安卓上常用的环形进度条,美观,又不占空间. 那么WPF中的环形进度条控件在哪呢? 很遗憾,自带组件中 ...
- 基于canvas 2D实现微信小程序自定义组件-环形进度条
基于canvas 2D实现微信小程序自定义组件-环形进度条 最近开发一个小程序项目博闻金榜答题小程序,需要使用到一个可以显示答题倒计时的组件,基于进度条实现,下面就主要介绍基于canvas2D实现一个 ...
- 用emWin的2D绘图函数画一个带圆形端点的环形进度条
先上效果图,首先是顺时针转: 然后是逆时针转: 大概讲一下思路吧,首先讲一下顺时针是怎么弄的,很简单. 画圆弧函数GUI_DrawArc有起始角度a0和终止角度a1两个参数,且a0必须小于a1否则无法 ...
- iOS通过CAShapeLayer和UIBezierPath画环形进度条
UIBezierPath可以绘制矢量路径,而CAShapeLayer是Layer的子类,可以在屏幕进行绘制,本文主要思想是:CAShapeLayer按照UIBezierPath的矢量路径进行绘制. 效 ...
- canvas实现半圆环形进度条
html部分 <canvas id="canvas" width="150" height="150"><p>抱歉, ...
- android canvas_Android 自定义View篇(七)实现环形进度条效果
前言 Android 自定义 View 是高级进阶不可或缺的内容,日常工作中,经常会遇到产品.UI 设计出花里胡哨的界面.当系统自带的控件不能满足开发需求时,就只能自己动手撸一个效果. 本文就带自定义 ...
- 仿MIUI音量变化环形进度条实现
Android中使用环形进度条的业务场景事实上蛮多的,比方下载文件的时候使用环形进度条.会给用户眼前一亮的感觉:再比方我大爱的MIUI系统,它的音量进度条就是使用环形进度条,尽显小米"为发烧 ...
- 使用css3制作一个简易的环形进度条
话不多说,直接上代码: css: section{width:2rem;height:2rem;position: relative;margin:2rem;} .wrap,.circle,.perc ...
- 【UE5】自定义环形进度条、方形进度条
UE5 实现 自定义环形进度条 1.1 新建Material 1.2 设置User Interface 1.3 使用PS绘制图形 (中间为透明,外部为白色的png图片) 方形: 星形: 1.4 图形显 ...
最新文章
- 5 个 APP 自动化测试辅助定位工具,你用过几个?
- bzoj3791作业*
- adb链接手机调试android应用
- 联想云:借助云计算助力中国企业数字化转型
- docker的安全管理与TLS/LLS加密通信
- Spring Boot 面试题
- 83998 连接服务器出错_服务端 TCP 连接的 TIME_WAIT 问题分析与解决
- 代码调试技巧小集合(C语言描述,但C/C++/Pascal通用)
- 项目经理的第二手准备-坚强的挺着(4)
- Visual C# 2008+SQL Server 2005 数据库与网络开发――3.1.2 C# 2005和2008之间的关系
- 自动控制原理5.1---频率特性
- camshift实现目标跟踪
- 企业数字化信息化运营管理规划方案
- 此Apple ID尚未在iTunes Store Apple Id登录苹果商店(App Store)身份验证提示需要官网技术支持
- Android中Device Provisioned引起的问题分析
- MOV Cutter使用记
- calcite解读专栏前言
- 于二〇〇八年十一月二十六日有所思而作
- 续篇( 一) TCP 为什么是三次握手,四次挥手?
- html5微信录音文件,微信H5录音实现