float strength = floor(vUv.x * 10.0) / 10.0 * floor(vUv.y * 10.0) / 10.0;
要获得像下图一样的复杂图案有点麻烦,因为GLSL里边没有原生随机函数可以使用,为此可以通过下面这种方法来获取随机数:
float random(vec2 st)
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
void main()
float strength = random(vUv);
下面的图案是上边俩个图案的结合,首先,我们创建一个名为gridUv的新vec2坐标,并使用舍入值:
vec2 gridUv = vec2(floor(vUv.x * 10.0) / 10.0, floor(vUv.y * 10.0) / 10.0);
然后通过random方法来使用:
float strength = random(gridUv);
下面的图案基于上边的图案,要获得倾斜效果,必须在创建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);
下边这个图案,离左下角越远,其值越大也就越亮。而这实际上是vUv的长度。
我们可以使用length(...)得到向量(vec2,vec3或vec4)的长度:
float strength = length(vUv);
反之如下图,我们要得到vUv到平面中心的距离。平面的UV中心值为0.5,0.5,为此我们要创建一个与中心相对应的vec2二维向量,再通过distance(...)函数获得与vUv的距离:
float strength = distance(vUv, vec2(0.5));

当创建只有一个值的向量时,该值将传递给向量的每个属性,在上边的代码中便是x和y值都是0.5。
下面的图案与上边的相反:
float strength = 1.0 - distance(vUv, vec2(0.5));
创建灯光镜头效果时,用下面的图案非常方便。为了得到这个结果,我们从一个小值开始,将其除以之前计算的距离:
float strength = 0.015 / (distance(vUv, vec2(0.5)));
与上图相同的图案,但只在y轴方向上进行压缩和移动:
float strength = 0.15 / (distance(vec2(vUv.x, (vUv.y - 0.5) * 5.0 + 0.5), vec2(0.5)));
在上图基础上乘以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)));
要获得下面这种图案非常费力,需要在中心点旋转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)));
使用带有distance(...)函数的step(...)函数来控制圆的偏移和其半径:
float strength = step(0.5, distance(vUv, vec2(0.5)) + 0.25);
与上图类似,但是使用abs(...)来保存数值为正值:
float strength = abs(distance(vUv, vec2(0.5)) - 0.25);
将俩函数结合得到个圆圈:
float strength = step(0.02, abs(distance(vUv, vec2(0.5)) - 0.25));
用1.0 减去上面的值,得到相反图案:
float strength = 1.0 - step(0.01, abs(distance(vUv, vec2(0.5)) - 0.25));
下面图案基于上图,区别在于圆环是波浪起伏的。
我们可以创建一个新的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));
与上图一样,但是在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));
增加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));
这个图案其实是vUv的角度,要从2D坐标中获取角度,可以使用atan(...):
float angle = atan(vUv.x, vUv.y);
float strength = angle;
与上图一样,但是在vUv上有0.5的偏移:
float angle = atan(vUv.x - 0.5, vUv.y - 0.5);
float strength = angle;
与前面俩者一样,但是角度是从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;
跟图案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);
这里是使用了sin(...):
float angle = atan(vUv.x - 0.5, vUv.y - 0.5) / (PI * 2.0) + 0.5;
float strength = sin(angle * 100.0);
跟图案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));
这种图案称为柏林噪声perlin noise。
柏林噪声有助于重建如云、水、火、地形等自然形状,但它同时也可以用于设置草或雪在风中移动的动画。
有许多柏林噪声算法具有不同的结果、不同的维度(2D、3D甚至4D),有些算法可以重复,有些算法性能更高等等。
以下是Github的要点,列出了我们可以为GLSL找到的一些最流行的柏林噪声:https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83
但要注意的是,正如下面将看到的,一些代码可能无法立即工作。现在,我们将测试一个经典柏林噪声,这是一个2D噪波——我们提供了一个vec2,我们得到了一个浮点数作为返回结果。
仅将代码复制到着色器,但先不要使用它:
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);
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;
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);
使用相同的噪波,但是经过step(...)运算:
float strength = step(0.0, cnoise(vUv * 10.0));
下面这种图案是经过1.0减去对噪波进行abs(..)运算得到的:
float strength = 1.0 - abs(cnoise(vUv * 10.0));
你可以用它来创造闪电、水下反射或等离子能量等类似东西。

