当初目標のウィジェットにしてねえな

昨日の帽子画像、メキシカンハット、て言うそうなんですよ。

昔々、MZ-2000のカタログ等に昨日の画像のような点群で描かれた3Dの帽子?画像が載っておりまして。
ただ、当時、プログラムで動いてたところは見た記憶がない。
なもんで、copilotに画像を見せて「こんなのを描画するスクリプト書いて」て言いました。

最終的に、copilotに作ってもらったのがこちら。
メキシカンハット くるくる比較・開始角選択版

ここにたどり着くまでに結構四苦八苦したけど、結構面白かった。

最初にでてきたのは、緑色の直線で頂点を四角形を結んだワイヤーフレームの3Dで
当時っぽいけど、元画像は点群なので違うだろう、と。

で、点群で描いてもらったんだけど、今度は山の稜線の向こう側にあるはずの点が見えるプログラムで。
元画像はどうにかして陰面処理してるようだ?どうやってる?
つーて、copilotがつくってくるスクリプトにダメ出しし続けて。
この頃は、copilotの陰面処理の提案が割とぽんすけで……

……というあたりはもう延々長いので、copilotにまとめさせました。
以下、暇な人向け。

MZ-2000の「帽子CG」をJavaScriptで再現するまで

昔見たMZ-2000のデモ画面には、帽子のような不思議な立体が描かれていた。その画像をブログ上で再現しようと始めた試みは、やがて当時の描画アルゴリズムを推理する作業へ発展していった。

1.発端は一枚のデモ画面

昔見た帽子状CGを探したところ、MZ-2000のデモ画像を発見。黒背景に緑色の立体を描くJavaScript版の制作を始めた。

2.初期案はワイヤーフレーム

正方形平面の格子点に高さを与え、隣接する点を線で結んだ。最初は一般的なワイヤーフレーム曲面として実装した。

3.帽子の断面形状を設計

中心からの距離で標高を決定。中央高原、浅い窪み、山麓の谷、外輪山、外周の谷を複数の関数で合成した。

4.真横から断面を確認

斜め視点だけでは形を判断しにくいため、真横視点を追加。正方形の対角線に沿った標高を見ながら、山谷の位置と幅を調整した。

5.ワイヤーフレームから点群へ

元画像は格子線よりも、高密度な点の集合に見えた。そこで線で結ぶ方式をやめ、計算した格子点を1ドットずつ表示する方式へ変更した。

6.点群にZバッファを導入

元画像では陰になる部分が見えないため、画素ごとの奥行きを記録。同じ画素に複数の点が重なった場合は、最も手前の点だけを残した。

7.「点では面を隠せない」と気付く

Zバッファが正しく働いても、1ドットが隠せるのは同じ画素の点だけだった。格子の隙間から、曲面の裏側にある点が見えてしまった。

8.セルを面として塗る案を提案

そこで私から、四つの格子点で囲まれたセルを面として塗りつぶせないかと提案。点単位から、ポリゴン的な面単位の処理へ発想を変えた。

9.奥から描き、手前の面で消去

格子セルを奥から手前へ処理し、投影したセル内部を黒く塗った後、四隅の格子点を緑色で打ち直す方式を実装した。

10.Canvas直接描画で消し残り

Canvasは斜めの線や多角形の縁をアンチエイリアス処理し、境界画素を半透明色として合成する。このため、黒い面を重ねても縁の緑が暗く残り、消し残りのように見えた。
さらに表示だけを拡大すると、Canvas内部の描画結果がCSSサイズへ引き伸ばされるため、半透明の境界画素やドットの濃淡が一層目立った。Canvasは内部寸法とCSS表示寸法が異なると拡大縮小される。

なお、image-rendering: pixelated や imageSmoothingEnabled = false は画像の拡大補間を抑える設定であり、Canvasのパス描画そのものにかかるアンチエイリアスを完全に無効化するものではない。

11.メモリ上で完成後に表示

Canvasへ直接描いて消す方式をやめ、ピクセル配列上で最終色を確定してから一括表示した。RGBA値を直接扱うことで、半透明の境界を作らず完全な黒と緑にできた。

12.元のBASICリストを発見

試行錯誤中、元のデモと思われるBASICリストの写真を発見。Copilotに解読させ、モデル形状、視点、座標計算、描画順を推定した。

13.元プログラムは浮動地平線法

Copilotの解析では、元の処理は浮動地平線法に近いとされた。画面各列の上下の可視境界を更新し、その内側へ入る後続の線を描かない方式だった。

浮動地平線法は英語で Floating Horizon Algorithm と呼ばれる一般的な隠線消去法で、二変数関数の曲面を線画として表示する用途に使われる。

14.セル消去方式と元祖方式を比較

同じ帽子形状と視点を使い、セルを黒く塗る面処理と、BASICリストを参考にした浮動地平線法をそれぞれHTMLで再現した。

