2014/01/13(月) [n年前の日記]
#1 [dxruby] DXRubyでラスタースクロールっぽいのができた
DXRuby の Shader を使って、サンダーフォースIVのステージ5っぽい背景ができた、ような気がする。下のような感じになりました。
GIFアニメのフレームレートが低くてなんだかアレだけど、PC上で動かして、60FPSのソレを眺めれば、結構イイ感じ。
一応、ソース貼っときますね。
_starrasterbg.rb
上記のソースを単体で実行しても表示できるけど、他のファイルから呼び出して使えるようにもしておきました。下のような感じで使えるかなと…。
ちなみに、先日から書いてる途中の、STGサンプルからも呼び出してみたり。 一気に雰囲気出てきた。これで勉強する気も湧くというもの。ちなみに、こっちのサンプル(?)は、まだ、アタリ処理だの、ゲームオーバー処理だのを書いてませんで。もうちょっと追加してから公開しようかなと。
さておき。HLSLについて、ちょっと分からないところが。
配列を作って、ラスター毎の速度差を並べて、16ラスター分を繰り返して処理しているのだけど。配列の添え字が float のままなわけで…。コレ、いいんだろうか? 一応動いちゃってるけど…。添え字の中に、小数点以下も持ってる値が入ってるはずなのだけど。HLSL側で、上手いことやってくれてるのかな。それともやっぱり何かマズイのかな。
配列と言えば、画面縦幅分のラスタースクロール値をどこかに格納しておかないといかんかなと思っていたけど、この程度の処理ならそこまで持たなくてもそれらしく見えてくれるようで。ただ、やっぱり汎用性(?)は無いかなと…。HLSL側は極力弄らずに、Ruby側で全てのラスターのスクロール値を作って、配列で渡せればいいのだけど。いや、そんなことができるのかどうか、まだ分からないのですが。
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サンプルからも呼び出してみたり。 一気に雰囲気出てきた。これで勉強する気も湧くというもの。ちなみに、こっちのサンプル(?)は、まだ、アタリ処理だの、ゲームオーバー処理だのを書いてませんで。もうちょっと追加してから公開しようかなと。
さておき。HLSLについて、ちょっと分からないところが。
配列を作って、ラスター毎の速度差を並べて、16ラスター分を繰り返して処理しているのだけど。配列の添え字が float のままなわけで…。コレ、いいんだろうか? 一応動いちゃってるけど…。添え字の中に、小数点以下も持ってる値が入ってるはずなのだけど。HLSL側で、上手いことやってくれてるのかな。それともやっぱり何かマズイのかな。
配列と言えば、画面縦幅分のラスタースクロール値をどこかに格納しておかないといかんかなと思っていたけど、この程度の処理ならそこまで持たなくてもそれらしく見えてくれるようで。ただ、やっぱり汎用性(?)は無いかなと…。HLSL側は極力弄らずに、Ruby側で全てのラスターのスクロール値を作って、配列で渡せればいいのだけど。いや、そんなことができるのかどうか、まだ分からないのですが。
◎ もしかして Sprite の変形機能を使えばよいのでは。 :
先日試した、床だの天井だのをラスタースクロールで云々、てな見せ方は、画像を変形描画できる Window.draw_morph とやらを使えばできるのではと今頃気付いたり。
ホントにそうかな…? ちょっと調べてみないと。
ホントにそうかな…? ちょっと調べてみないと。
[ ツッコむ ]
以上です。

