水着着色器
夏天要来了 (至少在我写这篇课程的时候),是时候举办泳池派对了!🩳
在本课程中,我们将使用 React Three Fiber 和 GLSL 创建以下水效果:
泡沫在泳池边缘和鸭子周围更为浓密。
为了实现这种效果,我们将探索 Lygia Shader 库 来简化着色器的创建,并将实践一种 render target 技术 来创建泡沫效果。
启动包
本课程的启动包中包含以下资源:
- 由谷歌 Poly 提供的 游泳池模型 CC-BY 通过 Poly Pizza
- 来自 Pmndrs 市场 的 鸭子模型
- 来自 Google Fonts 的 Inter 字体
其余部分为简单的灯光和相机设置。
阳光明媚的泳池日 🏊
水着色器
水面只是一个简单的平面,上应用了 <meshBasicMaterial />
。我们将用自定义着色器替换这个 material。
让我们创建一个新文件 WaterMaterial.jsx
,为着色器 material 添加样板代码:
import { shaderMaterial } from "@react-three/drei"; import { Color } from "three"; export const WaterMaterial = shaderMaterial( { uColor: new Color("skyblue"), uOpacity: 0.8, }, /*glsl*/ ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`, /*glsl*/ ` varying vec2 vUv; uniform vec3 uColor; uniform float uOpacity; void main() { gl_FragColor = vec4(uColor, uOpacity); #include <tonemapping_fragment> #include <encodings_fragment> }` );
我们的 material 有两个 uniforms: uColor
和 uOpacity
。
为了能够声明式地使用我们的自定义 material,在 main.jsx
文件中使用 @react-three/fiber
的 extend
函数:
// ... import { extend } from "@react-three/fiber"; import { WaterMaterial } from "./components/WaterMaterial.jsx"; extend({ WaterMaterial }); // ...
我们需要从一个先于使用自定义 material 的组件导入的文件中进行
extend
调用。这样,我们才能在组件中声明式地使用WaterMaterial
。这就是为什么我们在
main.jsx
文件中,而不是WaterMaterial.jsx
文件中进行这个操作。
现在在 Water.jsx
中,我们可以用自定义 material 替换 <meshBasicMaterial />
,并用对应的 uniforms 调整属性:
import { useControls } from "leva"; import { Color } from "three"; export const Water = ({ ...props }) => { const { waterColor, waterOpacity } = useControls({ waterOpacity: { value: 0.8, min: 0, max: 1 }, waterColor: "#00c3ff", }); return ( <mesh {...props}> <planeGeometry args={[15, 32, 22, 22]} /> <waterMaterial uColor={new Color(waterColor)} transparent uOpacity={waterOpacity} /> </mesh> ); };
我们成功地用自定义着色器 material 替换了基本 material。
Lygia Shader 库
为了创建动态泡沫效果,我们将使用 Lygia Shader 库。这个库通过提供一组实用程序和函数以更具声明性的方式创建 shaders 从而简化了 shaders 的创建。
我们感兴趣的部分是 generative。它包含了一组有用的函数,用于创建生成效果,如噪声、curl、fbm。
在 generative 部分,你可以找到可用函数的列表。
通过打开其中一个函数,你可以看到代码片段以在你的 shader 中使用它,并预览效果。
pnoise 函数页面
这是我们想要使用的效果。你可以在示例中看到,为了能够使用 pnoise
函数,他们包括了 pnoise
shader 文件。我们也将这样做。
Resolve Lygia
为了在我们的项目中使用 Lygia Shader 库,我们有两个选择:
- 将库的内容复制到我们的项目中并导入我们需要的文件。(我们已经在 shaders 入门课程 见过如何导入 GLSL 文件)
- 使用一个名为
resolve-lygia
的库,它将从网络中解析 Lygia Shader 库,并自动用文件内容替换与 lygia 相关的#include
指令。
根据你的项目、需要使用多少效果以及是否在使用其他 shader 库,你可能会偏好某个解决方案。
在本课程中,我们将使用 resolve-lygia
库。要安装它,运行以下命令:
yarn add resolve-lygia
然后,为了使用它,我们只需将我们的 fragment 和/或 vertex shader 代码用 resolveLygia
函数包裹:
// ... import { resolveLygia } from "resolve-lygia"; export const WaterMaterial = shaderMaterial( { uColor: new Color("skyblue"), uOpacity: 0.8, }, /*glsl*/ ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`, resolveLygia(/*glsl*/ ` varying vec2 vUv; uniform vec3 uColor; uniform float uOpacity; void main() { gl_FragColor = vec4(uColor, uOpacity); #include <tonemapping_fragment> #include <encodings_fragment> }`) );
在本课程中,我们只需在 fragment shader 中使用 resolveLygia
函数。
现在我们可以在 shader 中使用 pnoise
函数:
#include "lygia/generative/pnoise.glsl" varying vec2 vUv; uniform vec3 uColor; uniform float uOpacity; void main() { float noise = pnoise(vec3(vUv * 10.0, 1.0), vec3(100.0, 24.0, 112.0)); vec3 black = vec3(0.0); vec3 finalColor = mix(uColor, black, noise); gl_FragColor = vec4(finalColor, uOpacity); #include <tonemapping_fragment> #include <encodings_fragment> }
我们将 vUv
乘以 10.0
以使噪声更明显,并使用 pnoise
函数创建噪声效果。我们根据噪声值将 uColor
与黑色混合以创建最终颜色。
我们可以看到应用于水的噪声效果。
⚠️ Resolve Lygia 似乎有时会出现停机问题。如果你遇到任何问题,可以使用 Glslify 库 或从 Lygia Shader 库中复制你需要的文件,并像我们在 shaders 入门课程 中一样使用 glsl 文件。
泡沫效果
噪声效果是我们泡沫效果的基础。在微调效果之前,让我们创建我们所需的 uniforms,以便完全控制泡沫效果。
WaterMaterial.jsx
:
import { shaderMaterial } from "@react-three/drei"; import { resolveLygia } from "resolve-lygia"; import { Color } from "three"; export const WaterMaterial = shaderMaterial( { uColor: new Color("skyblue"), uOpacity: 0.8, uTime: 0, uSpeed: 0.5, uRepeat: 20.0, uNoiseType: 0, uFoam: 0.4, uFoamTop: 0.7, }, /*glsl*/ ` varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }`, resolveLygia(/*glsl*/ ` #include "lygia/generative/pnoise.glsl" varying vec2 vUv; uniform vec3 uColor; uniform float uOpacity; uniform float uTime; uniform float uSpeed; uniform float uRepeat; uniform int uNoiseType; uniform float uFoam; uniform float uFoamTop; void main() { float noise = pnoise(vec3(vUv * 10.0, 1.0), vec3(100.0, 24.0, 112.0)); vec3 black = vec3(0.0); vec3 finalColor = mix(uColor, black, noise); gl_FragColor = vec4(finalColor, uOpacity); #include <tonemapping_fragment> #include <encodings_fragment> }`) );
我们添加了以下 uniforms:
uTime
: 动画泡沫效果uSpeed
: 控制效果动画的速度uRepeat
: 缩放噪声效果uNoiseType
: 切换不同的噪声函数uFoam
: 控制泡沫效果开始的阈值uFoamTop
: 控制泡沫变得更浓密的阈值
现在我们需要将这些 uniforms 应用于材质。
Water.jsx
:
import { useFrame } from "@react-three/fiber"; import { useControls } from "leva"; import { useRef } from "react"; import { Color } from "three"; export const Water = ({ ...props }) => { const waterMaterialRef = useRef(); const { waterColor, waterOpacity, speed, noiseType, foam, foamTop, repeat } = useControls({ waterOpacity: { value: 0.8, min: 0, max: 1 }, waterColor: "#00c3ff", speed: { value: 0.5, min: 0, max: 5 }, repeat: { value: 30, min: 1, max: 100, }, foam: { value: 0.4, min: 0, max: 1, }, foamTop: { value: 0.7, min: 0, max: 1, }, noiseType: { value: 0, options: { Perlin: 0, Voronoi: 1, }, }, }); useFrame(({ clock }) => { if (waterMaterialRef.current) { waterMaterialRef.current.uniforms.uTime.value = clock.getElapsedTime(); } }); return ( <mesh {...props}> <planeGeometry args={[15, 32, 22, 22]} /> <waterMaterial ref={waterMaterialRef} uColor={new Color(waterColor)} transparent uOpacity={waterOpacity} uNoiseType={noiseType} uSpeed={speed} uRepeat={repeat} uFoam={foam} uFoamTop={foamTop} /> </mesh> ); };
我们现在可以在 shader 中创建泡沫逻辑。
通过 uTime
乘以 uSpeed
来计算我们的调整时间:
float adjustedTime = uTime * uSpeed;
然后使用 pnoise
函数生成噪声效果:
float noise = pnoise(vec3(vUv * uRepeat, adjustedTime * 0.5), vec3(100.0, 24.0, 112.0));
使用 smoothstep
函数应用泡沫效果:
noise = smoothstep(uFoam, uFoamTop, noise);
然后,我们创建更亮的颜色来表示泡沫。创建一个 intermediateColor
和一个 topColor
:
vec3 intermediateColor = uColor * 1.8; vec3 topColor = intermediateColor * 2.0;
根据噪声值调整颜色:
vec3 finalColor = uColor; finalColor = mix(uColor, intermediateColor, step(0.01, noise)); finalColor = mix(finalColor, topColor, step(1.0, noise));
当噪声在 0.01
和 1.0
之间时,颜色将是中间颜色。当噪声大于或等于 1.0
时,颜色将是顶部颜色。
这是最终的 shader 代码:
#include "lygia/generative/pnoise.glsl" varying vec2 vUv; uniform vec3 uColor; uniform float uOpacity; uniform float uTime; uniform float uSpeed; uniform float uRepeat; uniform int uNoiseType; uniform float uFoam; uniform float uFoamTop; void main() { float adjustedTime = uTime * uSpeed; // NOISE GENERATION float noise = pnoise(vec3(vUv * uRepeat, adjustedTime * 0.5), vec3(100.0, 24.0, 112.0)); // FOAM noise = smoothstep(uFoam, uFoamTop, noise); // COLOR vec3 intermediateColor = uColor * 1.8; vec3 topColor = intermediateColor * 2.0; vec3 finalColor = uColor; finalColor = mix(uColor, intermediateColor, step(0.01, noise)); finalColor = mix(finalColor, topColor, step(1.0, noise)); gl_FragColor = vec4(finalColor, uOpacity); #include <tonemapping_fragment> #include <encodings_fragment> }
我们现在在水面上有一个漂亮的泡沫效果。
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.