#!ruby -Ks # -*- mode: ruby; encoding: sjis -*- # Last updated: <2014/01/15 15:56:42 +0900> require 'dxruby' # # Shaderを使って床ラスタースクロールっぽい処理をするクラス # class FloorRasterScroll @@hlsl = <; 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