惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
爱范儿
爱范儿
Y
Y Combinator Blog
T
Tor Project blog
V
Visual Studio Blog
U
Unit 42
B
Blog RSS Feed
博客园 - 叶小钗
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
阮一峰的网络日志
阮一峰的网络日志
T
Tailwind CSS Blog
G
Google Developers Blog
I
InfoQ
Stack Overflow Blog
Stack Overflow Blog
IT之家
IT之家
Microsoft Azure Blog
Microsoft Azure Blog
T
The Blog of Author Tim Ferriss
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Cloudflare Blog
Google DeepMind News
Google DeepMind News
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
H
Hackread – Cybersecurity News, Data Breaches, AI and More
F
Fortinet All Blogs
人人都是产品经理
人人都是产品经理
Apple Machine Learning Research
Apple Machine Learning Research
The GitHub Blog
The GitHub Blog
Recorded Future
Recorded Future
博客园_首页
罗磊的独立博客
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
量子位
P
Proofpoint News Feed
Jina AI
Jina AI
博客园 - 【当耐特】
S
Security @ Cisco Blogs
I
Intezer
MyScale Blog
MyScale Blog
Simon Willison's Weblog
Simon Willison's Weblog
P
Privacy & Cybersecurity Law Blog
腾讯CDC
T
Tenable Blog
A
Arctic Wolf
T
Threat Research - Cisco Blogs
S
Securelist
Know Your Adversary
Know Your Adversary
Spread Privacy
Spread Privacy
C
Check Point Blog
NISL@THU
NISL@THU
Microsoft Security Blog
Microsoft Security Blog
V
Vulnerabilities – Threatpost

涛哥的博客

玩转QtQuick(2)-默认渲染器 | 涛哥的博客 玩转QtQuick(2)-默认渲染器 | 涛哥的博客 玩转QtQuick(1)-SceneGraph场景图简介 | 涛哥的博客 玩转QtQuick(1)-SceneGraph场景图简介 | 涛哥的博客 QQuickWidget中文输入法问题的正确解法 | 涛哥的博客 玩转Qml(18)-用户向导 | 涛哥的博客 玩转Qml(18)-用户向导 | 涛哥的博客 玩转Qml(17)-树组件的定制 | 涛哥的博客 玩转Qml(17)-树组件的定制 | 涛哥的博客 玩转Qt(15)-操控Web小车案例 | 涛哥的博客 玩转Qt(15)-操控Web小车案例 | 涛哥的博客 玩转Qt(14)-Qt与Web混合开发 | 涛哥的博客 玩转Qt(14)-Qt与Web混合开发 | 涛哥的博客 玩转Qt(13)-安卓5.12.4环境搭建 | 涛哥的博客 玩转Qt(13)-安卓5.12.4环境搭建 | 涛哥的博客 玩转Qt(12)-github-Actions缓存优化 | 涛哥的博客 玩转Qt(12)-github-Actions缓存优化 | 涛哥的博客 玩转Qt(11)-github-Actions自动化发行 | 涛哥的博客 玩转Qt(11)-github-Actions自动化发行 | 涛哥的博客 玩转Qt(10)-github-Actions自动化编译 | 涛哥的博客 玩转Qt(10)-github-Actions自动化编译 | 涛哥的博客 玩转Qt(9)-程序发布指南 | 涛哥的博客 玩转Qt(9)-程序发布指南 | 涛哥的博客 玩转Qt(8)-掌握信号槽使用细节 | 涛哥的博客 玩转Qt(8)-掌握信号槽使用细节 | 涛哥的博客 玩转Qt(7)-窥探信号槽的实现细节 | 涛哥的博客 玩转Qt(7)-窥探信号槽的实现细节 | 涛哥的博客 玩转Qt(6)-认清信号槽的本质 | 涛哥的博客 玩转Qt(6)-认清信号槽的本质 | 涛哥的博客 玩转Qt(5)-理解事件循环 | 涛哥的博客 玩转Qt(5)-理解事件循环 | 涛哥的博客 玩转Qml(16)-移植ShaderToy | 涛哥的博客 玩转Qml(15)-着色器效果ShaderEffect | 涛哥的博客 玩转Qml(15)-着色器效果ShaderEffect | 涛哥的博客 玩转Qml(14)-动画特效-梯度 | 涛哥的博客 玩转Qml(14)-动画特效-梯度 | 涛哥的博客 玩转Qml(13)-动画特效-飞入 | 涛哥的博客 玩转Qml(13)-动画特效-飞入 | 涛哥的博客
玩转Qml(16)-移植ShaderToy | 涛哥的博客
2019-07-04 · via 涛哥的博客
  • 简介
  • 源码
  • 效果预览
    • 穿云洞
    • 星球之光
    • 蜗牛
    • 超级马里奥
  • 关于ShaderToy
  • 关于ShaderEffect
  • ShaderToy原理
    • 约定的变量
    • glsl版本号
    • glsl版本兼容
    • ShaderToy适配
  • TaoShaderToy

