前言
本篇,继续来学习 webgl
极坐标

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

一个点的位置既可以用直角坐标来表示,也可以用极坐标来表示。
接下来我们说一下二维直角坐标系与极坐标系的转换方法。
极角与 x 轴的映射
我们可以通过极角与 x 轴的映射实现放射效果。
放射渐变
在片元着色器里基于画布尺寸计算画布中心位,声明 360° 所对应的弧度,以备后用。
| 12
 3
 
 | uniform vec2 u_CanvasSize;vec2 center=u_CanvasSize/2.0;
 float pi2=radians(360.0);
 
 | 
以画布中心点为极点,计算当前片元的极角 ang。
| 12
 3
 4
 5
 
 | void main(){vec2 p=gl_FragCoord.xy-center;
 float ang=atan(p.y,p.x);
 ……
 }
 
 | 
以极角为变量,计算与计算一个 x 值
将 x 值拼上一个随意的 y 值,构成向量 v
基于向量 v,通过 rand() 方法生成一个颜色
| 12
 3
 
 | vec2 v=vec2(int(x),0);float f = rand(v);
 gl_FragColor = vec4(f, f, f, 1);
 
 | 

完整代码
| 12
 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;
 
 float ang=atan(p.y,p.x);
 
 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);
 
 
 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
| 12
 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);
 })();
 
 | 
| 12
 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
| 12
 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);
 }
 
 | 
完整代码
| 12
 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 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);
 
 
 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 方法
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | 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);
 }
 
 | 
控制一下闪烁速度
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | let lastTime = 0const 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)
 })()
 
 | 
完整代码
| 12
 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);
 
 
 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) {
 
 
 
 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>
 
 | 
来自深渊的凝视
建立两个模型矩阵
| 12
 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,我这么做是为打破一下其中规中矩的旋转方式。
将通过极坐标获取亮度的方法封装一下。
| 12
 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 中基于两个极点,获取两个亮度值。
| 12
 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);
 ……
 }
 
 | 
对两个亮度值进行合成。
其合成思路是若两个亮度值都比较暗,那我就让当前片元变亮;若都比较亮,那我就让其变暗。
| 12
 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);
 }
 
 | 

数字山谷
修改矩阵变换的参数
| 12
 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个极点获取亮度值,然后对其合成
| 12
 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变量
| 12
 
 | float omega=7.0;float a=0.5;
 
 | 
omega 对应的是正弦函数式里的ω,在放射效果中此值会影响射线的数量
a 对应的是正弦函数式里的A,在放射效果中此值会影响亮度
在main方法中,以画布中心为极点,计算当前片元的极角
| 12
 3
 4
 5
 
 | void main(){vec2 p=gl_FragCoord.xy-center;
 float ang=atan(p.y,p.x);
 ……
 }
 
 | 
以极角为变量计算正弦函数值
| 12
 
 | 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]

光影沉浮
| 12
 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>
 
 | 

湍流
| 12
 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对象
| 12
 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()
 }
 
 | 

顶点着色器
| 12
 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>
 
 | 
片元着色器
| 12
 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>
 
 | 
综合案例-磨砂金属按钮
接下来我们将之前说过的渐变、杂色、极坐标扭曲、拉丝融为一体,做一个磨砂金属按钮。

磨砂材质
| 12
 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>
 
 | 
此时效果如下

绘制凸出效果,对复合亮度做一下加工。
| 12
 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)越小
以此原理,我们还可以再做一圈凹陷效果。
| 12
 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 是实现了一个自上到下,由暗到亮的效果。
我也可以对代码做一点优化,把亮度和半径各自装进一个集合里。
| 12
 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;
 
 
 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
| 12
 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()],
 });
 
 | 
使用
| 12
 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>
 
 | 
| 12
 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
| 12
 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>
 
 | 
金属材质
| 12
 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;
 
 
 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);
 }
 
 | 

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