mieki256's diary



2014/01/15(水) [n年前の日記]

#2 [dxruby] DXRuby+Shaderで床ラスタースクロールっぽいことができたような気がする

Shader利用サンプルの sample_spehari.rb を弄って、望んだ感じの床ラスタースクロール、っぽいことができたような気がする。たぶん。

sample_spehari.rb を眺めていて、気づいた点を列挙。 そのあたりを意識しながら弄ってみたり。

こんな感じに。
床ラスタースクロール

STGサンプルに組み込んでみたら、こんな感じ。
STGサンプルに組み込んでみた例
どう見ても、 _「お願い、私の星を助けて…」 ですな。アレも、こんな感じのことをしてたのかしら。

長いけど、一応ソースも貼っときます。

_floorrasterscroll.rb
require 'dxruby'

#
# Shaderを使って床ラスタースクロールっぽい処理をするクラス
#
class FloorRasterScroll

  @@hlsl = <<EOS
  float floor_distance;
  float screen_distance;
  float x_diff;
  float y_diff;
  float alpha;
  texture tex0;

  sampler Samp0 = sampler_state
  {
    Texture =<tex0>;
    MipFilter = LINEAR;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
    AddressU = WRAP;
    AddressV = WRAP;
  };

  float4 PS(float2 input : TEXCOORD0) : COLOR0
  {
    float4 output;
    input.y = screen_distance * floor_distance / -(input.y + y_diff);
    input.x = (input.x - x_diff) * -input.y / screen_distance;
    output = tex2D( Samp0, input );
    output.a = alpha;
    return output;
  }

  technique FloorRasterScroll
  {
   pass P0
   {
    PixelShader = compile ps_2_0 PS();
   }
  }
EOS

  attr_accessor :core, :shader, :image
  attr_accessor :w, :h
  attr_accessor :tilew, :tileh
  attr_accessor :rt_w, :rt_h, :rt_scaley
  attr_accessor :rt
  attr_accessor :floor_dist, :scr_dist, :x_diff, :y_diff, :alpha

  #
  # 初期化処理
  #
  # @param [Image] image 使用する画像
  # @param [float] floor_dist 床までの距離。1.0前後が目安
  # @param [float] scr_dist 画面までの距離。1.0以下が目安
  # @param [float] y_diff 視線方向を上下にずらす量。0.0から1.0の範囲
  # @param [float] alpha 透明度。0.0で透明。1.0で不透明
  # @param [int] w 描画横幅。nil ならウインドウ横幅
  # @param [int] h 描画縦幅。nil ならウインドウ縦幅
  #
  def initialize(image, floor_dist=0.3, scr_dist=0.3, y_diff=0.1, alpha=1.0,
                 w=nil, h=nil)
    self.core = Shader::Core.new(@@hlsl,{
                                   :floor_distance=>:float,
                                   :screen_distance=>:float,
                                   :x_diff=>:float,
                                   :y_diff=>:float,
                                   :alpha=>:float
                                 })
    self.shader = Shader.new(self.core, "FloorRasterScroll")

    self.image = image

    self.w = (w == nil)? Window.width : w
    self.h = (h == nil)? Window.height : h

    self.floor_dist = floor_dist
    self.scr_dist = scr_dist
    self.y_diff = y_diff
    self.alpha = alpha

    # 画面を覆い隠すタイル数を求める
    self.tilew = self.w.quo(self.image.width).ceil
    self.tileh = self.h.quo(self.image.height).ceil

    # RenderTarget の必要サイズを求める
    self.rt_w = self.image.width * self.tilew
    self.rt_h = self.image.height * self.tileh

    # y方向で拡大縮小して描画縦横幅に合わせる
    self.rt_scaley = self.h.quo(2).quo(rt_h)

    # Rendertarget生成
    self.rt = RenderTarget.new(rt_w, rt_h)

    # 画面の真ん中に消失点?を持ってくるための補正値を算出
    self.shader.x_diff = 0.5 * self.w.quo(self.rt_w)
  end

  #
  # 描画処理
  #
  # @param [int] x 描画x座標
  # @param [int] y 描画y座標
  # @param [float] scrl_x 横方向スクロール位置
  # @param [float] scrl_z 奥行方向スクロール位置
  # @param [Hash] opts オプション
  # @option opts [float] floor_dist 床との距離。1.0前後が目安
  # @option opts [float] scr_dist 画面との距離。1.0以下が目安
  # @option opts [float] y_dist 視線方向を上下にずらす量。0.0 - 1.0 の範囲
  # @option opts [int] z 描画奥行情報
  # @option opts [float] alpha 透明度。0.0で透明。1.0で不透明
  # @option opts [Boolean] vflip trueなら上下判定。falseなら反転しない
  #
  def draw(x, y, scrl_x, scrl_z, opts={})
    self.floor_dist = opts[:floor_dist] if opts.has_key?(:floor_dist)
    self.scr_dist = opts[:scr_dist] if opts.has_key?(:scr_dist)
    self.y_diff = opts[:y_diff] if opts.has_key?(:y_diff)
    self.alpha = opts[:alpha] if opts.has_key?(:alpha)

    self.shader.floor_distance = self.floor_dist
    self.shader.screen_distance = self.scr_dist
    self.shader.y_diff = self.y_diff
    self.shader.alpha = self.alpha

    vflip = 1
    vflip *= -1 if opts.has_key?(:vflip) and opts[:vflip] == true

    z = 0
    z = opts[:z] if opts.has_key?(:z)


    # Shader が参照する画像を RenderTarget に作成
    self.rt.draw_tile(0, 0, [[0]], [self.image],
                      scrl_x, scrl_z, self.tilew, self.tileh).update

    # Shader を使って透視変換しつつ描画
    Window.drawEx(x, y, self.rt, :shader=>self.shader,
                  :scaley=>self.rt_scaley * vflip, :centery=>0, :z=>z)
  end

  #
  # 動作確認用。グリッド画像を生成して返す
  #
  # @param [int] w 画像横幅
  # @param [int] h 画像縦幅
  # @return [Image] 生成した画像
  #
  def self.get_grid_image(w, h)
    image = Image.new(w, h, [0, 255, 0])
    image.box_fill(0, 0, w / 2 - 1, h / 2 - 1, [150,250,150])
    image.box_fill(w / 2, 0, w - 1, h / 2 - 1, [100,250,100])
    image.box_fill(0, h / 2, w / 2 - 1, h - 1, [200,250,200])
    image.box_fill(w / 2, h / 2, w - 1, h - 1, [0,220,0])
    return image
  end
