【可视化学习】75-从入门到放弃WebGL(十一)
发表于:2024-07-08 |

前言

本篇,继续来学习 webgl

极坐标

极坐标

  • 极点:极坐标的坐标原点,即点 O
  • 极轴:极坐标的起始轴,其对应的弧度为 0,即 Ox
  • 正方向:极坐标中,点位按此方向的旋转量越大,其相对于极轴的弧度越大,此方向通常为逆时针方向
  • 极径:极坐标系中一点到极点的距离,如|OM|
  • 极角:极坐标系中一点相对于极轴的角度,如 θ
  • 极坐标:由极坐标系中一点的极径和极角构成的有序数对,如(|OM|,θ)
  • 极坐标系:按照以上原理确定某点的坐标位的坐标系

直角坐标系

gl_FragCoord 所对应的二维直角坐标系中,y 轴是朝上的,以像素为单位。
直角坐标系

一个点的位置既可以用直角坐标来表示,也可以用极坐标来表示。

接下来我们说一下二维直角坐标系与极坐标系的转换方法。

极角与 x 轴的映射

我们可以通过极角与 x 轴的映射实现放射效果。

放射渐变

在片元着色器里基于画布尺寸计算画布中心位,声明 360° 所对应的弧度,以备后用。
1
2
3
uniform vec2 u_CanvasSize;
vec2 center=u_CanvasSize/2.0;
float pi2=radians(360.0);
以画布中心点为极点,计算当前片元的极角 ang。
1
2
3
4
5
void main(){
vec2 p=gl_FragCoord.xy-center;
float ang=atan(p.y,p.x);
……
}
以极角为变量,计算与计算一个 x 值
1
float x=ang*16.0;
将 x 值拼上一个随意的 y 值,构成向量 v
1
vec2 v=vec2(int(x),0);
基于向量 v,通过 rand() 方法生成一个颜色
1
2
3
vec2 v=vec2(int(x),0);
float f = rand(v);
gl_FragColor = vec4(f, f, f, 1);

放射

完整代码
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
<!-- 顶点着色器 -->
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 a_Position;
void main(){
gl_Position=a_Position;
}
</script>
<!-- 片元着色器 -->
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform vec2 u_CanvasSize;
vec2 center=u_CanvasSize/2.0;
float pi2=radians(360.0);

float rand(vec2 fragCoord){
vec2 a= vec2(0.1234,0.5678);
float n= dot(fragCoord,a);
return fract(sin(n)*10000.0);
}


void main(){
vec2 p=gl_FragCoord.xy-center;
//ang=[-π,π]
float ang=atan(p.y,p.x);
//x=[-π*32.0,π*32.0]
float x=ang*8.0;
vec2 v=vec2(int(x),0);

float f = rand(v);
gl_FragColor = vec4(f, f, f, 1);
}
</script>
<script type="module">
import { initShaders, parseColorStops } from "../jsm/Utils.js";
import Poly from "./jsm/Poly.js";

