mieki256's diary



2014/06/05(木) [n年前の日記]

#1 [dxruby] DXRuby上で射影変換ができた

元記事・参考記事の内容が全然理解できてないけど。写経してたらそれっぽく描画されるようになったのでアップロード。

projtrans.gif
Window.drawMorph による描画結果とは、ちょっと違うことが分かるかしら。

ソースは以下。

_projtrans.rb.txt
require 'dxruby'

# 4点を指定して射影変換描画
class ProjTrans

  attr_accessor :images
  attr_accessor :src_w
  attr_accessor :src_h
  attr_accessor :divx, :divy
  attr_accessor :mdivx, :mdivy
  attr_accessor :src_pos
  attr_accessor :src_idx

  # コンストラクタ
  #
  # @param [Object] img Imageオブジェクト
  # @param [Number] divx x方向の分割数
  # @param [Number] divy y方向の分割数
  #
  def initialize(img, divx = 8, divy = 8)
    self.divx = divx
    self.divy = divy
    self.src_w, self.src_h = img.width, img.height
    self.images = img.sliceTiles(divx, divy) # 画像を分割

    # 頂点列を登録
    self.src_pos = []
    w, h = 0, 0
    bx, by = 0, 0
    i = 0
    (0..self.divy).each do |iy|
      bx = 0
      (0..self.divx).each do |ix|
        if ix < self.divx and iy < self.divy
          i = iy * self.divx + ix
          w = self.images[i].width
          h = self.images[i].height
        end
        puts "#{i} : (#{bx}, #{by})" if false

        # 元画像のサイズで座標値を割っておく
        self.src_pos.push([bx.to_f / self.src_w, by.to_f / self.src_h])

        bx += w
        i += 1
      end
      by += h
    end

    # 頂点番号を登録
    self.src_idx = []
    self.divy.times do |iy|
      self.divx.times do |ix|
        addy = self.divx + 1
        i = ix + iy * addy
        self.src_idx.push([i, i + 1, i + 1 + addy,  i + addy])
      end
    end

    if false
      self.src_idx.each_with_index {|a, i| puts "#{i} : #{a}" }
    end
  end

  # 描画
  #
  # @param [Number] x1 点1のx座標
  # @param [Number] y1 点1のy座標
  # @param [Number] x2 点2のx座標
  # @param [Number] y2 点2のy座標
  # @param [Number] x3 点3のx座標
  # @param [Number] y3 点3のy座標
  # @param [Number] x4 点4のx座標
  # @param [Number] y4 点4のy座標
  # @param [Number] alpha アルファ値 0-255
  # @param [Number] z 描画深度
  # @param [Number] blend 合成種類
  # @param [Number] mdivx drawMorphに与えるx方向の分割数
  # @param [Number] mdivy drawMorphに与えるy方向の分割数
  #
  def draw(x1, y1, x2, y2, x3, y3, x4, y4,
           alpha = 255, z = 0, blend = :alpha,
           mdivx = 2, mdivy = 2)

    # a から h までの、8つの係数を求める
    sa, sb, sc, sd, se, sf, sg, sh = get_system(x1.to_f, y1.to_f,
                                                x2.to_f, y2.to_f,
                                                x3.to_f, y3.to_f,
                                                x4.to_f, y4.to_f)

    # 射影変換
    d = []
    self.src_pos.each do |x, y|
      m = (x * sg + y * sh + 1)
      u = (x * sa + y * sb + sc) / m
      v = (x * sd + y * se + sf) / m
      d.push([u, v])
    end

    self.src_idx.each_with_index do |a, i|
      i0, i1, i2, i3 = a
      Window.drawMorph(d[i0][0], d[i0][1],
                       d[i1][0], d[i1][1],
                       d[i2][0], d[i2][1],
                       d[i3][0], d[i3][1],
                       self.images[i],
                       :dividex => mdivx, :dividey => mdivy,
                       :alpha => alpha, :z => z, :blend => blend)
    end

  end

  # 8つの係数を求める
  #
  # @note 変換式に与える座標値を元画像のサイズで割らないとこの係数は使えない
  #
  # @param [Number] x0 点1のx座標
  # @param [Number] y0 点1のy座標
  # @param [Number] x1 点2のx座標
  # @param [Number] y1 点2のy座標
  # @param [Number] x2 点3のx座標
  # @param [Number] y2 点3のy座標
  # @param [Number] x3 点4のx座標
  # @param [Number] y3 点4のy座標
  # @return [Array] 8つの係数を配列で返す
  #
  def get_system(x0, y0, x1, y1, x2, y2, x3, y3)
    sx = (x0 - x1) + (x2 - x3)
    sy = (y0 - y1) + (y2 - y3)
    dx1 = x1 - x2
    dx2 = x3 - x2
    dy1 = y1 - y2
    dy2 = y3 - y2
    z = (dx1 * dy2) - (dy1 * dx2)
    g = ((sx * dy2) - (sy * dx2))/z
    h = ((sy * dx1) - (sx * dy1))/z

    return [
            x1 - x0 + g * x1,
            x3 - x0 + h * x3,
            x0,
            y1 - y0 + g * y1,
            y3 - y0 + h * y3,
            y0,
            g,
            h
            ]
  end
