塑形函数
着色器的逻辑是特殊的,它与我们在 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 将会很快成为你的好帮手。
是时候试验我们可用的不同函数了。
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
。
我们可以看到,框架的左半部分是黑色的,右半部分是 uColor
uniform 设定的颜色。
让我们以不同的阈值将其应用于垂直轴:
void main() { float pct = 1.0; pct = step(0.2, vUv.y); vec3 finalColor = pct * uColor; gl_FragColor = vec4(finalColor, 1.0); }
我们可以看到,框架底部的 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); }
我们现在看到了相反的效果。
Mix
mix 函数是一个简单的函数,返回两个值之间的线性插值。
它接受三个参数:
x
:第一个值y
:第二个值a
:在x
和y
之间插值的值
让我们试试看:
void main() { float pct = 1.0; pct = mix(0.0, 1.0, vUv.x); vec3 finalColor = pct * uColor; gl_FragColor = vec4(finalColor, 1.0); }
我们可以看到由黑色到紫色的漂亮渐变。
与许多其他函数一样,您可以将其用于其他类型的数据,例如矢量组件。我们用它在垂直轴上创建一个从白色到紫色的渐变:
void main() { vec3 whiteColor = vec3(1.0); vec3 finalColor = mix(whiteColor, uColor, vUv.y); gl_FragColor = vec4(finalColor, 1.0); }
我们可以看到由白色到紫色的漂亮渐变。
让我们通过首次调用 mix
函数来更改插值值以获得 pct
值,然后使用它在 whiteColor
和 uColor
之间插值:
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); }
紫色非常浅,因为 pct
的最大值是 0.3
。
使用 Graphtoy 试验 mix
函数并理解其工作原理。
使用 mix
函数的 3 种不同插值产生了完全不同的结果。
Smoothstep
平滑阶跃函数 是一个简单的函数,它返回两个值之间的平滑插值。
它接受三个参数:
edge0
:下边界edge1
:上边界x
:在edge0
和edge1
之间插值的值
它给出三种不同的结果:
- 如果
x
小于edge0
,则结果为0.0
- 如果
x
大于edge1
,则结果为1.0
- 如果
x
位于edge0
和edge1
之间,则为0.0
和1.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); }
现在的过渡仅发生在 0.4
到 0.6
之间。低于这个范围的全部为白色,高于这个范围的全部为紫色。
Min 和 Max
min
和 max
是简便函数,用于返回两个输入之间的最小或最大值。它们的工作方式与 JavaScript 中的 Math.min
和 Math.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); }
颜色变淡,过渡非常平滑。
让我们在 Graphtoy 上可视化它:
max(x, 0.4)
表示
min(x, 0.6)
表示
如果我们结合它们:
您可以看到我们的值从不低于 0.4
且从不高于 0.6
。
我们的
min
和max
函数组合可以用 clamp 函数替代,它是min(max(x, min), max)
的简写。
操作与模式
在我们探索其他许多有用函数之前,让我们看看如何通过操作来创建模式。
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
One-time payment. Lifetime updates included.