シェーダーのふたりのロボット
シェーダーには、ふたりの役割(やくわり)のちがうロボットがいるよ!
「舞台(ぶたい)づくり」担当
まずは、絵をかくための「画用紙(がようし)」を広げるよ。
どこに描くか、カタチはどうするかを決めるリーダーなんだ!
「色ぬり」担当
頂点くんが広げた画用紙に、一粒(ひとつぶ)ずつ色をぬっていくよ。
キラキラさせたり、虹色にしたりするのは全部この子の魔法!
頂点くんは、例えるならプロの「舞台設営(ぶたいせつえい)スタッフ」。 一度キャンバスを画面いっぱいに広げたら、あとはじっと出番を待っています。「色を変えたい!」「形を動かしたい!」と工夫するのは、ほとんどが「フラグメントくん」の担当
第1回で使ったこのテンプレート。どこに「頂点くん」と「フラグメントくん」が隠れているか、探してみよう!
const vert = `
// ❶ 頂点(ちょうてん)くん:舞台を作るお仕事
attribute vec3 aPosition;
attribute vec2 aTexCoord;
varying vec2 vTexCoord;
void main() {
vTexCoord = aTexCoord;
vec4 positionVec4 = vec4(aPosition, 1.0);
positionVec4.xy = positionVec4.xy * 2.0 - 1.0;
gl_Position = positionVec4;
}
//ここまで頂点(ちょうてん)くん
`;
const frag = `
// ❷ フラグメントくん:色をぬるお仕事
precision mediump float;
uniform float u_time; // 今何秒たったか教えてもらう「箱」
uniform vec2 u_resolution; // 画面の大きさを教えてもらう「箱」
varying vec2 vTexCoord; // 頂点くんからもらった「住所」のお手紙
// ☝️void mainの上と中で役割がちがうんだよ!
void main() {
// 赤色
float r = 1.0;
// 緑色
float g = 0.0;
// 青色
float b = 0.0;
// 透明度
float a = 1.0;
// 光の三原色(RGB)と透明度(A)vec4(R, G, B, A)
gl_FragColor = vec4(r, g, b, a);
}
//ここまでフラグメントくんくん
`;
// --- ここから下はJavaScript(p5.js) ---📏 「ものさし」の細かさを決めよう!
シェーダーの魔法を書き始める前に、「どのくらい細かく計算する?」と聞かれます。それに答えるのがこの一行です!
- precision(プレシジョン): 「正確に!」という。
- mediump(ミディアム): 「ふつうくらいの細かさで」という意味。
- float(フロート): 「小数(0.1 とか 0.001)」のこと。
(速いけど荒い)
(バランス最高!)
(綺麗だけど重い)
💡 なぜ決めるの?
100点満点の正確さを出そうとすると、すごく疲れて動きが遅くなっちゃうことがあります。だから「このくらいの細かさでいいよ!」と教えてあげることで、サクサク動くようになるんだよ。
ロボットへの命令
ここから、GLSL(ジー・エル・エス・エル)という「シェーダー専用のプログラミング言語」のルールをまとめるよ。
コードを書き始める前に、シェーダーの世界の「大切なルール」を教えるね。
型のルール:「箱」の種類
プログラムの中には、数字を入れるための「専用の箱(型)」がいくつかあるんだ。
数字が「1つ」だけ。
例:明るさ、時間
数字が「2つ」セット。
例:たて・よこの場所
数字が「3つ」セット。
例:赤・緑・青の色
数字が「4つ」セット。
例:色 + 透明度
「1つ用の箱」に「3つの数字」を入れようとすると、「はいりません!」って怒って画面を真っ黒にしちゃうから気をつけてね。
シェーダーの世界では、数字を書くときに必ず「.0」をつけよう!
「1」じゃなくて「1.0」、「0」じゃなくて「0.0」と書くのが、魔法を成功させるコツだよ。
宣言と代入:「箱」に名前をつけて、なかみを入れよう!
前回は「箱(型)」の種類を習ったね。でも、ただ箱があるだけじゃダメなんだ。プログラムを書くときは、「どんな名前の箱に、何を入れるか」を教えてあげる必要があるよ。
1. float color; ➔ 「color」という名前の、数字が1つ入る箱を用意するよ!(宣言)
2. color = 1.0; ➔ その箱の中に「1.0」を入れるよ!(代入)
● プログラミングの = は、算数の「同じ」という意味じゃなくて、「右側のものを左側の箱に入れる」という矢印(⬅)のような意味なんだ。
型(専用の箱)を書くのは「最初だけ」!
箱の中身をよぶ「ラベル」:Swizzle(スウィズル)のひみつ
🏷️ 箱の中身をよぶ「ラベル」
セットの箱(vec2やvec3)には、中身をひとつずつ取り出すためのラベルがついています。これを知っておくと、数字を自由に入れかえられるよ!
- 場所のラベル: x , y , z
- 色のラベル: r , g , b
たとえば、vec3 color という箱があったら、color.r と書くだけで「赤色の数字」だけを取り出せます。さらに……
🌟 じゅんばんを入れかえるマジック!
color.brg と書くと、一瞬で「青・赤・緑」のじゅんばんに入れかえた新しいセットが作れちゃう。これを Swizzle(スウィズル) っていうんだ。かっこいい名前だよね!
計算(けいさん)のルール
シェーダーのロボットたちは、計算がとっても得意!
ふだん使っている算数の記号とは、ちょっとだけ違うルールがあるんだ。
| 魔法の記号 | なまえ | つかいかた |
|---|---|---|
| + ・ – | たし算 ・ ひき算 | 1.0 + 2.0 |
| * ・ / | かけ算 ・ わり算 | 2.0 * 3.0 |
⚠️ ロボットとの「お約束」
1. 「.0」をわすれないで!
シェーダーくんはとってもマジメ。「1.0 + 2」 みたいにドットがない数字がまざるとエラーになっちゃうんだ。必ず全部に .0 をつけようね。
2. 「=」は「入れる」のサイン
プログラミングの = は、算数の「同じ(イコール)」じゃなくて、
「右側で計算した答えを、左側の箱に入れる」 という意味だよ!
🚀 時短(じたん)の呪文: += と *=
箱の中身を計算したいときに使う「魔法のショートカット」だよ。
uv.x += 0.5;
( uv.x = uv.x + 0.5; と書くのと同じ意味だよ)
uv.x *= 2.0;
( uv.x = uv.x * 2.0; と書くのと同じ意味だよ)
「今の自分を、さらに計算する」ってイメージすると分かりやすいね!
便利なので、みんなよく使うんだ!
💡 セットの箱(vec)はまとめて計算!
vec3(1.0, 2.0, 3.0) * 2.0 と書くと、中の数字ぜんぶを一気に2倍にして、vec3(2.0, 4.0, 6.0) を作ってくれるんだ。
まとめて魔法がかけられるから、とってもはやいんだよ!
関数:魔法の「加工マシン」!
数字をかっこよく変身させたいときは「関数(かんすう)」という魔法のマシンを使うよ。
たとえば、abs( 数字 ) というマシンにマイナスの数字を放り込むと、
反対側の出口からプラスの数字になって出てくる。
だから、float s = abs(-1.0); と書けば、
「s」という箱にはマシンを通った後の「1.0」がはいってるよ。
🌟 このマシンは山ほどある!
シェーダーの世界には、最初から用意されている「便利なマシン」が何百種類もあります。全部覚える必要はナシ!よく使うものから少しずつ仲良くなっていこう。
🌟 自分でもマシンが作れる!?
さらにすごいのは、マシンを組み合わせて「自分専用の加工マシン」を新しく発明することもできるんだ。これは、もう少し魔法に慣れてからのお楽しみだよ!
☝️void mainの上と中のちがいは?
🛠️ 道具箱と材料の準備
uniform float uTime;
varying vec2 vTexCoord;
「これからこの道具を使うよ!」「外からこんな材料(時間や住所)が届くよ!」というルールや持ち物の紹介を書く場所なんだ。
※ ここに書いておかないと、下の「中身」で使おうとしても「そんな道具知らないよ!」って怒られちゃうんだ。
🎬 魔法の「劇」を実行する!
// ここで実際に計算して色を塗る!
gl_FragColor = …
}
外で用意した道具を使って、実際に「色を塗れ!」「動かせ!」という命令(アクション)を書く場所だよ。
この
{ } の中身を、画面のピクセル一個一個に対して、ものすごいスピードで実行していくんだ。
- 外側: 名前の紹介。材料の受け取り。「知っておいてほしいこと」の宣言。
- 内側: 実際の動き。色の決定。一回一回の「お仕事」の中身。
3つのれんらくルート
コードにある「この英語はなんだ〜?」って思ったかな!!
説明しておくね。
画用紙の「角っこ」の場所など。頂点くんが最初から持っている情報だよ。
時間や画面のサイズなど。頂点くんもフラグメントくんも、みんなで同じ情報を共有(きょうゆう)するよ!
頂点くんが計算した住所を、フラグメントくんへ送るためのお手紙だよ。中身はリレーの間に少しずつ変化するんだ。
GLSL(シェーダー専用のプログラミング言語)のルールについては、ここまでだよ。
最初は「なにこれ、ややこしい!」って思うかもしれないけど、大丈夫。魔法使いだって、最初は呪文のつづりを何度も見直すものなんだ。
「どうやって書くんだっけ?」と迷ったときは、いつでもこのページに戻ってきてね。
座標(ざひょう)と3つの呪文
これをフラグメントくんの部屋の「main」の中の一番上に書こう!
vec2 uv = vTexCoord * 2.0 - 1.0;
uv.x *= u_resolution.x / u_resolution.y;
vec2 uv0 = uv;
void main() {
//ここだよ
}
「なんだか英語と数字がいっぱいでややこし〜!」って思ったら、
理解しなくてOK!
💡最強のコツ
「きれいな絵を描くためには、この3行をコピーしてあの場所に貼り付ければいい!」 と覚えるだけで、キミの魔法はちゃんと発動するよ。
座標:画面の「住所」をマスターしよう!
ロボットに命令する前に、知っておきたいのが「座標(画面の住所)」のことなんだ。
「この場所を赤くして!」と命令(めいれい)するとき、場所の名前がわからないと困るよね。そこで使うのが座標(ざひょう)という画面の住所だよ。
シェーダーでは、場所を uv(ユーブイ) という名前を使うことが多いんだ。
住所は、横(よこ)と縦(たて)の2つの数字が必要だから、vec2 という名前の箱に入っているんだ。
横のラベル
左 ⇄ 右
たてのラベル
下 ⇄ 上
uv という箱のうしろに、ドットをつけて .x や .y と書くことで、中身の数字をひとつずつ取り出せるんだ。これがさっき習った「ラベルの魔法」だよ!
画面の真ん中が 0.0になると便利なんだ。
右にいくほど uv.x が大きくなって、上にいくほど uv.y が大きくなる。この住所をたよりに、ロボットたちは色をぬっていくんだよ!

