ヒープ・Metaspace・スレッド数・開いたままのファイル数という4つの上限を、それぞれ別の壊れ方で突き破る様子を示した図

GCがあるのに、なぜTomcatは落ちるのか ── Dockerで4通りに壊して確かめた

iOSアプリを作っていると、メモリのことを嫌でも意識します。画像を何枚か抱えたまま画面を行き来していたら、ある日突然アプリが消える。クラッシュログを見ても理由がはっきりせず、循環参照を探して回る。そういう時間を過ごした人は多いはずです。 ところが同じ人がJavaでWebアプリを書くと、メモリのことをほとんど考えなくなります。Tomcatに載せて、何ヶ月も動かして、それで困らない。ガベージコレクション(以下GC)がメモリを自動で片付けてくれるからだ、と説明されればそのまま納得してしまいます。 この記事は、その納得が半分しか正しくないという話です。JavaでもメモリリークはしますしTomcatは落ちます。ただしiOSとは落ち方がまるで違い、しかも「落ちた」という言葉の意味さえ違う。最後にDockerでTomcatを立てて、実際に4通りに壊して確かめます。 何を「ゴミ」と見なすかが、そもそも違う まず前提から。iOSとJavaでは、そもそも何をゴミと判定するかの基準が違います。 iOSのARCは、その物を指している矢印が何本あるかを数えます。矢印がゼロ本になった瞬間に解放する、という単純明快な仕組みです。ところがAとBが互いを指し合うと、どちらも1本のまま永久にゼロになりません。誰も使っていないのに、いつまでも居座り続ける。これが循環参照です。しかもiOSはアプリのメモリをディスクに逃がさないので、上限を超えるとOSがアプリごと終了させます。開発中に強く意識させられるのは、こういう作りだからです。 JavaのGCは数えません。見ているのは「出発点から参照を辿って行き着けるか」だけです。出発点になるのは、static フィールドや、いま動いているスレッドが握っている変数など。そこから辿り着けないものは、互いに指し合っていようが構わず、まとめてゴミと見なされます。 だからJavaでは「解放し忘れ」型のリークは原理的に起きません。解放を書く場所そのものが無いし、循環参照も怖くない。ここまでは、GCのありがたさとして語られるとおりです。 GCが捨てるのは「使わないもの」ではない 問題は、この判定基準がもうひとつのことを意味している点です。GCは「もう使わない」を判定していません。判定しているのは辿り着けるかどうかだけです。 この2つは普段ほとんど一致するので混同しがちですが、ずれた瞬間にリークになります。「もう使わない」のに「辿り着ける」ものは、GCから見れば生きているデータです。何度回収しに来ても、大事に守って帰っていく。 代表例が static なコレクションです。 private static final Map<String, byte[]> CACHE = new HashMap<>(); 同じサムネイルを何度も作り直すのは無駄だから、と書いてしまうこの1行。static フィールドはクラスが読み込まれている限り生き続ける出発点そのものなので、上限も期限も付けなければ、入れたものは全部、アプリが動いている間ずっと守られ続けます。 同じ形のバグは他にもあります。Tomcatのワーカースレッドは使い回されるので、ThreadLocal に入れたものは明示的に消さない限り、リクエストが終わってもスレッドに貼り付いたまま残ります。HttpSession に巨大なオブジェクトを詰めれば、セッションが切れるまで消えません。どれも「参照が残っている」という一点で同じ型です。 それでも普段は問題にならない理由 では、なぜ日常的には困らないのか。理由は2つあって、どちらも「GCが優秀だから」とは少し違います。 ひとつは、Webアプリが扱うオブジェクトの大半がリクエストと一緒に生まれてレスポンスと一緒に死ぬからです。世代別GCはそういう短命なものを非常に安く回収できるように作られていて、生き残りが少ないほど仕事が楽になります。普通に書いていれば、勝手に片付いていく。 もうひとつは、サーバーが案外よく再起動されることです。デプロイのたび、設定変更のたびにプロセスが入れ替わる。じわじわ増えるタイプのリークは、顕在化する前にリセットされている。つまり気にせずにいられるのは、リークが育つ前に片付いているからでもあります。 裏を返すと、長期間動き続けるサーバーほど、そして再デプロイを繰り返すサーバーほど、この手のバグが表に出ます。 本当に落ちるのか、Dockerで確かめる ここからは実測です。公式イメージのTomcat 10.1(JDK 17)を立てて、わざとリークするサーブレットを置きます。 docker run -d --name leak-heap -p 18080:8080 \ -e CATALINA_OPTS="-Xmx64m -Xms64m -Xlog:gc" \ -v "$PWD/webapp:/usr/local/tomcat/webapps/leak" \ tomcat:10.1-jdk17-temurin ヒープを64MBに絞っているのは、実サーバーで何時間もかかることを数十秒に縮めるためです。仕組みは変わりません。 置いたサーブレットは、先ほどの上限なしキャッシュそのものです。 @WebServlet("/thumb") public class ThumbnailServlet extends HttpServlet { private static final Map<String, byte[]> CACHE = new HashMap<>(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException { String id = req.getParameter("id"); byte[] thumb = CACHE.get(id); if (thumb == null) { thumb = renderThumbnail(id); // 1枚 1MB のつもり CACHE.put(id, thumb); } Runtime rt = Runtime.getRuntime(); long usedMb = (rt.totalMemory() - rt.freeMemory()) / (1024 * 1024); res.getWriter().printf("cached=%d used=%dMB max=%dMB%n", CACHE.size(), usedMb, rt.maxMemory() / (1024 * 1024)); } } 異なるIDで順に叩いていくと、27リクエスト目で応答が返らなくなりました。ログには java.lang.OutOfMemoryError: Java heap space が出ています。 ...

公開: 2026年7月23日 · Toshihiko Arai
ピアノの鍵盤の図と、鍵盤を横軸にしたジャンプゲームの画面を並べたアイキャッチ

AIにコードを書かせて、音感トレーニングのジャンプゲームを作った ── Vibe Codingで個人開発した記録

ドミソ、と言われれば何も考えずに出てきます。ところがミソシ、と言われると止まります。頭の中で鍵盤を思い浮かべて、ミがあって、ひとつ飛ばしてソで、もうひとつ飛ばしてシ、と数えてようやくたどり着く。レファラも同じで、いちいち数えないと出てきません。 私は音楽アプリを作っていますが、ピアノをきちんと弾いてきた人間ではありません。子どもの頃から鍵盤で遊んでいた人は、たぶんこれを反射で答えます。指がその形を覚えているからで、考えていないから速い。うらやましいと思いつつ、いまさら音階練習をする気にもならない。 その、埋まらないまま放置していた穴を埋めるゲームを作りました。遊んでいるうちに、和音の形が指ではなく体に入る。そういう仕掛けのジャンプゲームです。 立体にしたら、遊びたくなってしまった きっかけは前回の記事です。音楽アプリのマスコット「えびふりゃ」を3D化した話 を書いて、その最後に、ここからゲームにするといった展望が具体的にあるわけではない、と正直に書きました。そのときは本当にそう思っていました。 ところが、立体になったものが跳ねている動画を眺めているうちに、どうしてもこれで遊びたくなってきた。翌日にはジャンプゲームを作りはじめていました。順番が逆だったら、たぶんこうはなっていません。先に「ジャンプゲームを作ろう」と考えたのではなく、跳ねている立体を見てしまったから作りたくなったわけで、この順番は自分では選べないものでした。 まずは傾けて動かす試作から ジャンプゲームと一口に言っても種類があります。横に走って穴を飛び越えるタイプ、足場を伝って上へ登っていくタイプ、決まったリズムで跳ぶタイプ。えびふりゃは丸くてぽよんと跳ねるので、足場を伝って上へ登っていく形が素直だろう、ということで方向が決まりました。 操作は、スマホを傾けて左右に動かす方式にしました。跳ねるのは自動で、プレイヤーがやることは着地する場所を選ぶことだけです。指でボタンを押すよりも、傾けた角度がそのまま横移動になるほうが、体で狙っている感じが出る。最初に組んだ試作はそこだけを確かめるためのもので、手触りは思っていたよりずっと良いものでした。 ここまでは順調でした。問題はこの先です。 音楽と結びつけようとして、最初は失敗した 作っているのは音楽アプリのマスコットが主役のゲームです。せっかくなら音楽と結びつけたい。そう考えて最初に試したのは、足場を踏むと音が鳴り、うまくつなげると得点が伸びる、というよくある作りでした。 これがまったく駄目でした。踏むたびに鳴る音と、得点が入ったときの効果音と、伴奏のような低音が混ざって、何がどうなって褒められているのか分からない。そもそもプレイヤーは「どこを狙って飛べばいいのか」が分からない。音が鳴っているだけで、音楽が遊びの一部になっていないのです。 このとき一度、音の要素を薄める方向に逃げかけました。音を減らせば手触りは整うけれど、それはもう「マスコットが跳ねるだけのゲーム」で、わざわざ音楽アプリのキャラでやる意味がない。逃げるくらいなら、音の使い方そのものを間違えていると考えるべきでした。 効いた一手 ── 画面の横軸を、そのままピアノの鍵盤にする 行き詰まりを抜けたのは、音を「演出」ではなく「地形」にしてからでした。 画面の横方向を、そのままピアノの鍵盤だと考えます。左端が低い音で、右へ行くほど高い音。マスコットが立つ足場は、その鍵盤の上に生えている板です。そして肝心なところは、足場を置く場所を「いま集めている和音の音の上」だけに限る、というルールです。ドミソを集めている場面なら、板はド・ミ・ソの真上にしか現れない。レやファの上には何もありません。 もうひとつ、真上には次の足場を作らないことにしました。これは実際に遊んでみて気づいたことです。同じ音の上に板が続いてしまうと、傾けなくても勝手に登っていけてしまう。それでは横移動する理由がなくなり、和音の並びを体で覚えるどころか、画面すら見なくなる。だから次の板は必ず別の音の上に置く。プレイヤーは「ド、その次はミ、その次はソ」と、鍵盤の上を横に渡り歩くことになります。 そこまで決めてから、和音に含まれない音の上に置いていた「引っかけ」の板も全部消しました。残っているのは常に正解の音だけです。罠を減らすと難易度が下がって間延びしそうなものですが、実際は逆でした。正解しか置かないからこそ、ド・ミ・ソという三つの位置関係が繰り返し体に入る。踏んだ板には、その音が和音の何番目かも小さく表示しています。根っこの音がR、三番目が3、五番目が5。 そしてステージが上がるごとに、集める和音がひとつずつずれていきます。ドミソの次はレファラ、その次がミソシ、ファラド、ソシレ。まさに私が咄嗟に出てこない並びを、順番に踏まされるわけです。自分で自分に課した練習台のような形になりました。 音は、木琴のような柔らかい音色にしました。板を踏むと、その音が根っこの音と重なって鳴る。和音がそろった瞬間には音がまとめて鳴ります。仕組みとしては単純ですが、ばらばらに踏んだ音が最後に和音として鳴る、という順番になっているのが大事なところで、音が答え合わせの役をしてくれます。 何を競うゲームなのか ── 関所と、その鍵 ここでもうひとつ壁がありました。和音を集めると言っても、ド・ミ・ド・ミと二つだけ踏み続けても登れてしまう。それでは何を競うゲームなのか分かりません。 そこで、ステージの境目に関所を置きました。木の柵で行く手が塞がっていて、その和音の音を全部そろえるまで通れない。二つしか踏んでいなければ弾き返されます。ステージが区切られると背景の色も少しずつ変わっていって、登っている実感が出るようになりました。 さらにもう一段、関所には鍵をかけました。和音がそろっても門は自動では開きません。そろえた瞬間だけ普段より高く跳べるようになり、その高跳びでぎりぎり届く位置に金色の鍵が浮かびます。通常のジャンプでは絶対に届かない高さで、横にもずらしてあるので、まっすぐ上がるだけでは取れない。鍵を取ったら、今度は門の鍵穴に自分で当てにいきます。うまく差し込めると錠前が跳ね上がり、柵が左右に割れて飛んでいく。 「和音をそろえる」「鍵を取る」「鍵穴に差す」という三段構えにしたことで、ステージの切り替わりが行為としてはっきりしました。点数が増えていくよりも、自分の手で開けたという手応えのほうが、区切りとしてずっと強い。 3Dで作ったのに、2Dのゲームに見えない問題 見た目のほうでも、途中で大きく作り直した箇所があります。 最初にBlenderで作った画面のモックはかなり気に入っていたのに、実際に動くゲームはそれと似ても似つかないものになっていました。原因は単純で、ブラウザで動かすために形を簡略化しすぎていたのです。せっかく立体にしたマスコットが、ただの丸い絵に戻ってしまっていました。 そこで作り方を変えて、Blenderで丁寧にレンダリングした絵を透過画像として書き出し、ゲームはその絵を並べるだけにしました。マスコットは三つの姿勢と五つの向き、木の板は音ごと、関所も鍵も巨人も、全部あらかじめレンダリングしておきます。動かす側は軽い2Dの処理しかしていないのに、画面に出ているものは3Dの質感そのままという格好です。昔のゲーム機で、あらかじめレンダリングした絵を使って立体的に見せていたのと同じ発想で、いま同じことをやるとこんなに素直に効くのか、というのは作っていて楽しい発見でした。 カメラの動かし方でも一度失敗しています。3Dらしさを出そうとして、マスコットが動くたびに寄ったり引いたりさせたら、画面が落ち着かずゲームとして成立しなくなりました。いまは基本の画角を固定して、和音がそろった瞬間と落ちはじめた瞬間だけ、ぐっと寄るようにしています。演出は常時かけるものではなく、ここぞという瞬間に取っておくものでした。 だんだん複雑になっていく 同じ和音を延々と繰り返していると、当然ながら飽きます。そこで全体を四つの段階に分けて、集める音が三つ、四つ、五つと増えていくようにしました。途中で調も変わるので、それまで白い鍵盤しか出てこなかったところに黒い鍵盤(♯の音)が混ざりはじめます。 背景は、草地から始まって、空、雲の上、成層圏と上がっていき、最後は宇宙です。星がだんだん見えてきて、最終ステージを抜けると宇宙に到達してクリア。全部で28ステージあります。 上に行くほど手強くなるのは音のほうも同じで、最初の三和音は素直な形をしていますが、四つ五つと重なると鍵盤の上での間隔がぐっと広がります。指が届かない、ではなく、傾けて渡りきれない、という形で難しさが出るのが面白いところでした。 邪魔をしにくる巨人 そこそこ登ると、画面の横から巨人がのっそり出てきます。えびふりゃを食べようとしている人間で、フォークを構えて横から突いてきます。 この巨人がいちばん手こずりました。最初は顔だけが上から降りてくる形にしていたのですが、それだと避けようがなくて理不尽なだけ。体を付けて、横からゆっくり出てくる形に作り直しました。それでも当たり判定がちぐはぐで、体に触れてもすり抜けるのに口に入ると即死する、という納得のいかない状態が続きました。 最終的には、当たり判定を目分量で置くのをやめて、レンダリングした絵そのものから輪郭を読み取って作るようにしました。そのうえでルールを整理して、体と腕は硬い壁として押し返され、危ないのは突き出しているフォークの先と、開いた口の中だけ。頭のてっぺんは踏めるようにして、踏まれた巨人はのけぞって画面の外へ倒れていきます。下から腹を突き上げれば体力ゲージが減って、三発で倒せる。 避けるだけの障害物にせず、こちらから倒せる相手にしたのには理由があって、大きな体が足場の前を塞いでしまうと、どうやっても先に進めない場面が生まれてしまうからです。踏めば消える、突き上げれば削れる、という逃げ道を用意しておけば、その心配がなくなります。 速くなったのは、手を動かす部分だけだった このゲームは、コードのほとんどをAIに書かせています。近ごろ Vibe Coding と呼ばれる作り方で、前回の3D化と同じく、私は「こうしてほしい」「それは違う」と言い続けていただけです。実装の速さでいえば、もう人間の出番はほとんどありません。 ただ、丸ごと頼めば同じものが出てくるかというと、それは違いました。素直に頼んだときにAIが持ってきたのは、踏むと音が鳴って得点が入る、あのつまらない案のほうだったのです。上から顔だけ降ってくる理不尽な巨人も、動くたびに寄って画面を壊すカメラも、最初はそういう案が出てきて、私が違うと言って捨てています。 だから、人間の役割は選別だ、という言い方はかなり当たっていると思います。AIがあれこれ出してきて、人間が通す・通さないを決める。ただ、それだけでもない気がしていて、画面の横軸を鍵盤にするという一手は、出てきた候補から選んだものではありませんでした。あれは選別ではなく持ち込みです。 もう少し正確に言うなら、人間が持ち込んでいるのは判断基準のほうなのだと思います。何を良しとし、何を却下するかの物差し。物差しがあるから捨てられるし、ときにはその物差し自体が新しい案を生む。選別しているように見えるのは、その物差しが外から見えたときの姿にすぎません。 そしてその物差しがどこから来たかというと、経験からです。ミソシが咄嗟に出てこないという体感がなければ、あの一手は思いつかなかった。えびふりゃを長いあいだ描いてきていなければ、主役がいなかった。前回わざわざ立体にしていなければ、遊びたいとすら思わなかった。手を動かす部分が一瞬になった一方で、こちらは相変わらず一瞬になっていません。 自分ひとりの痒みのために作れる時代 このゲームは、お金の話でいえば筋の悪い企画です。ミソシを覚えたい人がどれだけいるのか、私にも分かりません。 大きな予算をかけて作るなら、投資を回収しなければならないので、どうしても広い客層に向けて作ることになります。誰にとっても分かりやすいものが残り、私ひとりの半端な痒みに合わせたゲームは、そもそも企画として成立しない。これは誰が悪いという話ではなく、単に採算の構造がそうなっているというだけのことです。 ...

公開: 2026年7月23日 · Toshihiko Arai
連載『手で動かすAI』第3回のカバー。入力の数字が、重み付き和z・活性化という部品の鎖を左から右へ流れて予測yになり、途中の値がメモに残る計算グラフのイラスト

ネットワークの中は、数字が流れる鎖 ── 前向き計算

ここまでで、ニューロン1個(第1回)を重ねてネットワークにすれば、1本の線では割れない模様も分けられる(第2回)ところまで来ました。そのたびに「自動で学習する」を押して結果を眺めてきましたが、よく考えると、ネットワークの中で数字が実際にどう動いているのかを、まだ一度も手で追っていません。この回では、その中身を一歩ずつ追いかけます。地味に見えますが、ここを飛ばすと次回がまるごと宙に浮くので、土台としてきちんと踏みます。 ネットワークは、単純な部品の鎖 構えるような話ではありません。ニューラルネットワークの計算は、ごく単純な部品をいくつもつないだ鎖に、数字を通していくだけです。部品の種類も3つしかありません。何かを掛ける(× で重みをかける)、足す(+ でバイアスや和をとる)、そして曲げる(活性化関数に通す)。この3つの組み合わせだけで、ネットワーク全体ができています。 使うのは、新しく何かを足した鎖ではなく、第1回のニューロン1個です。入力を2つ——果物の大きさ $x_1$ と甘さ $x_2$——受け取り、それぞれに重みを掛けて足し、バイアスを加えて重み付き和 $z$ をつくり、最後に第1回で出てきた活性化に通して予測 $y$ を出す。それだけの鎖です。具体的な数を入れてみましょう。大きさ $x_1 = 2$、甘さ $x_2 = 1$、重みを $w_1 = 0.5$、$w_2 = 1$、バイアス $b = -1$ とします。 左から順に追います。まず重み付き和をつくります。大きさ $x_1 = 2$ に $w_1 = 0.5$ を掛けて $1$、甘さ $x_2 = 1$ に $w_2 = 1$ を掛けて $1$、この2つを足してバイアス $b = -1$ を加えると、$z = 1 + 1 - 1 = 1$ です。次にこの $z$ を、第1回で出てきた活性化に通します。ここでは第1回のデモで使ったシグモイド、どんな数も0から1のあいだへなめらかに押し込む関数を使います。$z = 1$ を通すと、予測は $y = 0.73$。この果物は「0.73くらいりんごらしい」という判定です。 注目してほしいのは、途中の $z$ が、宙に浮いた記号ではなく $1$ という具体的な数として確定していることです。数字を一度通せば、鎖のどの地点にも「いまここはこの数」という値が置かれます。$z = 1$ も $y = 0.73$ も、紙にメモするようにそのまま残しておける。いまはこのメモを残す意味がピンと来なくていいのですが、次回これが主役になります。 ...

公開: 2026年7月23日 · Toshihiko Arai
テンポ2倍おきに並んだ刻みの層が釣鐘型の音量の山の上を滑り、速すぎる端で消えて遅すぎる端から生まれ直すことで無限に加速し続けるビートになることを示す図

無限に速くなり続けるビート ── リセ・リズム

前回のシェパードトーン は、無限に上がり続けるのにいつまでも高くならない音階でした。あの記事の終わりに、切れ目なく滑る連続版はフランスの作曲家ジャン=クロード・リセによる拡張だと書きましたが、リセはさらに一歩進んだ人で、同じからくりを音の高さではなくリズムに移植してしまいました。どこまでも加速し続けているのに、いつまで経っても速くならないビート。リセ・リズムと呼ばれる錯覚で、この連載の「耳のバグ」の棚では、差音 ・ミッシング・ファンダメンタル ・シェパードトーンに続く4本目になります。 テンポにも「オクターブ」がある シェパードトーンの仕掛けを思い出すと、鍵は1オクターブ、つまり周波数2倍の関係にありました。周波数が2倍の音は、耳には「同じ音名の高いド」に聞こえる。この「2倍したら同じ仲間」という性質があったから、成分をこっそり入れ替えても気づかれなかったわけです。 実は、テンポにもまったく同じ性質があります。120BPMの曲に手拍子を打つとき、1拍ごとに打つ人もいれば、2拍に1回でゆったり打つ人も、倍の細かさで刻む人もいます。どれも曲に合っている。テンポ60も120も240も、互いに「同じビートの粗い刻み・細かい刻み」として自然に重なれるのです。周波数の2倍がオクターブなら、テンポの2倍は倍テンポ。リセ・リズムは、この対応関係を使ってシェパードトーンの図式を丸ごとリズムに輸入します。 山は動かさず、刻みの層が山を滑っていく 作り方はこうです。同じ刻みを、テンポ2倍おきに並べた何層も用意します。このデモでは30BPMから960BPMまで、2倍ずつの5層です。そして横軸をテンポ(対数)にとった釣鐘型の音量の山を固定しておき、全層を同じ速さで「速くなる方向」へ滑らせ続けます。 ちょうどよいテンポ帯の真ん中にいる層がいちばん大きく鳴り、速くなりすぎた層は山を下って小さくなりながら右端の無音で消える。同時に左端では、うんと遅い層が無音のまま生まれて、山を登りはじめる。全層がちょうどテンポ2倍まで加速しきった瞬間、層の配置は出発時とぴったり同じに戻ります。1層1層は確かに2倍速くなったのに、全体としては振り出しに戻っている。どの瞬間を切り取っても全層が本当に加速しているので、耳は「速くなっている」と正しく感じ続け、それでいて、いつまで経っても速くはならない。シェパードトーン で音の高さに起きていたことが、そっくりそのままテンポに起きます。 音階版にはなかった、ひとつの難所 ただし、リズムならではの落とし穴がひとつあります。音階版では、各成分は伸ばしっぱなしのサイン波でした。ところがリズム版の層は「刻み」なので、層どうしの拍のタイミングが揃っていないと、パラパラとずれた二重打ち(フラム)が聞こえて、その瞬間に層の存在がバレてしまいます。倍テンポの層たちが自然に混ざれるのは、速い層の刻みのちょうど2発に1発が、遅い層の刻みとぴったり重なっているときだけです。 そこで実装では、層ごとにバラバラのメトロノームを持たせるのではなく、全層の刻みをたった1本の主クロックから導きます。主クロックの位相を $\varphi$ とすると、各層の位相はその $2^k$ 倍($k$ は整数)。どの層の刻みも同じ $\varphi$ が整数に届く瞬間の親戚なので、拍の入れ子は数学的に崩れようがありません。層が端で生まれ直すときも、倍率 $2^k$ を掛け替えるだけなので、揃いはそのまま保たれます。 触ってみる デモを用意しました。釣鐘の山の上を5つの層が滑り続け、右端で消えた層が左端から生まれ直します。下のタイムラインでは、実際に鳴った刻みが1本1本の縦線で流れていき、細かい縦線(速い層)が薄れて消え、まばらな縦線(遅い層)が濃く育ってくるのが見えます。 うまく表示されないときは、デモを別タブで開く 。 おすすめは、鳴らしっぱなしにして「積み上げた加速」の数字を眺めることです。×100、×1000と計算上の加速は積み上がっていくのに、聞こえるビートはずっと同じ速さの帯にとどまります。向きを切り替えると、今度は永遠に遅くなり続けるビートになります。こちらは巨大な機械がどこまでも止まりきらずに減速し続けるようで、また違った不気味さがあります。 この手のエンドレスな加速は、クラブ音楽やDJのビルドアップ、つまり盛り上がりへ向けてテンポ感を煽り続ける場面と相性がよく、電子音楽の制作では実際にリセ・リズムを組み込んだトラックやツールが作られています。リセ自身は1960年代に計算機でこの種のリズムを合成しており、シェパードの音階とあわせて「終わりのない変化」の一族を築きました。 どう実現するか デモの中身は、シェパードトーンのコードの周波数をテンポに読み替えたものです。層 $i$ の位置 $u$(0〜$N$)に対して $$\text{tempo} = T_{\min} \cdot 2^{u}, \qquad a(u) = \tfrac{1}{2}\left(1 - \cos\tfrac{2\pi u}{N}\right)$$ で、テンポと音量が決まります。$a(u)$ は前回と同じ釣鐘で、両端でちょうどゼロ。ここまでは丸写しです。 リズム版で増えるのは、先ほどの主クロックです。刻みを鳴らすタイミングは、主クロックの位相 $\varphi$ を使って「層 $i$ の位相 $m_i\varphi$ が次の整数に届く時刻」を求めて予約します。加速中の $\varphi$ は指数関数で伸びるので、この時刻は近似ではなく式で厳密に解けます。 const N = 5, TMIN = 30; // 30〜960BPM の5層 const bell = u => 0.5 * (1 - Math.cos(2 * Math.PI * u / N)); // 層 i の刻み予約: 位相 m[i]·φ が整数 n に届く時刻を解いて鳴らす const phiNeeded = n / m[i]; const dt = Math.log2(1 + v * Math.LN2 * (phiNeeded - phi) / rate) / v; scheduleTick(now + dt, bell(u) * 2 / N); // v=倍加/秒, rate=現在の主クロック速度 // 端をまたいだ層は倍率を 2^N だけ掛け替えて反対側へ(音量0の瞬間なので無音) if (u >= N) { m[i] /= 2 ** N; } 実装上の勘どころもひとつ。位置 pos は無限に増え続けるので、$2^{\text{pos}}$ がいつか数値としてあふれます。そこで pos が一周($N$)を超えたら、pos から $N$ を引き、全層の倍率を $2^N$ 倍し、主クロック $\varphi$ を $2^N$ で割って帳尻を合わせます。積 $m_i\varphi$ が変わらなければ音はまったく連続のままです。実はこの $\varphi$ の割り忘れが開発中の実バグで、位相が一気に $2^N$ 倍飛んで刻みが止まりました。数値検証を書いていて拾えたバグです。 ...

公開: 2026年7月23日 · Toshihiko Arai
連載『手で動かすAI』第2回のカバー。1本の直線では割れないXOR配置と、ニューロンを重ねて複数の線を組み合わせ曲がった境界で分けるイラスト

1本の線では割れない問題 ── ニューロンを重ねて境界を曲げる

前回、ニューロン1個は平面に1本の直線を引く係だと分かりました。そして最後に、青と赤がぐちゃぐちゃに混ざっていたら1本の線では割れない、という限界にも触れました。今回はその壁を正面から扱います。壁の越え方が、そのままニューラルネットワークの正体であり、この連載でずっと待っていた「AIらしくなる」瞬間です。 1本の線では、どうやっても割れない いちばん有名な意地悪な配置が、XOR(エックスオア)と呼ばれるものです。正方形の4つの角に点を置き、向かい合う角どうしを同じ色にします。左上と右下が青、左下と右上が赤。たったこれだけの4点が、ニューロン1個には解けません。 左の絵のように、線をどこにどう引いても、4つの点すべてを正しく分けることはできません。うまく引けば3点までは合わせられますが、どうやっても最後の1点が反対側に取り残される。青2つを片側に、赤2つを反対側に、という引き方がそもそも存在しないのです。前回の言葉で言えば、この並びは線形分離できません。ニューロン1個の限界に、まっすぐぶつかったわけです。 ところが右の絵を見てください。線を2本使えるなら話は別です。2本の線で青い対角線をはさむように帯をつくれば、その帯の中が青、外が赤、ときれいに分かれます。まっすぐな線を組み合わせるだけで、結果として曲がった境界ができました。これがすべての鍵です。 ニューロンを重ねる では、どうやって線を2本使うのか。答えは単純で、ニューロンを2個並べればいい。1個が1本の線なら、2個で2本です。ただ並べるだけでなく、その2個の出力を受け取って最終的な判定を下す、もう1個のニューロンを後ろに置きます。前に並べた層を隠れ層、うしろの1個を出力層と呼びます。 隠れ層の各ニューロンが、それぞれ自分の線を引いて「その線のどちら側か」を答え、出力層がその答えたちを重みで混ぜて、最終的な色を決める。線を引く係を束ね、その結果をもう一段のつまみでまとめる。この一段重ねるという操作こそ、ニューラルネットワークという名前の中身です。名前が長いので、ここから先は縮めてネットワークと呼びます。 増やすと、境界が曲がる 理屈より、動かした方が早いです。前回と同じXORの配置(対角どうしが同じ色)を、今度はニューラルネットに解かせます。 うまく表示されないときは、デモを別タブで開く 。 最初、隠れニューロンは1個です。この状態で「自動で学習」を押しても、引けるのは1本の直線だけなので、まちがいは0になりません。前回のニューロン1個と同じ壁です。そこで、隠れニューロンの数を2個、3個と増やしてみてください。使える線が増え、「自動で学習」を押すと、塗り分けが直線から曲線へ、さらに入り組んだ形へと変わって、青と赤をぴたりと分けられるようになります。まちがいが0に落ちる瞬間が、壁を越えた瞬間です。 つまみの数が増えるほど、ネットワークが描ける境界は複雑になります。この「複雑な形を描ける度合い」を、表現力と呼びます。ニューロンを重ねて数を増やすことは、表現力を上げることそのものなのです。 中身のかたち 2層のネットの計算も、前回の重み付き和と活性化を2段にしただけです。式で書くと、まず隠れ層の各ニューロン $j$ が自分の線を計算します。 $$h_j = \text{活性化}\left( w_{j1} x_1 + w_{j2} x_2 + b_j \right)$$ そして出力層が、その $h$ をまた重みで混ぜてまとめます。$\sum$ は「$j$ について全部足す」という記号です。 $$y = \text{活性化}\Bigl( \sum_j v_j h_j + c \Bigr)$$ コードにしても、for ループが一段増えるだけです。 // 隠れ層: 各ニューロンが「自分の線」を引く const h = []; for (let j = 0; j < H; j++) { const z = w[j][0]*x1 + w[j][1]*x2 + b[j]; h[j] = Math.tanh(z); // なめらかな活性化 } // 出力層: 隠れの答えを混ぜて最終判定 let z2 = c; for (let j = 0; j < H; j++) z2 += v[j] * h[j]; const y = 1 / (1 + Math.exp(-z2)); // シグモイド コードに出てくる tanh(タンエイチ)は活性化関数のひとつで、前回の終わりに出てきたシグモイドと同じ、なめらかなS字の仲間です(こちらは出力が−1から1に収まります)。デモの「自動で学習」は、この全部のつまみ(w, b, v, c)を、まちがいが減る向きへ一斉に調整しています。層をまたいでつまみを調整するこの計算には、誤差逆伝播という名前がついていて、連載の要になる回で、数値微分からゼロで組み立て直します。今は、重ねれば境界が曲がる、という結果を手でつかめれば十分です。 ...

公開: 2026年7月22日 · Toshihiko Arai
波形を自分自身とずらして重ね、ぴたり一致するずれ幅から基本周期=音程を割り出すピッチ検出(自己相関)の図

マイクの音から音程を当てる ── ピッチ検出(チューナーの中身)

チューナーにギターの弦の音を聴かせると、即座に「A、ちょっと低い」と針が振れます。カラオケの採点も、スマホの楽器チューナーアプリも、やっていることは同じです。マイクに入ってくるのはただの空気の振動なのに、機械はどうやって「いま鳴っている音の高さ」を言い当てているのでしょうか。 種を明かすと、中身は驚くほど単純です。波形を自分自身と少しずつずらして重ね、いちばんよく一致する「ずれ幅」を探す。それだけです。この記事では、自己相関と呼ばれるこの一つの発想から、ブラウザで動くリアルタイム・チューナーを作るところまでをたどります。 ここで少し宣伝を。筆者は個人で音感トレーニングアプリ Harmonize を開発しています。機械が音程を言い当てる仕組みが今回の話だとすれば、あちらは同じことを自分の耳でできるようになるための練習アプリで、ちょうど裏表の関係にあたります。 Harmonize 相対音感と純正律の感覚を鍛える音感トレーニングアプリ 今すぐ無料でダウンロード App StoreGoogle Play 音の高さの正体は「くり返しの速さ」 出発点は、この連載の最初の記事 で見た「音は数の列」という事実です。マイクは1秒あたり44100個のペースで「いまの空気の圧力」を数として拾っていきます。プログラムから見た音とは、この数の列がすべてです。 そして、高さのある音——声や楽器の音——の波形には、共通のはっきりした特徴があります。同じ形が周期的にくり返されるのです。1秒に440回くり返せばラ(A4)、262回ならド(C4)。この「1回のくり返しにかかる時間」を基本周期と呼び、くり返しの回数が周波数、つまり音の高さです。逆に「サーッ」というノイズのような音にはくり返しがなく、だから高さも感じられません。 つまり「音の高さを当てる」という問題は、「数の列から、くり返しの間隔を見つける」という問題に化けます。ではその間隔を、どうやって測ればよいのでしょうか。 ずらして重ねると、周期が浮かび上がる ここが今回の核心です。波形のコピーをもう一枚つくり、元の波形の上で少しずつ横にずらしながら、どれだけ重なっているか(一致度)を測っていきます。 中途半端なずれ幅では、山と山が食い違ってバラバラです。ちょうど半周期ずらすと、山と谷が正面衝突して「逆向きに一致」します。そして、ずれ幅がちょうど1周期ぶんに達した瞬間——波形はぴたりと自分自身に重なります。同じ形がくり返されているのだから、くり返しの間隔ぶんずらせば元どおりに見える、というわけです。 この「ずれ幅を横軸、一致度を縦軸」にしたグラフを描くと、次のようになります。 一致度のカーブに、くっきりした山が立ちます。最初の大きな山の位置が、探していた基本周期です。ずれ幅0の場所にも山がありますが、これは「ずらしていない=自分自身とくらべた」だけなので除外します。山の位置が T サンプルなら、1秒=44100サンプルの中にくり返しが $44100 \div T$ 回入る計算なので、それがそのまま周波数になります。図の例では T=100 で441Hz。いちばん近い音名はラ(A4=440Hz)で、ぴったりではなく少しだけ高い、というところまで分かります。 この「少しだけ高い」を数字にする単位がセントです。半音1個ぶんをさらに100等分した音程のものさしで、441Hzはラより約4セント高い。チューナーの針が示しているのはこのセント値で、±数セントに収まれば実用上「合っている」と扱われます。ちなみに半音の幅が周波数の何倍にあたるかは平均律の回 でやったとおり $2^{1/12}$ 倍。セントはそれを100分割しているので、1セント=$2^{1/1200}$ 倍という比の単位です。 こうして道具立てが出そろいました。ずらして重ねて山を探すと基本周期が出る。周期を周波数に直し、いちばん近い音名とのずれをセントで示す。チューナーの中身は、これで全部です。 触ってみる 実際に動くチューナーを用意しました。入力は2系統あって、マイクのボタンを押せば自分の声や楽器で、テスト音源のボタンを押せば内蔵オシレータの音で試せます。マイクはブラウザの決まりで https か localhost のページでしか使えないため、使えない環境では自動でテスト音源に切り替わります(このページ上ならそのまま使えるはずです。マイクの音がスピーカーから鳴ることはありません)。 うまく表示されないときは、デモを別タブで開く 。 いちばん下のグラフが、さきほどの「ずれ幅ごとの一致度」を毎瞬描き直しているところです。テスト音源の周波数スライダーを動かすと、山の位置が左右にすべっていき、針とグラフが連動して動くのが見えます。スライダーの右側には鳴らしている音の理論値を出してあるので、検出結果との答え合わせもできます。マイクで声を「あー」と出しながら、ゆっくり高さを上げ下げしてみるのも面白いところです。声のような複雑な波形でも、くり返してさえいれば山はちゃんと立ちます。 ひとつ試してほしいのが、波形をノコギリ波や矩形波に切り替えることです。倍音の回 で見たとおり、波形が変わると音色はまるで違いますが、くり返しの間隔そのものは変わりません。だから一致度の山の形は変わっても、山の位置は同じ——音色が違っても音程は同じ、という当たり前の事実を、アルゴリズムの側から眺め直せます。 どう実現するか 一致度の計算は、正体を見るとただの掛け算と足し算です。波形の数の列を $x[n]$ とすると、ずれ幅 $\tau$(タウ)での自己相関は $$r(\tau) = \sum_{n} x[n] \cdot x[n+\tau]$$ と書けます。各時点の値と、$\tau$ サンプル先の値を掛けて、ぜんぶ足すだけ。波形が重なっていれば「プラス×プラス」と「マイナス×マイナス」ばかりになって合計は大きくなり、ずれていればプラスとマイナスが打ち消し合って小さくなります。「掛けて足す」が一致度の測定器になっているわけです。コードにしてもこれだけです。 /* 自己相関でピッチ検出(C風の擬似コード) */ double best = 0; int bestLag = 0; for (int lag = minLag; lag <= maxLag; lag++) { /* 試すずれ幅の範囲 */ double r = 0; for (int n = 0; n + lag < N; n++) r += x[n] * x[n + lag]; /* ずらして、掛けて、足す */ if (r > best) { best = r; bestLag = lag; } } double f0 = fs / (double)bestLag; /* 山の位置 → 周波数 */ 実用のチューナーにするには、これに手当てを少し足します。上のデモでは、一致度を −1〜+1 に正規化する、無音のときは判定しない、2周期・3周期の位置に立つ同じくらい高い山(オクターブ下と間違える原因になります)を避けて「最大値の9割を超える最初の山」を選ぶ、山の頂点を前後の点から放物線で補間して小数サンプルの精度まで詰める、という4つを入れています。最後の補間は地味に重要で、これがないと44100分の1秒刻みでしか周期を測れず、高い音ほどセント単位の精度が出ません。 ...

公開: 2026年7月22日 · Toshihiko Arai
Three.jsで作った夏の海岸。湾曲した砂浜と波打ち際、草原と低ポリの木、きらめく海

ブラウザで歩き回れる海辺を、AIに作ってもらった

前回の記事 で、長年アプリのマスコットだった「えびふりゃ」をAIに3D化してもらいました。最後はブラウザの中を歩き回れるところまで行って、そこで欲が出ました。歩けるなら、世界が欲しい。世界があるなら、遊びが欲しい。 というわけで今回は釣りゲームです。えびふりゃが海岸で竿を投げて魚を釣る。そういうものをブラウザで動かしたい。進め方は前回と同じで、コードはすべてAIに書かせて、私は指示とダメ出しだけ。使ったのはClaude Code(Anthropicのコーディング用AI)のフェイブル5です。技術はThree.js(ブラウザで3Dを描くJavaScriptライブラリ)にしました。UnityのようなゲームエンジンではなくWebを選んだのは、ビルドが要らず、URLを開くだけでスマホからでも確認できて、直しの往復が速いからです。 今回のように「コードは全部AIに書かせて、自分は注文とダメ出しに徹する」進め方は、少し前ならうまく回りませんでした。フェイブル5も登場した当初は評判がいまひとつでしたが、その後の調整でずいぶん使い勝手が良くなっています。一つ頼むたびに手を止めて確認を求めてくるのではなく、一時間規模のまとまった作業をそのまま任せておける。裏でずっと手を動かしていてくれる感覚に近く、細かい指示の往復や不毛なやり取りが減りました。正直、これに慣れるともう元のやり方には戻れません。もっとも、この調子で回すと消費も速く、数日もあればひと区切りの枠を使い切ってしまうくらいには働いてもらっています。 先に言っておくと、釣りの仕組みそのものは数時間でできました。大変だったのはその後です。キャラの体と、そして海。特に海には三度負けました。この記事はそのつまずきの記録です。 「すごいひどいことになってますから」── AIは自分の画面を見ていない 最初の釣りプロトタイプは威勢がよかった。キャスト、アタリ、糸のテンションを見ながらの駆け引き、釣果と表情のリアクション。仕組みは一通り入っています、URLをどうぞ、と来たので開いてみたら、キャラは画面からはみ出すほど巨大で、顔は見切れ、竿は宙に浮いていました。 考えてみれば当たり前で、AIはコードを書いてはいるけれど、レンダリングされた画面を一度も見ていないのです。3D空間のカメラ位置もキャラの大きさも、数値の上でつじつまが合っていれば「できました」と言ってくる。前回のモデリングでは「レンダリングして元絵と重ねて検証してから見せて」という約束で回っていたのに、ゲームになった途端そのループが消えていました。 そこで「自分で画面を確認できるようにしてくださいよ」と頼んだところ、AIはヘッドレスブラウザ(画面を持たないブラウザ)でゲームを開き、スクリーンショットを撮って自分で見る仕組みを作りました。これで開発が一変します。以降のやりとりでは、AIが自分で撮った画像を見て「キャラが地面に埋まっていました、直します」と自己申告してくるようになりました。埋まっているのを見つけるのが私の仕事から、AIの仕事になったわけです。 その仕組みで自己修正を重ねた結果が、いちおう遊べる最初の釣り画面です。長押しでパワーを溜めて離すとウキが飛び、アタリの「!」でタップ、魚が走ったら指を離し、落ち着いたら巻く。釣れれば魚種とサイズが出て、キャラが表情を変えて跳ねる。 操作の「当たり前」は、言わないと伝わらない 次に世界を歩けるようにしました。散歩モードと釣りモードを分け、波打ち際に近づくとキーひとつで釣りが始まる。ここでも細かい攻防があって、たとえば最初の移動操作は「Wで画面の奥へ、Aで画面の左へ」という、カメラ基準の平行移動でした。Dで右を向いたら、進むのはWですよね、という「よくある3Dゲームの操作」は、明示的に言うまで出てきませんでした。 もうひとつ面白かったのはカメラのバグです。キャラが向きを変えると、カメラが背後へ回り込む瞬間に一度ズームインしてしまう。AIに調べさせると、カメラの位置を直線で補間していたのが原因でした。円弧の上を回るべきところを直線で近道するので、旋回中だけキャラに寄ってしまう。角度を補間するよう直して解決です。言われてみればそれはそうだ、という話ですが、こういう「触ると気持ち悪いが、コード上は間違っていない」類のバグは、実際に触った人間が指摘しないと直りませんでした。 お手本のURLを渡したら、世界が変わった このころの見た目は、砂色の床と青い板が並んでいるだけの、いかにも工作然としたものでした。言葉で「もっと綺麗に」「夏っぽく」と言っても、良くなる気配がない。そこで方針を変えて、私が理想とする世界観のWebGL作品、Vicente Lucendo氏のSummer Afternoon のURLをそのまま渡しました。「このような世界観でできないものなの?」と。 効果は劇的でした。AIは作品を特徴ごとに分解して──暖色のパレット、グラデーションの空、風でなびく大量の草、様式化された水、柔らかい光──それを一つずつ実装に落とし始めました。百回言葉で注文するより、見本をひとつ渡すほうが早い。人間のデザイナーに頼むときと同じでした。 余談ですが、このSummer Afternoon、ただ眺めるだけでなく歩き回って遊べて、マップのどこかに秘密が5つ隠されています。探しはじめると止まらなくなって、私は4つまで見つけたものの、最後のひとつがどうしても見つかりませんでした。この世界に憧れて今回の海辺づくりが始まったので、スクリーンショットを貼るより、ぜひリンクから本物を歩いてみてほしいです。開くだけですぐ動きます。 人間キャラは、木偶にしかならなかった 世界観を変えるにあたって、キャラは「無理にえびふりゃじゃなくてもいい、少年にしよう」ということになりました。麦わら帽子の男の子が海辺で釣りをする。いいじゃないですか。 ところが、これが今回いちばんはっきりした失敗になりました。AIは円柱や球を組み合わせて少年を作り、関節の位置でパーツを回して歩かせようとします。出てきたものは、腕と脚の生えた木の人形が滑っていくような、見ていて萎える動きでした。おまけに一度は頭が丸ごと消える事故まで起きています(内部の座標変換が壊れて、頭のグループだけ描画されなくなっていました)。 AIに「抜本的に品質を上げるにはどうすればいいか」と正面から聞いたところ、返ってきた答えは率直でした。人型が自然に動いて見えるためには、骨格(リグ)と、骨に皮膚が追従して変形する仕組み(スキニング)と、ちゃんと作られた歩行アニメーションの三点セットが必要で、これはプリミティブの寄せ集めをコードでどう頑張っても埋まらない。つまり自作をやめて、リグとアニメーションが最初から入っているモデルを使うべきだ、と。 ライセンスを調べさせると、これがなかなか勉強になりました。AdobeのMixamoは商用無料だけれど素材単体の再配布が禁止で、ブラウザゲームは配信ファイルが誰でもダウンロードできてしまうためグレー。一方Quaternius などのCC0(実質パブリックドメイン)素材なら、商用でも帰属表示なしで完全に自由。Webで出すならCC0一択という結論です。 ちなみに、「無料で自由に使えるキャラクター」の代表格としてくまモンを思い浮かべる人もいるかもしれませんが、あれはCC0ではありません。くまモンの著作権は熊本県が持っていて、商品や広告など商用で使うには県への利用許諾の申請が要ります(許可されれば利用料はかからず、熊本のPRになることが条件です)。一方で、個人が非営利で楽しむぶんには申請もいらず、SNSや個人ブログへの掲載などかなり自由に使えます。CC0の「誰でも無条件・無申請で完全に自由」とは似て非なるもので、あの気前のよさは権利を手放しているのではなく、熊本県が許諾制度でうまく開放しているからなんですね。だからこの記事にも、くまモンの画像そのものは載せていません(この記事は自作アプリの宣伝も兼ねているので、勝手に使うなら許諾が要る側です)。 ひとまず確実に入手できたCC0のロボット(Three.js公式サンプルのRobotExpressive。歩き・待機・ジャンプなどのアニメーション入り)を組み込んでもらいました。移動すれば歩きへ、止まれば待機へなめらかに切り替わり、釣果が出ればワンショットで喜ぶ。木偶人形との差は歴然でした。少年の見た目は失いましたが、「動きの質は素材で買う」が正解だと納得した瞬間です。 木も草も、自作をやめてフリー素材に頼る キャラで学んだことは、そのまま背景にも効きました。AIが球と円柱で作った木はどう見てもおもちゃで、草に至っては「尖った針がゆらゆら動いている」ようで気持ち悪い。ここもQuaterniusのSimple Nature(CC0)に差し替えることにして、私がサイトからFBXファイルをダウンロードし、AIに渡しました。 ここで小さな学びがふたつ。ひとつは形式の話で、配布されていたのはFBX・OBJ・Blenderファイルの3種でしたが、Web(Three.js)で使うならglTF/glbが本命、なければFBXをBlenderで変換すればよく、OBJは避ける、.blendは自分で編集しない限り不要。つまり「今後はglbかFBXだけ落とせばいい」。 もうひとつは罠の話。FBXをglbに変換して組み込んだのに、木がまったく表示されない。AIが調べたら、変換の過程でマテリアルの不透明度が0(完全に透明)になっていました。木は透明人間として、ちゃんとそこに立っていたわけです。不透明に戻して一件落着。草は1本のモデルを1万5千本、インスタンシングという手法でまとめて描画し、シェーダーで根元を固定したまま穂先だけ風に揺らしています。 海との戦い その一 ── 画面が真っ黒になる呪い さて海です。私はこのゲームの主役は海だと思っています。普段海を見ない人が、画面の中の海を見てちょっと嬉しくなる。そういうものにしたかったので、「キラキラ水面に光る波、太陽の感じ、空の綺麗な青。ここは絶対に維持してほしい」と注文しました。 AIは水面のシェーダー(画素ごとの色を計算するプログラム)を書き、太陽へ向かう反射帯にきらめきを集める表現を作りました。これ自体は綺麗にできたのですが、その過程で妙な事故が続きます。発光表現のためのブルームという後処理を入れた途端、画面全体が真っ黒になるのです。 原因究明は探偵ものみたいで面白かった。AIは容疑者を一つずつ消していきました。花粉のパーティクルの計算に数値エラー(NaN、非数)を見つけて直す──まだ黒い。水面を消してみる──黒いまま。後処理を切る──直った。つまりブルームが、シェーダーのどこかで生まれるNaNを画面全体に塗り広げていた。数値のエラーが一画素でもあると、ぼかし処理がそれを全画面に拡散してしまうのです。結局ブルームは諦めて、きらめきはシェーダー側で表現することになりました。 海との戦い その二 ── 水際のチートは、ぜんぶバレる 次は波打ち際です。最初の実装は、砂浜と海の境界に白い帯を一本置いただけのもので、「境界線が一直線。こんなことありえない」と私が却下。ではとAIが持ってきたのが、水面の手前の縁だけをくねくね波打たせる方式でした。 これが今回いちばんの見ものだったかもしれません。スクリーンショットを撮ると、波の先端が蛇のようにうねっているだけで全然波じゃない。しかも波の先端と海本体の間に、なぜか砂浜が見えている。水が分裂しているわけです。私が写真を突きつけると、AIは「チートがバレましたね」と白状して、根本からやり直すことになりました。 やり直しの方針は、ごまかしをやめることでした。砂浜を、実際に海へ向かって下る斜面として作る。波で上下する水面がその斜面と交わる線が、すなわち波打ち際になる。水際線を「描く」のではなく、地形と水面の交差から「生まれさせる」わけです。こうすると、うねりで水面が持ち上がれば水際は自動で砂浜側へ押し寄せ、引けば濡れた斜面が現れる。水と砂の間に隙間ができようがありません。 この方式に変えた直後、もうひとつ豪快なバグが出ました。海と砂浜の間に、幅の広い謎の白帯が現れたのです。調べたら、地面のポリゴンが波打ち際の手前で終わっていて、引き波のときに水際がその先へ下がると、そこには地面が存在せず、透けた水の向こうに背景の空が見えていた。白帯の正体は泡ではなく「世界の穴」でした。地面を海の中まで延長して海底を作り、ついでに浅瀬は水深に応じて水を透明にしたら、砂→濡れ砂→砂が透ける浅瀬→青、という本物の海辺のグラデーションになりました。 おまけに、海底ができたことで副産物がありました。それまでキャラは波打ち際までしか歩けなかったのですが、地面が海中まで続いたので、そのまま海に入って腰まで浸かれるようになったのです。 ...

公開: 2026年7月21日 · Toshihiko Arai
連載『手で動かすAI』第1回のカバー。ニューロン1個の構造(入力×重み→重み付き和→活性化→出力)と、平面上の2グループを分ける境界線のイラスト

ニューロン1個は何をしているのか ── 重みで混ぜて、境界線を1本引く

前回 は、散らばった点に直線を1本当てはめ、つまみ(傾きと切片)を回して誤差を減らすところまでをやりました。「学習とはつまみ回しである」という、連載全体の背骨です。今回はそこから一歩進んで、いまのAIをかたち作っている最小の部品、ニューロンを1個だけ取り出して、その中で何が起きているのかを見ます。名前はいかめしいですが、やっていることは前回とほとんど地続きです。 ニューロンは「重みで混ぜて、発火するか決める」だけ ニューロンは、いくつかの入力を受け取って、一つの出力を返す小さな部品です。中身は3ステップしかありません。 まず、それぞれの入力に重みをかけます。重みは、その入力をどれだけ重く見るかを決める数で、前回のつまみ(傾き)を入力の数だけ増やしたものだと思ってください。次に、重みをかけた入力を全部足し合わせ、そこにバイアスという下駄を1つ加えます。これで重み付き和という一つの数になります。最後に、その数がある基準を超えたかどうかで出力を決めます。この最後のひと押しを活性化と呼びます。いちばん素朴な活性化は、0を超えたら1(発火)、超えなければ0、というステップ状の切り替えです。 式にすると、これだけです。入力の数だけ「重み × 入力」を足して、バイアス $b$ を加える。 $$z = w_1 x_1 + w_2 x_2 + \cdots + b$$ そしてニューロンの出力は、 $$y = \text{活性化}(z)$$ 重み $w$ とバイアス $b$ が、このニューロンのつまみです。入力 $x$ は与えられたデータなので動かせませんが、つまみは学習で回せる。ここが前回とまったく同じ構図です。 ニューロン1個 = 平面に引いた1本の線 つまみが何をしているのかは、入力が2つの場合を絵にすると一目で分かります。ただ、その前に抽象的な話を一度、地面に下ろしておきます。 これから出てくる点の一つひとつは、判断したい対象を1個だと思ってください。たとえば果物の仕分けなら、点1つが果物1個です。横軸 x1 をその大きさ、縦軸 x2 を甘さとすれば、点の位置は「その果物の大きさと甘さ」を表します。そして点の色が正解で、青はりんご、赤はみかん、というぐあいです。x1 や x2 のような入力は、対象から測った特徴のことだと考えると腑に落ちます。入力が3つなら、測る特徴が3つに増えるだけです。 ニューロンがやりたいのは、この大きさと甘さから「どっちの果物か」を当てることです。重み w1 は、その判断で大きさ(x1)をどれだけ重視するかを決めるつまみだと考えてください。w1 を大きくすれば大きさで強く決め、小さくすれば甘さ(x2 とその重み w2)の方を重く見ます。この2つの重みとバイアスで、平面上に1本の境界線が決まり、その片側を青、反対側を赤と判定します。線の向きは重みが、位置はバイアスが決めます。 言葉より動かした方が早いので、デモを用意しました。青と赤の点を、ニューロン1個の境界線で分けてみてください。 うまく表示されないときは、デモを別タブで開く 。 3つのつまみ(重み2つとバイアス)を動かすと、平面の塗り分け、つまりニューロンの判定と、境界線が動きます。目標はギリギリを狙うことではなく、青が片側・赤が反対側にそろって、まちがいが0になることです。それさえ満たせば、線の引き方はどれでも正解です。手で合わせてもいいですし、「自動で学習」を押せば、前回とまったく同じ勾配降下が、まちがいの減る向きへ3つのつまみを回して境界線を運びます。線を引く部品が、線の引き方を自分で見つける。これがニューロン1個の学習です。 ここで当然の疑問が浮かびます。もし青と赤が、きれいに片寄らずぐちゃぐちゃに混ざっていたら、どうなるのでしょう。答えは、1本の線ではどうやっても分けられない、です。これはデモの不備ではなく、ニューロン1個の正真正銘の限界です。まっすぐ1本の線で二分できる並び(専門的には線形分離可能といいます)しか、ニューロン1個では扱えません。そして、この「1本では割れない並び」を扱えるようにする工夫こそが、次回への入り口になります。 中身のコード ニューロン1個の計算は、本当にこれだけです。飾りのデモ用ではなく、いまのAIの内側でも同じ計算が延々とくり返されています。 // 入力 x1, x2 を重み w で混ぜ、バイアス b を足す const z = w1 * x1 + w2 * x2 + b; // 重み付き和 // 基準を超えたら発火(ステップ活性化) const y = z > 0 ? 1 : 0; 学習は前回と同じで、まちがいが減る向きへ w1, w2, b を少しずつ動かすだけです。ひとつだけ実用上の都合を挟むと、上のステップ活性化は超えたか超えないかでカクッと切り替わるため、勾配(傾き)が測れません。そこで実際にはもっと滑らかな活性化を使います。このデモでも、なめらかなS字を描くシグモイドという関数を活性化に使って、勾配降下が効くようにしています。活性化の種類の話は、あとの回でもう少し掘り下げます。 ...

公開: 2026年7月21日 · Toshihiko Arai
オクターブ違いの成分が釣鐘型の音量の山の上を滑り、上端で消えて下端から生まれ直すことで無限に上がり続ける音階になることを示す図

無限に上がり続ける音階 ── シェパードトーン

階段を上がっているのに、いつまでも同じ階に戻ってきてしまう。エッシャーのだまし絵に出てくるあの無限階段を、音で作ったらどうなるでしょうか。実は本当に作れます。ドレミファソラシド、と半音ずつ確かに上がり続けているのに、1分聴いても10分聴いても、いっこうに「一番上」に着かない音階。シェパードトーンと呼ばれる音の錯覚で、1964年に心理学者ロジャー・シェパードが計算機で合成して見せたものです。 この連載ではこれまでに差音 とミッシング・ファンダメンタル 、2つの「鳴らしていない低音が聞こえる」錯覚を扱ってきました。今回はその棚の3本目です。前の2本が耳の作る幻の低音だったのに対し、こちらは耳が音の高さをどう測っているかの隙を突く錯覚で、種明かしを知ると、プログラムでの作り方まで一直線につながります。 上がり続けるには、どこかで戻るしかないはず まず、なぜこれが不思議なのかをはっきりさせておきます。音の高さを切れ目なく滑らかに変えることをグリッサンドといいますが、サイレンのように1本の音をグリッサンドで上げ続けると、いつかは必ず限界が来ます。楽器の音域の上限か、耳に聞こえる周波数の上限か、いずれにせよ天井はある。上がり続けたければ、どこかで下へ戻るしかありません。そして戻った瞬間の「ヒュッと落ちる」は、誰の耳にもはっきり聞こえてしまいます。 シェパードトーンの答えは、右の図のほうです。音を1本ではなく、1オクターブずつ離した何本もの成分の束にする。そして「戻る」瞬間を、音量がゼロになる端っこに隠してしまう。落ちる音が聞こえないのは、落ちるときにはもう聞こえない小ささになっているからです。 この仕掛けが成立するには、耳の側にも条件がひとつ要ります。それが次の話です。 耳は「どのオクターブか」を音量の重心で聞いている 平均律の回 でやったとおり、1オクターブ上の音とは周波数がちょうど2倍の音のことです。そして人間の耳にとって、オクターブ違いの音はただの「別の高さ」ではありません。ピアノにドは8つほどありますが、どれを弾いても私たちは同じ「ド」だと感じます。音の高さには、ドかレかソかという音名の顔と、それが低いドか高いドかというオクターブの顔、2つの顔があるわけです。 ここで、同じ音名のドを、低いドから高いドまで全オクターブぶん一度に鳴らしたらどうなるか。音名のほうは文句なしに「ド」です。ところがオクターブのほうは、材料が全部の高さに散らばっているので、決め手がありません。このとき耳は、成分の音量がどのあたりに集まっているか、いわば音量の重心で「だいたいこの高さのド」と聞き取ります。逆に言えば、重心さえ動かなければ、中身の成分がこっそり入れ替わっても、耳には同じ高さ帯の音に聞こえ続けるということです。シェパードトーンは、この性質を正面から突きます。 山は動かさず、成分だけが山を滑っていく 作り方はこうです。1オクターブおきに並べた成分たちに、周波数に応じた音量の山を掛けます。山は釣鐘型で、真ん中の高さ帯がいちばん大きく、低すぎる端と高すぎる端ではちょうどゼロ。つまり両端は無音です。この山は最後まで固定しておいて、成分たちだけを全員一斉に、同じ速さで上へ滑らせます。 図の赤い1本を追ってみてください。出発時に上のほうにいた成分は、上がるにつれて山を降り、どんどん小さくなりながら右端へ抜けて消えます。同時に左端では、新しい成分が無音のまま生まれて、山を登りはじめる。そして全員がちょうど1オクターブ上がりきった瞬間、成分の配置は出発時と寸分たがわず同じになります。1本1本は確かに1オクターブ上がったのに、スペクトル全体としては振り出しに戻っている。音量の重心は、最初から最後までぴくりとも動いていません。 あとはこれを繰り返すだけです。どの瞬間を切り取っても、すべての成分は本当に上昇しているので、耳は「上がっている」と正しく感じ続けます。一方で、1オクターブ上がるごとに全体は元どおりになるので、いつまで経っても高くはならない。配役の入れ替えは毎回、音量ゼロの端で行われるため、耳には検出のしようがありません。床が下り続けるらせん階段を、延々と登らされているようなものです。 触ってみる この入れ替わりは、目で見ると一段と腑に落ちます。釣鐘の山の上を8本の成分が滑り続け、右端で消えた成分が左端から生まれ直すデモを用意しました。なめらかに滑る連続版と、半音ずつ階段で上がる版を切り替えられます。 うまく表示されないときは、デモを別タブで開く 。 おすすめは、鳴らしっぱなしにして「スタートからの上昇量」の数字を眺めることです。ピアノの全音域は7オクターブと少ししかないのに、この音階は10オクターブでも20オクターブでも平気で上がり続けます。もちろん音はずっと同じ高さ帯のまま。それから、向きを下降に切り替えると、今度は永遠に落ち続ける音になります。エレベーターの下りが終わらない感覚も、なかなかのものです。 この音、聴き覚えのある方も多いはずです。映画『ダンケルク』では、ハンス・ジマーがこの原理を音楽全体に織り込み、終わらない上昇で緊張がひたすら高まり続ける感覚を作りました。ゲームでは『スーパーマリオ64』の無限階段が有名で、登っても登っても続く階段の演出に、上がり続ける音階がぴったり重ねられています。錯覚と分かっていても、心拍のほうは正直に釣られてしまうのが面白いところです。実際にどんなふうに使われているかは、下から聴いて確かめてみてください。 映画『ダンケルク』 / ハンス・ジマー Supermarine スコア全体がシェパードトーンで組まれた作品ですが、単曲なら「Supermarine」が代表例です。上がり続ける音でひたすら緊張が高まっていく感覚が、はっきり味わえます。 YouTubeで探す Amazonで探す ゲーム / スーパーマリオ64 無限階段のBGM 登っても登っても終わらない階段の演出に、上がり続ける音階が重ねられた有名な例です。 YouTubeで探す Amazonで探す ところで、この錯覚の土台にあった「音の高さには音名とオクターブの2つの顔がある」という性質は、音感を鍛えるときにもそのまま顔を出します。ドの音名は当てられるのにオクターブを取り違える、というのはよくあることです。私事ですが、筆者は音を聴いて当てる練習ができる音感トレーニングアプリ Harmonize を個人開発しています。耳がどうやって高さを聞いているのかを知ってから練習すると、また違った面白さがあります。 Harmonize 相対音感と純正律の感覚を鍛える音感トレーニングアプリ 今すぐ無料でダウンロード App StoreGoogle Play どう実現するか デモの中身は、倍音の回 と同じくサイン波を並べる加算合成で、違いは周波数と音量を動かし続けることだけです。成分を $N$ 本(デモでは8本)用意し、いちばん下の周波数を $f_{\min}$ とすると、位置 $u$(0〜$N$ オクターブ)にいる成分の周波数と音量は $$f = f_{\min} \cdot 2^{u}, \qquad a(u) = \tfrac{1}{2}\left(1 - \cos\tfrac{2\pi u}{N}\right)$$ で決まります。$a(u)$ が例の釣鐘で、$u=0$ と $u=N$ の両端でちょうどゼロになります。あとは全成分の $u$ を同じ速さで増やし続け、$N$ を超えたら 0 に巻き戻すだけです。 ...

公開: 2026年7月21日 · Toshihiko Arai
平面イラストのマスコット(えびふりゃ)と、それを3D化したレンダリングを矢印で並べた図

平べったいマスコットを、AIに3D化してもらった ── イラストの輪郭をそのまま回転体にする

音楽アプリのマスコットに、オレンジ色の丸っこいキャラがいます。鉢巻きをして、後ろにエビの尻尾がちょこんと付いた「えびふりゃ」。このキャラはずいぶん長い付き合いで、いまの音楽アプリ「Harmonize」よりも前、えびふりゃだけが出てくる別のアプリのために描いたのが最初でした。もうずいぶん前のことです。平面のイラストとしてはずっと気に入っていて、いつか立体にしてみたいという気持ちも、かなり前からありました。 やらなかった理由は、単純に面倒くさかったからです。以前3Dプリンターを持っていた頃にBlenderでモデリングをしたことがあって、正方形の箱ひとつ作るのにも骨が折れた記憶があります。実物として印刷するとなると、ミリ単位で寸法を合わせないと困る。その調整のやり方を覚えるのが一番大変でした。丸や四角のパーツをぽこぽこ足していくだけなら本来そう難しくはないのでしょうが、ソフトに慣れる手間すら億劫で、ずっと手をつけずにいました。 今回それをやってみたのは、AIの時代なら、自分ではBlenderを操作せずに3Dモデリングができるのではないか、と思ったからです。実際、この記事で紹介する立体は、モデリングのためのコードをすべてAIに書かせて、私は「こうしてほしい」と指示を出し、出てきた結果を見て直しを頼む、という進め方で作りました。私が3D画面でポリゴンをこねた場面は一度もありません。丸1日、並行で他の作業をしながら、AIと会話しているうちにできあがっていた、という感覚に近いです。 長いあいだ、平べったいまま磨いてきた 先に、このキャラの見た目の変遷を並べておきます。いちばん左が初代。フラットで、目も点のような、いかにも手描きらしい素朴な顔です。真ん中が、その後じわじわ手を入れて、いまアプリに入っている質感のついた現行デザイン。鉢巻きに立体感が出て、ほおのぼかしや光沢が加わっています。そして右が、今回3Dにしたもの。ずっと平面のまま磨いてきたキャラが、はじめて立体になった格好です。 初代を立体にしなかったのは、繰り返しになりますが面倒くさかったからです。素朴なフラット絵を3Dにしても、たぶんそれはそれで可愛いのですが、当時はそこに手をかける気になれなかった。それが長い時を経て、平面の集大成である現行デザインからいきなり立体へ飛べたのは、間に入ってくれるAIがいたからです。 いちばん苦労したのは、楕円をやめさせることだった 意外なことに、難所は立体の作り方そのものではありませんでした。AIに意図を伝えることのほうでした。 「丸いマスコットを立体にして」と頼むと、AIはごく素直に、楕円体で近似しようとします。横から見たシルエットに楕円をあてはめれば、正面はそれらしく見える。ところが少し回すと、元のイラストが持っていた「下がぷっくり膨らんで上がすぼまる」あの肉まんのような愛嬌が消えて、のっぺりした卵になってしまう。人の顔でいう骨格が違うようなもので、色や表情をどれだけ寄せても「似ているけれど別人」から抜け出せません。 厄介なのは、これがなかなか言葉で伝わらないことでした。「楕円じゃない」「もっとイラストのままの形に」と言っても、卵型の関数に変えてみたり、輪郭からの距離で膨らませてみたりと、AIは別の「きれいな近似」を次々に持ってくる。どれも一長一短で、正面だけ合って側面が痩せたり、妙な稜線が縞になって出たり。汎用のなめらかな形へ寄せにいくほど、元の絵の個性が削れていく。この「近似から離れさせる」ところに、何度もやりとりを重ねました。 効いた一手 ── イラストの輪郭を、画像から読み取ってそのまま回す 行き詰まりを抜けたのは、発想を逆にしてからでした。楕円という「きれいな形」に絵を近づけるのをやめて、イラストの輪郭そのものを立体の材料にする。 やっていることは拍子抜けするほど単純です。まず元のイラスト画像から、マスコットのシルエットを画素単位で拾い出します。塗ってある範囲のふちをたどって、輪郭の点の並びを取り出すわけです。次に、その輪郭の右半分だけを使い、「中心の軸からどれだけ横に張り出しているか(半径)が、高さごとにどう変わるか」というプロファイルとして扱う。あとはこの一本の曲線を、縦の軸のまわりにぐるりと360度回すだけです。ろくろで壺をひくのと同じで、断面の曲線が決まれば立体が決まります。 大事なのは、この断面が楕円のような「作った形」ではなく、イラストから測り取った実物の輪郭だ、という点です。近似して立体をこしらえるのではなく、絵の輪郭を測って、測ったものを回す。この切り替えが効きました。 この作り方には、素直だからこその強みが三つあります。正面から見た形と真横から見た形が必ず一致すること。真上から見ると完璧な円になること。そして何より、断面がイラストの実際の輪郭なので、あの下ぶくらみの愛嬌がそのまま立体に乗ることです。回転体だから左右対称になりますが、退屈さの正体は左右対称そのものではなく、断面を楕円のような当たり障りのない形にしてしまうことのほうにありました。断面が絵から来ていれば、対称でも十分にキャラの顔になります。 尻尾の突起は左上にはみ出しているので、輪郭の右半分だけを使えば自然に無視できる、というのも都合がよいところでした。この「回してできた本体」が、以降のすべての土台になりました。 顔のパーツは「絵から座標を測って」載せる 本体ができたら、次は鉢巻き・目・眉・口・ほおの赤み、といった顔の要素です。ここでも方針は一貫していて、パーツの位置や大きさを勘で置くのではなく、元のイラストから一つひとつ測って決める、というやり方をAIに徹底させました。 たとえば目なら、イラストの中の「丸くて大きい黒い塊」を二つ探し、その中心と半径、白いハイライトの位置を数値として読み取る。読み取った座標を本体の表面に対応づけ、そこに少しつぶした黒い球を置きます。鉢巻きは、帯の色をした画素の並びから傾きと幅を測り、本体の表面に沿った帯として巻く。「えびふりゃ」の文字は帯のテクスチャとして貼りました。口や眉のような細い線は、中心を通る曲線と太さを測って、表面に浮かせた細いリボンとして再現しています。 測った値は、キャラごとに一つのデータとしてまとめておきます。すると立体を組み立てる側は、そのデータを読んでパーツを並べるだけの役割に徹することができる。元絵を差し替えて測り直せば、同じ仕組みが別のキャラにもそのまま使える、という見通しも同時に立ちました(これが後半で効いてきます)。 出来上がりが元絵とどれくらい合っているかは、毎回きちんと確かめました。3Dのレンダリングを元のイラストに半透明で重ねて、目や口や鉢巻きの位置がずれていないかを目で見る。数字が合っていても、重ねてみると微妙に印象が違う、ということはよくあります。下の画像は、3Dの顔を元絵にぴたりと重ねたところです。 正面が合っていても油断はできません。斜めから見たときに目のハイライトがずれる、細いリボンが表面から浮きすぎて影が出る、といった立体ならではのズレは、正面の重ね合わせだけでは見つからない。角度を変えたレンダリングを何枚も見比べて、少しずつ詰めていきました。 平面の「記号」を、立体の都合に合わせて翻訳する この作業でいちばん面白かったのは、平面の絵では成立していた表現が、立体にすると急に不自然になる場面でした。 いい例が眉です。元のイラストは、片方の眉だけを描いています。平面の「表情記号」としてはそれで十分伝わるのですが、実体のある立体で片眉だけだと、反対側がのっぺりして落ち着きません。そこで、描かれている片方の眉を、顔の中心線を基準にもう片方へ折り返して両眉に増やしました。単純な左右反転ではなく、顔がやや斜めを向いた構図であることを考えて、立体の表面に沿う形で折り返すのがコツでした。 尻尾も同じ話です。エビの尻尾は、イラストでは体の後ろからちょこんと出た羽根のように描かれていますが、正体は鉢巻きの結び目です。頭の後ろに縛ったこぶがあって、その結び端が尻尾に見えている。3Dでは律儀にそう作りました。後ろに回り込んだときに「ちゃんと縛ってある」感じが出ると、平面では気づかなかった立体らしさが生まれます。 ほおの赤みのような、輪郭を持たないぼかしも悩みどころでした。これは形として作るのではなく、本体の色そのものを、ほおの位置からじわっと赤側へにじませることで表現しています。表面の色として塗っているので、後でジャンプさせて体が伸び縮みしても、赤みがちゃんと一緒に動いてくれます。 表情を切り替える 元のアプリには、マスコットの表情がいくつか用意されています。ふつう顔、困り顔、目がキラキラの大喜び、にっこり、口笛。せっかくなので、これらを3Dでも切り替えられるようにしました。 仕組みは本体と同じで、各表情のイラストから顔パーツを測り、表情ごとのデータとして持っておく。あとは組み立てるときに「どの表情か」を指定すれば、対応するパーツに差し替わる。文字どおり、顔だけを付け替えているイメージです。 ここでも平面から立体への翻訳が何度も出てきました。困り顔の汗は、貼り付いた水滴ではなく、頭から飛び出して弧を描いて消える飛沫として作る。大喜びの目のキラキラは、平面の星マークを立体の光として置き直す。口笛のすぼめた口は、平面では単純な形ですが、立体でカメラが横に回り込むと向きの違和感が出るので、視点に応じて向きが変わるようにしています。どれも「元の絵が伝えたかったこと」を、立体の都合に合わせて訳し直す作業でした。 ジャンプさせる 止まっているだけでは寂しいので、ぴょんぴょん跳ねてもらいました。物理シミュレーションではなく、手付けのアニメーションです。 跳ねるときの気持ちよさは、踏み込みでぺしゃっとつぶれ、飛び出す瞬間に縦へ伸び、空中で丸に戻り、着地でまたつぶれる、という「つぶれと伸び」のメリハリから来ます。アニメーションの世界で昔から使われる原則で、このとき縦に伸ばしたぶんは横を細く、つぶしたぶんは横を太くして体積が変わらないようにすると、ゴムまりのような弾力が出ます。 面白いのは、ジャンプと表情を組み合わせたときです。踏み込みまではふつうの顔で、飛び出す瞬間に目をキラキラさせて口を開ける。そうやって動きと表情のタイミングを合わせると、ただの丸い物体が急に生き物らしくなります。 ちなみに、このマスコットが平面のまま暮らしている音楽アプリはこちらです。宣伝を少しだけ。 Harmonize 相対音感と純正律の感覚を鍛える音感トレーニングアプリ 今すぐ無料でダウンロード App StoreGoogle Play 二匹目は、ほとんど設定を足すだけだった このマスコットには兄弟がいます。ミントグリーンの鉢巻きをした「かきふりゃ」。えびふりゃで作った仕組みが本当に汎用なのかは、二匹目で試されます。 結論からいうと、拍子抜けするくらい素直に立体になりました。元絵を差し替えて、輪郭と顔パーツを測り直す。あとはキャラごとの違い ── 鉢巻きが緑であること、尻尾はなくて結び目のこぶだけがあること、ほおの代わりに星のイヤリングを下げていること ── を、設定として書き足すだけです。同じパイプラインが、そのまま二匹目を吐き出しました。 ...

公開: 2026年7月20日 · Toshihiko Arai