【夏休みの自由研究】増殖の魔法:forループ(第4回)

今回のテーマは、「反復(ループ)」
たった1つのプログラムを、ロボットに「100回やって!」と命じるだけ。
プログラムだからこそ描ける「圧倒的な数」。その扉を一緒に開いてみよう!
スポンサーリンク

コピー機「forループ」

1つずつコードを書くのは大変だけど、ロボットに「この作業を10回繰り返して!」と命令すれば、一瞬でたくさんの宝石が並びます。それが「for(フォー)ループ」というコピー機なんだ!

コピー機の使い方はとってもシンプル。カッコの中に「3つの約束」を書き込むだけだよ!

for ( float i = 0.0; i < 8.0; i++ ) {
  // ここに「増やしたいプログラム」を書く!
}
① はじまり
float i = 0.0;
背番号 0 からスタート!
② おわり
i < 8.0;
8個たまるまで続けるよ。
③ つぎへ
i++
1回終わるごとに番号を+1
インクリメントって呼ぶよ。
💡 ワンポイント!
プログラミングの数え方は、1じゃなくて 0(ゼロ) から始まるのが普通なんだ。背番号 0番から 7番まで、合計 8回のコピー作業をロボットにお願いしているよ!

プログラミングで一番よく使うのは、= がつかない <(未満) です。

🤔 8回くりかえしたい時はどっち?
i < 8.0

「8 未満」のとき
0, 1, 2, 3, 4, 5, 6, 7
ぴったり 8回

8になった瞬間、
「あ、8はダメなんだ」
と終わるイメージ!

i <= 8.0

「8 以下」のとき
0, 1, 2, 3, 4, 5, 6, 7, 8
合計 9回

8もOKだから、
もう1回おまけで
動いちゃうんだ。

なぜ fract(タイル)じゃダメなの?

第2回で学んだ fract は、画面をきれいに切り分ける「タイルの魔法」だったよね。
でも、この魔法には苦手なことがあるんだ。

それは…… 「重なり(かさなり)」ができないこと!

fract(タイル) で分けた画面は、ひとつひとつが独立した「個室」のようなもの。

  • 右の部屋の丸が、左の部屋にはみ出すことはできない。
  • 部屋をまたいで星を重ねることもできない。

これだと、規則正しい「壁紙」は作れるけど、何枚も壁紙を重ねるには、不便なんだ

「ステッカー」を重ねるように描く

そこで登場するのが for ループ
これはタイルを貼るのではなく、透明なフィルム(ステッカー)を何枚も重ねていくイメージだよ。

fract:タイル

画面を細かく区切って並べるよ。
「部屋をわける」イメージ!

for:ステッカー
💎

同じ場所に何度も描き足すよ。
「重なって深くなる」イメージ!

実践!「1個のプログラム」を「コピー機」へ

3つのステップで、コピー機を組み立てていこう!

STEP 1 「光のバケツ」を用意する

たくさんの光を足し合わせるために、まずは float finalStrength = 0.0; という空っぽのバケツ(変数)を作っておくよ。ループの中で見つけた光を、1つずつこの「バケツ」に放り込んでいく準備だね!

STEP 2 宝石の計算を for で包む

前回作った宝石のプログラムを、まるごと for(...) { ... } の中に入れちゃうんだ。これでロボットが何度も同じ計算を繰り返してくれるようになるよ。

STEP 3 「=」を「+=」に変える

ここが一番の魔法! 計算した光をバケツに「追加」するために、
finalStrength += strength; と書き換えよう
finalStrength = finalStrength + strength;と同じことだよ。
これで光がどんどんバケツに入っていくよ!

📍住所をちょっとずつズラす

for ループの中で使っている 「i 」

こいつの正体は、ロボットがコピー機をまわすたびに、カチッ、カチッと増えていく「カウンター」なんだ。

🎰 ループの魔法:住所をズラして形をふやす!

大事なのは、「1回ごとに i の数字が違う」ということ!
この「回数によって変わる数字」を計算に使うことで、1回めはここ、2回めはちょっと横……というズレが作れるんだ。

💎 i = 0.0 1回め
💎 i = 1.0 2回め
💎 i = 2.0 3回め
vec2 q = uv + i * 0.1;
// 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 からの距離を測ることで、そこに形が現れます!

// 📍 STEP 1: iを使って住所をちょっとずつズラす
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

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);
}

「光の強さバケツ」から「色のバケツ」へ

