2025年qpainter底层(qpainter qimage)

qpainter底层(qpainter qimage)最近在搞自己的图形引擎 干了两个多月的粒子系统 最近终于差不多搞完了 给大家看看效果 由于效果太上头了 忙于优化和扩展 教程就暂时给搁下了 老哥们别急 咱们慢慢来 这个图形引擎过段时间会发出来 有兴趣的老哥可以耍耍 简单回顾一下 在第一节中 我们了解了一下图形渲染管线的基本流程 如下图 OpenGL 就像是一个很大的工厂车间 着色器程序就像是工厂里的一条流水线

大家好,我是讯享网,很高兴认识大家。



最近在搞自己的图形引擎,干了两个多月的粒子系统,最近终于差不多搞完了,给大家看看效果:

由于效果太上头了,忙于优化和扩展,教程就暂时给搁下了,老哥们别急,咱们慢慢来,这个图形引擎过段时间会发出来,有兴趣的老哥可以耍耍。

在第一节中,我们了解了一下图形渲染管线的基本流程,如下图:

OpenGL就像是一个很大的工厂车间,着色器程序就像是工厂里的一条流水线,而着色器则相当于流水线上的一个处理器。

前两节中我们讲述的三个OpenGL对象,就相当生产图形所使用的原料。

这一节中我们将详细讲解着色器语言以及各个阶段的着色器。

在早期的OpenGL中,使用的是一种叫做立即渲染模式的方法来绘制图形,至今也还有许多人在使用这个方式(仍具有学习意义但确实是已经过时了),它的使用方式就像是下面这样:

立即模式没有VAO,VBO等一系列缓存对象,也不支持着色器编程,虽然它确实能用作图形绘制,但是在一些较复杂的情况性能较低,还记得之前说的吗?

通过顶点就已经能绘制任何图形了,现代OpenGL相对传统OpenGL的进步在于它将图形渲染流程中的各个细节都优化到了极致!

而我们现在所学的是核心(Core)渲染模式,目前官方更新到了4.6版本(Es为3.2),Qt(笔者使用的是5.14.2)默认使用的是OpenGL4.5版本 (也是支持的最高版本),如果想使用低版本的OpenGL版本,可以在OpenGL的窗口显示之前调用setFormat设置版本:

注意,使用以下方法无法正确设置版本:

QSurfaceFormat除了能设置版本外,还能设置如下选项,具体细则请查阅:

除了OpenGL的版本,我们还需要注意着色器语言(OpenGL Shading Language)的版本,Qt5.14.2  对GLSL版本的支持情况如下:

Qt默认使用的着色器版本是ES2.0,由于这个版本里面很多关键字都已经废弃了,我们将不再使用!本教程从这一节开始将全程使用4.5版本的GLSL。

需要说明的是前面教程我们使用的是es2.0,其中三个关键字,将不再使用(下文再做解释):

其中需要说明的是vec(向量),mat(矩阵),这里我们并不讨论它们的几何意义,如果你对这些不熟,笔者推荐你看一下《3D数学基础:图形和游戏开发》

这几种类型默认使用单精度(float)存储,添加类型前缀(d,u,b,i 等)说明使用何种类型进行存储。

在数学中,向量(也称为欧几里得向量、几何向量、矢量),指具有大小(magnitude)和方向的量。而在计算机中,向量只是几个连续的数字。GLSL支持二维(vec2),三维(vec3),四维(vec4)向量,其中二维向量其本质就是两个数字。

唯一值得说明的是,向量分量的访问方式,以vec4为例

这里我们使用xyzw访问向量的分量,GLSL还支持使用其他关键字【rgba】,【pqst】使用哪组关键字取决于使用场景以及个人喜好。

当然GLSL支持向量的加减乘除。

在数学中,矩阵(Matrix)是一个按照长方阵列排列的复数或实数集合。在计算机中,它同样只是一堆连续的数字。GLSL支持的最大矩阵尺寸为4x4,也就是mat4x4(这里用字母x代指乘号),因为是方阵所以可以简写为mat4