简介

这次涛哥将会教大家移植ShaderToy的特效到Qml

源码

《玩转Qml》系列文章,配套了一个优秀的开源项目:TaoQuick

github https://github.com/jaredtao/TaoQuick

访问不了或者速度太慢,可以用国内的镜像网站gitee

https://gitee.com/jaredtao/TaoQuick

效果预览

先看几个效果图

穿云洞

星球之光

蜗牛

超级马里奥

gif录制质量较低,可编译运行TaoQuick源码或使用涛哥打包好的可执行程序,查看实际运行效果。

可执行程序下载链接(包括windows 和 MacOS平台) https://github.com/jaredtao/TaoQuick/releases

关于ShaderToy

学习过计算机图形学的人,都应该知道大名鼎鼎的ShaderToy网站

用一些Shader代码和简单的纹理,就可以输出各种酷炫的图形效果和音频效果。

如果你还不知道,赶紧去看看吧https://www.shadertoy.com

顺便提一下,该网站的作者是IQ大神,这里有他的博客:

http://www.iquilezles.org/www/articles/raymarchingdf/raymarchingdf.htm

本文主要讨论图形效果,音频效果以后再实现。

关于ShaderEffect

Qml中实现ShaderToy,最快的途径就是ShaderEffect了。

上一篇文章《Qml特效-着色器效果ShaderEffect》已经介绍过ShaderEffect了, 本文重点是移植ShaderToy。

在涛哥写这篇文章之前,已经有两位前辈做过相关的研究。

陈锦明: https://zhuanlan.zhihu.com/p/38942460

qyvlik: https://zhuanlan.zhihu.com/p/44417680

涛哥参考了他们的实现,做了一些改进、完善。

在此感谢两位前辈。

下面正文开始

ShaderToy原理

OpenGL的可编程渲染管线中,着色器代码是可以动态编译、加载到GPU运行的。

而OpenGL又包括了桌面版(OpenGL Desktop)、嵌入式版(OpenGL ES)以及网页版(WebGL)

ShaderToy网站是以WebGL 2.0为基础,提供内置函数、变量,并约定了一些输入变量,由用户按照约定编写着色器代码。

只要不是太老的OpenGL版本,内置函数、变量基本都是通用的。

约定的变量

ShaderToy网站约定的变量如下:

1
2
3
4
5
6
7
8
9
10
11
vec3        iResolution             image/buffer        The viewport resolution (z is pixel aspect ratio, usually 1.0)
float iTime image/sound/buffer Current time in seconds
float iTimeDelta image/buffer Time it takes to render a frame, in seconds
int iFrame image/buffer Current frame
float iFrameRate image/buffer Number of frames rendered per second
float iChannelTime[4] image/buffer Time for channel (if video or sound), in seconds
vec3 iChannelResolution[4] image/buffer/sound Input texture resolution for each channel
vec4 iMouse image/buffer xy = current pixel coords (if LMB is down). zw = click pixel
sampler2D iChannel{i} image/buffer/sound Sampler for input textures i
vec4 iDate image/buffer/sound Year, month, day, time in seconds in .xyzw
float iSampleRate image/buffer/sound The sound sample rate (typically 44100)

Qml中的相应实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
ShaderEffect {
id: shader




readonly property vector3d defaultResolution: Qt.vector3d(shader.width, shader.height, shader.width / shader.height)
function calcResolution(channel) {
if (channel) {
return Qt.vector3d(channel.width, channel.height, channel.width / channel.height);
} else {
return defaultResolution;
}
}

readonly property vector3d iResolution: defaultResolution
property real iTime: 0
property real iTimeDelta: 100
property int iFrame: 10
property real iFrameRate
property vector4d iMouse;
property var iChannel0;
property var iChannel1;
property var iChannel2;
property var iChannel3;
property var iChannelTime: [0, 1, 2, 3]
property var iChannelResolution: [calcResolution(iChannel0), calcResolution(iChannel1), calcResolution(iChannel2), calcResolution(iChannel3)]
property vector4d iDate;
property real iSampleRate: 44100

...

}

其中时间、日期通过Timer刷新,鼠标位置用MouseArea刷新。