end

if $0 == __FILE__
  # ----------------------------------------
  # 動作確認

  img = Image.load("lena.png")
  prt = ProjTrans.new(img)

  if false
    # ----------------------------------------
    # 簡単な使い方の例

    Window.loop do
      break if Input.keyPush?(K_ESCAPE)

      alpha = 255
      z = 0
      blend = :alpha
      divx = 2
      divy = 2

      # 左上の点から時計回りに4点を指定
      prt.draw(160, 100,
               320, 20,
               600, 460,
               100, 300,
               alpha, z, blend, divx, divy)
    end

  else
    # ----------------------------------------
    # 動作確認

    font = Font.new(14)
    step = 0
    pos = []
    poly_pos = []
    morph_enable = false
    mdiv = 1

    Window.loop do
      break if Input.keyPush?(K_ESCAPE)

      if Input.mousePush?(M_LBUTTON)
        # マウスの左ボタンが押されたので座標を記録
        pos = [] if step == 0
        pos.push([Input.mousePosX, Input.mousePosY])
        step += 1
        if step >= 4
          # 4点が入力された
          step = 0
          poly_pos = pos.dup
        end
      end

      # zキーが押されたら drawMorph で描画するかどうかを切替
      morph_enable = !morph_enable if Input.keyPush?(K_Z)

      # 上下キーで分割数を変更 (1から64まで。128にすると落ちる…)
      mdiv *= 2 if (Input.keyPush?(K_UP) and mdiv < 64)
      mdiv /= 2 if (Input.keyPush?(K_DOWN) and mdiv >= 2)

      if poly_pos.size == 4
        # 4点が決まってるので、画像を変形して描画
        x1, y1 = poly_pos[0]
        x2, y2 = poly_pos[1]
        x3, y3 = poly_pos[2]
        x4, y4 = poly_pos[3]

        unless morph_enable
          # 射影変換で描画する場合
          prt.draw(x1, y1, x2, y2, x3, y3, x4, y4, 255, 0, :alpha, mdiv, mdiv)
        else
          # drawMorphで描画する場合
          Window.drawMorph(x1, y1, x2, y2, x3, y3, x4, y4, img,
                           :dividex => mdiv, :dividey => mdiv)
        end
      end

      # 指定済みの頂点位置を描画
      d = 5
      pos.each do |p|
        x, y = p
        Window.drawLine(x - d, y, x + d, y, C_GREEN)
        Window.drawLine(x, y - d, x, y + d, C_GREEN)
      end

      i = step + 1
      y = 2
      msg = [
        "#{Window.real_fps.to_i} FPS  CPU: #{Window.getLoad.to_i} %",
        "zキー : drawMorph 描画との切替 [#{(morph_enable)? "drawMorph" : "射影変換"}]",
        "↑↓キー : drawMorph 分割数 [#{mdiv}]",
        "時計回りで、4回、マウスクリックしてください (#{step+1}/4)"
      ].each_with_index do |s, iy|
        Window.drawFont(4, 2 + iy * (font.size + 8), s, font)
      end
    end
  end

end
一応、グリッドを上から描き込んだlena様画像も置いときます。

_lena.png (125KB)