end

# ----------------------------------------

if $0 == __FILE__
  # 動作テスト

  # 画像を読み込む
  # imgfilename = "lena.png"
  imgfilename = "cloud.png"

  if File.file?(imgfilename)
    image = Image.load(imgfilename)
  else
    # 画像ファイルが見つからなければ、タイル画像を作成
    image = FloorRasterScroll.get_grid_image(160, 160)
  end

  test_mode = 1

  case test_mode
  when 0
    # ----------------------------------------
    # シンプルな使い方の例

    bg = FloorRasterScroll.new(image)
    x = 0
    z = 0
    Window.loop do
      break if Input.keyPush?(K_ESCAPE)
      x += 8
      z += 5
      bg.draw(0, 240, x, z)
    end

  when 1
    # ----------------------------------------
    # 動作テスト。各パラメータを細かく変更しながら描画してみる

    bg = FloorRasterScroll.new(image) # 床用
    bg2 = FloorRasterScroll.new(image) # 天井用

    x = 0
    z = 0
    x_spd = 8
    z_spd = 5
    floor_dist = 0.3
    scr_dist = 0.3
    y_diff = 0.1

    font = Font.new(16)
    fade_enable = false
    count = 0

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

      # 速度リセット
      if Input.keyDown?(K_C)
        x_spd = 0
        z_spd = 0
      end

      # 横方向スクロール速度を変更
      x_spd += 0.1 * Input.x

      # 奥行方向スクロール速度を変更
      z_spd -= 0.1 * Input.y

      # 床との距離を変更
      floor_dist += 0.01 if Input.keyDown?(K_W)
      floor_dist -= 0.01 if Input.keyDown?(K_S)

      # 画面までの距離を変更
      scr_dist += 0.005 if Input.keyDown?(K_D)
      scr_dist -= 0.005 if Input.keyDown?(K_A)
      scr_dist = 0.05 if scr_dist < 0.05

      # 視線を上下にずらす値を変更
      y_diff += 0.005 if Input.keyDown?(K_R)
      y_diff -= 0.005 if Input.keyDown?(K_F)

      # 透明度を変更
      fade_enable = !fade_enable if Input.keyPush?(K_Q)
      alpha = (fade_enable)? Math.sin(3 * count * Math::PI / 180.0).abs : 1.0

      # スクロール位置変更
      x += x_spd
      z += z_spd

      # 描画
      bg.draw(0, 240, x, z,
              :vflip=>false,
              :floor_dist=>floor_dist,
              :scr_dist=>scr_dist,
              :y_diff=>y_diff,
              :z=>-255, :alpha=>alpha)

      bg2.draw(0, 240, x, -z,
               :vflip=>true,
               :floor_dist=>floor_dist,
               :scr_dist=>scr_dist,
               :y_diff=>y_diff,
               :z=>-255, :alpha=>alpha)

      # 説明文を描画
      tx = 4
      h = 20
      Window.drawFont(tx, h * 0, "RIGHT , LEFT : x speed = #{x_spd}", font)
      Window.drawFont(tx, h * 1, "UP , DOWN    : z speed = #{z_spd}", font)
      Window.drawFont(tx, h * 2, "W , S : floor dist = #{floor_dist}", font)
      Window.drawFont(tx, h * 3, "D , A : screen dist = #{scr_dist}", font)
      Window.drawFont(tx, h * 4, "R , F : y_diff = #{y_diff}", font)
      Window.drawFont(tx, h * 5, "Q : Fade in/out", font)
      Window.drawFont(tx, h * 6, "C : Speed clear", font)

      count += 1
    end

  end

