相关文章推荐
气宇轩昂的竹笋  ·  R 数据框 | 菜鸟教程·  2 年前    · 
深情的可乐  ·  .net - Why did a ...·  2 年前    · 
魁梧的单车  ·  DDMS File Explorer ...·  2 年前    · 
开朗的洋葱  ·  java - Cannot load ...·  2 年前    · 

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

像上篇笔记中的一样,场景中有个使用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()
    // ...

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

varying vec2 vUv;
void main()
    gl_FragColor = vec4(vUv, 1.0, 1.0);

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

事情开始有趣起来,为了获得上图的梯度渐变效果,我们只需用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);

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

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

float strength = vUv.y;

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

float strength = 1.0 - vUv.y;

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

float strength = vUv.y * 10.0;

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

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

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

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

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

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

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

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

可以将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));

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

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

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

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

这是前面几种图案的组合,在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;

在俩个轴上添加点偏移:

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;

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

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

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

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

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

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

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

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

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

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

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

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

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

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;
// 合并为一行
// float angle = atan(vUv.x - 0.5, vUv.y - 0.5) / (PI * 2.0) + 0.5;

跟图案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,我们得到了一个浮点数作为返回结果。
仅将代码复制到着色器,但先不要使用它:

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

使用相同的噪波,但是经过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);

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

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

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

介绍通常在创建着色器时,我们需要绘制特定如星星、圆圈、光透镜、波等图案。初始设置像上篇笔记中的一样,场景中有个使用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 对象的构造函数,从而创建了一个红色的立方体。
import { GammaCorrectionShader } from 'three/examples/jsm/shaders/GammaCorrectionShader.js' // Gamma Correction pass const gammaCorrectionPass = new ShaderPass(GammaCorrectionShader) effectComposer.addPass(gammaCorrectionPass)
three.js学习笔记(十九)——后期处理 EffectComposer 不支持编码,但我们可以添加一个 pass 来修复颜色 这个过程被命名为 GammaCorrectionShader,它将线性编码转换为 sRGB 编码 three.js学习笔记(十九)——后期处理 由于使用的是 EffectComposer,因此颜色较深 renderer.outputEncoding = THREE.SRGBEncoding 不再起作用,因为我们在渲染目标内部渲染,而那些渲染目标不支持编码