15.元祖方式は固定視点向き

浮動地平線法は軽量だが、走査方向と視点の関係が重要だった。想定された固定視点では有効でも、自由に回転させると隠線判定が破綻しやすい。

16.三画面切替で回転デモを構想

MZ-2000の三つのグラフィックプレーンへ、方位角の異なる三枚を事前描画。表示ページの切替だけで回転して見せる案を考えた。

17.「くるくる」と「ぱたぱた」

三枚を「1→2→3→1」と循環させる回転版と、「1→2→3→2→1」と往復させる首振り版の二種類を構想した。

18.セル塗りつぶしは実機には重い?

セル方式では、各セルの座標変換、四角形内部の黒塗り、格子点の再描画が必要になる。ブラウザでは動いても、当時のZ80には重すぎた可能性がある。

19.実機らしさは浮動地平線法にある

浮動地平線法なら、画面各列の上端と下端だけを記録すればよい。全面を塗る必要がなく、セル方式より少ない計算量とメモリで隠線を除去できる。

形状再現から、描画技法の推理へ

この試みは、正方形格子を線で結ぶワイヤーフレームから始まった。

元画像との違いから点群描画へ移り、Zバッファによる奥行き判定も追加した。しかし、そこで「点では面の向こう側を隠せない」という根本的な問題に気付いた。

手前の格子点が1ドットなら、隠せるのも同じ画素にある1ドットだけである。格子点の間にある本来の地表面には面積がないため、その隙間から奥の点が見えてしまう。

そこで私から、格子点で囲まれたセルを面として塗りつぶす方式を提案した。これは、点単位の可視判定から、ポリゴン的な面処理への大きな転換だった。

ところが、Canvasへ直接黒い多角形を描いて消すと、境界の緑が暗く残った。原因は単純な「黒塗り不足」ではなく、斜めの輪郭に対してCanvasが自動的に行うアンチエイリアスと半透明合成だった。

そこで、描画途中のCanvasへ黒や緑を重ねるのではなく、メモリ上のピクセル配列で最終的な色を確定し、完成画像だけを一括表示する方式へ変更した。これにより、消し残りや不均一な明るさを解消できた。

そして最大の転機となったのが、元のBASICリストらしき写真の発見である。Copilotによる解析から、元プログラムは浮動地平線法に近い処理を使っていた可能性が高まった。

こうしてテーマは、単に「帽子らしい形を作る」ことから、1980年代の8ビット機が、限られた計算能力で立体をどう表現したのかを推理することへ変わっていった。

コメント

  1. J より:

    ここ数日間の日記はプログラマみたいですねw

    しかしメキシカンハット懐かしいなー。
    僕は昔の人間なので、手前から奥に向かって点を打っていって
    手前に描いた点がそれより高かったら点を打たないってロジックしか
    思い付かなかったけど(これに浮動地平線法なんて名前が付いてることも知らんかった)、
    まさか奥から描いていって手前の面で上書きしていくなんて方法が実用できるとは・・・。
    当時のPCでこれは少々キツかったろうなぁ(遠い目)。

  2. Jさんへ

    本当だよ!なんかプログラマみたいだよ!

    うちとしても、手前の面で上書きなんて、当時のZ80Aだと荷が重そうなやり方はしたくなかったんですが……

    だって、copilotが前にあるのが点なのに「Zバッファ法で陰面処理した」とか言って、後ろが丸見えだったんですもの。

    どうにかせにゃ、つーて、考えましたとも。

    その後、平行投影で60×60くらいの格子なら、最大で80個ちょっとくらいの前後関係を処理してドットだけを黒で塗り潰せば、なんとかなるんじゃ?とか色々考えたんですが。

    一応、塗りつぶしだと、オーバーハングとか中に浮かぶドーナツだとかも処理できるんじゃないかなあ、みたいな話をcopilotとしてたんですが、当初目的と大分ずれるし、たぶんどっかの車輪の再発明。

  3. J より:

    https://posfie.com/@yone/p/M1lSCup

    このサイトに色んな人がリストを載せてますね(全部 BASIC だけど)。
    まぁ大体 20~30行くらいのプログラムなんですけど、
    全横軸分の配列を持つってのが今の AI には想像つかないかもですね(解像度的に)。
    今なら点描じゃなく塗りつぶしにするのが普通でしょうし、それなら奥から描いていくよなぁ。

  4. そのtwitterまとめ?、を見つけてcopilotに見せて、元のプログラムがその地平線法というやつだとわかった、というお話でして。

    その前に奥から順に黒塗りしちゃえ、つーてやらせたらどうにかなってたんですが、
    同時に当時のPCだと速度的に厳しいかなあ、とも思ってたんで、元アルゴリズム探そう、つーてそのページ見つましたw