ちいつる
A-08リファレンス

3D/WebGLインタラクション実装パターン

操作可能キャラや街のプロシージャル生成など、Three.js実装の型を示す。

触ってみるドラッグして回転させてみてください

同じ形なのに「安っぽいプラスチック」にも「実在感のある物体」にも転ぶのは、IBL(環境光)と3点の照明、そして要素ごとに変えたroughnessの合わせ技によるもの。指を離して眺めている間もコアがゆっくり回り続けているのは、止め絵に見せないための小さな仕込みだ。

触ってみる光源をドラッグ。ベタ塗り/光を設計を切り替えて見比べて

明るさは法線Nと光方向Lの内積 N·L。輪郭のリムライトと、光の映り込み(鏡面ハイライト)が加わって初めて、平面の円が球体に化ける。

冒頭のシーンで質感を分けていたのは、この重ね方だ。「ベタ塗り」に切り替えると陰影ゼロの円(ステッカー)に落ちるが、拡散反射(N·L)にリムライトと鏡面ハイライトを足した「光を設計」では、光の向きを動かすだけで同じ円が丸みと素材感を持った球体に化ける。

Three.jsの基本構成(Scene/Camera/Renderer)

Three.jsの最小構成は「Scene(何を置くか)」「Camera(どこから見るか)」「Renderer(どう描くか)」の3点セット。ここに光源(Light)とオブジェクト(Mesh)を追加していく。描画ループはrenderer.setAnimationLoopを使わず、他のアニメーションライブラリと同じrAFに統一しておくとスクロール演出と噛み合わせやすい。

触ってみる立方体をドラッグで回し、FOVとカメラ距離を変えてみてください

FOVはズーム(映る大きさ)だけを変え、遠近の“歪み”はカメラ距離で決まる。近づくほど手前の辺が誇張される。

Cameraの仕事は、3D座標を平面へどう潰すかを決めること。FOVが受け持つのは映る範囲(ズーム)、カメラ距離が受け持つのは遠近の強さ。役割が別々だから、片方のつもりでもう片方を動かすと画作りが一気に崩れてしまう。

触ってみるドラッグで周回、タップで手前の球を狙って
お題:重なりの 手前 を選べ未選択

タップ位置からシーンへレイ(光線)を飛ばし、当たった球のうち深度が最小=最も手前の1個を拾う。重なっていても奥は貫通せず、必ず手前が選ばれる。

タップ位置からシーンへ「レイ(光線)」を飛ばし、当たった球のうち最も手前だけを拾う。重なりの奥は貫通せず、必ず手前が選ばれる——カメラの投影を逆にたどるこの仕組みが、3D空間でのタップ操作の土台になっている。

操作可能キャラクターの実装

キー入力・タッチ入力から「移動方向」を決め、キャラクターの位置を毎フレーム加算する。カメラはキャラクターの少し後ろ上空に「遅れて追従」させると、機械的な同期よりも自然な体感になる。当たり判定は簡易な円柱・球で近似し、地形との衝突は高さマップ(座標から地面の高さを引ける表)で処理すると軽量に保てる。

触ってみるドラッグでキャラクターを動かし、カメラ追従の速さを変えてみてください

追従を遅くすると、キャラクターは画面中央(+印=カメラの注視点)から先へ抜け出し、カメラが一拍おいて追いかける。この「1フレームでは追いつかない」わずかな遅れが、ぴったり同期させたときには決して出ない移動の気持ちよさを生む。

触ってみるパッドをドラッグ。クラフトが推進する手応えを
出力 X+0.00
出力 Y+0.00
推力 |v|+0.00

推力は常に最大1にクランプされるので、斜めに倒しても速くならない。中央の遊び(デッドゾーン)を抜けた瞬間から 0→1 に滑らかに立ち上がり、指を離すと慣性を残してクラフトが中心へ戻る。

キャラの「移動方向」はこうして生の入力から作る。出力ベクトルがそのままクラフトの推力になるので、効きが指で分かる。強さを常に最大1に正規化するから斜めに倒しても速くならず、中央の遊び(デッドゾーン)が指の微ブレを無視する。離すと慣性を残して中心へ戻るのが、生の座標をそのまま渡さない理由だ。

街・ステージのプロシージャル生成

建物を1つずつ手作業で配置する代わりに、グリッド上の座標にシード付き乱数で高さ・色・種類を決めて自動配置すると、少ないコードで広い街並みを作れる。同じシード値なら毎回同じ結果が再現できるため、デバッグや共有がしやすい。

触ってみる「別の街にする」で夜景がせり上がります
seed 2026

塔のリストはどこにも保存していない。シードから乱数で高さ・色・窓の点灯をその場で決めているだけ。それでも同じシードを入れ直せば一棟残らず同じ街が戻ってくる——データではなく手順で世界を書き記す、それがプロシージャル生成だ。

この夜景は、塔のリストをどこにも保存していない。シード値から乱数で高さ・色・窓の点灯までその場で決めているだけだ。それでも同じシードを入れ直せば、一棟残らず同じ街が戻ってくる。データではなく手順で世界を書き記す。これがプロシージャル生成のいちばん面白いところだ。

触ってみるシード・周波数・オクターブを変えて地形を作り変えてください

乱数の格子を滑らかに補間した値ノイズを、周波数を倍にしながら重ねる(fBm)。オクターブを増やすほど、大きな山並みに細かい尾根と谷が乗って一枚の地図になる。

街のブロックと違い、地形は「滑らかにつながる」必要がある。乱数を格子に置いて補間する値ノイズを何層も重ねる(fBm)と、大きな山並みに細かい起伏が乗った自然な地形が一発で生まれる。標高で色を割り当て、傾斜からヒルシェードと等高線を引けば、そのまま一枚の地図になる。

PC・スマホ両対応のパフォーマンス設計

スマホではrenderer.setPixelRatio(Math.min(devicePixelRatio, 2))でDPRの上限を切るのが鉄則。オブジェクト数・影の描画・パーティクル数はデバイス性能に応じて段階的に間引き、画面外に出たオブジェクトの描画・当たり判定計算は停止する設計にすることで、低スペック端末でもフレームレートを保てる。

触ってみる距離を変え、LODを切り替えて、どこまで粗くしてもバレないか確かめてください(球はドラッグで回せます)
LOD2三角形 252 / 1,472
節約したポリゴン
83%

遠ざかって豆粒になった球は、面をどれだけ粗く割っても輪郭がほとんど変わらない。だから距離に応じてポリゴンを落としても見た目はそのまま、節約したポリゴン数のぶんだけ描画負荷がすとんと下がる。逆にLODを切って球へ寄っていくと、節約がどこで“バレ”はじめるのかが分かるはずだ。

触ってみるカメラの向きをドラッグし、FOVを変えて描画対象数を見てください
描画対象
0 / 44

視錐台(扇形)の外にある灰色の点はGPUへ送らない。向きやFOVを変えるたび描画対象数が跳ねる——見えないものを描かないことが、モバイルでフレームレートを保つ第一歩だ。

視野の外にある物は、そもそもGPUへ送らない。これがフラスタムカリングだ。カメラを振るたびに描画対象数が跳ね上がったり減ったりするのを眺めていると、「見えないものは描かない」がなぜこれほど効くのかが腑に落ちる。