極座標再び 2K/01/11

データロード時の背景スクロールパターン(?)を増やすため、 「極座標」をもう一度作ることにしました。手元には昔の「kiloDemos」のコードがあるし、 それを切り貼りすればなんとかなるか、とか思ってたらなぜか「kiloDemos」のフォルダが 発見できず(^^;)、一から作る羽目になりました。

アルゴリズムが分かっているとはいえ、さっぱり忘れてしまったので、 ルーズリーフに書いて考えてみました。そうしたら「おっ」てな具合で思い出しました。 そういえば、直交座標のX座標は極座標では角度、Y座標は中心からの距離、だったんですね。 それがわかれば、話は簡単で、高速化のための極座標テーブルを作り、 それに従いテクスチャ座標を求め、全ピクセルに適用してやればいいわけです。 一応、ソースコードを載せておきます。


//極座標を表示するサイズは320*240なので
//スクリーン座標からテクスチャ座標を求めるための
//テーブルを作成します
var x,y,tx,ty,dx,dy:Integer;
    p:PPoint;
begin
  //極座標テーブルを作成
  p:=@kyokuTable;
  for y:= 0 to 239 do begin
    for x:= 0 to 319 do begin
      //中心座標による変換
      tx:=x-160;
      ty:=120-y;
      //直交→極
      dx:=trunc(Atan2(tx,ty) * SurfaceWidth);
      dy:=trunc(sqrt(tx*tx+ty*ty));

      //テーブルに入れる
      p^:=Point(dx,dy);
      //オフセットを求める
      Inc(p);
    end;
  end;
end;
こんな感じになりました。 このテーブルは、直交座標での座標に対応した極座標での座標を入れてあります。 これでテクスチャ座標逆算テーブルを求めているわけです。 あ、逆算てのは転送元の点は転送先のどこへいくかを計算するかではなく、 転送先の点は転送元のどこにあたるかを計算することです。逆算の理由は、 前者の方法だと画像に穴があくからです。このあたりは、多分画像変形処理の基礎知識 だと思われますので、画像処理に興味のある人は知っておくといいと思います。

※極座標
文章だけで使えるのは難しいので、ここでは説明を省略。 なにやら難しいそうな本を調べると書かれていると思われる。 もしかしたら、過去の「EXTREME」に図があるかもしれない。

※kiloDemos
僕が昔やっていた、へっぽこデモプログラムたち。 今はもうありません。ほしい方はメールか何かで連絡してください。

※ルーズリーフ
プログラムを作ってて、考える必要があると僕はルーズリーフに 図を書いて考え出すのです。頭の中だけで考えるよりも はるかに効率的だと思う。

※テーブル
何かしろ重い処理をするとき、高速化のために計算結果をあらかじめ 求めておき、それを配列などにぶちこんでおいて、使い時はそれを参照する というテクニックだと思う。デモなどでは処理の高速化のため、頻繁に 使われる。ゲームプログラムなどは、Sinなどがテーブルで使われる(と思うよ)。

※スクリーン座標
ディスプレー上での座標。

さて、高速化のためのテーブルが出来たのでいっきに表示をやってしまいますか。 表示するには、先ほどの極座標テーブルを使用します。具体的には 表示先の全ピクセルにアクセスし、その座標に対応するテクスチャ座標を求め、 その画素を1ピクセルずつコピーしていく、という感じです(ごめん、説明下手)。


//テクスチャサイズは256*256、表示サイズは320*240、
//DirectXを使っています。
//高速化(速くなっているかは未確認)のため、
//かなりポインタを駆使しています。
var p:PPoint;
    x,y,sx,sy:Integer;
    pl,p1,p2,pSrc:pByte;
    ddsd1,ddsd2:DDSURFACEDESC;
begin
  //ロック
  List.Buffer.Lock(ddsd1);
  tex.Lock(ddsd2);

  //各ポインタをゲット
  p   :=@kyokuTable;
  pl  :=ddsd1.lpSurface;
  pSrc:=ddsd2.lpSurface;

  //全画面に書きこみ
  for y:= 0 to 239 do begin
    p1:=pl;
    for x:= 0 to 319 do begin
      //テーブルから座標ゲット(ANDクリップつき)
      sx:=p^.x AND 255; sy:=p^.y AND 255;
      //テクスチャの先頭アドレスをゲット
      p2:=pSrc;
      //アドレスをインクリメント
      Inc(p2,sx+sy*ddsd2.lPitch);
      //座標を入れとく
      p1^:=p2^;
      //次へ
      Inc(p1);
      Inc(p);
    end;
    Inc(pl,ddsd1.lPitch);
  end;

  //アンロック
  List.Buffer.UnLock;
  tex.UnLock;
end;
さらなる高速化を目指すなら、いちいちテーブルから座標をゲットして計算するよりも(「アドレスをインクリメント」ってとこね) 先に計算しておいてそれをテーブルに入れておく、という方法が考え付きます。ここではなぜか実装していませんね.....(^^;)

注意点としては、表示先、元テクスチャはともに「システムメモリ」上に確保しておいたほうがいいでしょう。 ビデオメモリ上に確保すると、フレームレートががたおちする可能性があります。気になる方は試してください。

※システムメモリ
DirectXでは画像を確保する場所を指定できるみたいなんだけど、 確保する場所にはシステムメモリ(CPUが通常使う)とビデオカード上のメモリが指定できて、 システムメモリは何回もアクセスするのに向き、ビデオカードは単純な画像転送に向く。 そのため、今回のようにソフトウェア計算で画像処理を行う場合には システムメモリ上に画像を確保したほうがいい、っていうわけです(ごめん、長すぎ)。






もどる