需要特别注意的是:OpenGL使用的矩阵是的(QMatrix也是)。比如下面这个矩阵

它其实代表的行矩阵是

存储方式的不同不仅仅影响矩阵的访问方式,更为重要的是它将影响矩阵的运算顺序,下图描述了不同存储方式下,三维矩阵与三维向量相乘的结果是什么类型:

我们在GLSL中使用矩阵一般是对向量进行变换,因此我们运算是一般是从右往左进行计算。如下所示:

另外,我们还需要注意的是,除了矩阵和向量的运算要反过来,矩阵的一些内部运算也得反着来。比如我们在客户端中使用QMatrix4x4想对一个图形先进行旋转,再进行平移,应该这么来使用:

在之前的版本中我们使用了两个关键字:attribute,varying则是用来描述输入输出的,不过随着OpenGL特性不断增多,这两个关键字已经无法满足着色器编程的需求了,现代OpenGL统一使用以下三个关键字描述输入输出:

我们先来看一个顶点着色器的代码

其中 layout() 被称作布局限定符。它可以调整变量内存的布局方式,比较常用的是设置location,还记得我们调用ShaderProgram设置顶点解析方式吗?这里就对应着客户端代码:

VAO存储着这些解析格式,其中第一个参数就是布局参数。第一节我们是直接使用:

这其实就做了两件事情,首先去查询【aPos】在着色器程序中的布局,然后再进行设置,所以最好是通过第一种方式直接通过布局来设置解析格式。

回到我们的顶点着色器代码,输入数据是根据VAO存储的数据来源和解析格式,读取相应的数据,其中glDraw控制着读取的数量(也就是有多少个顶点会被顶点着色器进行处理),顶点着色器中我们必须对gl_Position进行赋值,另外,我们输出了一个【color】属性到接下来的阶段之中。

接下来我们看与之对应的片段着色器(虽然这期间还有其他的着色器程序,但不是必须的)

这里使用了接受从顶点着色器输出的颜色变量,需要注意,我们Multi Render Target)技术,允许一个图形同时绘制到多个“ 图片”上,你可能会想,这有什么用?

乍一看确实没什么用,或许我们直接复制缓冲区内容也能实现,但是复制只能是完全复制,试想,我们能不能输出两个颜色不同的图形呢?这在后期渲染之中尤为重要,之后我们会进行了解。

因此,为了兼容这一特性,OpenGL废除了gl_FragColor这一关键字,需要我们手动定义输出。

还有更为重要的一点,那就是 顶点着色器中的【out vec4 color】和片段着色器中的【in vec4 color】并不是一一对应的。

试想一个三角形只有三个顶点,说明顶点着色器只处理了三个顶点数据,而片段着色器是用来处理像素的,难道一个三角形只有三个像素吗?当然不是,OpenGL是怎么从三个顶点着色器中的color转变成多个像素的color呢?这是在光栅化阶段通过一个叫线性插值的方法进行计算的,关于光栅化及线性插值,有兴趣的朋友可以自己去了解。

下面一个图描述的就是片段着色器的处理过程

其中稍微欠妥的地方就是,本质上处理的并不是屏幕上的像素,图形显示一般都是通过双缓冲的技术来避免图形闪烁的问题,也就是说,我们是在一张未显示的缓冲区上绘制图形,等绘制完毕之后,再统一进行显示。

哦,对了,还有uniform没说,uniform其实很简单,uniform修饰表示变量是全局的,共用的,OpenGL是怎么共用的呢?相信看了下图你很容易就能理解。

还记得上一节中我们是怎么绘制纹理的?是不是添加了一个uniform修饰的采样器【sample2D】,通过shaderProgram.setUniformValue,设置来采样器的值,你可能会突发奇想,既然是共用的,那是不是在顶点着色器也能用,那是当然的,只要在客户端设置了这个值,并在任意着色器代码中声明,都是能用的!

GLSL基本上内置了所有在图形开发中所使用的函数,比如:

数学函数,向量运算(点乘,叉乘),矩阵转置,求逆,混合函数,插值函数等等的,基本你能想到的,需要的,基本都会有,完整的你可以查阅下面的官方文档:

Qt虽然可以提供了一个编写GLSL代码的IDE,但是很久没有更新了,所以使用一些API可能会出现红线的情况,请放心编译,出错请看着色器编译日志,Qt默认在Debug模式下着色器出错会在应用程序输出窗口中显示错误信息,你也可以在编译着色器或者link着色器程序之后,调用.log()获取编译日志。


讯享网

以上就是我们这一节所需要了解的内容,这一节主要是根据官方文档对一些常用的功能进行说明,具体的细节以及完善的功能,请仔细阅读官方文档:

https://www.khronos.org/registry/OpenGL/specs/gl/GLSLangSpec.4.60.html

现在你一定想动手写点代码了吧,想想我们该写什么呢?先来点简单的吧,我们来给上一节的纹理上点色:

也就是这个项目的代码:

由于很简单,这里就不录视频了。

之前我们的着色器代码是这样的:

顶点着色器:

片段着色器:

首先我们首先使用最新版本的GLSL,并替换废弃的关键字:

替换之后跟之前的显示结果是一致的。接下来我们在片段着色器中做点小科技=.=。

显示之后是这样的:

明明只有四个顶点,每个顶点处理之后只输出一个outCoord,也就是4个outCoord,怎么会出现渐变的效果呢?

这就是之前所说的光栅化插值,根据顶点对outCoord进行插值处理,为每个片段都生成了独立的outCoord。

接着我们借助一个叫mix的函数跟纹理进行混合:

我们会得到下面的效果

并不是很有趣是吧,我们来做点动画吧!

还记得上一节的时钟吗?首先我们在widget构造函数中让OpenGL窗口定时刷新:

添加一个QTimer成员变量:

然后再构造函数让QTimer与窗口的刷新函数绑定在一起,并启动定时器:

接着我们在片段着色器中使用这个值作为混合因子:

运行之后你会发现我们实现了一个交叉淡化的“特效”

你可以在这里找到这个项目的代码:

国外有两个非常火的GLSL资源网站,分别是

我们这节要搭建一个glslsandbox的运行环境。

glslsandbox上面有很多图形效果:

随便打开一个图形你能看到他们的代码:

你有没有发现这些代码是一个片段着色器的代码(因为最终对gl_FragColor进行了赋值)

然后再来仔细看看这个代码里面干了什么?看完之后能可能会有这样的感叹:

卧槽,这是什么鬼东西哦!我人傻了,这是在干什么?

我第一次看到这些代码的时候也是这种感觉:他们是怎么只使用一个片段着色器就能绘制出这么炫酷的动画???别说只用一个片段着色器,就是让我来通过顶点来绘制,我都觉得是一件很困难的事情。

其实他们是根据一些数学函数的特点,不断叠加来生成一些图像,因此这需要很强的数学功底,还要掌握一些绘制技巧(我是真佩服,这辈子是没指望能学会了)

如果你对使用片段生成图形敢兴趣的话可以看看这个教程(全英文)

单独使用片段着色器来绘制图形是一个开销很大的操作,因为片段着色器是逐像素运行的,所以在实际图形渲染中,它的意义并不大,不过可以用它来生成一些动态纹理,或者当成一个数学玩具还是可以的。

废话不多说,接下来我们来思考一下如何搭建好glslsandbox的运行环境吧。

首先我们需要绘制一个全屏的矩形,这样我们的片段着色器就能对“窗口上的”每个像素进行处理了。

以这个代码为例

你会发现只有两个uniform变量,根据名称我们大概能猜出意思:

所以我们只需要搭建一个绘制全屏矩形的流水线,传递这两个变量进去就行,并且让窗口能够动态刷新就行了,由于这部分操作很简单,就不录制操作视频了。

我们只需拷贝glsl的代码到【GLSLSandbox.frag】文件中,运行之后你会得到如下结果

项目代码你可以在这里找到:

https://github.com/Italink/QtOpenGL-Essential-Training/tree/main/Day04_GLSLSandbox

小讯
上一篇 2025-04-29 22:40
下一篇 2025-05-30 18:35

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/140773.html