第4回クリエイティブ・コーディング:React Three Fiberでシェーダーを召喚

スポンサーリンク

CodeSandboxで試してみよう

今回は CodeSandbox を使って、最短ルートで React Three Fiber の舞台を作ります。

Step 1:新しいプロジェクトを作る

CodeSandbox を開き、「Create Sandbox」ボタンから 「React」を選択。これだけで、React が動く環境が整う。


Step 2:必要な「道具」を追加する

次に、3D を動かすためのライブラリを 2 つ追加する。画面左側の 「Dependencies」 という項目を探して、以下の 2 つを検索・追加します。

  • 1. three
    すべての基礎となる 3D エンジン本体。
  • 2. @react-three/fiber
    Three.js を React で簡単に扱えるようにする「魔法の橋」。

⚠️ 注意:検索窓に正確に入力して、最新バージョンを選べば OK!
CodeSandbox でライブラリを探すとき、ついつい「three.js」と検索したくなるが、正解は 「three」 です。

🛠️ Step3:コードを上書きします

style.css の中身をすべて上書きしてください。

/* 全要素の余計なマージンをリセット */
* {
  box-sizing: border-box;
}

html,
body,
#root {
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden; /* スクロールバーを消す */
  background-color: #000; /* 読み込み前の背景を黒にしておく */
}

/* Canvas要素を画面いっぱいに固定 */
canvas {
  display: block;
  width: 100vw;
  height: 100vh;
}

App.js中身をすべて上書きしてください。CodeSandbox で React Three Fiber (R3F) でシェーダーを利用するための、基本的なテンプレートです。

import React, { useMemo, useRef } from "react";
import { Canvas, useFrame } from "@react-three/fiber";
import * as THREE from "three";
import "./styles.css";

// --- 💡 1. 頂点シェーダー(基本はこのままでOK) ---
const vertexShader = `
  varying vec2 vUv;
  void main() {
    vUv = uv;
    gl_Position = vec4(position, 1.0);
  }
`;

// --- 🔥 2. フラグメントシェーダー(ここを書き換える!) ---
const fragmentShader = `
  precision mediump float;
  uniform float uTime;
  uniform vec2 uResolution;
  varying vec2 vUv;

  void main() {
    // Shadertoyのコードを貼る時は、ここから下を書き換える
    // 例:(vUv * 2.0 - 1.0) で座標の中心を真ん中にする処理など
    vec2 uv = (vUv * 2.0 - 1.0) * vec2(uResolution.x / uResolution.y, 1.0);
    
    vec3 color = vec3(uv.x, uv.y, abs(sin(uTime)));
    gl_FragColor = vec4(color, 1.0);
  }
`;

const ShaderPlane = () => {
  const meshRef = useRef();

 // 📦 1. JSからGLSLへ送る「荷物(Uniforms)」の準備
 // Reactは画面に変化があると「再レンダリング(再計算)」して、中身を新しく作り直す性質があります。
 // もし useMemo を使わずに直接書くと、レンダリングのたびにシェーダー側では「さっきまで使っていた変数が壊された!」とパニックになります。
 // この設定は1回しか作らないから、勝手に作り直して捨てないでね!と、Reactに釘を刺して記憶させるのが useMemo の重要な役割です。
 //空のカッコ「[]」が、最初の一回だけ作るという印
 const uniforms = useMemo(() => ({
  uTime: { value: 0.0 },
  uResolution: { value: new THREE.Vector2() },
}), []); 

  // 毎フレーム、荷物の中身を最新の状態に更新する(自動配線)
  useFrame((state) => {
    const { width, height } = state.size;
    uniforms.uTime.value = state.clock.getElapsedTime();
    uniforms.uResolution.value.set(width, height);
  });

  return (
    <mesh ref={meshRef}>
      <planeGeometry args={[2, 2]} />
      <shaderMaterial
        uniforms={uniforms}
        vertexShader={vertexShader}
        fragmentShader={fragmentShader}
      />
    </mesh>
  );
};

export default function App() {
  return (
    <Canvas>
      <ShaderPlane />
    </Canvas>
  );
}

⚠️貼り付ける時の注意!

React Three Fiber でシェーダーを書くとき、コードを ` `(バッククォート)で囲みます。これは JavaScript の「テンプレートリテラル」という機能です。

Shadertoy のコードをコピーしてきたとき、貼り付ける場所は大きく分けて 2 箇所あります。

// — 1. ここは「関数の置き場」 —
// Shadertoy で mainImage の「外」に書いてある関数はここに貼る

void main() {
  // — 2. ここは「メインロジックの置き場」 —
  // mainImage の「中身」はここに書き込む
  gl_FragColor = …
}

