第2回では画面にポツンと浮かぶ「完璧(かんぺき)な丸」を描くことに成功したね。 でも、今の丸はじっと止まったまま、眠っているみたい……。
今回使う魔法は、とってもシンプル。
算数(さんすう)で習った「たし算」と「かけ算」、
そして秘密のマシン「虹色パレット」を使いこなすだけ!
数式が「命」に変わる、シェーダーの本当の楽しさがついに始まります。
ワクワクする準備はできてるかな?
🚀 たし算で「動き」を作る
景色をグイッと動かす「スライド魔法」に変わるんだ!
そのヒミツは、「今の場所に、時間をプラスする」という考え方にあるよ。
さっそく、魔法の仕組みをのぞいてみよう!
住所をズラせば、景色が動く!
第2回で、uv は「画面の住所(じゅうしょ)」だと習ったよね。
この住所に、監督(JS)からもらった「時間(u_time)」をたし算すると、不思議なことが起きるんだ!
住所(uv)に時間をプラスするのは、止まっている地面を「ベルトコンベア」に変えるようなもの。
地面(住所)そのものが動いていくから、その上に描いた「丸」も一緒に流れていくんだよ!
uv.x += u_time;(今のヨコの住所に、時間をどんどん足していくよ!)
フラグメントくんは、1秒間に60回も色をぬりなおしています。
「0秒のときの住所」「1秒たったときの住所」「2秒たったときの住所」……と、
たし算によって住所が少しずつズレていくから、私たちの目にはスルスル動いているように見えるんだね!
ヨコ(x)に足せばヨコに、タテ(y)に足せばタテに動くよ。
両方に足せば、ななめにスーッと動く魔法になるんだ。面白そうでしょ?
魔法をかける「順番(じゅんばん)」が大事!
シェーダーの魔法は、上から順番にかかっていくよ。
「走る魔法」をかける場所をまちがえると、丸が動かなかったり、変な方向にいっちゃうことも……。
一番きれいに動く「黄金(おうごん)のタイミング」はここだ!
// (1) 3つの呪文で地図を準備…
vec2 uv = vTexCoord * 2.0 – 1.0;
vec2 uv = vTexCoord * 2.0 – 1.0;
vec2 uv0 = uv;
uv.x += u_time; // ← 住所を走らせる!
float d = length(uv);
float circle = step(0.3, d);
…
}
せっかくきれいに整えた「住所(地図)」を、色をぬる直前にグイッ!と動かすイメージだよ。描く直前に地図をズラすから、上に描かれる丸も一緒に動いて見えるんだね!
✨ かけ算で「光」をあやつる
「かけ算」は光の強さを変える魔法だよ。
数字を大きくすればピカーッとまぶしくなり、小さくすればフワッと暗くなる。
この魔法を使えば、丸をチカチカ点滅させたり、宝石みたいに輝かせたりできるんだ。
さあ、どうやって光のボリュームをひねるのか、その仕組みを見てみよう!
光のボリュームをひねってみよう
ステップ1:名前をアップデート
第2回の最後で計算した「丸のカタチ(0.0か1.0か)」の数字を、これからは「光の強さ(strength)」という箱に入れて進めていくよ。
シェーダーの世界では、「光の強さ」のことを一般的に strength(ストレングス) と呼ぶことが多いから2ヶ所、変更しておこうね。
中身は同じ「丸」のデータだよ!
ステップ2:割り算をつかう
今までは step というハサミを使って、パキッとした「形」を作ってきたね。
0.3より小さければ 0.0(黒)、大きければ 1.0(白) になるしくみだったよね
float strength = step(0.3, d);
でも、本物の光はもっとフワフワして、中心が一番まぶしいはず。ここで、ハサミ(step)を捨てて新しい魔法「わり算(/)」を使ってみよう!
float strength = 0.1 / d;
- step(ハサミ): 「0.0 か 1.0 か」の2種類。
- わり算(光): 真ん中から外にむかって、なめらかな数字の階段ができる!
この「なめらかな階段」があるからこそ、あとで時間をかけ算したときに、ついたり消えたりするような動きができるようになるんだよ。
「0.1個」のケーキを、分ける計算なんだけど!
どうして1個ではなくて、 0.1個なの?
| 場所(キョリ) | 1個を分けると? 1.0 / d |
0.1個を分けると? 0.1 / d |
|---|---|---|
| 0.1 (中心の近く) |
10.0 超まぶしい! |
1.0 ちょうどいい白 |
| 0.5 (まんなか辺) |
2.0 まだ真っ白! |
0.2 いい感じの暗さ |
| 1.0 (画面のはし) |
1.0 はしっこまで白! |
0.1 ほぼ真っ暗 |
シェーダーの画面は、数字が「1.0」を超えると全部真っ白に見えちゃうんだ。
ケーキが1個(1.0)だと、画面のはじっこまで全部の計算が「1.0」以上になっちゃうから、ただの白い四角になっちゃうんだね。
例えば:mod(uv, 0.2) を使って、画面を 0.2 というすごく小さなサイズで区切ってる場合。
strength = 0.02 / d;
光のパワーを弱めれば、
ハジっこに届く前に暗くなるから、きれいな丸になるよ!
uv = mod(uv, 1.0) - 0.5;
タイルのサイズを大きくすれば、
光が消えるまでの「余裕」ができるんだ。
ステップ3:「かけ算」の魔法をかける
シェーダーでの光の強さは、0.0(真っ暗)〜 1.0(全開)という数字で決まるね。
ここに「かけ算」の魔法をかけると、光のパワーを自由自在にコントロールできるよ!
| かけ算の魔法 | ロボットへの命令 | 光のすがた |
|---|---|---|
| パワー2倍! | strength * 2.0 |
✨ 限界突破してまぶしくなる! |
| パワー半分… | strength * 0.5 |
🌙 フワッと優しい光になるよ |
| パワー・ゼロ! | strength * 0.0 |
🌑 どんな光も一瞬で消えちゃう |
一番最後、色をぬる gl_FragColor の手前で計算してあげよう!
strength *= 2.0; と書くだけで、画面の中のすべての丸が一気にまぶしくなるよ。
でも、ずっと同じ明るさじゃつまらないよね?
次は、このかけ算に「時間(u_time)」を組み合わせて、ついたり消えたりさせるテクニックに挑戦だ!
ついたり消えたりする方法
sin は、「-1.0 から 1.0」の間をいったりきたりする波の形を作ってくれるんだ。
時間をいれると、時間がどれだけ進んでも、自動で「1.0 ➔ 0 ➔ -1.0 ➔ 0…」と波打つよ。
マイナスをプラスに変える魔法!これで、マイナスにいかない。「0.0 ➔ 1.0 ➔ 0.0」
地面をバウンドするようなイメージだね。
光の強さに「マイナスの数字」をかけると、画面が真っ暗なまま止まって見えちゃうんだ。
abs を使うことで、暗くなってもすぐに明るく戻ってくる、動きになるんだよ!
できたかな?
うまくいかない場合は、このコードをフラグメントくんのところにはりつけてね!
precision mediump float;
uniform float u_time;
uniform vec2 u_resolution;
varying vec2 vTexCoord;
void main() {
vec2 uv = vTexCoord * 2.0 - 1.0;
uv.x *= u_resolution.x / u_resolution.y;
vec2 uv0 = uv;
//uv.y += u_time;
uv = fract(uv * 3.0) - 0.5;
float d = length(uv);
float strength =0.1/ d;
strength *= abs(sin(u_time));
gl_FragColor = vec4(vec3(strength), 1.0);
}
白黒の世界におさらば!
今までずっと画面が白黒だったのには、ちゃんとした理由があったんだ。
最後に色をぬっている、この1行を見てみて。
gl_FragColor = vec4(vec3(strength), 1.0);
実は、シェーダーくんは色を 「赤・緑・青・透明度」 の4つのセットで考えています。
色は R(赤)・G(緑)・B(青) の3つの光を混ぜて作ります。
vec3(strength) と書くと、ロボットは気を利かせて「赤も緑も青も、ぜーんぶ同じ強さ(strength)」にしてしまうんだ。
gl_FragColor = vec4( strength, strength, strength, 1.0 );
と同じなんだよ!
魔法の配合(はいごう)を変えてみよう
strength, // 赤
0.0, // 緑
0.0, // 青
1.0 // 透明度
);
「緑」と「青」を 0.0 にして、「赤」のところだけに strength(光のつぶ)を入れてあげると……画面の丸が一瞬で真っ赤な光に変わるんだ!
gl_FragColor = vec4(strength, 0.0, 0.0 ,1.0);
🌈 彩りの魔法:虹色パレットの召喚
これから使うのは、数字をいれるだけでキラキラした色を返してくれる
「パレット関数」という魔法の道具!
キミが作った「光のつぶ」を、虹色に変身させるよ。
【重要】2つの住所(uv と uv0)
虹色パレットでは「uv0」を使うよ!
length(uv)
「丸のカタチ」を描く担当。
fractで区切られた狭いお部屋の中だけの住所だよ。だから、全部のタイルに同じ丸がコピーされるんだ。
palette(length(uv0))
「虹色のグラデーション」担当。
切り分ける前の広い世界の住所だよ。タイルを無視して、画面全体に1つだけの大きな虹をかけることができるんだ。
fract を使うと、地図が細かく切り刻まれて「全体で今どこにいるか」が分からなくなっちゃうんだ。
でも、切り刻まれる前の住所を uv0 にメモしておけば、たとえタイルの中にいても「自分は画面全体のどのへんに立っているか」を思い出せるんだよ!
「Palette(パレット)関数」で画面を虹色にしよう!
「赤・緑・青のバランスを考えるなんてムリ〜!」というキミに、とっておきの道具を教えるね。
それが、世界中のシェーダー職人が愛用している「Palette(パレット)関数」という自動着色マシンなんだ!
このマシンの中では、こんなカッコいい数式が動いているんだよ。
…(中略)…
return a + b * cos( 6.28318 * (c * t + d) );
}
palette( 好きな数字 ) と書いて「呼び出す」だけで、赤・緑・青(vec3)がバッチリ計算された「色」がポンッと飛び出してくるんだ!
ステップ1:「Palette(パレット)関数」(自動着色マシン)をセットしよう!
シェーダーくんは、とってもマジメな読書家。上の行から順番に一文字ずつ読んでいくよ。
だから、お絵かき(main)の中でマシンを使うなら、その前にマシンを組み立てておかないといけないんだ。
vec3 palette(float t) { … }
← ここでpalette(d)を呼び出す
… }
お絵かきの最中に「パレットマシンを使って!」と命令されても、シェーダーくんは「そんなマシン、まだ見てないよ!」とパニック(エラー)になって、画面が真っ暗になっちゃうんだ。
🌟これが「Palette(パレット)関数」だ〜!!
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の上に設置しよう!!
ステップ2:「Palette(パレット)関数」を使ってみよう!
さあ、「Palette(パレット)関数」に、第2回でメモしておいた「よびの地図(uv0)」を放り込んでみよう。
void mainの中で呼び出すよ!!
数字をいれると、マシンは 「赤・緑・青」がセットになった「vec3」 を返してくれるんだ。
vec3 color = palette( length(uv0) );
// 2. まずは「色」だけを画面に映してみる!
gl_FragColor = vec4( color, 1.0 );
円形の虹色のグラデーションになってる?
これが、「色のベース」だよ。

