Perspective illusion(遠近法イリュージョン)
スタイルの概要(1パラグラフ)
水平に構えたカメラを非常に長いレンズ(約2度、400m先から)で使用すると、奥行きのあるシーンが平面の絵のように見えるように圧縮されます。次に、ドリージームを行います。レンズを広角にしながらカメラを前方に移動させることで、被写体のサイズは変わらず、その背景の空間が広がります。カメラはさらにスイングしてコーナービュー(角からのビュー)に移行します。それぞれのビューはフラットな状態から始まり、3Dへと開いていきます。ビューがフラットな間に、オブジェクトは誰にも見えないところで奥行きを入れ替えます。そのため、奥行きが戻ったときには、一直線の列が斜めの列になっています。すべてのオブジェクトは、クリーンなインクの線画として描かれ、フラットな2トーンのシェーディング、ハッチングされたキャストシャドウ、ペイントされた空が施され、全体がシームレスにループします。
クイックスタート
mkdir -p ~/Movies/edits/<date>-<name>/stills && cd $_
printf '{"type":"module"}' > package.json && npm i [email protected] [email protected]
cp -r ~/simcraft/skills/video/perspective-illusion/templates/* .
cp scramble_night.html scene.html # または最も近いテンプレート(下記参照)をコピーし、編集
node render.mjs scene.html x --stills=0.3,1.5,3,6,8.5 # 各ショットのスチルをstills/に出力
node render_fast.mjs scene.html preview.mp4 --preview # ループ全体の半分サイズ、約1.5分
node render_fast.mjs scene.html final.mp4 # フル品質、10秒で約30分
python3 ~/simcraft/skills/video/vox-explainer/scripts/motion_check.py final.mp4
python3 ~/simcraft/skills/video/perspective-illusion/scripts/blink_map.py ffmpeg final.mp4 blink.png
問題があればスチルとプレビューで修正してください。プレビューが承認された後にのみ、フル品質のレンダリングを行います。ffmpegがPATHにない場合は、$FFMPEGを設定してください。
テンプレート
新しいシーンに最も近いテンプレートから始めてください。カメラリグ、タイムライン、ポストパス、レンダリングフックはそのままにし、シーンセクション(// ---------- kitと// ---------- camerasの間)のみを置き換えてください。
| テンプレート | シーン | レンダラー |
|---|---|---|
street.html, street_plus.html | ポール、コーン、横断歩道、赤い空のある昼間の街路。street_plusは詳細とコーナービューを追加。 | render_smooth.mjs |
market.html, market_real.html | スーパーマーケットの通路。描画スタイルとリアルなスタイル。 | render_smooth.mjs (market.html: render.mjs) |
scramble_night.html | 雨の夜の大きな交差点。歩行者、濡れた反射、広告スクリーン。すべての高速化機能を備える。 | render_fast.mjs |
新しいシーンでは、scramble_night.htmlがエンジンのベースとして最適です。なぜなら、高速レンダラーフックを備えた唯一のテンプレートだからです。昼間の見た目にするには、夜間の追加要素を削除してください(reference.md参照)。
ステップ1: 目を欺くことができるシーンを構築する
以下の特性があれば、どのような場所でも機能します。
- 1つの長い軸。 -z方向に沿ってシーンを配置します(街路、通路、プラットフォーム、廊下)。カメラはその軸を見下ろします。この軸に沿った奥行きが、望遠レンズによって隠されます。
- 異なる奥行きにある繰り返しオブジェクト。 ポール、ランプ、棚、柱、看板、木、人物。望遠レンズでは、それらは1つの平面に重なります。一直線に並んだポールは1つに融合し、遠くのファサード上の看板の積み重ねは1つの壁に積み重なります。
- スワップオブジェクト。 3~6個の類似オブジェクト(コーン、人物、ゴミ箱、椅子、箱)。その配置が一直線と斜め列の間で変化します。また、フラットなビュー(
cs)では、フラット時に縮尺が正しく読めるように、実際よりも大きく描かれることもあります。 - メートル単位の実世界スケール。 レンズと焦点面は、実スケールでのみ意味をなします。0.75mのコーン、1.7mの人物、3.2mの階高。
- コーナービューが見る場所の詳細。 すべてのコーナービューは、建物の側面、オブジェクトの背面、地面を明らかにします。これらを埋めてください。側窓、排水管、ケーブル、縁石、車の下の接触影など。フラットなビューはいい加減さを隠し、コーナービューはそれを露呈します。
- すべての詳細を1つの表面の一部にする(テクスチャ、積み重なったスリーブなど)、または別の表面を少なくとも0.4m以上離す。400m離れた場所からは、数センチ離れた表面はちらつきます。
ステップ2: カメラを振り付ける
カメラは決して傾けません。水平を保ち、垂直線が垂直のままになるようにします(建築家の図面のように)。各ショットはプレーンなオブジェクトです。
{ x, y, zf, W, fov, H, el, yaw, lay, cs }
| キー | 意味 |
|---|---|
x, y | カメラの水平位置と高さ(m) |
zf, W | 焦点面と、そこでフレーミングされる幅(m)。fovを変えながらWを維持することが、ドリージームです。 |
fov | 水平レンズ角度。約2度でフラット、25~90度で奥行きがあります。 |
H | レンズシフト:地平線の位置(-1=下端、0=中央、1=上端)。 |
el | カメラを上げてレンズをシフトすることで見下ろす。傾けてはいけません。 |
yaw | 焦点の周りをスイングしてコーナービューにする。 |
lay, cs | スワップオブジェクトのレイアウト(0=一直線、1=斜め列)と、フラットビューでのスケール。 |
有効なショットの語彙:
| ショット | 設定 |
|---|---|
| フラット | fov約2、H0。シーン全体が1つの立面図として読める。 |
| 虫の目 | 膝の高さ、fov90、地平線を低く(H約-0.7)。 |
| 高い位置 | カメラを高く中程度のレンズで、地平線はフレームの上。 |
| コーナービュー | yaw ±18~35度、el 9~50度。ペアで登場:最初に望遠バージョン(fov4)、次に同じビューを広角(fov25~45)。 |
| 斜めと低い | スワップオブジェクトを奥行きで見せる中程度のレンズ。 |
ループ構造。 フラット、次に深いビュー、次にコーナービュー(フラット→3D)、フラットに戻る(ここでオブジェクトをスワップ)、高い斜め、もう一方のコーナービュー、低いショット、そして再びフラット。ループは開始したショットで終了します。
タイミング。 すべてのパラメータは、単調な3次曲線(Fritsch-Carlson)をキータイムにわたって通過するため、速度が急に変化することはありません。Wとfovは対数空間で補間します。小さな「ドリフト」ショット(W×0.96)を追加して、静止画が凍りつかずに息づいているようにします。10秒がスムーズに感じられ、7秒は急ぎすぎに感じられます。長さを変更するには、キータイムを書き換えるのではなくスケーリングします。
フラットな状態でごまかす。 layはフラットなホールド中にのみ変更します。カメラが奥行き方向に動いている間にオブジェクトが動くと、トリックが露呈します。
最初のコーナービューは、ヒーローオブジェクト(車、看板)が覆っていない側面に向けるべきです。そうすることで、リビールが新しい何かを示します。
ステップ3: 見た目
ページは4つのパスをレンダリングします。カラー、法線と深度、フラットカラー(ライティングなし)、および太陽のみのシャドウマスクです。最終的なシェーダーでそれらを組み合わせます。
- インク線は、深度(2次差分)、法線、またはフラットカラーが変化する場所に描画します。近くにある線は太くします。路面標示は細い灰色の線にします。1ピクセルが数メートルをカバーする場合、または遠くのテクスチャがノイズになる場合は、カラー線をフェードアウトします。
- 2トーンシェーディング。 明るい部分と影の部分。グラデーションはありません。マスクのライトを
2*PIに設定します。これは、three.jsがランバートをπで割るためで、そうしないとすべてが影になってしまいます。 - キャストシャドウのみをハッチングし、ハッチングはワールド空間(スクリーン空間ではない)にアンカーし、
fwidthでフェードアウトします。 - 空は視野方向に基づいてペイントし、スクリーン位置に基づかないようにします。これにより、カメラが動いても雲は固定されたままになります。雲のエッジはソフトに保ちます。
- パレット。 6~10色のフラットカラーと1つのアクセントカラー(赤い空、黄色い看板)。リファレンスに合わせるには、そのフレームからk-meansで色を抽出します。
- ライト。 低い位置にある1つの硬い太陽を、主要な被写体が照らされ、地面に長い影を落とすように配置します。高い側からの太陽は、シーンを1つの大きな影に埋めてしまいます。
夜間バリアント(月明かり、窓の明かり、濡れた反射、グロー、雨、グレード)、歩行者の群衆、広告スクリーンや看板については、reference.mdを参照してください。
scramble_night.htmlから昼間の見た目を得るには:
- 月を暖かい太陽に置き換え、アンビエントライトを上げます。
- ポイントライトのループを削除します。
- ポストシェーダーから、濡れた地面ブロック、ヘイズライン、夜間グレードを削除します。
- 雨を削除し、窓の
emissiveIntensityを0に設定します。 - 昼間の
skyAtをペイントします。
ステップ4: ディテールパス(優れたものにするために)
最初のバージョンは常に空っぽに見えます。優れたバージョンは、意図的なパスを経て作成されます。各パスは、次のパスを始める前にスチルでチェックされます。
- フォルム。 実スケールの主要な塊、長い軸、スワップオブジェクト、カメラタイムライン。フラットショットが本当にフラットに見えること、すべての動きが何かを明らかにすることを確認します。
- 構造。 実際の場所にあるすべてのもの。実世界のロジックに基づいて構築します。街路の場合、縁石、排水溝、マンホール、アームとボックス付きのポール、両面の看板、フェンス、ゴミ箱、自動販売機、自転車ラック、頭上ワイヤー、窓枠とエアコンユニットなどがあります。実際の場所の写真を見て、構築する前に何があるかをリストアップします。
- 風合いとストーリー。 そこに生活感を与える小さなもの。ステッカー、テープ、コードで描かれたテキストのポスター、水たまりの跡、寄りかかったままの自転車、洗濯物、猫、コーヒーカップ。カメラが留まる表面ごとに3つ以上を目指します。
- コーナービュー監査。 各コーナービューについて、広角のスチルをレンダリングし、そこで裸に見えるものをすべて埋めます。通常、それは建物の側面、看板の背面、屋上(タンク、手すり、アンテナ)、オブジェクト間の地面です。
- ライトとシャドウ。 すべてのオブジェクトが読み取り可能な影を落としていることを確認し、駐車中の物の下に接触影を追加し、すべての主要なフォルムの影側が2トーンステップに入っていることを確認します。1つのビューにしか現れない影は、カメラの動きに対するご褒美です。
- 生命(オプション)。 一定速度で動く歩行者、電車、車、スクリーン、鳥、旗。すべてがループの整数倍で動きます。
- 仕上げ。 パレットの調和、線の太さ、空、ビネット、グレード。次に、最終出力で点滅チェックを行います。
密度のルール:フラットな望遠ショットから、フレームは端から端まで満たされているように感じられる必要があります。密度の高いイラストのように、意図的に目の休息となる領域がない限り、大きな空の壁や地面の領域は避けてください。
品質基準
フルレンダリングの前に、すべてのキータイム(および動きの中間点)でスチルをチェックし、このリストと照合します。失敗するものがあれば修正し、再度チェックします。
- フラットショットが平面の絵として読める。奥行きを感じさせるものがない。
- すべての動きがリビールで終わる。隠された斜めの列、脇道、遠く離れていたオブジェクト。
- コーナービューはフラットに始まり、開き、最初のものはヒーローオブジェクトから離れる方向を向く。
- 線は鮮明で均一であり、二重線、光る面へのインク、遠くのテクスチャのノイズがない。
- 2トーンシェーディングはすべての主要なフォルムで明確であり、ハッチングはキャストシャドウにのみ現れる。
- 裸のプリミティブとして読めるものがない。すべてのボックスにはトリム、継ぎ目、ベース、ラベルがある。
- テキストは読みやすく、正しく綴られており、実際のフォントを使用し、架空のブランド名を使用している。
- 空はシーンのライトの方向と色に一致している。
- パレットは制限され、調和しており、1つの明確なアクセントがある。
- ループの継ぎ目は見えない(フレーム0=最後のフレーム)。ポップするものがない。
- プレビューにスタッターがない。最終出力では、
blink_mapのカウントが低く、点滅するものは意図された動き(雨、スクリーン)のみである。
スチルをレビューするときは、イラストレーターのように見てください。各フレームの最も弱い3つの領域を挙げ、それらを修正し、繰り返します。「機能している」で止まらないでください。
ステップ5: レンダリングとチェック
render_fast.mjsは、各フレームをジッターサブレンダリングの平均としてGPU上でレンダリングします。アンチエイリアシング用に4回、高速な動きではギャップのないモーションブラー用に最大32回です。サブレンダリングのサイズは、window.probeポイントが画面上でどれだけ移動するかによって決まります。--previewを使用して、ハーフサイズで30fpsにします。--from、--to、--fpsも使用できます。render_smooth.mjsは、renderManyのないページに対して同じ品質を提供しますが、ffmpegで平均化するため、数倍遅くなります。- ロード時にマテリアルごとに静的メッシュをマージする(
BufferGeometryUtils.mergeGeometries)。あるシーンでは、ドローコールが7,654から530に減少し、約7倍高速になりました。動くものはマージしないでください。 - 最終出力をチェックするには、
motion_check.py(フリーズがないことを確認)とblink_map.py(ほぼ静止したフレームでちらつくピクセルをカウント)を使用します。変更の前後でカウントを比較してください。プレビューは設計上ちらつくため、最終出力でのみ点滅を判断してください。
ページコントラクト
すべてのシーンページは、以下の機能を提供すればレンダラーと連携します。
| フック | 機能 |
|---|---|
#frame | スクリーンショットが撮られるキャンバス。 |
window.READY | アセットとフォントがロードされたら設定。 |
window.render(ms) | 時間msでフレームを描画する。 |
window.probe(ms) | いくつかのワールドポイントのスクリーン位置を返し、モーションブラーのサイズを決定するために使用される。 |
window.setJitter(x, y) | ピクセル単位のサブピクセルレンズオフセット。projectionMatrix.elements[8]と[9]を介して設定される。 |
window.renderMany([[ms, jx, jy], ...]) | 1回の呼び出しで複数のレンダリングを平均化する(render_fastに必要)。 |
?scale=.5 | ハーフサイズでレンダリングする。 |
アニメーションするすべてのものは、msの純粋な関数であり、ループ長に対して周期的でなければなりません。ステップ、クロール、雨には、ループごとに整数サイクルを使用してください。
点滅とグリッチ
| 原因 | 修正 |
|---|---|
| スクリーン空間のもの(ワブル、ハッチング、ノイズ、カメラ距離によるヘイズ) | ワールドまたは視野方向にアンカーする。 |
| 数センチ離れた表面 | 1つの表面にマージするか、少なくとも0.4m以上離す。 |
| 重なり合うコプラナー平面(道路標示、床の断片) | 標示を1つの平面にペイントし、断片は端から端まで合わせる。 |
| サブピクセルの詳細(レール、グリッド、ケーブル、雨) | すべてのフレームでアンチエイリアシングをジッターし、密な線をメートル/ピクセルでフェードアウトする。 |
| フレームごとに1回のレンダリング | 最終出力では決してこれを行わない。 |
| 雲や反射のハードエッジ | ソフトにし、画面の境界でエフェクトをフェードアウトする。 |
| ラップまたはポップする動くもの | window.probeで見つけた、ループの最も速いカメラ移動中にラップする。 |
落とし穴
//コメントを1行のコードに追加すると、行の残り(scene.add、ライトなど)が飲み込まれます。/* */を使用してください。- シーンルートに追加するヘルパー(
plane()など)は、現在のグループを無視し、物の上に重なる可能性があります。グループ内で構築してください。 - 発光面(スクリーン、看板パネル)をフラットカラーパスとシャドウパスから除外してください。そうしないと、すべてのグリフにインクが適用され、暗くなります。
ffmpeg -stream_loop ... -c copyは、一部のプレーヤーで早く停止するループを作成します。代わりに再エンコードしてください。- Xの場合、32:9のサイドバイサイドはアップロードするには広すぎます。垂直にスタックしてください。
- オリジナルのキャラクターと架空のブランドのみを使用してください。リファレンスの技法はコピーしても、そのアートワークはコピーしないでください。