それと、他のスクリプトからも、require すれば使えるように書いておきました。
# ProjTransの使用例

require 'dxruby'
require_relative 'projtrans'

img = Image.load("lena.png")
prt = ProjTrans.new(img)

Window.loop do
  break if Input.keyPush?(K_ESCAPE)

  alpha = 255
  z = 0
  blend = :alpha
  divx, divy = 2, 2

  # 左上の点から時計回りに4点を指定
  prt.draw(160, 100, 320, 20, 600, 460, 100, 300,
           alpha, z, blend, divx, divy)
end

スクリプト内でやってることを簡単に説明すると…。
  1. 元画像を 4x4 とか 8x8分割。
  2. 4点を指定することで、射影変換の変換式で使う8つの係数を求める。
  3. 分割された各画像に対して、射影変換後の4点を求める。
  4. その4点で、Window.drawMorph を使って描画。
Window.drawMorph だけで全てを変形描画すると平面的な?変形になってしまいますが。大雑把に、4x4 や 8x8 で分割して射影変換した、そのマス目の中だけを Window.drawMorph で描画すれば、パッと見は遠近感がついてるように見えるだろ、という…。要するに、インチキです。

インチキとは言うけれど、PS1やSSの3Dゲームも似たようなことをして誤魔化してたので、ゲームなら全然アリじゃないかなと。 *1

問題点。 :

一応できたものの、問題が。
  • 予想より、CPU負荷が高い。1枚描画するだけで、CPU使用率が 15〜20% ぐらいになってしまう。
  • 使い道が思いつかない。
2Dゲームの背景演出に使えるんじゃないかと思って試していたのですけど、さて、コレを何に使えるのかなあ、みたいな。

各ポリゴン?の描画面積が小さい場合や、正方形に近い描画の場合は、Window.drawMorph のみで描画しちゃってもいいだろうと思うわけですよ。小さいポリゴンがチラチラ飛んでる分には、どうせ違いなんてパッと見では分かりませんし。おそらく、広い面積を描画する場合に限って、こういった遠近感がついてる描画を使えるのかなと。ただ、どんな演出・背景構成を思いつくかというと、うーん。

それはともかく、これを Shader でやったらどうなるんだろう…。いや、DXRuby のサンプルスクリプトの中に、既にあった気もしますが…。

参考ページ。 :

_射影変換(ホモグラフィ)について理解してみる - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その11 - デジタル・デザイン・ラボラトリーな日々
m2とm5は近似値なのに、それ以外は結構数値の大きさが違います。これでなぜ射影変換出来るのかと調べたところ、描画処理の部分が下記のようなっており、なんと描画サイズ(dstWidth,dstHeight)で割っておりました。

射影変換(ホモグラフィ)について理解してみる その11 - デジタル・デザイン・ラボラトリーな日々 より

昨日スクリプトを書いた時は描画がおかしくて首を捻ったけれど。この一文を目にしたので同じことをしてみたら、それっぽく描画されて「おお…」と思いました。

他の参考資料もメモ。

_平面射影変換
_重なる気持ち -台形補正- | _level0 | Kayac Front End Engineer's Blog
_射影変換(ホモグラフィ)について理解してみる その2 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その3 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その4 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その5 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その6 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その7 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その8 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その9 - デジタル・デザイン・ラボラトリーな日々
_射影変換(ホモグラフィ)について理解してみる その10 - デジタル・デザイン・ラボラトリーな日々
_ホモグラフィ - Shogo Computing Laboratory
_Saqoosha :: スーパー高速に射影変換するには
_Processingで射影変換(ホモグラフィ) | United Field:中西泰人研究室
_遠近法の射影変換パラメータ計算の高速化(pdf)
_Homography << HIDIHO!
_Projection Matrix - wonderfl build flash online
_射影変換 (Homography) - jsdo.it - Share JavaScript, HTML5 and CSS
_テクスチャマッピング入門 射影変換(ホモグラフィ) - jsdo.it - Share JavaScript, HTML5 and CSS

*1: PS1もSSも、ハードウェアでは、三角形 or 四角形の平面的な変形描画機能しか持ってなかったので、そのまま広い面積を描画してしまうと妙な画面になってしまうわけで。なので、必要な時はポリゴンを分割して、とかやってました。まあ、3Dゲームの場合は、z値を使って分割が必要かどうか判定してたはずですが。

