着色器
事实上,着色器是 WebGL 的主要组件之一。如果我们在没有 Three.js 的情况下开始学习 WebGL,着色器将是我们首先要学习的内容之一,这也是原生 WebGL 如此困难的原因所在。
着色器是用 GLSL 编写并发送到 GPU 的程序。它们用于定位几何体的每个顶点,并为几何体的每个可见像素着色。像素 “一词并不准确,因为渲染中的每个点并不一定与屏幕上的每个像素相匹配,这也是我们更喜欢使用 “片段 “一词的原因,所以如果你同时看到这两个术语,请不要感到惊讶。
然后,我们会向着色器发送大量数据,如顶点坐标、网格变换、摄像机及其视场的信息(其实就是定位图像投影的矩阵),以及颜色、纹理、灯光、雾气等参数。然后 GPU 根据着色器指令处理所有这些数据,我们的几何体就会出现在渲染中
Vertex shader 顶点着色器
顶点着色器的作用是定位几何体的顶点。我们的想法是发送顶点位置、网格变换(如位置、旋转和缩放)、摄像头信息(如位置、旋转和视野)。然后,GPU 将按照顶点着色器中的指令处理所有这些信息,以便将顶点投射到二维空间中,该空间将成为我们的渲染,换句话说,就是我们的画布。
使用顶点着色器时,其代码将应用于几何体的每个顶点。但有些数据(如顶点位置)会在每个顶点之间发生变化。这种在顶点之间变化的数据被称为attribute。但有些数据不需要在每个顶点之间切换,例如网格的位置。是的,网格的位置会影响所有顶点,但影响的方式是一样的。这种不在顶点之间变化的数据被称为uniform。
顶点着色器首先开始工作。一旦放置了顶点,GPU 就会知道几何图形中哪些像素是可见的,从而可以进入片段着色器。
Fragment shader 片段着色器
片段着色器的作用是为几何体的每个可见片段着色。
几何体的每个可见片段都将使用相同的片段着色器。我们可以像顶点着色器一样,通过使用 uniforms 向片段着色器发送颜色等数据,也可以从顶点着色器向片段着色器发送数据。我们将这种从顶点着色器发送到片段着色器的数据称为 varying。我们稍后再讨论这个问题。
片段着色器中最直接的指令就是用相同的颜色为所有片段着色。如果我们只设置了颜色属性,就会得到等同于网格基本材质MeshBasicMaterial的效果。
或者,我们可以向着色器发送更多数据,例如灯光位置。这样,我们就能根据面向光源的位置的多少,来为片段着色。如果场景中只有一束光,我们就会得到与 MeshPhongMaterial 相同的效果。
着色器作用总结
- 顶点着色器在渲染器中定位顶点。
- 片段着色器会为几何体的每个可见片段(或像素)上色。
- 片段着色器在顶点着色器之后执行。
- 每个顶点上会发生变化的数据(如位置)称为属性,只能在顶点着色器中使用。
- 顶点上不会变化的数据(如网格位置或颜色,网格代表的是整体)称为统一数据,可在顶点着色器和片段着色器中使用。
- 我们可以使用 varying 将数据从顶点着色器发送到片段着色器。
创建第一个shader
要创建第一个着色器,我们需要创建一个特定的材质。这种材质可以是 ShaderMaterial(着色器材质),也可以是 RawShaderMaterial(原始着色器材质)。这两种材质的区别在于,ShaderMaterial 会在着色器代码中自动添加一些代码,而 RawShaderMaterial 则顾名思义,什么代码都没有。
我们将从 RawShaderMaterial 开始,以便更好地理解发生了什么。
const material = new THREE.RawShaderMaterial({
vertexShader: `
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
attribute vec3 position;
void main()
{
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
precision mediump float;
void main()
{
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
`
})
vite配置处理glsl文件
在行内写shader,不太符合项目流程,抽离到文件中,方便复用和修改。
在vite项目中可以安装yarn add vite-plugin-glsl --dev
该插件可以处理glsl语法,并处理其在js中的引入和调用
// vite.config.js
import glsl from 'vite-plugin-glsl'
// ...
export default {
// ...
plugins:
[
glsl()
]
}
// shader.js
import testVertexShader from './shaders/test/vertex.glsl'
import testFragmentShader from './shaders/test/fragment.glsl'
const material = new THREE.RawShaderMaterial({
vertexShader: testVertexShader,
fragmentShader: testFragmentShader,
wireframe: true // 显示其网格结构
})
我们在其他材质(如wireframe, side, transparent, flatShading)中介绍过的大多数常用属性,在 RawShaderMaterial 中仍然可用,但是,map、alphaMap、opacity、color等属性将不再起作用,因为我们需要自己在着色器中编写这些特性。
GLSL
在这类文件中,没有控制台,因此无法记录数值。这是因为每个顶点和每个片段都要执行代码。记录一个值是没有意义的。缩进也不是必须得,但是代码行尾的分号是必须的。
数据类型
// float
float a = - 0.123;
// int
int b = 100
// 不同类型进行运算需要先转换
float c = a * float(b)
// boolean
bool d = false
// vector
vec2 foo = vec2(1.0, 2.0);
vec2 foo = vec2();
vec2 foo = vec2(0.0 );
foo.x = 1.0;
foo.y = 2.0;
foo *= 2.0;
vec3 bar = vec3(1.0, 2.0, 3.0);
bar.z = 4.0;
// vec3还可表示颜色,语法糖而已
vec3 purpleColor = vec3(0.0);
purpleColor.r = 0.5;
purpleColor.b = 1.0;
// 自动解构
vec2 foo = vec2(1.0, 2.0);
vec3 bar = vec3(foo, 3.0);
// vec4 的工作原理与它的两个前身相似,但它的第四个值被命名为 w 或者 a,因为字母表中 z 后面没有字母,而 a 代表 "alpha"(透明度)
vec4 foo = vec4(1.0, 2.0, 3.0, 4.0);
vec4 bar = vec4(foo.zw, vec2(5.0, 6.0)); // zw代表只解构foo的z和w
函数
// 定义返回类型,入参类型等
float add(float a, float b)
{
return a + b;
}
GLSL 有许多内置的经典函数,如 sin、cos、max、min、pow、exp、mod、camp,也有一些非常实用的函数,如 cross、dot、mix、step、smoothstep、length、distance、reflect、refract、normalize
Kronos Group OpenGL reference pages
本文档涉及 OpenGL,但您将看到的大多数标准函数都与 WebGL 兼容。不要忘了 WebGL 只是让js可以访问 OpenGL 的api
The book of shaders mainly focus on fragment shaders and has nothing to do with Three.js but it is a great resource to learn and it has its own glossary.
理解顶点着色器
顶点着色器的作用是将几何体的每个顶点定位到渲染的 2D 空间。换句话说,顶点着色器会将 3D 顶点坐标转换为我们的 2D 画布坐标。
gl_Position 变量已定义。我们需要对其进行赋值。该变量将包含顶点在屏幕上的位置。主函数中指令的目的就是正确设置这个变量
void main()
{
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
// 屏幕所有顶点坐标的x和y都增加0.5,就是向右上方移动屏幕的一半
// 需要注意的是:我们并没有真正在三维空间中移动平面,就像在Three.js中处理位置一样。其实只是在二维空间中移动了投影平面而已。
gl_Position.x += 0.5;
gl_Position.y += 0.5;
}
把它想象成你在纸上画的一幅画。在这幅画中,你的消失点遵循透视。然后,你把整幅画移至桌子的右上角。画中的透视并没有改变。
你可能想知道,如果 gl_Position 的最终目标是在二维空间中定位顶点,那么为什么需要4个值。实际上,这是因为坐标并不精确地位于二维空间中,而是位于我们所说的裁剪空间中,而裁剪空间需要4个维度。
裁剪空间是一个沿三个方向(x、y 和 z)的空间,范围从 -1 到 +1。这就好比把所有东西放在一个3D盒子里。任何超出这个范围的内容都将被”剪切”并消失。第四个值(w)负责透视
相同的代码适用于几何体的每个顶点。Attributes是唯一会在顶点之间发生变化的变量。相同的顶点着色器将应用于每个顶点,并且位置属性将包含该特定顶点的 x、y 和 z 坐标
矩阵及作用
每个矩阵都会变换位置,直到得到最终的裁剪空间坐标。 我们的代码中有 3 个矩阵,由于它们的值对于几何体的所有顶点都是相同的,因此我们使用uniforms来获取它们。
每个矩阵将完成部分变换:
// modelMatrix 将应用相对于网格的所有变换。如果我们缩放、旋转或移动网格,这些变换将包含在模型矩阵中,并应用到位置上。
uniform mat4 modelMatrix;
// viewMatrix 将应用相对于摄像机的变换。如果我们将摄像机向左旋转,顶点应该在右边。如果我们沿网格方向移动摄像机,顶点应该变大,等等。
uniform mat4 viewMatrix;
// projectionMatrix 最后会将我们的坐标转换为最终的剪辑空间坐标。
uniform mat4 projectionMatrix;
矩阵的应用
想要应用矩阵的变换,很简单,让vec4左乘矩阵就可以了,矩阵之间还可以连乘,但是矩阵的乘法需要注意先后顺序
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
其中viewMatrix * modelMatrix
有个简写矩阵modelViewMatrix
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
attribute vec3 position;
void main()
{
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
通过矩阵控制点的位置
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
attribute vec3 position;
void main()
{
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
// 模型的z坐标依据x的sin值来控制, 0.1是振幅
modelPosition.z += sin(modelPosition.x * 10.0) * 0.1;
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectedPosition = projectionMatrix * viewPosition;
gl_Position = projectedPosition;
}
理解片段着色器(fragment shader)
片段着色器代码将应用于几何体的每个可见片段。这就是片段着色器排在顶点着色器之后的原因。与顶点着色器相比,片段着色器代码更易于管理。
void main()
{
// 通过下面这条头部指令,可以确定浮点数的精确度。有不同的可能值:highp、mediump、lowp
// highp 可能会影响性能,甚至可能无法在某些设备上运行。我们通常使用 mediump。我们也可以设置顶点着色器的精度,但这不是必需的。
// 当我们使用 ShaderMaterial 而不是 RawShaderMaterial 时,这部分内容会自动处理
precision mediump float;
// ...
}
gl_FragColor
gl_FragColor
和gl_Position
一样,都是预先定义好的。重新赋值来设置颜色
// r g b a
gl_FragColor = vec4(0.5, 0.0, 1.0, 1.0);
如果想要设置透明度,那在设置RawShadermaterial的时候,要把transparent
设置为true
const material = new THREE.RawShaderMaterial({
vertexShader: testVertexShader,
fragmentShader: testFragmentShader,
transparent: true
})
attributes 想要给每个顶点的z一个随机的变化
const count = geometry.attributes.position.count
const randoms = new Float32Array(count)
for(let i = 0; i < count; i++)
{
randoms[i] = Math.random()
}
// BufferAttribute的第二个参数 1,代表每个顶点分配一个值,如果是position,就是 3
geometry.setAttribute('aRandom', new THREE.BufferAttribute(randoms, 1))
在shader中使用新增的属性aRandom
// 要先声明该属性,才可使用
attribute float aRandom;
void main()
{
// ...
modelPosition.z += aRandom * 0.1;
// ...
}
varying
我们在模型中定义的属性aRandom
没办法直接在片段着色器中使用,但是有一种从顶点着色器向片段着色器发送数据的方法,称为 “varying”。
我们必须在顶点着色器和片段着色器中都使用这种方法。在顶点着色器中,需要在主函数之前创建varying。我们将把varying函数称为 vRandom
// vertex.glsl
varying float vRandom;
void main()
{
// ...
vRandom = aRandom;
}
// fragment.glsl
precision mediump float;
varying float vRandom;
void main()
{
// ...
gl_FragColor = vec4(0.5, vRandom, 1.0, 1.0);
}
现在你可以得到一个带有彩色尖峰的醒目图形。
变化的一个有趣之处在于,顶点之间的值是插值。如果 GPU 在两个顶点(一个顶点的变化值为 1.0,另一个顶点的变化值为 0.0)之间绘制片段,那么片段值将为 0.5。
让我们删除或注释高程部分和变化,这样就回到了我们的紫色平面
uniforms 使用uniforms来传递值来控制shader
const flagTexture = textureLoader.load('/textures/flag-french.jpg')
const material = new THREE.RawShaderMaterial({
vertexShader: testVertexShader,
fragmentShader: testFragmentShader,
uniforms:
{
uFrequency: new THREE.Vector2(10, 5),
uTime: { value: 0.0 },
uColor: { value: new THREE.Color('orange') },
uTexture: { value: flagTexture }
}
})
// 不关注material的话,我们仍可以直接控制模型的position, scale and rotation
const mesh = new THREE.Mesh(geometry, material)
mesh.scale.y = 2 / 3
scene.add(mesh)
// ...
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
// 更新 uTime
material.uniforms.uTime.value = elapsedTime
// ...
}
使用uFrequency来控制震荡幅度
// vertex.glsl
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
uniform vec2 uFrequency; // 类型为vec2
uniform float uTime;
attribute vec2 uv;
attribute vec3 position;
varying vec2 vUv;
void main()
{
vec4 modelPosition = modelMatrix * vec4(position, 1.0);
// 我们要根据顶点的x和y的sin值来控制亮度
float elevation = sin(modelPosition.x * uFrequency.x - uTime) * 0.1;
elevation += sin(modelPosition.y * uFrequency.y - uTime) * 0.1;
// x,y的坐标会定义摄像机的远近
modelPosition.z += elevation;
vec4 viewPosition = viewMatrix * modelPosition;
vec4 projectedPosition = projectionMatrix * viewPosition;
gl_Position = projectedPosition;
// 将模型的uv坐标透过varying传递给片段着色器
vUv = uv;
vElevation = elevation;
}
// fragment.glsl
precision mediump float;
uniform vec3 uColor;
uniform sampler2D uTexture; // 获取从js读取的纹理
varying vec2 vUv;
varying float vElevation;
precision mediump float;
varying float vRandom;
void main()
{
// texture2D(...) 是一个 vec4,因为它包含 r、g、b 和 a,即使我们的纹理没有 alpha 变化
vec4 textureColor = texture2D(uTexture, vUv);
// r、g、b均增加相同的倍数,就是增加亮度,这是一个可正可负的数
textureColor.rgb *= vElevation * 2.0 + 0.65;
// 使用纹理绘制
gl_FragColor = vec4(0.5, vRandom, 1, 1);
// js控制颜色绘制
// gl_FragColor = vec4(uColor, 1.0);
// 根据顶点着色器的uv坐标来绘制颜色,可以产生远近的变化
// gl_FragColor = vec4(vUv, 1.0, 1.0);
}
将 RawShaderMaterial 替换 ShaderMaterial,删除以下声明,效果依然一致,因为 ShaderMaterial 会自动添加这些
uniform mat4 projectionMatrix;
uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
attribute vec3 position;
attribute vec2 uv;
precision mediump float;
shader pattern
学会理解uv,使用uv坐标来控制shader的表现
glsl内置函数
角度相关函数
- sin(x) 弧度 正弦函数
- cos(x) 弧度 余弦函数
- tan(x) 弧度 正切函数
- asin(x) 弧度 反正弦函数
- acos(x) 弧度 反余弦函数
- atan(x) 弧度 反正切函数
- radians(x) 角度 角度转换为弧度
- degrees(x) 弧度 弧度转换为角度
数学函数
- pow(x,y) x的y次方。如果x小于0,结果是未定义的。同样,如果x=0并且y<=0,结果也是未定义的。
- exp(x) e的x次方
- log(x) 计算满足x等于e的y次方的y的值。如果x的值小于0,结果是未定义的。
- exp2(x) 计算2的x次方
- log2(x) 计算满足x等于2的y次方的y的值。如果x的值小于0,结果是未定义的。
- sqrt(x) 计算x的开方。如果x小于0,结果是未定义的。
- inversesqrt(x) 计算x的开方之一的值,如果x小于等于0,结果是未定义的。
常用函数
- abs(x) 返回x的绝对值
- sign(x) 如果x>0,返回1.0;如果x=0,返回0,如果x<0,返回-1.0
- floor(x) 返回小于等于x的最大整数值
- ceil(x) 返回大于等于x的最小整数值
- fract(x) 返回x-floor(x),即返回x的小数部分
- mod(x, y) 返回x和y的模
- min(x, y) 返回x和y的值较小的那个值。
- max(x, y) 返回x和y的值较大的那个值。
- clamp(x, minVal, maxVal) 将x值钳于minVal和maxVal之间,意思就是当x<minVal时返回minVal,当x>maxVal时返回maxVal,当x在minVal和maxVal之间时,返回x
- mix(x, y, a) 返回线性混合的x和y,如:x*(1−a)+y*a
- step(edge, x) 如果x < edge,返回0.0,否则返回1.0
- smoothstep(edge0, edge1, x) 如果x <= edge0,返回0.0 ;如果x >= edge1 返回1.0;如果edge0 < x < edge1,则执行0~1之间的平滑埃尔米特差值。如果edge0 >= edge1,结果是未定义的。
几何函数
- length(x) 返回向量x的长度
- distance(p0,p1) 计算向量p0,p1之间的距离
- dot 向量x,y之间的点积
- cross(x, y) 向量x,y之间的叉积
- normalize(x) 标准化向量,返回一个方向和x相同但长度为1的向量
- faceforward(N, I, Nref) 如果Nref和I的点积小于0,返回N;否则,返回-N;
- reflect(I, N) 返回反射向量
- refract(I, N, eta) 返回折射向量
// vertex.glsl
varying vec2 vUv;
void main()
{
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
vUv = uv;
}
// fragment.glsl
#define PI 3.1415926535897932384626433832795
varying vec2 vUv;
// 对每个点进行随机处理
float random(vec2 st)
{
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
}
// 对uv坐标点进行旋转,旋转角度是rotation,旋转中心是mid
vec2 rotate(vec2 uv, float rotation, vec2 mid)
{
return vec2(
cos(rotation) * (uv.x - mid.x) + sin(rotation) * (uv.y - mid.y) + mid.x,
cos(rotation) * (uv.y - mid.y) - sin(rotation) * (uv.x - mid.x) + mid.y
);
}
// Classic Perlin 2D Noise
// by Stefan Gustavson
//
vec4 permute(vec4 x)
{
return mod(((x*34.0)+1.0)*x, 289.0);
}
vec2 fade(vec2 t)
{
return t*t*t*(t*(t*6.0-15.0)+10.0);
}
float cnoise(vec2 P)
{
// 柏林噪声 https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83
// 柏林噪声 噪声有助于再现云、水、火、地形高差等自然形状,也可用于制作风中移动的草或雪的动画。
vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);
vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);
Pi = mod(Pi, 289.0); // To avoid truncation effects in permutation
vec4 ix = Pi.xzxz;
vec4 iy = Pi.yyww;
vec4 fx = Pf.xzxz;
vec4 fy = Pf.yyww;
vec4 i = permute(permute(ix) + iy);
vec4 gx = 2.0 * fract(i * 0.0243902439) - 1.0; // 1/41 = 0.024...
vec4 gy = abs(gx) - 0.5;
vec4 tx = floor(gx + 0.5);
gx = gx - tx;
vec2 g00 = vec2(gx.x,gy.x);
vec2 g10 = vec2(gx.y,gy.y);
vec2 g01 = vec2(gx.z,gy.z);
vec2 g11 = vec2(gx.w,gy.w);
vec4 norm = 1.79284291400159 - 0.85373472095314 * vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11));
g00 *= norm.x;
g01 *= norm.y;
g10 *= norm.z;
g11 *= norm.w;
float n00 = dot(g00, vec2(fx.x, fy.x));
float n10 = dot(g10, vec2(fx.y, fy.y));
float n01 = dot(g01, vec2(fx.z, fy.z));
float n11 = dot(g11, vec2(fx.w, fy.w));
vec2 fade_xy = fade(Pf.xy);
vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);
float n_xy = mix(n_x.x, n_x.y, fade_xy.y);
return 2.3 * n_xy;
}
gl_FragColor所代表的rgba四个维度的最大值是1,UV坐标的x和y的最大值也是1
void main()
{
// Pattern 1
// gl_FragColor = vec4(vUv, 1.0, 1.0);
// Pattern 2
// gl_FragColor = vec4(vUv, 0.0, 1.0);
// Pattern 3
// float strength = vUv.x;
// Pattern 4
// float strength = vUv.y;
// Pattern 5
// float strength = 1.0 - vUv.y;
// Pattern 6
// float strength = vUv.y * 10.0;
// Pattern 7
// mod函数的作用就是取模,类似于js的%,mod(0.7, 1.0) = 0.7,mod(1.2, 1.0) = 0.2
// 产生10个阶层
// float strength = mod(vUv.y * 10.0, 1.0);
// Pattern 8
// 函数step(edge, x) 如果x < edge,返回0.0,否则返回1.0
// float strength = mod(vUv.y * 10.0, 1.0);
// strength = step(0.5, strength);
// Pattern 9
// float strength = mod(vUv.y * 10.0, 1.0);
// strength = step(0.8, strength);
// Pattern 10
// float strength = mod(vUv.x * 10.0, 1.0);
// strength = step(0.8, strength);
// Pattern 11
// 这里的相加不要疑惑,可能在uv的x代入为0,但是uv的y计算后却为1,因此会出现纵横交错的情况
// float strength = step(0.8, mod(vUv.x * 10.0, 1.0));
// strength += step(0.8, mod(vUv.y * 10.0, 1.0));
// strength = clamp(strength, 0.0, 1.0);
// Pattern 12
// float strength = step(0.8, mod(vUv.x * 10.0, 1.0));
// 这里 strength的值只可能为0或者1,在分析shader的代码时,最好先计算出值所在的范围
// 所以下面这行代码的值,只有在右侧表达式为1时,才为1,否则为0
// strength *= step(0.8, mod(vUv.y * 10.0, 1.0));
// Pattern 13
// float strength = step(0.4, mod(vUv.x * 10.0, 1.0));
// strength *= step(0.8, mod(vUv.y * 10.0, 1.0));
// Pattern 14
// float barX = step(0.4, mod(vUv.x * 10.0, 1.0)) * step(0.8, mod(vUv.y * 10.0, 1.0));
// float barY = step(0.8, mod(vUv.x * 10.0, 1.0)) * step(0.4, mod(vUv.y * 10.0, 1.0));
// float strength = barX + barY;
// strength = clamp(strength, 0.0, 1.0);
// Pattern 15
// float barX = step(0.4, mod(vUv.x * 10.0 - 0.2, 1.0)) * step(0.8, mod(vUv.y * 10.0, 1.0));
// float barY = step(0.8, mod(vUv.x * 10.0, 1.0)) * step(0.4, mod(vUv.y * 10.0 - 0.2, 1.0));
// float strength = barX + barY;
// strength = clamp(strength, 0.0, 1.0);
// Pattern 16
// float strength = abs(vUv.x - 0.5);
// Pattern 17
// 想要画面颜色降低就选择 min、- 等操作,增加就是使用 +
// float strength = min(abs(vUv.x - 0.5), abs(vUv.y - 0.5));
// Pattern 18
// float strength = max(abs(vUv.x - 0.5), abs(vUv.y - 0.5));
// Pattern 19
// 想要制作中心对称图形,那就要 -0.5,把坐标原点移到中间
// float strength = step(0.2, max(abs(vUv.x - 0.5), abs(vUv.y - 0.5)));
// Pattern 20
// float strength = step(0.2, max(abs(vUv.x - 0.5), abs(vUv.y - 0.5)));
// 1 减去 对应的值再做乘,其实是求两个区域相交的部位,这个操作适合用来实现某个范围
// strength *= 1.0 - step(0.25, max(abs(vUv.x - 0.5), abs(vUv.y - 0.5)));
// Pattern 21
// float strength = floor(vUv.x * 10.0) / 10.0;
// Pattern 22
// float strength = floor(vUv.x * 10.0) / 10.0 * floor(vUv.y * 10.0) / 10.0;
// Pattern 23
// float strength = random(vUv);
// Pattern 24
// vec2 gridUv = vec2(floor(vUv.x * 10.0) / 10.0, floor(vUv.y * 10.0) / 10.0);
// float strength = random(gridUv);
// Pattern 25
// vec2 gridUv = vec2(floor(vUv.x * 10.0) / 10.0, floor((vUv.y + vUv.x * 0.5) * 10.0) / 10.0);
// float strength = random(gridUv);
// Pattern 26
// float strength = length(vUv);
// Pattern 27
// float strength = distance(vUv, vec2(0.5));
// Pattern 28
// float strength = 1.0 - distance(vUv, vec2(0.5));
// Pattern 29
// float strength = 0.015 / (distance(vUv, vec2(0.5)));
// Pattern 30
// float strength = 0.15 / (distance(vec2(vUv.x, (vUv.y - 0.5) * 5.0 + 0.5), vec2(0.5)));
// Pattern 31
// float strength = 0.15 / (distance(vec2(vUv.x, (vUv.y - 0.5) * 5.0 + 0.5), vec2(0.5)));
// strength *= 0.15 / (distance(vec2(vUv.y, (vUv.x - 0.5) * 5.0 + 0.5), vec2(0.5)));
// Pattern 32
// vec2 rotatedUv = rotate(vUv, PI * 0.25, vec2(0.5));
// float strength = 0.15 / (distance(vec2(rotatedUv.x, (rotatedUv.y - 0.5) * 5.0 + 0.5), vec2(0.5)));
// strength *= 0.15 / (distance(vec2(rotatedUv.y, (rotatedUv.x - 0.5) * 5.0 + 0.5), vec2(0.5)));
// Pattern 33
// float strength = step(0.5, distance(vUv, vec2(0.5)) + 0.25);
// Pattern 34
// float strength = abs(distance(vUv, vec2(0.5)) - 0.25);
// Pattern 35
// float strength = step(0.01, abs(distance(vUv, vec2(0.5)) - 0.25));
// Pattern 36
// float strength = 1.0 - step(0.01, abs(distance(vUv, vec2(0.5)) - 0.25));
// Pattern 37
// vec2 wavedUv = vec2(
// vUv.x,
// vUv.y + sin(vUv.x * 30.0) * 0.1
// );
// float strength = 1.0 - step(0.01, abs(distance(wavedUv, vec2(0.5)) - 0.25));
// Pattern 38
// vec2 wavedUv = vec2(
// vUv.x + sin(vUv.y * 30.0) * 0.1,
// vUv.y + sin(vUv.x * 30.0) * 0.1
// );
// float strength = 1.0 - step(0.01, abs(distance(wavedUv, vec2(0.5)) - 0.25));
// Pattern 39
// vec2 wavedUv = vec2(
// vUv.x + sin(vUv.y * 100.0) * 0.1,
// vUv.y + sin(vUv.x * 100.0) * 0.1
// );
// float strength = 1.0 - step(0.01, abs(distance(wavedUv, vec2(0.5)) - 0.25));
// Pattern 40
// float angle = atan(vUv.x, vUv.y);
// float strength = angle;
// Pattern 41
// float angle = atan(vUv.x - 0.5, vUv.y - 0.5);
// float strength = angle;
// Pattern 42
// float angle = atan(vUv.x - 0.5, vUv.y - 0.5) / (PI * 2.0) + 0.5;
// float strength = angle;
// Pattern 43
// float angle = atan(vUv.x - 0.5, vUv.y - 0.5) / (PI * 2.0) + 0.5;
// float strength = mod(angle * 20.0, 1.0);
// Pattern 44
// float angle = atan(vUv.x - 0.5, vUv.y - 0.5) / (PI * 2.0) + 0.5;
// float strength = sin(angle * 100.0);
// Pattern 45
// float angle = atan(vUv.x - 0.5, vUv.y - 0.5) / (PI * 2.0) + 0.5;
// float radius = 0.25 + sin(angle * 100.0) * 0.02;
// float strength = 1.0 - step(0.01, abs(distance(vUv, vec2(0.5)) - radius));
// Pattern 46
// 柏林噪声 https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83
// 柏林噪声 噪声有助于再现云、水、火、地形高差等自然形状,也可用于制作风中移动的草或雪的动画。
// float strength = cnoise(vUv * 10.0);
// Pattern 47
// float strength = step(0.0, cnoise(vUv * 10.0));
// Pattern 48
// 你可以用它来制造闪电、水下倒影或等离子能量。
// float strength = 1.0 - abs(cnoise(vUv * 10.0));
// Pattern 49
// float strength = sin(cnoise(vUv * 10.0) * 20.0);
// Pattern 50
// float strength = step(0.9, sin(cnoise(vUv * 10.0) * 20.0));
// Final color
vec3 blackColor = vec3(0.0);
vec3 uvColor = vec3(vUv, 1.0);
vec3 mixedColor = mix(blackColor, uvColor, strength);
// gl_FragColor = vec4(vec3(strength), 1.0);
gl_FragColor = vec4(mixedColor, 1.0);
}