Shadertoy からの「翻訳マニュアル」

Shadertoy のコードはそのままコピペしても動きません。なぜなら、Shadertoy 独自の「方言」で書かれているからです。ここでは、標準的な GLSL(今回作った R3F の環境)で動くようにするための翻訳ルールを整理。

基本の書き換えルール表

Shadertoy の方言 R3F / 標準 GLSL 役割
void mainImage(…) void main() 魔法の入り口(関数)
fragColor gl_FragColor 最終的なピクセルの色
iTime uTime 経過時間(アニメ用)
iResolution uResolution 画面の解像度
fragCoord.xy / iResolution.xy vUv 画面の座標(0.0 〜 1.0)

AI を「専属翻訳家」にして効率アップ!

自力で書き換えるのも勉強になりますが、この翻訳作業こそ AI の得意分野です。

💡 AI へのプロンプト例

「以下の Shadertoy のコードを、React Three Fiber の shaderMaterial で使えるように翻訳してください。 uTimevUv を使う形式にし、初心者にも分かりやすいように日本語でコメントを入れてください。」

(ここに Shadertoy のコードを貼り付ける)

実践:万華鏡エフェクトでシェーダーの真髄に触れる

フラグメントシェーダーの部分をこちらに書き換えてみてください。
Youtubeで詳しく解説されているものです。

const fragmentShader = `
uniform vec2 uResolution;
uniform float uTime;
varying vec2 vUv;

vec3 palette(float t) {
    vec3 a = vec3(0.5, 0.5, 0.5);
    vec3 b = vec3(0.5, 0.5, 0.5);
    vec3 c = vec3(1.0, 1.0, 1.0);
    vec3 d = vec3(0.000, 0.333, 0.667);
    return a + b * cos(6.28318 * (c * t + d));
}

void main() {
    vec2 uv = (vUv * 2.0 - 1.0) * vec2(uResolution.x / uResolution.y, 1.0);
    vec2 uv0 = uv;
    vec3 finalColor = vec3(0.0);

    for (int i = 0; i < 4; i++) {
        float fi = float(i); 
        uv = fract(uv * 1.5) - 0.5;
        float d = length(uv) * exp(-length(uv0));

        vec3 col = palette(length(uv0) + fi * 0.4 + uTime * 0.4);

        d = sin(d * 8.0 + uTime) / 8.0;
        d = abs(d);
        d = pow(0.01 / d, 1.2);
        finalColor += col * d;
    }
    gl_FragColor = vec4(finalColor, 1.0);
}
`;

深堀:2つの「平らな板(PlaneGeometry)」

同じ PlaneGeometry でも、その役割によってシェーダーの書き方は異なります。
「画面そのもの」として扱うのか、「空間内のオブジェクト」として扱うのか。
この違いを理解することが、第一歩になると思います。

画面の「歪み」を支配する黄金の1行

移植したコードの中で、ひときわ異彩を放つこの計算式。実はこれ、シェーダーの数学における「画面と対話するための魔法」

vec2 uv = (vUv * 2.0 – 1.0) * vec2(uResolution.x / uResolution.y, 1.0);
  • 1. 「世界の中心」を定義する
  • 2. 「真円」を守り抜く
    ブラウザの画面は、正方形じゃない。そのまま描画すると、円はラグビーボールのように横に潰れてしまう。 そこで uResolution(解像度)を使って縦横比(アスペクト比)を計算し、座標を補正しています。

「背景」vs「看板」コードの書き分けガイド

項目 ① 背景スタイル (Screen Space) ② 看板スタイル (World Space)
頂点シェーダー gl_Position = vec4(position, 1.0); gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
UV座標の基準 ブラウザの画面全体(ピクセル) 板(Mesh)そのものの表面
歪み補正 上で説明したuResolution を使った数学的計算が必要 不要(板の形がそのまま描画範囲になる)
「背景スタイル」はモニターという額縁に合わせて絵を描くイメージ。「看板スタイル」はキャンバスという板に絵を描いてから、それを3D空間の好きな場所に置くイメージ。 今回は、「背景スタイル」にしています。

おわりに:0円で広がる無限のクリエイティブ

全4回にわたって、コードで絵を描くと、クリエイティブ・コーディングについて書きました。
一見難しそうに見える技術も、「AIという最強の家庭教師」がいれば、手軽に(材料費は0円で!)楽しむことができようになったと思う今日この頃です😊。

ぜひ、OpenProcessingやShadertoyでお宝コードを探し出し、AIと一緒に自分だけのWebアートを作り上げてみてください!


👇AIを家庭教師にしてサクッと「Vite環境構築」を作る方法。