mieki256's diary



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

#1 [dxruby] DXRubyでラスタースクロール処理に再チャレンジ

_2014/01/12 に、DXRuby の Shader (HLSL)を使ってラスタースクロールの実験を ―― ラスタースクロールだけで疑似3Dっぽい表現ができないか実験していたのですが。ラスタースクロールに使える画像の作り方が分からなくて、諦めてしまったのでした。

ふと、今頃になって、「こういう画像を作ればよかったのではないか?」とピンと来たので試してみたり。

結果的には、こんな感じの画面になりました。
天井と壁と床でラスタースクロール
うむ。ストIIですな。

画像を作る際のコツ。 :

たぶん、こういう画像を作ればいいんじゃないかな…。
ラスタースクロール用画像
ピンク色のところが、最終的に欲しい画像。

例えば、GIMPで作業するなら、
  • 変形ツールの遠近法?だかで、上辺896ドット、下辺1792ドットの台形画像を作成。
  • 真ん中あたりから、896 x 160ドットぐらいをクリップ。
要するに…。
  • 台形画像を作る際、下辺の長さを、上辺の2倍の長さにすると、処理や計算が楽。
  • 最終的に必要な画像の横幅は、ウインドウサイズ+グリッドサイズ(64ドット) x 4 にしておくと楽。

グリッドに合わせて線を引いていくのは面倒臭かったので、下地画像を作成するスクリプトを書いてみたり。

_mk_ras_img.rb
#
# ラスタースクロール用BGの下絵画像を生成して保存する。
# 天井、壁、床、の3つの画像を保存する。
#

require 'dxruby'

# ----------------------------------------
# 定義部分

wdw_w = 640 # ウインドウ横幅
wdw_h = 480 # ウインドウ縦幅

cw = 128 / 2 # 1マス分のドットサイズ

h0 = 32 * 5
h2 = 32 * 5
h1 = wdw_h - h0 - h2

w = wdw_w + cw * 4 # 画像横幅

# 出力ファイル名
output_fname = [
  "bg_ras_a_base.png",
  "bg_ras_b_base.png",
  "bg_ras_c_base.png",
]

# 定義部分ここまで
# ----------------------------------------

# 新規画像作成
img0 = Image.new(w, h0, [0, 0, 0, 0])
img1 = Image.new(w, h1, [0, 0, 0, 0])
img2 = Image.new(w, h2, [0, 0, 0, 0])

# 線を引く
cx = w / 2
0.step(cx, cw) do |d|
  a0 = d
  a1 = d * 2
  img0.line(cx + a1, 0, cx + a0, h0, C_WHITE)
  img0.line(cx - a1, 0, cx - a0, h0, C_WHITE)

  img1.line(cx + a0, 0, cx + a0, h1, C_WHITE)
  img1.line(cx - a0, 0, cx - a0, h1, C_WHITE)

  img2.line(cx + a0, 0, cx + a1, h2, C_WHITE)
  img2.line(cx - a0, 0, cx - a1, h2, C_WHITE)
end

# 保存する
img0.save(output_fname[0])
img1.save(output_fname[1])
img2.save(output_fname[2])

font = Font.new(14)

# 表示して確認
Window.loop do
  break if Input.keyPush?(K_ESCAPE)

  x = (Window.width - w) / 2
  y = 0

  Window.draw(x, y, img0)
  y += img0.height

  Window.draw(x, y, img1)
  y += img1.height

  Window.draw(x, y, img2)
  y += img2.height

  [
    output_fname[0],
    output_fname[1],
    output_fname[2],
    "を保存しました"
  ].each_with_index do |s, i|
    Window.drawFont(4, 4 + i * 20, s, font)
  end
end
実行すると、以下の3つの画像が保存される。

bg_ras_a_base.png

bg_ras_b_base.png

bg_ras_c_base.png


そして、これらの下地画像を参考にしつつ、GIMPで画像を作成。以下のような感じに。

bg_ras_a.png

bg_ras_b.png

bg_ras_c.png

  1. 128x128の画像を作成。これが1マス分。
  2. フィルタ → マップ → 並べる、で、1792 x 640 ドットの画像を作成。
  3. 遠近法ツールで、遠近感がついた台形に変形。ガイドを作って、頂点をガイドに合わせるようにすれば作業が少し楽。x=448,x=1344の位置にガイドを作ればいいのかな。
  4. ガイドの作り方は、画像 → ガイド → 新規ガイド。それと、表示 → ガイドにスナップ、にチェックが入っていること。
  5. 変形できたら、矩形選択ツールで、必要な部分だけコピー。
  6. ファイル → 画像の生成 → クリップボードから、を選べば新規画像ができる。
  7. ブレンドツール(グラデーションを塗るツール)や、色調整で、見た目を修正。

ついでに、柱の画像もテキトーに作成。テクスチャを適当に作って、フィルター → マップ → オブジェクトにマップ → 円柱、とかそんな感じ。

pole.png

スクリプトはこんな感じに。 :

スクリプトはこんな感じに。

_bgrasterscroll3.rb.txt
# Shaderを使ってラスタスクロールのテスト
# 天井、壁、床をラスタースクロールさせるっぽい感じの処理
# 画像を読み込んで表示する版

require 'dxruby'