同时涛哥导出了hoverEnabled、running属性和restart函数,以方便Qml中控制Shader的运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
ShaderEffect {
id: shader
...

property alias hoverEnabled: mouse.hoverEnabled
property bool running: true
function restart() {
shader.iTime = 0
running = true
timer1.restart()
}

Timer {
id: timer1
running: shader.running
triggeredOnStart: true
interval: 16
repeat: true
onTriggered: {
shader.iTime += 0.016;
}
}
Timer {
running: shader.running
interval: 1000
onTriggered: {
var date = new Date();
shader.iDate.x = date.getFullYear();
shader.iDate.y = date.getMonth();
shader.iDate.z = date.getDay();
shader.iDate.w = date.getSeconds()
}
}
MouseArea {
id: mouse
anchors.fill: parent
onPositionChanged: {
shader.iMouse.x = mouseX
shader.iMouse.y = mouseY
}
onClicked: {
shader.iMouse.z = mouseX
shader.iMouse.w = mouseY
}
}
...
}

glsl版本号

GLSL Versions

OpenGL Version GLSL Version
2.0 110
2.1 120
3.0 130
3.1 140
3.2 150
3.3 330
4.0 400
4.1 410
4.2 420
4.3 430

GLSL ES Versions (Android, iOS, WebGL)

OpenGL ES Version GLSL ES Version
2.0 100
3.0 300

glsl版本兼容

ShaderToy限定了WebGL 2.0,而我们移植到Qml中,自然是希望能够在所有可以运行Qml的设备上运行ShaderToy效果。

所以要做一些glsl版本相关的处理。

涛哥研究了Qt的GraphicsEffects模块源码,它的版本处理要么默认,要么 150 core,显然是不够用的。

glsl各个版本的差异,可以参考这里 https://github.com/mattdesl/lwjgl-basics/wiki/glsl-versions

涛哥总结出了如下的代码和注释说明:

注意”#version xxx”必须是着色器的第一行,不能换行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
    





readonly property string gles2Ver: "
#define texture texture2D
precision mediump float;
"





readonly property string gles3Ver: "#version 300 es
#define varying in
#define gl_FragColor fragColor
precision mediump float;

out vec4 fragColor;
"





readonly property string gl3Ver: "#version 150
#define varying in
#define gl_FragColor fragColor
#define lowp
#define mediump
#define highp

out vec4 fragColor;
"


readonly property string gl2Ver: "#version 110
#define texture texture2D
"
property string versionString: {
if (Qt.platform.os === "android") {
if (GraphicsInfo.majorVersion === 3) {
console.log("android gles 3")
return gles3Ver
} else {
console.log("android gles 2")
return gles2Ver
}
} else {
if (GraphicsInfo.majorVersion === 3 ||GraphicsInfo.majorVersion === 4) {
return gl3Ver
} else {
return gl2Ver
}
}
}
readonly property string forwardString: versionString + "
varying vec2 qt_TexCoord0;
varying vec4 vertex;
uniform lowp float qt_Opacity;

uniform vec3 iResolution;
uniform float iTime;
uniform float iTimeDelta;
uniform int iFrame;
uniform float iFrameRate;
uniform float iChannelTime[4];
uniform vec3 iChannelResolution[4];
uniform vec4 iMouse;
uniform vec4 iDate;
uniform float iSampleRate;
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform sampler2D iChannel3;
"

versionString 这里,主要测试了Desktop和 android设备,Desktop只要显卡不太搓,都能运行的。

Android ES3的也是全部支持,ES2的部分不能运行,比如iq大神的蜗牛Shader,使用了textureLod等一系列内置函数,就不能在ES2上面跑。

ShaderToy适配

本来是不需要写顶点着色器的。如果我们想把ShaderToy做成一个任意坐标开始的Item来用,就需要适配一下坐标。

涛哥写的顶点着色器如下,仅在默认着色器的基础上,传递qt_Vertex给下一阶段的vertex

1
2
3
4
5
6
7
8
9
10
11
12

vertexShader: "
uniform mat4 qt_Matrix;
attribute vec4 qt_Vertex;
attribute vec2 qt_MultiTexCoord0;
varying vec2 qt_TexCoord0;
varying vec4 vertex;
void main() {
vertex = qt_Vertex;
gl_Position = qt_Matrix * vertex;
qt_TexCoord0 = qt_MultiTexCoord0;
}"

片段着色器这里处理一下,适配出一个符合shaderToy的mainImage作为入口函数

1
2
3
4
5
6
7
8
9
10
11
12
readonly property string startCode: "
void main(void)
{
mainImage(gl_FragColor, vec2(vertex.x, iResolution.y - vertex.y));
}"
readonly property string defaultPixelShader: "
void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
fragColor = vec4(fragCoord, fragCoord.x, fragCoord.y);
}"
property string pixelShader: ""
fragmentShader: forwardString + (pixelShader ? pixelShader : defaultPixelShader) + startCode