const canvas = document.querySelector("#canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

// 获取着色器文本
const vsSource = document.querySelector("#vertexShader").innerText;
const fsSource = document.querySelector("#fragmentShader").innerText;

//三维画笔
const gl = canvas.getContext("webgl");

//初始化着色器
initShaders(gl, vsSource, fsSource);

//声明颜色 rgba
gl.clearColor(0, 0, 0, 1);

const source = new Float32Array([-1, 1, -1, -1, 1, 1, 1, -1]);

const rect = new Poly({
gl,
source,
type: "TRIANGLE_STRIP",
attributes: {
a_Position: {
size: 2,
index: 0,
},
},
uniforms: {
u_CanvasSize: {
type: "uniform2fv",
value: [canvas.width, canvas.height],
},
},
});

gl.clear(gl.COLOR_BUFFER_BIT);
rect.draw();
</script>

有了渐变的效果后,我们还可以让其旋转起来。

放射渐变旋转

通过 requestAnimationFrame() 方法向着色器传递一个时间戳 u_Stamp
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
const rect = new Poly({
gl,
source,
type: "TRIANGLE_STRIP",
attributes: {
a_Position: {
size: 2,
index: 0,
},
},
uniforms: {
u_CanvasSize: {
type: "uniform2fv",
value: [canvas.width, canvas.height],
},
u_Stamp: {
type: "uniform1f",
value: 0,
},
},
});

!(function ani(stamp) {
rect.uniforms.u_Stamp.value = stamp;
rect.updateUniform();
gl.clear(gl.COLOR_BUFFER_BIT);
rect.draw();
requestAnimationFrame(ani);
})();
在着色器中建立名为 u_Stamp 的 uniform 变量,并基于此变量建立只负责旋转的模型矩阵 modelMatrix。
1
2
3
4
5
6
7
8
9
10
11
12
13
uniform vec2 u_CanvasSize;
uniform float u_Stamp;

vec2 center=u_CanvasSize/2.0;
float pi2=radians(360.0);

float angOffset=u_Stamp*0.001;
float cosAng=cos(angOffset);
float sinAng=sin(angOffset);
mat2 modelMatrix=mat2(
cosAng,sinAng,
-sinAng,cosAng
);
在 main() 方法中使用 modelMatrix 旋转点 p
1
2
3
4
5
6
7
8
9
10
void main(){
vec2 p=gl_FragCoord.xy-center;
p=modelMatrix*p;
float ang=atan(p.y,p.x);
float x=ang*16.0;

vec2 v=vec2(int(x),0);
float f = rand(v);
gl_FragColor = vec4(f, f, f, 1);
}
完整代码
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
<!-- 顶点着色器 -->
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 a_Position;
void main(){
gl_Position=a_Position;
}
</script>
<!-- 片元着色器 -->
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform vec2 u_CanvasSize;
uniform float u_Stamp;

vec2 center=u_CanvasSize/2.0;
float pi2=radians(360.0);

float angOffset=u_Stamp*0.002;
//float angOffset=u_Stamp*0.001;
float cosAng=cos(angOffset);
float sinAng=sin(angOffset);
mat2 modelMatrix=mat2(
cosAng,sinAng,
-sinAng,cosAng
);

float rand(vec2 fragCoord){
vec2 a= vec2(0.1234,0.5678);
float n= dot(fragCoord,a);
return fract(sin(n)*10000.0);
}


void main(){
vec2 p=gl_FragCoord.xy-center;
p=modelMatrix*p;
float ang=atan(p.y,p.x);
float x=ang*16.0;
vec2 v=vec2(int(x),0);

float f = rand(v);
gl_FragColor = vec4(f, f, f, 1);
}
</script>
<script type="module">
import { initShaders, parseColorStops } from "../jsm/Utils.js";
import Poly from "./jsm/Poly.js";

const canvas = document.querySelector("#canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

// 获取着色器文本
const vsSource = document.querySelector("#vertexShader").innerText;
const fsSource = document.querySelector("#fragmentShader").innerText;

//三维画笔
const gl = canvas.getContext("webgl");

//初始化着色器
initShaders(gl, vsSource, fsSource);

//声明颜色 rgba
gl.clearColor(0, 0, 0, 1);

const source = new Float32Array([-1, 1, -1, -1, 1, 1, 1, -1]);

const rect = new Poly({
gl,
source,
type: "TRIANGLE_STRIP",
attributes: {
a_Position: {
size: 2,
index: 0,
},
},
uniforms: {
u_CanvasSize: {
type: "uniform2fv",
value: [canvas.width, canvas.height],
},
u_Stamp: {
type: "uniform1f",
value: 0,
},
},
});

gl.clear(gl.COLOR_BUFFER_BIT);
rect.draw();

!(function ani(stamp) {
rect.uniforms.u_Stamp.value = stamp;
rect.updateUniform();
gl.clear(gl.COLOR_BUFFER_BIT);
rect.draw();
requestAnimationFrame(ani);
})();
</script>

放射渐变闪烁

修改一下 main 方法
1
2
3
4
5
6
7
8
9
10
void main(){
vec2 p=gl_FragCoord.xy-center;
//p=modelMatrix*p;
float ang=atan(p.y,p.x);
float x=ang*16.0;

vec2 v=vec2(int(x),int(u_Stamp));
float f = rand(v);
gl_FragColor = vec4(f, f, f, 1);
}
控制一下闪烁速度
1
2
3
4
5
6
7
8
9
10
11
12
let lastTime = 0
const timeLen = 100
!(function ani(stamp) {
if (stamp % timeLen < lastTime % timeLen) {
rect.uniforms.u_Stamp.value = stamp
rect.updateUniform()
gl.clear(gl.COLOR_BUFFER_BIT)
rect.draw()
}
lastTime = stamp
requestAnimationFrame(ani)
})()
完整代码
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
<!-- 顶点着色器 -->
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 a_Position;
void main(){
gl_Position=a_Position;
}
</script>
<!-- 片元着色器 -->
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform vec2 u_CanvasSize;
uniform float u_Stamp;

vec2 center=u_CanvasSize/2.0;
float pi2=radians(360.0);

float rand(vec2 fragCoord){
vec2 a= vec2(0.1234,0.5678);
float n= dot(fragCoord,a);
return fract(sin(n)*10000.0);
}


void main(){
vec2 p=gl_FragCoord.xy-center;
float ang=atan(p.y,p.x);
float x=ang*16.0;
vec2 v=vec2(int(x),int(u_Stamp));

float f = rand(v);
gl_FragColor = vec4(f, f, f, 1);
}
</script>
<script type="module">
import { initShaders, parseColorStops } from "../jsm/Utils.js";
import Poly from "./jsm/Poly.js";

const canvas = document.querySelector("#canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

// 获取着色器文本
const vsSource = document.querySelector("#vertexShader").innerText;
const fsSource = document.querySelector("#fragmentShader").innerText;

//三维画笔
const gl = canvas.getContext("webgl");

//初始化着色器
initShaders(gl, vsSource, fsSource);

//声明颜色 rgba
gl.clearColor(0, 0, 0, 1);

const source = new Float32Array([-1, 1, -1, -1, 1, 1, 1, -1]);

const rect = new Poly({
gl,
source,
type: "TRIANGLE_STRIP",
attributes: {
a_Position: {
size: 2,
index: 0,
},
},
uniforms: {
u_CanvasSize: {
type: "uniform2fv",
value: [canvas.width, canvas.height],
},
u_Stamp: {
type: "uniform1f",
value: 0,
},
},
});

gl.clear(gl.COLOR_BUFFER_BIT);
rect.draw();

//上一次请求动画帧的时间戳
let lastTime = 0;
//动画的时间间隔
const timeLen = 100;
!(function ani(stamp) {
//lastTime=99
//stamp=lastTime+16=115
//115%100=15<99%100
if (stamp % timeLen < lastTime % timeLen) {
rect.uniforms.u_Stamp.value = stamp;
rect.updateUniform();
gl.clear(gl.COLOR_BUFFER_BIT);
rect.draw();
}
lastTime = stamp;
requestAnimationFrame(ani);
})();
</script>

来自深渊的凝视

建立两个模型矩阵
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
float angOffset1=u_Stamp*0.0002;
float cosAng1=cos(angOffset1);
float sinAng1=sin(angOffset1);
mat2 modelMatrix1=mat2(
cosAng1,sinAng1,
-sinAng1,cosAng1
);

float angOffset2=u_Stamp*0.0008;
float cosAng2=cos(angOffset2);
float sinAng2=sin(angOffset2);
mat2 modelMatrix2=mat2(
cosAng2,sinAng1,
-sinAng2,cosAng2
);

modelMatrix1 是用于旋转片元位的
modelMatrix2 是用于旋转极点的。
注:modelMatrix2 中的第二个元素是sinAng1,不是sinAng2,我这么做是为打破一下其中规中矩的旋转方式。

将通过极坐标获取亮度的方法封装一下。
1
2
3
4
5
6
7
8
9
float getBright(vec2 pole){
pole=center+modelMatrix2*(pole-center);
vec2 p=gl_FragCoord.xy-pole;
p=modelMatrix1*p;
float ang=atan(p.y,p.x);
float x=ang*16.0;
vec2 v=vec2(int(x),0);
return rand(v);
}
在mian 中基于两个极点,获取两个亮度值。
1
2
3
4
5
6
7
void main(){
vec2 min=u_CanvasSize*0.35;
vec2 max=u_CanvasSize*0.65;
float bright1 = getBright(min);
float bright2 = getBright(max);
……
}
对两个亮度值进行合成。

其合成思路是若两个亮度值都比较暗,那我就让当前片元变亮;若都比较亮,那我就让其变暗。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main(){
vec2 min=u_CanvasSize*0.35;
vec2 max=u_CanvasSize*0.65;
float bright1 = getBright(min);
float bright2 = getBright(max);

float f=0.0;
float sum=bright1+bright2;
if(sum>1.0){
f=bright1*bright2;
}else{
f=sum;
}
gl_FragColor = vec4(f, f, f, 1);
}

效果图

数字山谷

修改矩阵变换的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
float angOffset1=u_Stamp*0.00015;
float cosAng1=cos(angOffset1);
float sinAng1=sin(angOffset1);
mat2 modelMatrix1=mat2(
cosAng1,sinAng1,
-sinAng1,cosAng1
);

float angOffset2=u_Stamp*0.0004;
float cosAng2=cos(angOffset2);
float sinAng2=sin(angOffset2);
mat2 modelMatrix2=mat2(
cosAng2,sinAng1,
-sinAng2,cosAng2
);
通过4个极点获取亮度值,然后对其合成
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void main(){
vec2 min=u_CanvasSize*0.25;
vec2 max=u_CanvasSize*0.75;
float bright1 = getBright(min);
float bright2 = getBright(max);
float bright3 = getBright(vec2(min.x,max.y));
float bright4 = getBright(vec2(max.x,min.y));
float f=0.0;
float sum=bright1+bright2+bright3+bright4;
if(sum>2.0){
f=bright1*bright2*bright3*bright4*4.0;
}else{
f=sum/2.0;
}
gl_FragColor = vec4(f, f, f, 1);
}

效果图

正弦型放射

先回顾一下正弦型函数:

y=Asin(ωx+φ)

A 影响的是正弦曲线的波动幅度
φ 影响的是正弦曲线的平移
ω 影响的是正弦曲线的周期,ω 越大,周期越小
接下来咱们说一下代码实现。

声明omega和a变量
1
2
float omega=7.0;
float a=0.5;

omega 对应的是正弦函数式里的ω,在放射效果中此值会影响射线的数量
a 对应的是正弦函数式里的A,在放射效果中此值会影响亮度

在main方法中,以画布中心为极点,计算当前片元的极角
1
2
3
4
5
void main(){
vec2 p=gl_FragCoord.xy-center;
float ang=atan(p.y,p.x);
……
}
以极角为变量计算正弦函数值
1
2
float f = a*sin(omega*ang)+0.5; 
gl_FragColor = vec4(f, f, f, 1);

上面求f时加的0.5 是为了在[0,1]之间去亮度值:

asin(omegax)∈[-0.5,0.5]
asin(omegax)+0.5∈[0,1]
效果图

光影沉浮

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
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform vec2 u_CanvasSize;
uniform float u_Stamp;

vec2 center=u_CanvasSize/2.0;
float pi2=radians(360.0);

float omega=24.0;
float a=0.5;

float angOffset1=u_Stamp*0.001;
float cosAng1=cos(angOffset1);
float sinAng1=sin(angOffset1);
mat2 modelMatrix1=mat2(
cosAng1,sinAng1,
-sinAng1,cosAng1
);

float angOffset2=u_Stamp*0.001;
float cosAng2=cos(angOffset2);
float sinAng2=sin(angOffset2);
mat2 modelMatrix2=mat2(
cosAng2,sinAng2,
-sinAng2,cosAng2
);

float getBright(vec2 pole){
pole=center+modelMatrix2*(pole-center);
vec2 p=gl_FragCoord.xy-pole;
p=modelMatrix1*p;
float ang=atan(p.y,p.x);
return a*sin(omega*ang)+0.5;
}

void main(){
vec2 min=u_CanvasSize*0.35;
vec2 max=u_CanvasSize*0.65;
float bright1 = getBright(min);
float bright2 = getBright(max);
float f=(bright1+bright2)*0.55;
gl_FragColor = vec4(f, f, f, 1);
}
</script>

效果图

湍流

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
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform vec2 u_CanvasSize;
uniform float u_Stamp;

vec2 center=u_CanvasSize/2.0;
float pi2=radians(360.0);

float omega=64.0;
float a=0.5;

float angOffset1=u_Stamp*0.0004;
float sinAng1=sin(angOffset1);

float angOffset2=u_Stamp*0.0002;
float cosAng2=cos(angOffset2);
float sinAng2=sin(angOffset2);
mat2 modelMatrix2=mat2(
cosAng2,sinAng1,
-sinAng2,cosAng2
);

float getBright(vec2 pole){
pole=center+modelMatrix2*(pole-center);
vec2 p=gl_FragCoord.xy-pole;
float ang=atan(p.y,p.x);
return a*sin(omega*ang)+0.5;
}

void main(){
vec2 min=u_CanvasSize*0.25;
vec2 max=u_CanvasSize*0.75;
float bright1 = getBright(min);
float bright2 = getBright(max);
float bright3 = getBright(vec2(min.x,max.y));
float bright4 = getBright(vec2(max.x,min.y));
float f=0.0;
float sum=bright1+bright2+bright3+bright4;
if(sum<2.0){
f=1.0;
}
gl_FragColor = vec4(f, f, f, 1);
}
</script>

效果图

全景图的极坐标扭曲

准备全景图

全景图

建立带贴图的rect对象
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
const source = new Float32Array([
-1, 1, 0, 1,
-1, -1, 0, 0,
1, 1, 1, 1,
1, -1, 1, 0
]);

const rect = new Poly({
gl,
source,
type: 'TRIANGLE_STRIP',
attributes: {
a_Position: {
size: 2,
index: 0
},
a_Pin: {
size: 2,
index: 2
},
},
uniforms: {
u_CanvasSize: {
type: 'uniform2fv',
value: [canvas.width, canvas.height]
}
}
})

const image = new Image()
image.src = './images/room.jpg'
image.onload = function () {
rect.maps = {
u_Sampler: { image },
}
rect.updateMaps()
render()
}

//渲染
function render() {
gl.clear(gl.COLOR_BUFFER_BIT);
rect.draw()
}

极坐标扭曲

顶点着色器
1
2
3
4
5
6
7
8
9
<script id="vertexShader" type="x-shader/x-vertex">
attribute vec4 a_Position;
attribute vec2 a_Pin;
varying vec2 v_Pin;
void main(){
gl_Position=a_Position;
v_Pin=a_Pin;
}
</script>
片元着色器
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
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform vec2 u_CanvasSize;
uniform sampler2D u_Sampler;
varying vec2 v_Pin;
vec2 center=u_CanvasSize/2.0;
float diagLen=length(center);
float pi2=radians(360.0);

float getAngle(vec2 v){
float ang=atan(v.y,v.x);
if(ang<0.0){
ang+=pi2;
}
return ang;
}

void main(){
vec2 p=gl_FragCoord.xy-center;
float ang=getAngle(p);
float x=ang/pi2;
float len=length(p);
float y=len/diagLen;
vec4 color=texture2D(u_Sampler,vec2(x,y));
if(p.x>0.0&&abs(p.y)<1.0){
color=texture2D(u_Sampler,vec2(0,y));
}
gl_FragColor=color;
}
</script>

综合案例-磨砂金属按钮

接下来我们将之前说过的渐变、杂色、极坐标扭曲、拉丝融为一体,做一个磨砂金属按钮。
磨砂金属

磨砂材质
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
<script id="fragmentShader" type="x-shader/x-fragment">
precision mediump float;
uniform vec2 u_CanvasSize;
vec2 center=u_CanvasSize/2.0;
float diagLen=length(center);
float pi2=radians(360.0);
float omega=4.0;
float a=0.5;

//渐变
float gradient(float ang){
return a*sin(omega*ang)+0.5; ;
}

//水平拉丝
float wire(vec2 v){
vec2 a= vec2(0.0,1.0);
float n= dot(v,a);
return fract(tan(n)*10000.0);
}

//杂色
float noise(vec2 v){
vec2 a= vec2(0.1234,0.5678);
float n= dot(v,a);
return fract(tan(n)*10000.0);
}

//获取弧度
float getAngle(vec2 v){
float ang=atan(v.y,v.x);
if(ang<0.0){
ang+=pi2;
}
return ang;
}

void main(){
vec2 p=gl_FragCoord.xy-center;
//极径
float len=length(p);
//极角
float ang=getAngle(p);

float x=u_CanvasSize.x*ang/pi2;
float y=(len/diagLen)*u_CanvasSize.y;

//渐变
float f1 = gradient(ang);
f1=0.65*f1+0.5;

//拉丝
float f2 = wire(vec2(int(x),int(y)));
f2=clamp(f2,0.75,0.8);

//杂色
float f3 = noise(gl_FragCoord.xy);
f3*=0.07;

//复合亮度
float f=f1*f2+f3;

gl_FragColor = vec4(vec3(f), 1);

}
</script>

此时效果如下
磨砂材质

绘制凸出效果,对复合亮度做一下加工。
1
2
3
4
5
6
float ratio1=smoothstep(-1.0,1.0,sin(ang));
float r=150.0;
float expand1=r+4.0;
if(len>r&&len<expand1){
f*=ratio1+0.3;
}

复合亮度加工
smoothstep(edge0,edge1,x) 求x在edge0和edge1间的插值[0,1]

若x<edge0 返回0

若x>edge1 返回1

否则返回x 在edge0和edge1间的插值

例子:

smoothstep(3,7,1)=0

smoothstep(3,7,8)=1

smoothstep(3,7,5)=(5-3)/(7-3)=2/4=0.5

sin(ang)的单调性:

ang∈[-π/2,π/2] 时,ang越大,sin(ang)越大
ang∈[π/2,π/2+π] 时,ang越大,sin(ang)越小

以此原理,我们还可以再做一圈凹陷效果。
1
2
3
4
5
6
7
8
9
10
float ratio1=smoothstep(-1.0,1.0,sin(ang));
float ratio2=1.0-ratio1;
float r=150.0;
float expand1=r+4.0;
float expand2=expand1+12.0;
if(len>r&&len<expand1){
f*=ratio1+0.3;
}else if(len>expand1&&len<expand2){
f*=ratio2+0.1;
}

凹陷效果
上面的ratio2 是实现了一个自上到下,由暗到亮的效果。

我也可以对代码做一点优化,把亮度和半径各自装进一个集合里。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//片元亮度集合
float ratio1=(sin(ang)+1.0)/2.0;
float ratio2=1.0-ratio1;
float ls[3];
ls[0]=f*(ratio1+0.3);
ls[1]=f*(ratio2+0.1);
ls[2]=f*(ratio2+0.3);

//初始半径
float r=150.0;
//半径集合
float rs[3];
rs[0]=r+4.0;
rs[1]=rs[0]+12.0;
rs[2]=rs[1]+2.0;

//基于len值,计算片元亮度
for(int i=0;i<3;i++){
if(len>=r&&len<rs[i]){
f=ls[i];
break;
}
r=rs[i];
}

使用Element3+WebGL

添加安装依赖

使用vite初始化安装依赖之后,添加依赖

1
rollup-plugin-element3-webgl
vite.config.js
1
2
3
4
5
6
7
8
9
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import element3Webgl from "rollup-plugin-element3-webgl";

// https://vitejs.dev/config/
export default defineConfig({
base: "/",
plugins: [vue(), element3Webgl()],
});
使用
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
<template>
<div>
<DrawBlock
:u_Width="600"
:u_Height="600"
:u_Radius="200"
width=600
height=600
></DrawBlock>
</div>
</template>
<script lang="ts">

import { defineComponent } from "vue";
import DrawBlock from "./pure.frag";

export default defineComponent({
name: "App",
components: {
DrawBlock
},
setup(){
return {

}
}
});

</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
precision mediump float;

uniform float u_Width;
uniform float u_Height;
uniform float u_Radius;

void main() {
vec2 center=vec2(u_Width,u_Height)/2.0;
vec2 p=gl_FragCoord.xy-center;
float l = length(p);
if(l<u_Radius){
gl_FragColor=vec4(1,1,0,1);
}else{
gl_FragColor = vec4(0, 0, 0, 1);
}
}

最终效果就是一个红色的圆圈。

接下来我们把之前写过的磨砂金属作为Element3 组件的皮肤。

金属Switch
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
<template>
<E3Switch
v-model="enabled"
class="
relative
focus:outline-none
"
>
<draw
width="100"
height="100"
:iTime="0"
:iWidth="buttonWidth"
:iHeight="buttonHeight"
:style="enabled?buttonEnterStyle:buttonStyle"
></draw>
<div :style="backgroundStyle"></div>
</E3Switch>
</template>

<script>
import { ref } from "vue";
import { E3Switch } from "element3-core";
import draw from "./metal.frag";
export default {
components: {
E3Switch,
draw,
},
setup() {
const enabled = ref(false);

const buttonStyle = {
width:"40px",
height:"40px",
transform:"translate(0px,0px)",
borderRadius:"50%",
position:"absolute",
left:"17px",
top:"14px",
boxShadow:"0px 3px 4px #000",
transition:"all .2s ease-in"
}

const buttonEnterStyle = {
width:"40px",
height:"40px",
transform:"translate(32px,0px)",
borderRadius:"50%",
position:"absolute",
left:"17px",
top:"14px",
boxShadow:"0px 3px 4px #000",
transition:"all .2s ease-in"
}

const buttonWidth = ref(100);
const buttonHeight = ref(100);

const backgroundStyle = {
background: "linear-gradient(#333, #666)",
width: "70px",
height:"44px",
borderRadius:"25px",
border:"2px solid #333",
}

return { buttonWidth,buttonHeight,backgroundStyle,enabled,buttonStyle,buttonEnterStyle};
},
};
</script>

<style></style>
金属材质
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
precision mediump float;

uniform float iWidth;
uniform float iHeight;

vec2 u_CanvasSize = vec2(iWidth, iHeight);
vec2 center = u_CanvasSize / 2.0;
float diagLen = length(center);
float pi2 = radians(360.0);
float omega=4.0;
float a=0.5;

//渐变
float gradient(float ang){
return a*sin(omega*ang)+0.5; ;
}

//水平拉丝
float wire(vec2 v){
vec2 a= vec2(0.0,1.0);
float n= dot(v,a);
return fract(tan(n)*10000.0);
}

//杂色
float noise(vec2 v){
vec2 a= vec2(0.1234,0.5678);
float n= dot(v,a);
return fract(tan(n)*10000.0);
}

//获取弧度
float getAngle(vec2 v){
float ang=atan(v.y,v.x);
if(ang<0.0){
ang+=pi2;
}
return ang;
}

void main(){
vec2 p=gl_FragCoord.xy-center;
float len=length(p);
float ang=getAngle(p);
float x=u_CanvasSize.x*ang/pi2;
float y=(len/diagLen)*u_CanvasSize.y;

float f1 = gradient(ang);
f1=0.65*f1+0.5;

float f2 = wire(vec2(int(x),int(y)));
f2=clamp(f2,0.75,0.8);

float f3 = noise(gl_FragCoord.xy);
f3*=0.07;

//复合亮度
float f=f1*f2+f3;

//片元亮度集合
float ratio1=smoothstep(-1.0,1.0,sin(ang));
float ratio2=1.0-ratio1;
float ls[3];
ls[0]=f*(ratio1+0.3);
ls[1]=f*(ratio2+0.1);
ls[2]=f*(ratio2+0.3);

//初始半径
float r=150.0;
//半径集合
float rs[3];
rs[0]=r+4.0;
rs[1]=rs[0]+12.0;
rs[2]=rs[1]+2.0;

//基于len值,计算片元亮度
for(int i=0;i<3;i++){
if(len>=r&&len<rs[i]){
f=ls[i];
break;
}
r=rs[i];
}

gl_FragColor = vec4(vec3(f), 1);
}

效果图

结语

本篇文章就到这里了,更多内容敬请期待,债见~

上一篇:
【可视化学习】76-从入门到放弃WebGL(十二)
下一篇:
【可视化学习】74-简易员工风采展示