Water Shader
夏が近づいていますね (少なくともこのレッスンを書いている時点では), そろそろプールパーティーを開く時です! 🩳
このレッスンでは、React Three FiberとGLSLを使用して次のような水の効果を作成します:
フォームはプールの端やアヒルの周りでより濃くなります。
この効果を作成するために、シェーダーの作成を簡単にするためのLygia Shader libraryを紹介し、フォーム効果を作成するためのレンダーターゲット技法を実践します。
スターターパック
このレッスン用のスターターパックには以下のアセットが含まれています:
- プールモデル by Poly by Google CC-BY via Poly Pizza
- アヒルモデル from Pmndrs marketplace
- Interフォント from Google Fonts
それ以外は簡単なライティングとカメラの設定です。

プールでの晴れた日🏊
Water shader
この水面は単なる平面に<meshBasicMaterial />を適用したものです。これをカスタムシェーダーで置き換えます。
新しいファイル WaterMaterial.jsx を作成し、シェーダーマテリアル用のボイラープレートを追加します:
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> }` );
このマテリアルには二つのユニフォーム: uColor と uOpacity が含まれています。
カスタムマテリアルを宣言的に使用できるようにするために、main.jsxファイルで@react-three/fiberからextend関数を使用しましょう:
// ... import { extend } from "@react-three/fiber"; import { WaterMaterial } from "./components/WaterMaterial.jsx"; extend({ WaterMaterial }); // ...
カスタムマテリアルを使用するコンポーネントよりも前にインポートされるファイルから
extendを呼び出す必要があります。こうすることで、コンポーネント内で宣言的にWaterMaterialを使用できるようになります。そのため、この処理を
WaterMaterial.jsxファイルではなく、main.jsxファイルで行います。
次にWater.jsxで<meshBasicMaterial />をカスタムマテリアルに置き換え、対応するユニフォームでプロパティを調整します:
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> ); };
基本的なマテリアルをカスタムシェーダーマテリアルで成功裏に置き換えることができました。
Lygia Shader ライブラリ
アニメーションの泡効果を作成するために、Lygia Shader ライブラリを使用します。このライブラリは、宣言的な方法でシェーダーを作成するためのユーティリティと関数のセットを提供することで、シェーダーの作成を簡素化します。
私たちが興味を持つセクションは、generativeセクションです。これには、ノイズ、カール、fbmなどの生成効果を作成するための便利な関数が含まれています。

生成セクション内には、利用可能な関数のリストがあります。
関数の1つを開くと、シェーダーで使用するためのコードスニペットと効果のプレビューを見ることができます。

pnoise関数ページ
これが使用したい効果です。例を見ると、pnoise関数を使用するためには、pnoiseシェーダーファイルを含める必要があることがわかります。私たちも同じことを行います。
Resolve Lygia
プロジェクトでLygia Shaderライブラリを使用するために、2つのオプションがあります:
- ライブラリの内容をプロジェクトにコピーし、必要なファイルをインポートする方法。(GLSLファイルのインポート方法についてはシェーダー入門レッスンで確認しました。)
resolve-lygiaというライブラリを使用して、WebからLygia Shaderライブラリを解決し、lygiaに関連する#includeディレクティブを自動的にファイルの内容で置き換える方法。
プロジェクトの規模や使用したい効果の数、また他のシェーダーライブラリを使用しているかどうかに応じて、どちらの方法を選択するかが決まります。
このレッスンでは、resolve-lygiaライブラリを使用します。インストールするには、以下のコマンドを実行します:
yarn add resolve-lygia
その後、使用するためには、fragmentおよび/またはvertexシェーダーコードを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> }`) );
このレッスンでは resolveLygia 関数をfragmentシェーダーにのみ使用します。
これでシェーダーで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ライブラリから必要なファイルをコピーし、glslファイルをシェーダー入門レッスンのように使用してください。
泡のエフェクト
ノイズエフェクトは、泡のエフェクトの基礎です。エフェクトを微調整する前に、泡のエフェクトを完全に制御するために必要なuniformを作成しましょう。
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> }`) );
以下のuniformを追加しました:
uTime: 泡エフェクトをアニメートするためuSpeed: エフェクトアニメーションの速度を制御するためuRepeat: ノイズエフェクトをスケールするためuNoiseType: 異なるノイズ関数の切り替えのためuFoam: 泡エフェクトが始まる閾値を制御するためuFoamTop: 泡が濃くなる閾値を制御するため
次に、これらのuniformをmaterialに適用する必要があります。
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> ); };
これでシェーダ内で泡のロジックを作成できます。
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 以上の場合、色はトップカラーになります。
こちらが最終的なシェーダコードです:
#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.