アスペクト比:画面のカタチと「ゆがみ」
シェーダーで「きれいな丸」をかこうとした時、最初にぶつかるのが「画面ののびちぢみ」の問題なんだ。
1. 画面にはいろんな「カタチ」がある!
テレビは横長(よこなが)、スマホは縦長(たてなが)だよね。この「よことたてのバランス」のことを アスペクト比 って呼ぶよ。
でも、シェーダーくんは画面のカタチを気にせず、どんな画面でもムリやり 0.0 〜 1.0 の住所をわりふっちゃうんだ。
2. そのままだと「丸」が「タマゴ」に!?
正方形のゴム板に「丸」をかいて、それを横にグイーッ!とのばしたところを想像してみて。丸が横にのびちゃうよね?
きれいな丸!
横にのびたタマゴ!
これが「ゆがみ」の正体。横に長い画面なのに、住所をムリやり 1.0 までにおさめようとするから起きるトラブルなんだ。
3. 魔法の計算でシャキッとなおそう!
これをなおすには、「よこが、たてに比べてどれくらい長いか」を計算して、よこの住所(uv.x)を広げてあげればいいんだよ。
舞台作りから地図描きまで
シェーダーの魔法は、「外側のワク(頂点)」と「内側の地図(フラグメント)」の2つのステップで動いているよ!
🖼️ 画用紙を「画面」にピッタリ貼り付ける!
まずは頂点くんの仕事。p5.jsが用意した小さな四角形を、ディスプレイ全体のサイズに引きのばすよ。
シェーダーの世界は、真ん中が 0.0、左端が -1.0、右端が 1.0 というルールなんだ。
でも、届いた四角形は「0.0 〜 1.0」のサイズ。これを2倍して1引くことで、画面のハシからハシまで(-1.0 〜 1.0)ピッタリに広げているんだよ!
※これは「外枠」の話。模様を描くための準備だよ。
✨ 魔法のリレー:自動ぬりえ機能
頂点くんが「四角の4つのカドの住所(aTexCoord)」を教えてあげると、その間にある数万個のピクセルの住所を、「自動で計算して」フラグメントくんに届けてくれるんだ。これを補間(ほかん)と呼ぶよ!
🎨 自分だけの「魔法の地図」を描き込む!
バトンを受け取ったフラグメントくん。届いた住所(0.0 〜 1.0)をそのまま使うと「左下」が基準になっちゃう。だから、ここでも呪文を使って地図を書き換えるよ。
これで「画面の真ん中」が 0.0 になったよ!円を描いたり、万華鏡を作ったりするとき、真ん中が 0.0 だと計算がとってもラクになるんだ。
アスペクト比のところで勉強したね。
例えば、PCの画面は横長だよね?そのままだと地図が横に伸びちゃう。「よこ ÷ たて」の比率をかけることで、正方形のきれいな地図に直すよ。
これから地図をぐにゃぐにゃに曲げたり、バラバラにしたりするけど、最初に作った「きれいで真っ直ぐな地図」を uv0 に保存しておけば、いつでも元の場所を思い出せるんだ!
頂点くんが「外側のワク」を画面に合わせ、
フラグメントくんが「内側の地図」を使いやすく整える。
この「準備」ができると、いよいよ色塗りの魔法が始まるよ!
でも、キミは一番大変な「魔法の基礎(きそ)」をマスターしました!
準備はすべて整ったよ。ここからは……
実際にコードを動かして、目に見える変化を楽しむ時間だよ!🚀
【実践】画面いっぱいに「丸」を描いてみよう!
ここからは、「座標(住所)」と「関数(マシン)」を組み合わせて、実際に絵を描いていくよ。
加工マシン:length(ながさ)と step(ステップ)
丸を描くために使う、2つの強力な「加工マシン」を紹介するよ。これらがどんなお仕事をしているか見てみよう!
中心 (0,0)から、その場所までの「きょり」をはかってくれるマシンだよ。
円の半径になるよ
【出てくるもの】:d(中心からのきょり)
※中心に近いほど「0」に近くなり、遠くにいくほど数字が大きくなるよ。
数字を比べて、「ここから先は白!」とパキッと分けるマシンだよ。
【出てくるもの】:0.0(黒) か 1.0(白)
step(0.3, d) と書くと、
きょりが 0.3 より小さいなら ➔ 0.0(黒)
きょりが 0.3 より大きいなら ➔ 1.0(白)
という風に、一瞬で色を分けてくれるんだ!
この2つを組み合わせると、「真ん中から 0.3 のところまでは黒くして、それより外は白くする」という命令になって、コンパスをつかったのと同じきれいな「丸」が浮かび上がるんだよ。
下のコードを、フラグメントくんの部分にはりつけてね
precision mediump float;
uniform float u_time;
uniform vec2 u_resolution;
varying vec2 vTexCoord;
void main() {
// --- 🌟 3つの呪文で地図を準備しよう! ---
vec2 uv = vTexCoord * 2.0 - 1.0;
uv.x *= u_resolution.x / u_resolution.y;
vec2 uv0 = uv;
// --- 📏 加工マシンでお絵かき開始! ---
// 真ん中からの「きょり」をはかる(lengthマシン)
float d = length(uv);
// 「0.3」より遠いところを白くする(stepハサミ)
float circle = step(0.3, d);
// 最後に色をぬる!
gl_FragColor = vec4(circle, circle, circle, 1.0);
}コードで
vec4(vec3(circle), 1.0); と書くと、 vec4(circle, circle, circle, 1.0)と書かなくても、シェーダーくんが気を利かせて「赤・緑・青の全部をcircleの数字にしてくれるよ。 vec4(vec3(circle), 1.0);に変更してみてね。!!

