⚡️ Limited Black Friday Deal
Get 50% off on the React Three Fiber Ultimate Course with the promo code ULTIMATE50
Buy Now
Fundamentals
Core
Master
Shaders
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> }
これで水にきれいな泡のエフェクトが加わりました。
End of lesson preview
To get access to the entire lesson, you need to purchase the course.