塑形函数

Starter pack

着色器的逻辑是特殊的,它与我们在 JavaScript 或其他编程语言中习惯的方式不同。尽管语法相似,但实现逻辑的方式不同。它有点像一个黑箱,你输入一些数据,然后你会为 fragment shader 获取一些颜色,或者为 vertex shader 获取一些位置。

但你如何控制输出呢?你如何使之看起来像你想要的那样?这就是塑形函数的用武之地。

我们将在本课中重点关注 fragment shader,但相同的原则也适用于 vertex shader。

实践区

要掌握塑形的艺术,你需要练习和实验。熟悉着色器的工作方式需要时间,但一旦掌握了,你将能够创造出惊人的效果。

为了让你有更好的成功机会,我为你准备了这个 3D 艺术画廊场景,让你在一个宜人的环境中实时地可视化你的着色器。

你能感觉到你的创造力在流动吗?所有那些空相框在等待着你的杰作!

使用的 3D 模型是来自 Maxim Mavrichev 的 VR Gallery,根据 Creative Commons Attribution 许可进行授权。

我使用 <CameraControls /> 创建了一个第一人称相机,可以在画廊内部导航,并使用 Squoosh 减小纹理的大小。

SimpleShaderMaterial

画廊中的所有相框都是 SimpleShaderMaterial 的副本,这是我为你准备的一个基本自定义着色器,包含:

  • uColor:一个颜色
  • uTime:自应用程序启动以来经过的时间
  • vUv:fragment 的 UV 坐标(在两个轴上从 0 到 1)

它们都在 <App.jsx /> 中被扩展,以便能够在我们的场景中声明式地使用它们。

它们根据在画廊中的位置命名,但一旦你用它们创造了杰作,可以随意重命名为更有意义的名称!

函数

对于我们的着色器的每个片段,具有不同输入的相同代码将被执行。

如果我们想绘制一条线,我们可以使用 if 语句来检查像素是否在这条线内。但制作更复杂的形状会非常困难且效率低下。

相反,我们使用函数来塑造我们的输出。不用担心,使用它们你不需要成为数学专家。你只需要理解有哪些函数可用,以及它们的功能。

可以将函数视为一个机器,它接受一些输入并给出一些输出。例如,函数 f(x) = x * 2 接受一个数字 x 并给出 x * 2 作为结果。

为了可视化不同函数的效果,我们将使用 Graphtoy,这是一款简单的工具,可用于输入函数并查看其输出。当我们在着色器中试验这些函数时,它帮助验证我们对它们的理解。

让我们在 Graphtoy 中可视化 f(x) = x * 2 函数:

Graphtoy

当你实验着色器时,Graphtoy 将会很快成为你的好帮手。

是时候试验我们可用的不同函数了。

Step

step 函数 是一个简单的函数,如果输入小于某个阈值则返回 0,如果输入大于或等于阈值则返回 1

它有两个参数:

  • edge: 阈值
  • x: 输入值

让我们在前面的帧 ArtFront02Material.jsx 片段着色器中尝试一下:

void main() {
  float pct = 1.0;
  pct = step(0.5, vUv.x);
  vec3 finalColor = pct * uColor;
  gl_FragColor = vec4(finalColor, 1.0);
}

我们声明了一个变量 pct 来确定我们想显示的颜色比例。我们默认将其设置为 1.0,然后我们使用 step 函数将其设置为 0.0,如果 vUv.x 小于 0.5

Step function representation

我们可以看到,框架的左半部分是黑色的,右半部分是 uColor uniform 设定的颜色。

让我们以不同的阈值将其应用于垂直轴:

void main() {
  float pct = 1.0;
  pct = step(0.2, vUv.y);
  vec3 finalColor = pct * uColor;
  gl_FragColor = vec4(finalColor, 1.0);
}

Step function vertical representation

我们可以看到,框架底部的 20% 是黑色,其余是紫色。

UV 坐标的原点在框架的左下角,因此 [0, 0] 是左下角,而 [1, 1] 是右上角。

如果你想反转效果,可以简单地交换 step 函数的参数:

void main() {
  float pct = 1.0;
  pct = step(vUv.y, 0.2);
  vec3 finalColor = pct * uColor;
  gl_FragColor = vec4(finalColor, 1.0);
}

Step function vertical reverted representation

我们现在看到了相反的效果。

Mix

mix 函数是一个简单的函数,返回两个值之间的线性插值。

