mieki256's diary



2014/01/13(月) [n年前の日記]

#1 [dxruby] DXRubyでラスタースクロールっぽいのができた

DXRuby の Shader を使って、サンダーフォースIVのステージ5っぽい背景ができた、ような気がする。下のような感じになりました。

ラスタースクロールっぽい処理
GIFアニメのフレームレートが低くてなんだかアレだけど、PC上で動かして、60FPSのソレを眺めれば、結構イイ感じ。

一応、ソース貼っときますね。

_starrasterbg.rb
require 'dxruby'

# Shaderを使ってラスタスクロールさせるクラス
#
# 宇宙空間っぽいBG画像をラスタースクロールさせる
#
class StarRasterScrollShader < DXRuby::Shader
  hlsl = <<EOS
  float2 size;    // 画像サイズ
  float d;        // スクロール位置
  float spd;      // ラスター毎の変化量
  float afactor;  // アルファ値
  texture tex0;

  // ラスター毎のスクロール量。16ラスターずつ繰り返し
  float dd[16] = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15};

  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 PS(PixelIn input)
  {
    PixelOut output;
    input.UV.x += ((d + (d * spd * dd[fmod(input.UV.y * size.y, 16)])) / size.x);
    output.Color = tex2D( Samp0, input.UV );
    output.Color.a *= afactor;
    return output;
  }

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

  @@core = DXRuby::Shader::Core.new(hlsl,
                                    {
                                      :size => :float,
                                      :d => :float,
                                      :spd => :float,
                                      :afactor => :float
                                    })

  attr_accessor :speed

  #
  # 初期化処理
  #
  # @param [float] speed   スクロール速度
  # @param [float] v       ラスター毎の速度変化量。大きくすると速度差が大きくなる
  # @param [int]   width   画面横幅。省略するとウインドウ横幅
  # @param [int]   height  画面縦幅。省略するとウインドウ縦幅
  #
  def initialize(speed=1, v=0.4, width=nil, height=nil)
    super(@@core, "StarRasterScroll")
    w, h = Window.width, Window.height
    w = width if width
    h = height if height
    self.size = [w, h]
    self.d = 0.0
    self.spd = v
    self.afactor = 1.0
    @speed = speed
  end

  #
  # スクロール値更新処理
  #
  def update
    self.d += @speed
  end

  #
  # スクロール値初期化
  #
  def reset_scroll
    self.d = 0
  end

  #
  # スクロール速度変更
  #
  # @param [float] speed   スクロール速度
  # @param [float] v       ラスター毎の速度変化量。大きくすると速度差が大きくなる
  #
  def set_speed(speed=1, v=0.4)
    @speed = speed
    self.spd = v
  end

  #
  # アルファ値変更
  #
  # @param [float] v   アルファ値、0.0で透明、1.0で不透明
  #
  def set_alpha(v)
    self.afactor = v
  end

  #
  # 星が描かれた背景用画像を作成して返す
  #
  # @param [int] width      画像横幅(省略時はWindow横幅)
  # @param [int] height     画像縦幅(省略時はWindow縦幅)
  # @param [int] num        散布するドット数
  # @param [bool] len_sync  trueなら線の長さをラスタースクロール速度に合わせる
  #                         デフォルトはfalse
  #
  # @return [Image] 作成した画像(Image)
  #
  def get_star_image(width=nil, height=nil, num=0x3fff, len_sync=false)
    width = Window.width unless width
    height = Window.height unless height
    img = Image.new(width, height, [0, 0, 0, 0])

    srand(0)

    # 速度テーブル(16ラスター分)
    spdtbl = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]

    # 速度と対応した線の長さ
    wtbl = [5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 12, 24, 36, 480]

    # ドットの色データ(3色に決め打ち)
    colordata = [[0, 47, 191], [69, 119, 209], [180, 191, 227]]

    # 画像に対してランダムに線を引く
    img.height.times do |y|
      # next if y % 16 != 0 and y % 16 != 1
      ymod = y % spdtbl.length

      if len_sync
        # ラスター毎のスクロール速度と対応した線の長さにする場合
        w = wtbl[spdtbl[ymod]]
      else
        # ラスター毎のスクロール速度と対応してない線の長さにする場合
        w = wtbl[ymod]
      end

      b = ((spdtbl[ymod] + 1) / spdtbl.length.to_f) * 0.5 + 0.5

      if w == wtbl[-1]
        w = rand(w) + wtbl[0]
        b *= (rand(70) + 31) / 100.0
      end

      l = ((spdtbl.length - ymod) * 0.75).to_i
      l = 1 if l == 0
      l.times do |i|
        x = rand(img.width - w)
        if w <= 8
          wt = [[x, w], [x + 1, w - 2], [x + 2, w - 4]]
        else
          wt = [[x, w], [x + w * 0.08, w * 0.84], [x + w * 0.16, w * 0.68]]
        end
        colordata.each_with_index do |d, i|
          lx, lw = wt[i]
          img.line(lx, y,  lx + lw, y, [d[0] * b, d[1] * b, d[2] *b])
          break if i== 1 and wt[i][0] == wt[i+1][0]
        end
      end
    end

    # 画像に対してランダムに点を打つ
    num.times do |i|
      c = colordata[rand(3)]
      b = rand(50) / 100.0 + 0.5
      img[rand(img.width), rand(img.height)] = [c[0] * b, c[1] * b, c[2] * b]
    end

    return img
  end

  #
  # 動作確認用。垂直な線を引いた画像を作成して返す
  #
  # @param [int] width   画像横幅(省略時はWindow横幅)
  # @param [int] height  画像縦幅(省略時はWindow縦幅)
  #
  # @return [Image] 作成した画像(Image)
  #
  def get_vertical_line_image(width=nil, height=nil)
    width = Window.width unless width
    height = Window.height unless height
    img = Image.new(width, height, [0, 0, 0, 0])
    img.line(0, 0, 0, img.height, C_WHITE)
    return img
  end
