たった1つのプログラムを、ロボットに「100回やって!」と命じるだけ。
プログラムだからこそ描ける「圧倒的な数」。その扉を一緒に開いてみよう!
コピー機「forループ」
1つずつコードを書くのは大変だけど、ロボットに「この作業を10回繰り返して!」と命令すれば、一瞬でたくさんの宝石が並びます。それが「for(フォー)ループ」というコピー機なんだ!
コピー機の使い方はとってもシンプル。カッコの中に「3つの約束」を書き込むだけだよ!
// ここに「増やしたいプログラム」を書く!
}
float i = 0.0;
背番号 0 からスタート!
i < 8.0;
8個たまるまで続けるよ。
i++
1回終わるごとに番号を+1
インクリメントって呼ぶよ。
プログラミングの数え方は、1じゃなくて 0(ゼロ) から始まるのが普通なんだ。背番号 0番から 7番まで、合計 8回のコピー作業をロボットにお願いしているよ!
プログラミングで一番よく使うのは、= がつかない <(未満) です。
「8 未満」のとき
0, 1, 2, 3, 4, 5, 6, 7
ぴったり 8回
8になった瞬間、
「あ、8はダメなんだ」
と終わるイメージ!
「8 以下」のとき
0, 1, 2, 3, 4, 5, 6, 7, 8
合計 9回
8もOKだから、
もう1回おまけで
動いちゃうんだ。
なぜ fract(タイル)じゃダメなの?
第2回で学んだ fract は、画面をきれいに切り分ける「タイルの魔法」だったよね。
でも、この魔法には苦手なことがあるんだ。
それは…… 「重なり(かさなり)」ができないこと!
fract(タイル) で分けた画面は、ひとつひとつが独立した「個室」のようなもの。
- 右の部屋の丸が、左の部屋にはみ出すことはできない。
- 部屋をまたいで星を重ねることもできない。
これだと、規則正しい「壁紙」は作れるけど、何枚も壁紙を重ねるには、不便なんだ
「ステッカー」を重ねるように描く
そこで登場するのが for ループ!
これはタイルを貼るのではなく、透明なフィルム(ステッカー)を何枚も重ねていくイメージだよ。
実践!「1個のプログラム」を「コピー機」へ
3つのステップで、コピー機を組み立てていこう!
たくさんの光を足し合わせるために、まずは float finalStrength = 0.0; という空っぽのバケツ(変数)を作っておくよ。ループの中で見つけた光を、1つずつこの「バケツ」に放り込んでいく準備だね!
前回作った宝石のプログラムを、まるごと for(...) { ... } の中に入れちゃうんだ。これでロボットが何度も同じ計算を繰り返してくれるようになるよ。
ここが一番の魔法! 計算した光をバケツに「追加」するために、
finalStrength += strength; と書き換えようfinalStrength = finalStrength + strength;と同じことだよ。
これで光がどんどんバケツに入っていくよ!
📍住所をちょっとずつズラす
for ループの中で使っている 「i 」
こいつの正体は、ロボットがコピー機をまわすたびに、カチッ、カチッと増えていく「カウンター」なんだ。
大事なのは、「1回ごとに i の数字が違う」ということ!
この「回数によって変わる数字」を計算に使うことで、1回めはここ、2回めはちょっと横……というズレが作れるんだ。
// 1回め(i=0.0) ➔ uv + 0.0 (そのまま)
// 2回め(i=1.0) ➔ uv + 0.1 (0.1だけズレる)
// 3回め(i=2.0) ➔ uv + 0.2 (0.2もズレる!)
STEP 1 住所(q)をズラす:
i を使って「今から形をかく中心地」をズラした新しい住所 q を作ります。
STEP 2 距離(d)を測る:
ズレた後の住所 q からの距離を測ることで、そこに形が現れます!
vec2 q = uv + i * 0.1;
// 📍 STEP 2: 「ズレた住所」から中心までの距離を測る
float d = length(q);
// ➔ これで、回数ごとに違う場所に宝石が現れるよ!
📍float strength = 0.01 / d; にするよ!
なぜ数字を「0.01」に下げるの?
第3回の宝石のときは、光の強さが「0.1」でちょうどよかったよね。でも、今回はループを使って懐中電灯を「8本」に増やしました!
1本のとき(第3回)
光の太さ:0.1
ちょうどいい明るさ!
8本のとき(今回)
全部 0.1 で照らすと…
まぶしすぎて真っ白!
✅ 解決策:
1本あたりの光を「0.01」くらいに細〜くしてあげよう!
改造後の main 関数の中は、こうなるよ!
void main() {
vec2 uv = vTexCoord * 2.0 - 1.0;
uv.x *= u_resolution.x / u_resolution.y;
vec2 uv0 = uv;
// --- 🪄 ここから改造スタート! ---
// STEP 1 空のバケツを用意
float finalStrength = 0.0;
// STEP 2 コピー機(8回まわす)
for (float i = 0.0; i < 8.0; i++) {
uv = fract(uv * 3.0) - 0.5;
// 📍 iを使って住所をちょっとずつズラす
vec2 q = uv + i * 0.1;
// 📍「ズレた住所」から中心までの距離を測る
float d = length(q);
// 📍 0.1のままだと大きすぎるんだ!
float strength = 0.01 / d;
strength *= abs(sin(u_time));
// STEP 3 バケツに光を足していく!
finalStrength += strength;
}
vec3 color = palette(length(uv0) + u_time);
// 最後に「合計された光finalStrength」と「虹色」をガッチャンコ!
gl_FragColor = vec4(color * finalStrength, 1.0);
}
「光の強さバケツ」から「色のバケツ」へ
今のままでも円は動いているけれど、全部同じ色だとちょっと寂しいよね。
そこで、「白黒の光を数える」のをやめて、「色のついた光を混ぜる」という大改造をやってみよう!
バケツを「色用」にかえよう!
これまではバケツに「光の強さ」だけをためたけど、これからは「色つけて」バケツにためるよ。
ループの中で、カウンター「i」を使うよ!
改造後の main 関数の中は、こうなるよ!
void main() {
vec2 uv = vTexCoord * 2.0 - 1.0;
uv.x *= u_resolution.x / u_resolution.y;
vec2 uv0 = uv;
// 【STEP 1】色のバケツを用意!
// 色(vec3)が入る大きなバケツに進化させたよ
vec3 finalColor = vec3(0.0);
// 魔法のコピー機(8回ループ)
for (float i = 0.0; i < 8.0; i++) {
// 【STEP 2】iくんを使って、1枚ずつ違う色をゲット!
// 今の回にぴったりの色を選んで col に入れるよ
vec3 col = palette(length(uv0) + i * 0.4 - u_time);
uv = fract(uv * 3.0) - 0.5;
vec2 q = uv + i * 0.1;
float d = length(q);
float strength = 0.01 / d;
strength *= abs(sin(u_time + i));
// 【STEP 3】バケツに「色」と「光の強さ」をドバドバ足す!
// 8回分がたまって、キラキラの宇宙ができあがる!
finalColor += col * strength;
}
// 最後に色でいっぱいになった、finalColorを画面に出そう!
gl_FragColor = vec4(finalColor, 1.0);
}