介绍

通常在创建着色器时,我们需要绘制特定如星星、圆圈、光透镜、波等图案。

初始设置

像上篇笔记中的一样,场景中有个使用ShaderMaterial着色器材质的PlaneBufferGeometry平面缓冲几何体

const geometry = new THREE.PlaneBufferGeometry(1, 1, 32, 32)

// Material
const material = new THREE.ShaderMaterial({
    vertexShader: testVertexShader,
    fragmentShader: testFragmentShader,
    side: THREE.DoubleSide
})

// Mesh
const mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)

vertex.glsl

void main()
{
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}

fragment.glsl

void main()
{
    gl_FragColor = vec4(0.5, 0.0, 1.0, 1.0);
}

开始

发送uv坐标到片元着色器

因为要绘制图案,所以大部分代码都在片元着色器中。下面就要将uv坐标从顶点着色器发送到片元着色器。
在顶点着色器中应该这样写:

attribute vec2 uv;

但是因为我们目前使用的是着色器材质,这行代码已经被预先添加至顶点着色器里边,所以会报错。

所以要将该值从顶点着色器传到片元着色器,我们需要变量varying命名为vUv并将uv赋值给它:

varying vec2 vUv;

void main()
{
    // ...

    vUv = uv;
}

然后在片元着色器中以同样声明方式接收:

varying vec2 vUv;

void main()
{
    // ...
}

图案1

在这里插入图片描述
只需要在gl_FragColor中使用vUv并将蓝色通道值设为1.0,即可得到上面图案。

varying vec2 vUv;

void main()
{
    gl_FragColor = vec4(vUv, 1.0, 1.0);
}

图案2

在这里插入图片描述
代码如图案1,只不过蓝色通道值设为0.0。

图案3

在这里插入图片描述

事情开始有趣起来,为了获得上图的梯度渐变效果,我们只需用vUvx属性,并且都应用到gl_FragColor的rgb值上边:

varying vec2 vUv;

void main()
{
    gl_FragColor = vec4(vUv.x, vUv.x, vUv.x, 1.0);
}

或者,我们可以创建一个名为strength的浮点型变量:

varying vec2 vUv;

void main()
{
    float strength = vUv.x;

    gl_FragColor = vec4(vec3(strength), 1.0);
}

从这里开始,下边将专注于这个变量并尝试绘制以下图案。

图案4

在这里插入图片描述
这个图案和上图是一样的,只不过是换成y属性:

float strength = vUv.y;

图案5

在这里插入图片描述
一样的形式,只不过把值换成1.0 - ...

float strength = 1.0 - vUv.y;

图案6

要像下图这样压缩渐变梯度变化曲线,我们只需将值相乘。强度将迅速跳到1,但我们无法显示比白色更亮的颜色,因此其余渐变保持白色:

float strength = vUv.y * 10.0;

在这里插入图片描述

图案7

为了得到如下图一样的重复梯度渐变,要使用到modulo,模运算。
在许多语言中,我们可以使用%来应用模,但在GLSL中,我们必须使用mod(…)函数:

float strength = mod(vUv.y * 10.0, 1.0);

在这里插入图片描述

图案8

此图案基于上个图案,但是不使用梯度渐变,而是只用0.01.0
我们可以使用if语句来实现这一点,因为条件语句在GLSL中确实有效,但还是建议出于性能原因避免使用条件语句。
我们可以使用step(…)函数。我们提供一个边缘值作为第一个参数,一个数字作为第二个参数。如果数值小于边缘值,则得到0.0。如果高于边缘值,则得到1.0:

float strength = mod(vUv.y * 10.0, 1.0);
strength = step(0.5, strength);

在这里插入图片描述

图案9

如图案8,不过用了更高的边缘值:

float strength = mod(vUv.y * 10.0, 1.0);
strength = step(0.8, strength);

在这里插入图片描述

图案10

如前者,但是用的是vUv的x属性:

float strength = mod(vUv.x * 10.0, 1.0);
strength = step(0.8, strength);

在这里插入图片描述

图案11

可以将x轴和y轴二者结合起来,在这里是二者的计算结果相加:

float strength = step(0.8, mod(vUv.x * 10.0, 1.0));
strength += step(0.8, mod(vUv.y * 10.0, 1.0));

在这里插入图片描述

图案12

与上图类似,只不过这里是二者相乘,这样只看到他们的交点:

float strength = step(0.8, mod(vUv.x * 10.0, 1.0));
strength *= step(0.8, mod(vUv.y * 10.0, 1.0));

在这里插入图片描述

图案13

同样,但是调低了x轴的边缘值:

float strength = step(0.4, mod(vUv.x * 10.0, 1.0));
strength *= step(0.8, mod(vUv.y * 10.0, 1.0));

在这里插入图片描述

图案14

这是前面几种图案的组合,在x轴上创建横柱,再添加y轴方向上的柱形:

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;

在这里插入图片描述

图案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;

在这里插入图片描述

图案16

为了得到如下效果,首先需要偏移vUv.x使得值为-0.5到0.5,然后再给其取绝对值使得数值范围为0.5到0再到0.5,为此使用abs(...)函数

float strength = abs(vUv.x - 0.5);

在这里插入图片描述

图案17

在下图中我们可以看到在x轴和y轴二者图案上的最小值,因此使用min(...)函数:

float strength = min(abs(vUv.x - 0.5), abs(vUv.y - 0.5));

在这里插入图片描述

图案18

和上图一样原理,只不过改用了max(...)函数:

float strength = max(abs(vUv.x - 0.5), abs(vUv.y - 0.5));

在这里插入图片描述

图案19

在上一个图案的基础上使用step(...)函数即可得到下图:

float strength = step(0.2, max(abs(vUv.x - 0.5), abs(vUv.y - 0.5)));

在这里插入图片描述

图案20

下图是一个正方形与另一个正方形相乘:

float strength = step(0.2, max(abs(vUv.x - 0.5), abs(vUv.y - 0.5)));
strength *= 1.0 - step(0.25, max(abs(vUv.x - 0.5), abs(vUv.y - 0.5)));

在这里插入图片描述

图案21

vUv.x乘以10.0,用floor(...)将其向下取整,然后再除以10.0,得到介于0.0到1.0之间的数值:

float strength = floor(vUv.x * 10.0) / 10.0;

在这里插入图片描述

图案22

跟上边一样,两个方向上相乘:

float strength = floor(vUv.x * 10.0) / 10.0 * floor(vUv.y * 10.0) / 10.0;

在这里插入图片描述

图案23

要获得像下图一样的复杂图案有点麻烦,因为GLSL里边没有原生随机函数可以使用,为此可以通过下面这种方法来获取随机数:

float random(vec2 st)
{
    return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
}

void main()
{
    // ...

    float strength = random(vUv);

    // ...
} 

在这里插入图片描述

图案24

下面的图案是上边俩个图案的结合,首先,我们创建一个名为gridUv的新vec2坐标,并使用舍入值:

vec2 gridUv = vec2(floor(vUv.x * 10.0) / 10.0, floor(vUv.y * 10.0) / 10.0);

然后通过random方法来使用:

float strength = random(gridUv);

在这里插入图片描述

图案25

下面的图案基于上边的图案,要获得倾斜效果,必须在创建gridUv的时候将vUv.x添加到vUv.y

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);

在这里插入图片描述

图案26

下边这个图案,离左下角越远,其值越大也就越亮。而这实际上是vUv的长度。
我们可以使用length(...)得到向量(vec2,vec3或vec4)的长度:

float strength = length(vUv);

在这里插入图片描述

图案27

反之如下图,我们要得到vUv到平面中心的距离。平面的UV中心值为0.5,0.5,为此我们要创建一个与中心相对应的vec2二维向量,再通过distance(...)函数获得与vUv的距离:

float strength = distance(vUv, vec2(0.5));

在这里插入图片描述
当创建只有一个值的向量时,该值将传递给向量的每个属性,在上边的代码中便是x和y值都是0.5。

图案28

下面的图案与上边的相反:

float strength = 1.0 - distance(vUv, vec2(0.5));