end

if __FILE__ == $0
  # 動作テスト。使い方

  spd = 1 # スクロール速度
  spd_sub = 0.5 # ラスター毎のスクロール速度差

  # Window.resize(1280, 720)
  # Window.fps = 10
  # "Window.scale = 2.0

  # スクロール速度を渡して生成
  shader = StarRasterScrollShader.new(spd, spd_sub, Window.width, Window.height)

  # 星画像を生成・取得(引数無しならWindowのサイズで作る)
  img_nosync = shader.get_star_image(Window.width, Window.height, 0x3fff, false)
  img_sync = shader.get_star_image(Window.width, Window.height, 0x3fff, true)

  # 動作テスト用。垂直線画像を生成・取得
  img_vline = shader.get_vertical_line_image(Window.width, Window.height)

  alpha_chg = false
  framecnt = 0

  # メインループ
  Window.loop do
    break if Input.keyPush?(K_ESCAPE)

    # スクロール値更新
    shader.update

    # 描画
    if Input.keyDown?(K_X)
      # Xキーで垂直線を表示
      Window.draw_shader(0, 0, img_vline, shader)
    elsif Input.keyDown?(K_Z)
      # Zキーでスクロール速度と対応した星画像を表示
      Window.draw_shader(0, 0, img_sync, shader)
    else
      Window.draw_shader(0, 0, img_nosync, shader)
    end

    # ----------------------------------------
    # 以下は動作テスト用
    #
    # Fキー : フルスクリーン表示
    # Cキー : スクロール値リセット
    # Vキー : 押してる間はスクロール速度を変更
    # Aキー : アルファ値の変化を切替
    #
    Window.windowed = !Window.windowed? if Input.keyPush?(K_F)
    shader.reset_scroll if Input.keyPush?(K_C)
    if Input.keyDown?(K_V)
      shader.set_speed(0.5, 0.05)
    else
      shader.set_speed(spd, spd_sub)
    end
    alpha_chg = !alpha_chg if Input.keyPush?(K_A)
    if alpha_chg
      v = Math.sin(framecnt * Math::PI / 180.0).abs
      shader.set_alpha(v)
    else
      shader.set_alpha(1.0)
    end

    framecnt += 1
  end
end
サンプルスクリプトをコピペして少し弄って、みたいな感じで作ったので、コレもサンプル同様、Public Domain ってことで。

上記のソースを単体で実行しても表示できるけど、他のファイルから呼び出して使えるようにもしておきました。下のような感じで使えるかなと…。
require 'dxruby'
require_relative 'starrasterbg'

shader = StarRasterScrollShader.new(0.5, 0.6)
img = shader.get_star_image()

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

  shader.update
  Window.draw_shader(0, 0, img, shader)
end

ちなみに、先日から書いてる途中の、STGサンプルからも呼び出してみたり。
STGサンプルで使ったみた例
一気に雰囲気出てきた。これで勉強する気も湧くというもの。ちなみに、こっちのサンプル(?)は、まだ、アタリ処理だの、ゲームオーバー処理だのを書いてませんで。もうちょっと追加してから公開しようかなと。

さておき。HLSLについて、ちょっと分からないところが。

配列を作って、ラスター毎の速度差を並べて、16ラスター分を繰り返して処理しているのだけど。配列の添え字が float のままなわけで…。コレ、いいんだろうか? 一応動いちゃってるけど…。添え字の中に、小数点以下も持ってる値が入ってるはずなのだけど。HLSL側で、上手いことやってくれてるのかな。それともやっぱり何かマズイのかな。

配列と言えば、画面縦幅分のラスタースクロール値をどこかに格納しておかないといかんかなと思っていたけど、この程度の処理ならそこまで持たなくてもそれらしく見えてくれるようで。ただ、やっぱり汎用性(?)は無いかなと…。HLSL側は極力弄らずに、Ruby側で全てのラスターのスクロール値を作って、配列で渡せればいいのだけど。いや、そんなことができるのかどうか、まだ分からないのですが。

もしかして Sprite の変形機能を使えばよいのでは。 :

先日試した、床だの天井だのをラスタースクロールで云々、てな見せ方は、画像を変形描画できる Window.draw_morph とやらを使えばできるのではと今頃気付いたり。

ホントにそうかな…? ちょっと調べてみないと。

以上、1 日分です。

過去ログ表示

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