ステップ1:RawShaderMaterial で最小構成を知る
Three.js で 最も“生の GLSL”に近い書き方ができるのが
RawShaderMaterial です。
普通の ShaderMaterial は Three.js が内部で
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
などの 便利ユニフォームを自動で追加してくれます。
しかし RawShaderMaterial では、 「GPU に渡すものを全部、自分で書く」 必要があります。
言い換えると、
- WebGL(OpenGL)の素の書き方に最も近い
- Three.js の介入が最小限
- “GPU と直接話す”感覚が一番分かりやすい
これが、シェーダーの最初のつかみとして最適なんです。
最小の RawShaderMaterial
const material = new THREE.RawShaderMaterial({
vertexShader: vs,
fragmentShader: fs,
uniforms: {
uTime: { value: 0 }
}
});
ここで重要なのは 3つだけ。
🟦 1. vertexShader: vs
頂点をどう変形させるか(=形を変える処理)。
🟩 2. fragmentShader: fs
最終的にピクセルの色をどう塗るか(=見た目の処理)。
🟥 3. uniforms
CPU → GPU に渡す変数(時間・色・行列・テクスチャなど)。
「便利機能を捨てる」ことで得られるもの
RawShaderMaterial を使うと、Three.js が普段やっている…
- 自動で行列を入れてくれる
- 自動で varying を補完してくれる
- 自動でフラグメントの精度宣言を入れてくれる
…といった “親切機能が一切ない状態” になります。
最初は不便ですが、 実はこれが シェーダー学習の最強の環境 です。
なぜなら、
「GLSL を書くと画面が変わる」 という ダイレクトな体験 が得られるから。
Three.js のレイヤを挟むほど “GPU がどう動いているのか” は見えづらくなります。
RawShaderMaterial は 💡 GPUへの道筋が一番シンプルで分かりやすい という点が最大の利点です。
このステップで到達したいゴール
- GLSL が「ただの関数」だと理解する
- GPU が「大量の頂点に同じ処理をする」ことを感じる
- “動く” → “反応する” → “楽しい” のサイクルを作る
このあと書く 頂点を揺らすサンプル が、 まさにこの RawShaderMaterial の真価を感じられる部分。
Three.js の経験が長いあなたでも、 「シェーダーの核心ってこれか」と実感できるはず。
ステップ2:最小の Vertex / Fragment シェーダー
ここでは 「Three.js が用意した便利機能を全て取り払った状態」 の 純粋な GLSL を書いていきます。
この 2つのシェーダーだけで
👉 “自分の GLSL が GPU 上で動いている”
という実感が得られます。
Vertex Shader(頂点を画面に送るだけの最小形)
precision highp float;
attribute vec3 position;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
void main() {
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
これが「最小の頂点シェーダー」
ポイントは4つ。
-
precision GPU に「float の精度」を指定する。 WebGL では 書かないとエラー になる。
-
attribute vec3 position; メッシュの頂点座標がここに入ってくる。 Three.js → GPU に自動で渡される。
-
uniform mat4 modelViewMatrix / projectionMatrix Three.js が自動で毎フレーム渡してくれる行列(RawShader でも渡る)。
modelViewMatrix= モデル位置 × カメラ位置projectionMatrix= 遠近感(FOV)
-
gl_Position GPU が「画面に描く座標」。 頂点シェーダーの最終アウトプット。
Fragment Shader(赤色を塗るだけの最小形)
precision highp float;
void main() {
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
これ以上シンプルにはできない
- precision(精度)
- 出力する gl_FragColor(RGBA)
これだけ。
このステップで「何が分かるか?」
あなたのように Three.js も Unity もやっている人でも、 この最小形を書くと、GPU が何を要求しているのかが一気に見える。
特に重要なのは以下の3つ。
① 「頂点 → gl_Position」さえあれば描画される
どんなシェーダーでも核は同じ。
頂点が来る
↓
行列で変換する
↓
gl_Position に入れる
どれだけ難しい水面・炎・発光シェーダーでも、 この“根っこ”は一生変わらない。
② fragment shader は“画家”にすぎない
Vertex シェーダーが頂点を決めて、 Fragment シェーダーがその後ろから色を塗る。
これを理解すると 👉 頂点で形が作られる 👉 fragment は形の内側を塗るだけ
という “役割の分業” が自然に見えてくる。
③ Three.js の「便利機能」が全部外れた感覚を得る
RawShaderMaterial の良さは:
- 行列を自分で受け取る
- 頂点を自分で gl_Position に入れる
- 精度指定も自分で書く
- varying も自分で作る必要がある
この “不親切な素の環境” こそが GLSL を理解する最短ルート なんだよね。
ステップ3:頂点を “揺らす”(sin 波)
ここからいよいよ “形を動かす” 体験に入ります。
Three.js 時代にあなたが磨いた sin / cos の直感 が、そのまま頂点変形に直結する瞬間です。
下の頂点シェーダーは、 1行だけ頂点を加工して「揺れる面」を作る最小の例 です。
頂点を上下に揺らす GLSL(sin 波による変形)
uniform float uTime;
attribute vec3 position;
void main() {
vec3 pos = position;
pos.z += sin(pos.x * 4.0 + uTime * 2.0) * 0.2;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
この1行がすべての魔法
pos.z += sin(pos.x * 4.0 + uTime * 2.0) * 0.2;
pos.x
横方向に対して波の模様を作る。
* 4.0
波の細かさ。(値を大きくすると波が増える)
uTime * 2.0
時間で波を“動かす”。
→ Three.js 側で uniforms.uTime.value += delta; すればアニメする。
* 0.2
振幅。小さいほど“繊細な揺れ”。
ゲーム開発のあらゆる場面で使われる
sin 波の変形は、たった1行なのに応用範囲が異常に広い。
① 海・湖・川の水面(周期的な波)
波の揺れ方はほぼこれの応用。
② 草・木の揺れ(風アニメ)
pos.y や pos.x + pos.z を使うと風でそよぐ。
③ キャラの呼吸アニメ(ULTRAKILL でも使われる手法)
胸のボーンを揺らすのと同じこと。
④ 髪のふわっとした揺れ
小さい振幅の sin を複数混ぜるだけ。
⑤ 光、UI の脈動エフェクト
sin で“弱→強→弱”が自然に作れる。
Three.js のあなたなら分かる“本質”
Three.js の Canvas 時代に、 マウス追従や波紋や弾幕で何度も書いたこの式:
y = sin(x + time)
あの感覚が GPU の空間でそのまま動く。
しかも CPU と違い、 数千~数万頂点に同時に sin を掛けても平気 (GPU が並列で処理するため)。
だからシェーダーは「大量のオブジェクトを一瞬で動かせる」わけです。
ここが “シェーダー開眼ポイント”
- 頂点を受け取る
- 位置を少し変える
- gl_Position に戻す
ただそれだけ。
ステップ4:色を “時間で変える”
頂点を動かすのが Vertex Shader なら、 色を動かすのが Fragment Shader です。
ここでは「時間 uTime をそのまま色に変換する」だけで、
“画面が生きて見える” 状態を作ります。
時間で色を変える最小 Fragment Shader
uniform float uTime;
void main() {
float r = abs(sin(uTime));
gl_FragColor = vec4(r, 0.5, 1.0, 1.0);
}
何が起きているか(超シンプル)
sin(uTime)
時間が進むにつれて -1〜+1 の間を往復する。
abs(…)
マイナスを消して 0〜1 の範囲に整える。 → 明るさ(強さ)として使いやすくなる。
float r
赤成分を 0〜1で変化させる。 だから色が呼吸するように脈動する。
これが “ネオンっぽさ” の正体
ネオンや発光っぽい表現って、実は難しいことをしてない。
- 明るさが周期的に揺れる
- 一定の色味を保ちながら脈動する
これだけで “それっぽく” 見える。
そしてこの abs(sin(time)) は、
その最小構成。
応用が効く理由
この1行を使い回すだけで、いろんな表現が作れる:
UI の点滅(注意・警告・選択)
r をアルファに入れるだけで “点滅UI”。
体力が減った時の危険色
赤だけ脈動 → 危機感が出る。
アイテムの発光・レア感
発光の強弱 → それだけで価値が上がって見える。
魔法陣・オーラ・エフェクト
“脈動”が入ると生き物っぽくなる。
このステップのゴール
- Fragment Shader は「塗るだけ」ではなく 動かせる
- 時間 uTime を入れるだけで アニメーションになる
- “シェーダー=難しい” の抵抗感が消える
ステップ5:なぜ GLSL は “生の理解” に繋がるのか?
ここまでで 「頂点を動かす」「色を動かす」 を体験しました。
では、なぜ GLSL を書くと 3D の理解が一気に深まるのか?
理由はシンプルに、この3つです。
① 行列を自分で扱うから、仕組みを“強制的に”理解する
GLSL では、頂点を画面に出すために必ず
modelMatrix(オブジェクトの世界位置)viewMatrix(カメラ)projectionMatrix(遠近感)
この3つを毎フレーム掛け合わせる必要があります。
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
Three.js は行列を全部そのまま見せる。 Unity は逆で、
UnityObjectToWorldUNITY_MATRIX_VPTransformObjectToHClip
など、抽象化した関数を使うため「見えにくい」。
だから Three.js で GLSL を通ると 空間変換の仕組みが丸見えになる。
これは Unity のシェーダーを読む時に ものすごく強い味方になる。
② vec2 / vec3 / vec4 「型の感覚」が直に掴める
GLSL は C言語に近い “硬い” 言語で、 型を誤ると絶対にコンパイルが通りません。
- UV →
vec2 - 位置 →
vec3 - 色 →
vec4 - 行列 →
mat4
こうした 3D の基礎型が身体レベルで理解できるのが、 GLSL を書く最大の副産物です。
Unity(HLSL)でも型の考え方は全く同じなので、 GLSL を触った後だと HLSL の吸収速度が爆速になります。
③ GPU で何が走っているかを意識できるようになる
GLSL は 「GPU のための低レベル言語」 と言っていい。
- for を回すと GPU がその回数だけ計算する
- 分岐は遅い
- テクスチャはサンプルするだけでコストがある
- sin/cos は比較的高コスト
- 計算は頂点単位・ピクセル単位で並列に実行される
こういった性質が コードに直接影響する世界 だからこそ、 「GPUとは何か?」が自動的に理解されていく。
普通の高級言語では味わえない感覚。
まとめ:GLSL = 3D理解の“地力”を作る最短ルート
GLSL を書くことで…
- 行列
- 座標変換
- 光
- 法線
- 色
- 時間
- 並列計算
- GPU の特性
これら3Dの土台すべてが 一ヶ所に集まって見える。
Three.js の経験を持っているあなたにとって、 GLSL はまさに
「すべての技術を一本の線で繋ぐ言語」
になっていく。
ステップ6:Three.js 側で uTime を更新して、実際に動かす
ここまででシェーダー側には
- uTime(時間の uniform)
- sin での頂点アニメーション
- 色を変える fragment アニメーション
が揃っています。
最後に必要なのは、 JavaScript(Three.jsの animate ループ)で時間を毎フレーム更新すること。
Three.js の animate ループで uTime を加算する
function animate(time) {
requestAnimationFrame(animate);
material.uniforms.uTime.value = time * 0.001; // 秒に変換
renderer.render(scene, camera);
}
animate();
解説:なぜ time * 0.001 するの?
time は requestAnimationFrame によって
ミリ秒(ms)で渡される ため、そのままだと数値が大きすぎる。
- 1000
- 2000
- 3000
…とどんどん増えるので、
sin(time) の周期が速すぎて “点滅地獄” になる。
* 0.001 で 秒(s)に変換すると、
人間が見て心地よいスピードになる。
完全な minimum 例(読者がコピペで動かせる)
(※記事に貼る時は「全コードは GitHub に置いてます」としてもOK)
// 平面
const geometry = new THREE.PlaneGeometry(2, 2, 100, 100);
const material = new THREE.RawShaderMaterial({
vertexShader: vs,
fragmentShader: fs,
uniforms: {
uTime: { value: 0 }
},
wireframe: false
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// アニメーションループ
function animate(time) {
requestAnimationFrame(animate);
material.uniforms.uTime.value = time * 0.001;
renderer.render(scene, camera);
}
animate();
これで「動く GLSL シェーダー」が完成する
このステップまで来ると読者は、
- 三角形が波打ち始める
- 色が脈動する
- CPU の update と GPU の shader が連動する
という “初めてのシェーダー体験” を確実に味わえる。
あなたも Three.js から Unity に移る時に、 この“CPU → GPU の感覚”が分かった瞬間、理解が一気に進んだはず。
このステップ6で読者が得られるもの
- シェーダーは“静的なコード”ではなく“動く部品”
- JavaScript と GLSL が一緒に動いて作品を作る
- 自分のコードで GPU がアニメする感動
- Unity/HLSL に行く前の最高の準備
Unity × URP:最小の「波シェーダー」(HLSL)
ファイル名は 📄 MinimalWave.shader として Project に入れればOK。
MinimalWave.shader(URP / 頂点 sin 波 / 色変化)
Shader "Lain/MinimalWave"
{
Properties
{
_Color("Base Color", Color) = (1,1,1,1)
_Amplitude("Wave Amplitude", Float) = 0.2
_Frequency("Wave Frequency", Float) = 4.0
_Speed("Wave Speed", Float) = 2.0
}
SubShader
{
Tags { "RenderType"="Opaque" "Queue"="Geometry" }
Pass
{
// URP と互換性のあるシェーダーヘッダ
HLSLPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
// Properties → HLSL に転送される
float4 _Color;
float _Amplitude;
float _Frequency;
float _Speed;
// 時間(Unity が自動で供給)
UNITY_DECLARE_TIME;
struct Attributes
{
float4 positionOS : POSITION; // Object Space 頂点
};
struct Varyings
{
float4 positionHCS : SV_POSITION; // Homogeneous Clip Space
float3 color : TEXCOORD0;
};
Varyings vert(Attributes IN)
{
Varyings OUT;
float t = UNITY_TIME.y; // 秒ベースの時間
float3 pos = IN.positionOS.xyz;
pos.y += sin(pos.x * _Frequency + t * _Speed) * _Amplitude;
OUT.positionHCS = TransformObjectToHClip(float4(pos, 1.0));
OUT.color = float3(
abs(sin(t)),
0.5,
1.0
);
return OUT;
}
half4 frag(Varyings IN) : SV_Target
{
return half4(IN.color, 1.0) * _Color;
}
ENDHLSL
}
}
}
Three.js の GLSL と “1:1 対応” の解説
あなたの Three.js の GLSL を思い出してほしい。
pos.z += sin(pos.x * 4.0 + uTime * 2.0) * 0.2;
Unity 版はこれ:
pos.y += sin(pos.x * _Frequency + t * _Speed) * _Amplitude;
位置が z→y に変わっただけで、 計算は 完全に同じ。
Three.js と Unity の対応表
| Three.js(GLSL) | Unity(HLSL) | 役割 |
|---|---|---|
position |
positionOS |
オブジェクト座標の頂点 |
gl_Position |
positionHCS |
画面座標 |
projectionMatrix * modelViewMatrix |
TransformObjectToHClip() |
行列変換 |
uniform float uTime |
UNITY_TIME.y |
時間 |
sin() |
sin() |
全く同じ |
vec3 |
float3 |
型もほぼ同じ |
あなたの GLSL の理解がそのまま HLSL に移植できている。
Unity側の設定(これだけで動く)
-
Plane を作る
-
Material を作る
- Shader → Lain / MinimalWave
-
Plane に割り当てる
-
Sceneに置いて再生するだけ
→ 頂点が波打つ → 色が脈動する → Three.js と同じシェーダーが Unity で動く をそのまま確認できる。
なぜこれは“①+②ミックス”と言える?
① Three.js とほぼ同じロジック
- sin 波
- time
- 頂点変形
- 色変化 100% 同じコンセプト
② URPで動く
- Core.hlsl 使用
- TransformObjectToHClip
- Properties → HLSL
- Universal RP互換 あなたの Unity6 のプロジェクトと完全一致。
まとめ:Three.js で「シェーダーの基礎体験」を自分の手で掴む
第2回では、 Three.js の RawShaderMaterial を使って “最小のシェーダー” を動かしました。
やったことは非常にシンプルでしたが、 GPU と直接対話する感覚がハッキリ分かる、もっとも重要なステップです。
1. RawShaderMaterial で “素のGLSL” を触った
Three.js の便利機能をすべて外し、 行列・型・出力すべてを自分で書く環境を体験しました。
これが シェーダーの本質。
2. 頂点を動かして「形が変わる瞬間」を見た
sin 波によって、 平面の 頂点が生き物のように揺れる 経験をしました。
これは、
- 水
- 草
- 呼吸
- 髪
- 魔法エフェクト
- UI の脈動
あらゆる表現の“種”になります。
3. 色を変えて「fragment も動かせる」ことを理解
abs(sin(time)) のような
シンプルな式が強力なアニメーションになることを確認しました。
これはゲームの世界で本当によく使われる技法です。
4. JavaScript → GLSL の連動で、初めて“作品”になる
uTime を animate ループで更新することで、
CPU と GPU が協力して画面を作ることが体感できます。
これは Three.js でも Unity でも共通する “本質” です。
5. GLSL を学ぶと、3Dの本質が一気に繋がる
- 行列
- 座標変換
- 頂点
- ピクセル
- 並列計算
- 時間
すべてが 一本の理屈で統合されるため、 3Dの理解が急に深まります。
この“生の理解”は、 Unity/HLSL に進んだときに 最大の武器 になります。
次回(第3回):Unity – 同じ処理を HLSL で書いてみる
Three.js で書いた GLSL を Unity の HLSL に書き換えるだけで、 全く同じ波・色変化を再現できます。
さらに Unity 版では:
- Properties
- SubShader
- Pass
- URP の行列関数
_Time- HLSL の型(float2/float3/float4)
など、Three.jsとの“違いと対応”が一気に見えるようになります。
ここが Three.js と Unity の二刀流が完成する回です。
💬 コメント