#2 [ruby] RubyのStructって速度的にはどうなんだろう

上記のスクリプトを書いてた際に、最初は x,y 座標だけを格納するクラスを作って座標管理してたのですけど。速度的によろしくないのかなと配列で管理するようにしちゃったのです。もしかして、クラスじゃなくて Struct を使えば良かったのだろうか…。

と思ってググっていたら、DXRuby作者様が、 _classとStruct - mirichiの日記 において速度測定していて。Ruby 1.9.1 では、Struct のほうがクラスより遅いのか…。

自分も実験。

# class, Struct, Array のベンチマーク

require 'benchmark'

IDX = 0

class Hoge
  attr_accessor :data

  def initialize(d = 0)
    self.data = d
  end
end

Fuga = Struct.new(:data)

Benchmark.bmbm { |x|
  x.report("class-new   ") { 1000000.times { val = Hoge.new(0) } }

  x.report("Struct-new  ") { 1000000.times { val = Fuga.new(0) } }

  x.report("Array-new   ") { 1000000.times { val = [0] } }

  val = Hoge.new
  x.report("class-data= ") { 1000000.times { val.data = 0 } }

  val = Fuga.new
  x.report("Struct-data=") { 1000000.times { val.data = 0 } }

  a = [0, 0, 0, 0]
  x.report("Array-data= ") { 1000000.times { a[IDX] = 0 } }

  v = Hoge.new(0)
  x.report("=class-data ") { 1000000.times { b = v.data } }

  v = Fuga.new(0)
  x.report("=class-data ") { 1000000.times { b = v.data } }

  a = [0, 0, 0, 0]
  x.report("=Array-data ") { 1000000.times { b = a[IDX] } }
}

> ruby --version
ruby 1.9.3p545 (2014-02-24) [i386-mingw32]

> ruby struct_bench.rb
Rehearsal ------------------------------------------------
class-new      0.312000   0.000000   0.312000 (  0.314018)
Struct-new     0.265000   0.000000   0.265000 (  0.261015)
Array-new      0.109000   0.000000   0.109000 (  0.108006)
class-data=    0.172000   0.000000   0.172000 (  0.175010)
Struct-data=   0.187000   0.000000   0.187000 (  0.175010)
Array-data=    0.094000   0.000000   0.094000 (  0.099006)
=class-data    0.078000   0.000000   0.078000 (  0.077004)
=class-data    0.078000   0.000000   0.078000 (  0.077005)
=Array-data    0.062000   0.000000   0.062000 (  0.065004)
--------------------------------------- total: 1.357000sec

                   user     system      total        real
class-new      0.312000   0.000000   0.312000 (  0.310017)
Struct-new     0.250000   0.000000   0.250000 (  0.256015)
Array-new      0.109000   0.000000   0.109000 (  0.106006)
class-data=    0.171000   0.000000   0.171000 (  0.177010)
Struct-data=   0.172000   0.000000   0.172000 (  0.175010)
Array-data=    0.109000   0.000000   0.109000 (  0.099005)
=class-data    0.078000   0.000000   0.078000 (  0.078005)
=class-data    0.078000   0.000000   0.078000 (  0.077004)
=Array-data    0.063000   0.000000   0.063000 (  0.065004)
生成時に値を渡すなら、Struct のほうが速いのか…。でも、Array を生成するほうが、もっと速いな…。値を代入するのも、読み取るのも、Array のほうが速いっぽい。

もちろん、配列ばかり使うと、どこに何の値が入ってるか分かりづらくなるし、途中に何か要素を追加すると修正が大変だし。書きやすさ・メンテナンスと、処理速度の、トレードオフだったりするのかな。

以上、1 日分です。

過去ログ表示

Prev - 2014/06 - Next
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30

カテゴリで表示

検索機能は Namazu for hns で提供されています。(詳細指定/ヘルプ


注意: 現在使用の日記自動生成システムは Version 2.19.6 です。
公開されている日記自動生成システムは Version 2.19.5 です。

Powered by hns-2.19.6, HyperNikkiSystem Project