它接受三个参数:

  • x:第一个值
  • y:第二个值
  • a:在 xy 之间插值的值

让我们试试看:

void main() {
  float pct = 1.0;
  pct = mix(0.0, 1.0, vUv.x);
  vec3 finalColor = pct * uColor;
  gl_FragColor = vec4(finalColor, 1.0);
}

Mix function representation

我们可以看到由黑色到紫色的漂亮渐变。

与许多其他函数一样,您可以将其用于其他类型的数据,例如矢量组件。我们用它在垂直轴上创建一个从白色到紫色的渐变:

void main() {
  vec3 whiteColor = vec3(1.0);
  vec3 finalColor = mix(whiteColor, uColor, vUv.y);
  gl_FragColor = vec4(finalColor, 1.0);
}

Mix function two colors

我们可以看到由白色到紫色的漂亮渐变。

让我们通过首次调用 mix 函数来更改插值值以获得 pct 值,然后使用它在 whiteColoruColor 之间插值:

void main() {
  vec3 whiteColor = vec3(1.0);
  float pct = mix(0.0, 0.3, vUv.y);
  vec3 finalColor = mix(whiteColor, uColor, pct);
  gl_FragColor = vec4(finalColor, 1.0);
}

Mix function two colors with pct

紫色非常浅,因为 pct 的最大值是 0.3

使用 Graphtoy 试验 mix 函数并理解其工作原理。

Graphtoy mix function

使用 mix 函数的 3 种不同插值产生了完全不同的结果。

Smoothstep

平滑阶跃函数 是一个简单的函数,它返回两个值之间的平滑插值。

它接受三个参数:

  • edge0:下边界
  • edge1:上边界
  • x:在 edge0edge1 之间插值的值

它给出三种不同的结果:

  • 如果 x 小于 edge0,则结果为 0.0
  • 如果 x 大于 edge1,则结果为 1.0
  • 如果 x 位于 edge0edge1 之间,则为 0.01.0 之间的平滑插值

让我们用它来更改我们的 pct 值:

void main() {
  vec3 whiteColor = vec3(1.0);
  float pct = smoothstep(0.4, 0.6, vUv.y);
  vec3 finalColor = mix(whiteColor, uColor, pct);
  gl_FragColor = vec4(finalColor, 1.0);
}

Smoothstep function representation

现在的过渡仅发生在 0.40.6 之间。低于这个范围的全部为白色,高于这个范围的全部为紫色。

Min 和 Max

minmax 是简便函数,用于返回两个输入之间的最小或最大值。它们的工作方式与 JavaScript 中的 Math.minMath.max 函数完全相同。

让我们使用它们来微调我们的 pct 值。首先确保它从不低于 0.4 (这意味着从不会完全白):

void main() {
  vec3 whiteColor = vec3(1.0);
  float pct = smoothstep(0.4, 0.6, vUv.y);
  pct = max(pct, 0.4);
  vec3 finalColor = mix(whiteColor, uColor, pct);
  gl_FragColor = vec4(finalColor, 1.0);
}

并且从不高于 0.6 (这意味着从不会完全紫):

void main() {
  vec3 whiteColor = vec3(1.0);
  float pct = smoothstep(0.4, 0.6, vUv.y);
  pct = max(pct, 0.4);
  pct = min(pct, 0.6);
  vec3 finalColor = mix(whiteColor, uColor, pct);
  gl_FragColor = vec4(finalColor, 1.0);
}

Min & Max function representation

颜色变淡,过渡非常平滑。

让我们在 Graphtoy 上可视化它:

Max graph

max(x, 0.4) 表示

Min graph

min(x, 0.6) 表示

如果我们结合它们:

Min & Max graph

您可以看到我们的值从不低于 0.4 且从不高于 0.6

我们的 minmax 函数组合可以用 clamp 函数替代,它是 min(max(x, min), max) 的简写。

操作与模式

在我们探索其他许多有用函数之前,让我们看看如何通过操作来创建模式。

Three.js logoReact logo

React Three Fiber: The Ultimate Guide to 3D Web Development

✨ You have reached the end of the preview ✨

Go to the next level with Three.js and React Three Fiber!

Get full access to this lesson and the complete course when you enroll:

  • 🔓 Full lesson videos with no limits
  • 💻 Access to the final source code
  • 🎓 Course progress tracking & completion
  • 💬 Invite to our private Discord community
Unlock the Full Course – Just $85

One-time payment. Lifetime updates included.