three.js学习笔记(十五)——着色器图案
介绍
通常在创建着色器时,我们需要绘制特定如星星、圆圈、光透镜、波等图案。
初始设置
像上篇笔记中的一样,场景中有个使用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
事情开始有趣起来,为了获得上图的梯度渐变效果,我们只需用vUv
的x
属性,并且都应用到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.0
或1.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);
之后就没问题了:
更多推荐
所有评论(0)