今のままでも円は動いているけれど、全部同じ色だとちょっと寂しいよね。
そこで、「白黒の光を数える」のをやめて、「色のついた光を混ぜる」という大改造をやってみよう!

バケツを「色用」にかえよう!

これまではバケツに「光の強さ」だけをためたけど、これからは「色つけて」バケツにためるよ。

光の強さバケツ
🥛
float finalStrength = 0.0;

「光の強さ」という数字を1つだけ入れるバケツ。

色のバケツ
🎨
vec3 finalColor = vec3(0.0);

「赤・緑・青」の数字が3つ入るバケツ。
( 0.0, 0.0, 0.0 ) と書くのと同じだよ!

ループの中で、カウンター「i」を使うよ!

paletteを置く場所で、世界が変わる!

❶ 今までは、ループの外

🖼️ ➔ 👓

8枚かさねた絵の上に、さいごに1枚だけ「色のついたメガネ」をかける感じ。みんな同じ色になっちゃう。

// ループが終わったあとに…
vec3 color = palette(…);

❷ ループの中(今回!)

🌈 ➔ 🎆

1枚かさねるたびに、毎回ちがう色でぬる感じ。色がまざりあって、宝石みたいな「深み」が出るよ!

for (…) {
  vec3 col = palette(… + i * 0.4);
}
💡 なぜ「中」がいいの?:
ループの外だと 背番号 i が使えないから、みんな同じ色にするしかありません。
でも、ループの中(おうちの中)なら i が使えるから、「1枚めは赤、2枚めは青…」とわがままな命令ができるんだよ!

i * 0.4 でズラすと…

🔴+🟡+🔵

1枚目は赤、2枚目は黄色、3枚目は青……と「ちがう色」をかさねるから、色が混ざりあってオーロラみたいになるんだ!

📍 ここが「色の地層」を作るコード!
vec3 col = palette(length(uv0) + i * 0.4 - u_time);

※ さいごに - u_time を追加すると、色がゆっくり流れるようになって、より宇宙っぽくなりますよ!( + u_timeでも実験してね)

💡 なぜ「0.4」なの?:
パレットは「1.0」進むと色が1周して元に戻るルール。だから「1.0」をかけちゃうと全部おなじ色に戻っちゃうんだ。0.4みたいな中途半端な数字をかけることで、「赤 ➔ オレンジ ➔ 青」と、ちょうどいい色の変化が作れるんだよ!
バケツに「色」と「光」をドバドバ足そう!

さいごの計算は、まるで「光の魔法」をまぜるみたい!

🎨 色 (col)
×
🔦 まぶしさ (strength)
🎆 重なる光!
// 🪄 バケツの中身を「色×光の強さ」で満たしていく!
finalColor += col * strength;
🤔 どうして「かけ算(*)」してから「足し算(+=)」なの?
  • かけ算(*): 1本1本のライトを、「どの色で光らせるか」決める工程だよ。
  • 足し算(+=): 作った色をバケツ(finalColor)に「ドバドバ足していく」工程だよ。

改造後の 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);
}

⚠️ロボットも疲れる?ループの限界

ループの数字を大きくしすぎると、ロボットが悲鳴をあげちゃうかも!

🤖✨

ループ 8回 〜 20回

「まだまだ余裕だよ!」

画面はヌルヌル動くし、パソコンも涼しい顔。これくらいが一番きれいに見えるよ。

🤖🥵

ループ 100回 〜

「もう限界…カクカクする…」

計算が多すぎて、画面が止まりそうになったり、スマホがホカホカに熱くなっちゃう。

💡 なぜダメなの?:
画面にある数百万個の小さな点(ピクセル)すべてで、計算しています。 例えばループ100回だと、1秒間に数億個計算してることになるんだ。
たくさんのロボットが働いているけど、それでも数億個は疲れるよね!!

✨ 第5回の予告:ついに完成!「万華鏡」

NEXT STEP

次回はいよいよ最終回!画面に広がる幻想的な万華鏡を作り上げよう。新しい「3つの呪文」をチラ見せするよ。

⭕️ 輪っかの呪文

ただの「丸い光」を、「きれいな輪っか」に変えちゃうよ!

🌌 奥行きの呪文

画面の端っこを暗くして、「立体感」を出すよ。

💎 輝きの呪文

光をギュッと凝縮して、まぶしい輝きを与えるんだ!

スポンサーリンク