length(uv0) は、「真ん中からのキョリ」をはかる呪文だったよね。
キョリが同じ場所(=円の形)には、同じ色がぬられる。だから、真ん中から外にむかって色がかわっていく「円形のグラデーション」になったんだよ!
palette( uv0.x )palette( uv0.y )
palette(length(uv0) + u_time) にして「時間(u_time)」をプラス(+)すると
マシンに「時間(u_time)」を伝えると、色がどんどん変わっていくよね。
「でも、時間が1万秒、1億秒って増え続けたら、マシンが爆発しちゃうんじゃない?」って心配になるかもしれないけど、大丈夫!
パレットマシンの中は、こんな「虹色の時計」になっているよ!
夜中の12時をすぎたら、また「1時」にもどるよね?
このマシンも同じで、虹を1周したら、また最初にもどってぐるぐる回るだけなんだ!
中のプログラムには
cos という「円をえがく魔法」が入っているよ。だから、どれだけ数字が増えても、虹の中から色を選びつづけてくれるんだ。
👨🔬マイナス(-)でも実験してみてね
ステップ3:これぞ究極の魔法:color * strength
さあ、準備はすべて整ったよ。手元には2つの魔法のデータがあるね。
- color:画面全体に広がる、うつくしい「虹色の波紋(はもん)」。
- strength:前につくった、ついたり消えたりする「白黒の光」。
この2つを「かけ算」したら、一体どうなると思う?
シェーダーの算数では、「黒=0.0」 だったよね。
どんなに綺麗な虹色(color)でも、0.0 をかけたら真っ暗になって消えちゃう。
つまり、「光がある場所(strengthが1.0に近い場所)だけ、虹色が浮き上がって見える」 というわけ!
これで、真っ暗な中に、虹色に輝く宝石がフワフワ浮いているような、世界が完成するんだ!
precision mediump float;
uniform float u_time;
uniform vec2 u_resolution;
varying vec2 vTexCoord;
// 🏗️ 1. Palette(パレット)関数:mainの上に書くのがルール!
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 = vTexCoord * 2.0 - 1.0;
uv.x *= u_resolution.x / u_resolution.y;
vec2 uv0 = uv;
// タイルを作る
uv = fract(uv * 3.0) - 0.5;
// それそれのタイルのきょり
float d = length(uv);
// わり算で光らせる(👨🔬実験:もっと小さく0.02くらいにしてみよう!)
float strength = 0.1 / d;
// ついたり消えたりする (👨🔬実験:消してみよう!)
strength *= abs(sin(u_time));
// 全体を虹色に
// (👨🔬実験:palette(uv0.x + u_time);にしてみよう!)
vec3 color = palette(length(uv0) + u_time);
// 運命のガッチャンコ(虹色のポスター × 懐中電灯)
gl_FragColor = vec4(color * strength, 1.0);
}🤔 「あれ? どっちが動いてるの?」
今のコードには、実は「暗くする魔法」が2つ入っています。だから、どっちが効いているのかわからなくなっちゃうんだ。
お部屋の「電気のスイッチ」をカチカチして、全体を明るくしたり暗くしたりする魔法だよ。
着ている「服の色」をカラフルに変える魔法。でも、この虹には「黒」も混ざっているんだ。
「電気を消したとき(スイッチOFF)」と「服がたまたま黒くなったとき」は、どちらも画面が真っ暗に見えるよね。
今のコードは、この2つのリズムが重なっているから、違いがわかりにくくなっているんだよ!
ためしに strength の方を消してみると、「光はずっと出ているけど、色が順番に黒くなっているだけ」なのがわかるはずだよ!

🚀 第4回の予告:増殖の魔法:forループ
スポンサーリンク