class BgRasterScroll3

  attr_accessor :gridsize
  attr_accessor :imgs
  attr_accessor :shaders

  # コンストラクタ
  #
  # @param [Number] gridsize 1マス分のドットサイズ
  # @param [Array] imgs 天井、壁、床のImageオブジェクトを配列に入れて渡す
  #
  def initialize(gridsize, imgs)
    hlsl = <<EOS
  float2 size;
  float d;
  float dd;
  texture tex0;

  sampler Samp0 = sampler_state
  {
    Texture =<tex0>;

    // こっちだと画面がチラチラする (´・ω・`)
    // MinFilter = LINEAR;
    // MagFilter = LINEAR;
    // MipFilter = LINEAR;

    // こっちならチラチラしない
    MinFilter = POINT;
    MagFilter = POINT;
    MipFilter = NONE;

    AddressU = WRAP;
    AddressV = WRAP;
  };

  struct PixelIn
  {
    float2 UV : TEXCOORD0;
  };

  struct PixelOut
  {
    float4 Color : COLOR0;
  };

  PixelOut PS1(PixelIn input)
  {
    PixelOut output;
    input.UV.x = input.UV.x + ((d + (d * input.UV.y * dd)) / size.x);
    output.Color = tex2D( Samp0, input.UV );
    return output;
  }

  PixelOut PS2(PixelIn input)
  {
    PixelOut output;
    input.UV.x = input.UV.x + ((d + (d * (1.0 - input.UV.y) * dd)) / size.x);
    output.Color = tex2D( Samp0, input.UV );
    return output;
  }

  PixelOut PS3(PixelIn input)
  {
    PixelOut output;
    input.UV.x = input.UV.x + d / size.x;
    output.Color = tex2D( Samp0, input.UV );
    return output;
  }

//   float4 PS(float2 input : TEXCOORD0) : COLOR0
//   {
//     float4 output;
//     // input.x -= ((d / size.x) + ((d / size.x) * input.y * dd));
//     input.x = input.x - ((d + (d * input.y * dd)) / size.x);
//     output = tex2D( Samp0, input);
//     return output;
//   }

  technique FloorScroll
  {
    pass P0
    {
      PixelShader = compile ps_2_0 PS1();
    }
  }
  technique CeilingScroll
  {
    pass P0
    {
      PixelShader = compile ps_2_0 PS2();
    }
  }
  technique WallScroll
  {
    pass P0
    {
      PixelShader = compile ps_2_0 PS3();
    }
  }
EOS

    core = Shader::Core.new(hlsl, {:size=>:float, :d=>:float, :dd=>:float})
    self.shaders = []
    self.imgs = []
    self.gridsize = gridsize

    shader_names = [
      ["CeilingScroll", 1.0],
      ["WallScroll", 1.0],
      ["FloorScroll", 1.0]
    ]

    shader_names.each_with_index do |dt, i|
      sname, dd = dt
      img = imgs[i]
      self.imgs.push(img)
      shr = Shader.new(core, sname)
      shr.size = [img.width, img.height]
      shr.d = 0 # スクロール量
      shr.dd = dd # 上辺と下辺で、x方向に何ドットずれるか
      self.shaders.push(shr)
    end
  end

  # スクロール量を更新
  #
  # @param [Number] d スクロール量
  #
  def update(d)
    d = (d % self.gridsize)
    self.shaders.each do |shr|
      shr.d = d
    end
  end

  # 描画
  #
  # @param [Number] bx 描画位置 x
  # @param [Number] by 描画位置 y
  #
  def draw(bx, by)
    x, y = bx - self.gridsize * 2, by
    3.times do |i|
      Window.drawShader(x, y, self.imgs[i], self.shaders[i])
      y += self.imgs[i].height
    end
  end

end


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

  imgs = []

  # 画像ファイル名一覧に従って画像ロード
  [
    "bg_ras_a.png", # 天井
    "bg_ras_b.png", # 壁
    "bg_ras_c.png" # 床
  ].each {|fn| imgs.push(Image.load(fn)) }

  # ラスタースクロール用オブジェクトを発生
  ras = BgRasterScroll3.new(64, imgs)

  # 柱スプライトを発生
  pole = Image.load("pole.png")
  sprs = []
  4.times do |i|
    spr = Sprite.new(i * (96 * 2) + 72, 480 - (160 /2) - 128 + 8, pole)
    sprs.push(spr)
  end

  font = Font.new(14)
  x = 0
  Window.loop do
    break if Input.keyPush?(K_ESCAPE)

    # マウス位置に応じてスクロール量を変化
    dx = ((Window.width / 2) -  Input.mousePosX).to_f / Window.width * 32
    x -= dx
    ras.update(x) # スクロール量を指定

    ras.draw(0, 0) # 描画

    # 柱を描画
    tdx = dx + dx * 0.5
    sprs.each do |spr|
      spr.x += tdx
      spr.x += Window.width + 128 if dx < 0 and spr.x + 64 < 0
      spr.x -= (Window.width + 128) if dx > 0 and spr.x > Window.width
    end

    Sprite.update(sprs)
    Sprite.draw(sprs)

    Window.drawFont(4, 4, "dx = #{dx} , x = #{x}", font)
  end
end

実行すれば、前述のスクリーンショットのような画面になるはず。マウスカーソルのx座標で、スクロール速度が変わります。

他のスクリプトから呼んで使えるようにもしておきました。以下は使用例。
# BgRasterScroll3.rb の呼び出しテスト

require 'dxruby'
require_relative 'bgrasterscroll3'

imgs = []
[
  "bg_ras_a_base.png", # 天井
  "bg_ras_b_base.png", # 壁
  "bg_ras_c_base.png" # 床
].each {|fn| imgs.push(Image.load(fn)) }

ras = BgRasterScroll3.new(64, imgs)

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

  ras.update(x) # スクロール量を指定
  ras.draw(0, 0) # 描画
  x += 3
end


画像も含めて、一式をzipにして置いときます。ライセンスは、Public Domain ってことで。

_bg_raster_scroll3_20140607.zip (2.9MB)

以上、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