在这里插入图片描述

图案29

创建灯光镜头效果时,用下面的图案非常方便。为了得到这个结果,我们从一个小值开始,将其除以之前计算的距离:

float strength = 0.015 / (distance(vUv, vec2(0.5)));

在这里插入图片描述

图案30

与上图相同的图案,但只在y轴方向上进行压缩和移动:

float strength = 0.15 / (distance(vec2(vUv.x, (vUv.y - 0.5) * 5.0 + 0.5), vec2(0.5)));

在这里插入图片描述

图案31

在上图基础上乘以x轴方向上的变化:

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)));

在这里插入图片描述

图案32

要获得下面这种图案非常费力,需要在中心点旋转vUv坐标。执行2D旋转需要混合sin(...)cos(...),在main函数前添加下面这个函数:

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
    );
}

然后我们可以使用它去创建一组新的UV,称之为rotatedUV。
现在的问题是我们要旋转八分之一圈,可惜我们无法在GLSL中直接访问π(PI),但是我们可以创建一个近似π的变量:

float pi = 3.1415926535897932384626433832795;

由于该变量永远不会改变,我们可以在代码开头通过#define进行宏定义:

#define PI 3.1415926535897932384626433832795

然后我们将其用于rotate()函数的第二个参数(旋转角度)中:

vec2 rotatedUv = rotate(vUv, PI * 0.25, vec2(0.5));

最后用rotatedUV替换原来的vUv

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)));

在这里插入图片描述

图案33

使用带有distance(...)函数的step(...)函数来控制圆的偏移和其半径:

float strength = step(0.5, distance(vUv, vec2(0.5)) + 0.25);

在这里插入图片描述

图案34

与上图类似,但是使用abs(...)来保存数值为正值:

float strength = abs(distance(vUv, vec2(0.5)) - 0.25);

在这里插入图片描述

图案35

将俩函数结合得到个圆圈:

float strength = step(0.02, abs(distance(vUv, vec2(0.5)) - 0.25));

在这里插入图片描述

图案36

用1.0 减去上面的值,得到相反图案:

float strength = 1.0 - step(0.01, abs(distance(vUv, vec2(0.5)) - 0.25));

在这里插入图片描述

图案37

下面图案基于上图,区别在于圆环是波浪起伏的。
我们可以创建一个新的uv变量,称为wavedUv,并在y值上添加经过sin(...)处理的x值:

vec2 wavedUv = vec2(
    vUv.x,
    vUv.y + sin(vUv.x * 30.0) * 0.1
);

然后使用wavedUv替换原来的vUv:

float strength = 1.0 - step(0.01, abs(distance(wavedUv, vec2(0.5)) - 0.25));

在这里插入图片描述

图案38

与上图一样,但是在wavedUv的x值上也做处理:

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));

在这里插入图片描述

图案39

增加sin(...)频率以产生迷幻效果:

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));

在这里插入图片描述

图案40

这个图案其实是vUv的角度,要从2D坐标中获取角度,可以使用atan(...)

float angle = atan(vUv.x, vUv.y);
float strength = angle;

在这里插入图片描述

图案41

与上图一样,但是在vUv上有0.5的偏移:

float angle = atan(vUv.x - 0.5, vUv.y - 0.5);
float strength = angle;

在这里插入图片描述

图案42

与前面俩者一样,但是角度是从0.0到1.0。
现在,atan(...)的返回值介于-π和+π之间,首先除以PI*2:

float angle = atan(vUv.x - 0.5, vUv.y - 0.5);
angle /= PI * 2.0;
float strength = angle;

现在得到一个-0.5到0.5之间的值,只需再加上0.5:

float angle = atan(vUv.x - 0.5, vUv.y - 0.5);
angle /= PI * 2.0;
angle += 0.5;
float strength = angle;

// 合并为一行
// float angle = atan(vUv.x - 0.5, vUv.y - 0.5) / (PI * 2.0) + 0.5;

在这里插入图片描述

图案43

跟图案7一样使用了模运算,不过这次是对角度进行使用:

float angle = atan(vUv.x - 0.5, vUv.y - 0.5) / (PI * 2.0) + 0.5;
float strength = mod(angle * 20.0, 1.0);

在这里插入图片描述

图案44

这里是使用了sin(...)

float angle = atan(vUv.x - 0.5, vUv.y - 0.5) / (PI * 2.0) + 0.5;
float strength = sin(angle * 100.0);

在这里插入图片描述

图案45

跟图案36一样,但是用到自己重新定义的半径值:

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));

在这里插入图片描述

图案46

这种图案称为柏林噪声perlin noise
柏林噪声有助于重建如云、水、火、地形等自然形状,但它同时也可以用于设置草或雪在风中移动的动画。
有许多柏林噪声算法具有不同的结果、不同的维度(2D、3D甚至4D),有些算法可以重复,有些算法性能更高等等。

以下是Github的要点,列出了我们可以为GLSL找到的一些最流行的柏林噪声:https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83

但要注意的是,正如下面将看到的,一些代码可能无法立即工作。现在,我们将测试一个经典柏林噪声,这是一个2D噪波——我们提供了一个vec2,我们得到了一个浮点数作为返回结果。
仅将代码复制到着色器,但先不要使用它:

//  Classic Perlin 2D Noise 
//  by Stefan Gustavson
//
vec2 fade(vec2 t)
{
    return t*t*t*(t*(t*6.0-15.0)+10.0);
}

float cnoise(vec2 P)
{
    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;
}

不幸的是,这段代码似乎破坏了我们的着色器,这是因为缺少一个名为permute(...)的函数。在这里,你可以将其添加到fade(...)函数之前:

vec4 permute(vec4 x)
{
    return mod(((x*34.0)+1.0)*x, 289.0);
}

现在就可以将vUv传入cnoise()函数并使用了:

float strength = cnoise(vUv);

虽然得到的是一个粗略的结果,但我们仍然有一些东西。要在预览中查看更多类似的图案,请将vUv乘以10.0:

float strength = cnoise(vUv * 10.0);

在这里插入图片描述

图案47

使用相同的噪波,但是经过step(...)运算:

float strength = step(0.0, cnoise(vUv * 10.0));

在这里插入图片描述

图案48

下面这种图案是经过1.0减去对噪波进行abs(..)运算得到的:

float strength = 1.0 - abs(cnoise(vUv * 10.0));

你可以用它来创造闪电、水下反射或等离子能量等类似东西。
在这里插入图片描述

图案49

下图是对噪波进行sin(...)运算处理:

float strength = sin(cnoise(vUv * 10.0) * 20.0);

在这里插入图片描述

图案50

sin(...)step(...)结合起来:

float strength = step(0.9, sin(cnoise(vUv * 10.0) * 20.0));

在这里插入图片描述

进行颜色测试

下面开始用渐变色去替换白色。

混合颜色

我们要使用mix(...)函数,接受三个值:

  • 第一个值可以是浮点型,vec2,vec3或vec4
  • 第二个值应与第一个参数类型一致
  • 第三个值必须是浮点型,以百分比形式混合前俩个值,值可以低于0.0也可以高于1.0并且值会进行外推,值为0返回第一个值,值为1返回第二个值

创建前俩个值:

vec3 blackColor = vec3(0.0);
vec3 uvColor = vec3(vUv, 1.0);

根据strength的值来混合俩种颜色:

vec3 mixedColor = mix(blackColor, uvColor, strength);

在不改变alpha值的情况下,在gl_FragColor中使用mixedColor

gl_FragColor = vec4(mixedColor, 1.0);

然后慢慢测试上面的图案吧
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

修复strength

如果你使用这个UV渐变去测试图案11、14和15,你会发现在交点处有些奇怪。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
似乎交点处太亮了,确实如此。
这是因为我们在mix(...)中使用的strength值超过1.0了,使得输出值外推,超出了函数中接收的第二个值。
要限制该值大小可以对strength使用clamp(...)函数,该函数会设置下限值和上限值:

// 图案11
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);

// ...

// 图案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);

// 图案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);

之后就没问题了:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