下图是对噪波进行sin(...)运算处理:
float strength = sin(cnoise(vUv * 10.0) * 20.0);
将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);
然后慢慢测试上面的图案吧




如果你使用这个UV渐变去测试图案11、14和15,你会发现在交点处有些奇怪。



似乎交点处太亮了,确实如此。
这是因为我们在mix(...)中使用的strength值超过1.0了,使得输出值外推,超出了函数中接收的第二个值。
要限制该值大小可以对strength使用clamp(...)函数,该函数会设置下限值和上限值:
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);
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);
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);
之后就没问题了:



介绍通常在创建着色器时,我们需要绘制特定如星星、圆圈、光透镜、波等图案。初始设置像上篇笔记中的一样,场景中有个使用ShaderMaterial着色器材质的PlaneBufferGeometry平面缓冲几何体const geometry = new THREE.PlaneBufferGeometry(1, 1, 32, 32)// Materialconst material = new THREE.ShaderMaterial({ vertexShader: testVertexSha
1,基本介绍
(1)在 Three.js 中,使用 THREE.Color 对象来表示颜色。
(2)在构造 Color 对象时,可以使用十六进制字符串("#c0c0c0")或者十六进制值(0xc0c0c0)来指定颜色,还可以使用 RGB 颜色值(0.3,0.5,0.6)。
//推荐使用十六进制值
new THREE.Color(0x00000, 1.0)
2,THREE.Color 对象的方法
我们将使用调试面板来设置波浪的动画并保持对各项参数的控制。
现在,我们只有一个使用MeshBasicMaterial的平面,该几何体具有128x128的细分。我们将为顶点设置动画以获得波浪效果,为此我们需要非常多顶点。128x128可能不够多,但如果需要,我们将增加该值。
现在将材质替换为着色器材质ShaderMaterial
const waterMaterial = new THREE.Shade
“渲染”(Rendering)是即使非计算机专业的都不会觉得陌生的词,虽然在很多人说这个词的时候,并不清楚“渲染”究竟意味着什么。相反,“着色器”(Shader)很可能是大家比较陌生的词,从名字看上去似乎是用来上色的,但它具体能做什么呢?
在解释着色器之前,我们先来聊聊渲染。
用通俗的话来说,渲染就是将模型数据在屏幕上显示出来的过程。
这听起来好像很简单呢!但正如
接下来我们往场景中添加三个网格物体
//球形缓冲几何体
const sphere = new THREE.Mesh(new THREE.SphereBufferGeometry(0.5,16,16),material)
//平面缓冲几何体
const plane = new THR
yarn build
将要生产的应用程序生成到dist文件夹。 它在生产模式下正确捆绑了React,并优化了构建以获得最佳性能。
生成被最小化,并且文件名包括哈希值。 您的应用已准备好进行部署!
yarn deploy
将应用程序部署到github页面
three.js 中的着色器(Shader)是一种用于在 GPU 上运行的程序,它们可以用来实现高级的图形效果。three.js 使用着色器来渲染场景中的物体,包括光照、阴影、纹理、透明度等效果。
在 three.js 中使用着色器需要创建一个 ShaderMaterial 对象,并且为其指定 vertex shader 和 fragment shader。vertex shader 用于处理顶点数据,fragment shader 用于处理像素数据。例如,下面是一个简单的着色器示例:
var vertexShader = `
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
var fragmentShader = `
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
var shaderMaterial = new THREE.ShaderMaterial({
vertexShader: vertexShader,
fragmentShader: fragmentShader
var geometry = new THREE.BoxGeometry(1, 1, 1);
var cube = new THREE.Mesh(geometry, shaderMaterial);
scene.add(cube);
上面的着色器将所有的顶点位置乘以投影矩阵和模型视图矩阵,然后将所有像素设置为红色。在这个例子中,我们创建了一个 ShaderMaterial 对象,并将其作为参数传递给一个 BoxGeometry 对象的构造函数,从而创建了一个红色的立方体。
three.js学习笔记(十九)——后期处理
three.js学习笔记(十九)——后期处理