end
動作テスト部分のほうが、長いという…。

今回も、単体で実行しても動くけど、他のファイルから呼んでも使えるようにしておきました。本体ファイル?と同じフォルダに floorrasterscroll.rb を入れといて、以下のような記述で使えるかなと…。
# 床ラスター処理のテスト

require 'dxruby'
require_relative 'floorrasterscroll'

image = Image.load("lena.png")
bg = FloorRasterScroll.new(image)
x = 0
z = 0
Window.loop do
  break if Input.keyPush?(K_ESCAPE)
  x += 8
  z += 4
  bg.draw(0, 240, x, z)
end

サンプルを弄って作っただけなので、これもサンプル同様、Public Domain にしておきます。それと、画像は以下からDLして使ってみてください。

_cloud.png (320x320 dot)
cloud.png


もやもやした模様ではよく分からないでしょうから、あの有名な、 _lena様画像 も置いときますね…。

_lena.png (160x160 dot)

使うと、以下のような感じになります。
lena様で試した結果

やっぱり、lena様のような画像で実験すると、なんだかやる気がでてくるなあ。

透視変換で頭が混乱してきたのでメモ。 :

透視変換をするShader ―― HLSLを書くあたりで、ちょっとハマった。なんだかややこしいので、一応図解してメモ。

まず、画面座標は、縦横 0.0 〜 1.0 の範囲になってる、と考える。また、元画像は、床に、つまり平面に、ずらりと並んでると考える。z値として得た値は、uv値のvとして使う、とでも言えばいいのか…。
概念図
もしかしたら v の方向は逆かもしれないけど。上が1.0なのか、下が1.0なのか、よく分かってません…。

真横から見た状態を考える。
真横から見た状態
作図ツールの関係で、上方向が +y になってるけど、プログラム上では、下方向が +y なので、ちと注意。

値が分かってるものと、値が分かってないものを、分類してみる。
  • sy は、画面上の座標。HLSL 内の input.y が、そのものズバリなので、これは値が分かってる。
  • sz は、画面までの距離。これはテキトーに決めるので、値が分かってる。画面の縦横が 0.0 〜 1.0 なので、sz も 0.n みたいな値になりそう。
  • py は、床までの距離。これもテキトーに決めるので、値が分かってる。おそらくこれも、値が 0.n ぐらいになりそう。
  • pz は、参照する画像の、v値。これが分からない。
ということで。pz を求めればいい。

(sz, sy) が作る三角形と (pz, py) が作る三角形は相似なので、以下が成り立つ。
sz : sy = pz : py
sz * py = sy * pz
pz = (sz * py) / sy
これで、参照する画像の v 値は得られるので、HLSL内の input.y に代入してやればいい。はず。

さらに、真上から見た状態を考える。
真上から見た状態

これも、値が分かってるものと、分かってないものを、分類してみる。
  • sx は、画面上の座標。HLSL内の input.x から得られる。これは値が分かってる。
  • sz は、画面までの距離。これはテキトーに決めるので、値が分かってる。
  • pz は、参照する画像の v値。これはさっき求めたから、値が分かってる。
  • px は、参照する画像の u値。これが分からない。コイツを求めたい。
ということで。
sz : sx = pz : px
sz * px = sx * pz
px = sx * pz / sz

で、注意点が。

sx は input.x から求まるのだけど、input.x の範囲は、画面左から右に向かって 0.0〜1.0 なので、真ん中は 0.5。なので、sx = input.x - 0.5 として、ずらしてやらないといけない。

sy は…。今回は、床を表示したいなーと思ってるので、画面の下半分だけ考えればいいよなと。だったら、sy = input.y でいいかなと。画面の上半分も含めて何かしたいなら、sy = input.y - 0.5 になるのだろうけど。

そんな感じで、HLSL内では、以下のようなソレを書けば良さそう。
sz = 画面までの距離
py = 床までの距離

input.y = sz * py / input.y
input.x = (input.x - 0.5) * input.y / sz
と思ったけど、試してみたら上下が逆になった。やっぱり v値は、上が1.0なのかもしれない…? そんなわけで、ちょっと修正。
input.y = sz * py / -input.y
input.x = (input.x - 0.5) * -input.y / sz
見た目はそれらしくなったから、これでいいか…。

天井はどうしてるかというと。 :

天井は、床を上下反転して描画してるだけです。Window.drawEx() で描画する際に、:scaley => -n を渡してるだけ。

以上です。

過去ログ表示

Prev - 2014/01 - 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 31

カテゴリで表示

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


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

Powered by hns-2.19.6, HyperNikkiSystem Project