たった1つの丸だけど、これはキミが『数の方程式』で作り出した特別な丸。このあとの1行で、この丸を100個にも1000個にも増やせるんだ!
fract(フラクト):「タイル」で丸を増やそう!
丸が1つ描けたら、次はシェーダーが一番とくいな「タイルの魔法」を使いましょう。
使うのは fract(フラクト)というマシンです!
この一行を、さっきのコードの length の上に入れてみて!
uv = fract(uv * 3.0) - 0.5;
すると……どうかな? 1個だった丸が、タイルみたいにきれいに並んで一気に増えたはずだよ!
① fractは「リセットボタン」
fract は、数字の「小数点(0.123…)」だけを抜き出すマシンです。これに3倍にした住所を入れると、不思議なことが起きます。
| 入れた住所 | 3倍にした数字 | fract の結果 |
|---|---|---|
| 0.1 | 0.3 | 0.3 |
| 0.3 | 0.9 | 0.9 |
| 0.4 | 1.2 | 0.2(←1.0を超えて0に戻った!) |
住所を3倍にする、ということは、「今までの3倍の広さの地図を、同じ画面に無理やり詰め込む」ということ!
これで、画面の中に0.0〜3.0までの広い住所ができました。
ここで「小数点だけ残す」ハサミが発動!
0.0〜3.0 までの長い道が、0〜0.99が3回くり返される道に切り分けられます。これでタイルが3つ並んだよ。
※でもこのままだと、タイルの「左下」が 0.0 だから、丸がカドっこに描かれちゃう!
すべてのタイルの住所から0.5を引きます。すると…
もともと 0.5 だった「タイルの真ん中」が、計算して 0.0 になるよね?
「タイルの真ん中が 0.0」になったから、そこにきれいな丸が描けるようになるんだ!
🤔 アレ?「3倍」したのに「6個」ある!?
uv * 3.0 と書いたのに、画面には 6個の丸が並んでる……。
算数まちがい? いえいえ、これこそが「真ん中 0.0 の魔法」の正体なんだよ!
0.0 〜 1.0
長さは 1.0
⬇️ 3倍すると ⬇️
3タイル!
-1.0 〜 1.0
長さは 2.0
⬇️ 3倍すると ⬇️
6タイル!
真ん中を 0.0 にするために 地図(uv)を* 2.0 して広げていたvec2 uv = vTexCoord * 2.0 - 1.0;とかいたから、タイルの数も 2倍になって届いたんだね。
🌟 キミの画面では、丸は何個見えたかな?
「私の画面ではヨコに10個あるよ!」「ボクのは6個だ!」
実は、それで大正解!
シェーダーの魔法は、「使っている道具(スマホやPC)のカタチ」をちゃんと読み取って、それに合わせてお絵かきをしているんだ。
- タテの数: 魔法の数字(* 3.0)で決まってる!
- ヨコの数: 画面のヨコ長さに合わせて自動で調整される!
これこそが、世界中のどんな画面で見ても美しく映る「レスポンシブな魔法」の正体なんだよ。
おまけ!自由自在に増やそう:mod(モジュロ)編
fract よりもさらに自由度が高いのが、この mod(モジュロ) という魔法。これは算数で習う「わり算のあまり」を計算してくれるマシンなんだ!
mod(x, y) は、x を y でわった「あまり」を計算するよ
たとえば、mod(7.0, 3.0) なら、答えは 1.0 になります。
これを使うと、「数字が y になるたびに 0 にもどる」という繰り返しが作れるんだ。タイルの大きさを0.2にしたければ、mod(uv, 0.2) – 0.1 と書くとOK!
「タイルの部屋を 0.2 にしたのに、画面が真っ黒になっちゃった!」というキミ。実はこれ、「部屋よりも大きな家具を置こうとしている」からなんだよ。
- タイルの部屋の広さ: 0.2
- 描こうとした丸のサイズ: 0.3
部屋の大きさが最大でも 0.1(中心からカドまで)しかないのに、「0.3より遠いところを白くして!」と命令しても、そんな場所はどこにもないよね。だから全部が黒くなってしまったんだ。
✅ 解決さく:丸も小さくリサイズしよう!
部屋を小さくしたときは、中に入れる丸(家具)も小さくしてあげれば解決だよ!
uv = mod(uv, 0.2) – 0.1;
// ❷ 丸の大きさも部屋に合わせて 0.05 に変更!
float circle = step(0.05, d);
これで、小さな丸が画面いっぱいにきれいに並ぶはずだよ。
自由研究:どっちのタイル生成マシンがすごい?
実験してわかったことをメモしておこう!
| マシン名 | とくいなこと | リセットのタイミング |
|---|---|---|
| fract | 同じ大きさ(1.0)でサクサク並べる | 1.0 になったら自動で 0 へ |
| mod | 好きな大きさで並べる | 決めた数字になったら 0 へ |
💡「 中心をずらす」して「タイルの真ん中」を「0.0」にするのをわすれないでね〜
第3回予告:光をあやつれ!「動き」と「色」の魔法
スポンサーリンク