稍微说明一下,qyvlik大佬的Shader使用gl_FragCoord作为片段坐标传进去了,这种用法的ShaderToy坐标将会占据整个Qml的窗口,

而实际ShaderToy坐标不是整个窗口的时候,超出去的地方就会被切掉,显示出来的只有一小部分。

涛哥研究了一番后,顶点着色器把vertex传过来,vertex.x就是x坐标,vertex.y坐标从上到下是0 - height,而gl_FragCoord 从下到上是0 - height,

所以要翻一下。

TaoShaderToy

最后,看一下代码的全貌吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187

import QtQuick 2.12
import QtQuick.Controls 2.12













ShaderEffect {
id: shader




readonly property vector3d defaultResolution: Qt.vector3d(shader.width, shader.height, shader.width / shader.height)
function calcResolution(channel) {
if (channel) {
return Qt.vector3d(channel.width, channel.height, channel.width / channel.height);
} else {
return defaultResolution;
}
}

readonly property vector3d iResolution: defaultResolution
property real iTime: 0
property real iTimeDelta: 100
property int iFrame: 10
property real iFrameRate
property vector4d iMouse;
property var iChannel0;
property var iChannel1;
property var iChannel2;
property var iChannel3;
property var iChannelTime: [0, 1, 2, 3]
property var iChannelResolution: [calcResolution(iChannel0), calcResolution(iChannel1), calcResolution(iChannel2), calcResolution(iChannel3)]
property vector4d iDate;
property real iSampleRate: 44100


property alias hoverEnabled: mouse.hoverEnabled
property bool running: true
function restart() {
shader.iTime = 0
running = true
timer1.restart()
}
Timer {
id: timer1
running: shader.running
triggeredOnStart: true
interval: 16
repeat: true
onTriggered: {
shader.iTime += 0.016;
}
}
Timer {
running: shader.running
interval: 1000
onTriggered: {
var date = new Date();
shader.iDate.x = date.getFullYear();
shader.iDate.y = date.getMonth();
shader.iDate.z = date.getDay();
shader.iDate.w = date.getSeconds()
}
}
MouseArea {
id: mouse
anchors.fill: parent
onPositionChanged: {
shader.iMouse.x = mouseX
shader.iMouse.y = mouseY
}
onClicked: {
shader.iMouse.z = mouseX
shader.iMouse.w = mouseY
}
}




readonly property string gles2Ver: "
#define texture texture2D
precision mediump float;
"




readonly property string gles3Ver: "#version 300 es
#define varying in
#define gl_FragColor fragColor
precision mediump float;

out vec4 fragColor;
"




readonly property string gl3Ver: "#version 150
#define varying in
#define gl_FragColor fragColor
#define lowp
#define mediump
#define highp

out vec4 fragColor;
"


readonly property string gl2Ver: "#version 110
#define texture texture2D
"

property string versionString: {
if (Qt.platform.os === "android") {
if (GraphicsInfo.majorVersion === 3) {
console.log("android gles 3")
return gles3Ver
} else {
console.log("android gles 2")
return gles2Ver
}
} else {
if (GraphicsInfo.majorVersion === 3 ||GraphicsInfo.majorVersion === 4) {
return gl3Ver
} else {
return gl2Ver
}
}
}

vertexShader: "
uniform mat4 qt_Matrix;
attribute vec4 qt_Vertex;
attribute vec2 qt_MultiTexCoord0;
varying vec2 qt_TexCoord0;
varying vec4 vertex;
void main() {
vertex = qt_Vertex;
gl_Position = qt_Matrix * vertex;
qt_TexCoord0 = qt_MultiTexCoord0;
}"
readonly property string forwardString: versionString + "
varying vec2 qt_TexCoord0;
varying vec4 vertex;
uniform lowp float qt_Opacity;

uniform vec3 iResolution;
uniform float iTime;
uniform float iTimeDelta;
uniform int iFrame;
uniform float iFrameRate;
uniform float iChannelTime[4];
uniform vec3 iChannelResolution[4];
uniform vec4 iMouse;
uniform vec4 iDate;
uniform float iSampleRate;
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform sampler2D iChannel2;
uniform sampler2D iChannel3;
"
readonly property string startCode: "
void main(void)
{
mainImage(gl_FragColor, vec2(vertex.x, iResolution.y - vertex.y));
}"
readonly property string defaultPixelShader: "
void mainImage(out vec4 fragColor, in vec2 fragCoord)
{
fragColor = vec4(fragCoord, fragCoord.x, fragCoord.y);
}"
property string pixelShader: ""
fragmentShader: forwardString + (pixelShader ? pixelShader : defaultPixelShader) + startCode
}