mieki256's diary



2014/01/16(木) [n年前の日記]

#1 [dxruby] 床ラスター処理をもうちょっと修正

DXRubyを使って床ラスター処理の実験をしていたのだけど、回転して描画したらもうちょっと色々できるんじゃないかと思えてきたので少し修正。

こんなのとか。
壁として描画
floorraster_test2.rb
# 壁として表示

require 'dxruby'
require_relative 'floorraster'

image = Image.load("wall1.png")

# 90度回転して表示するので、正方形の描画サイズを指定してる
bg = FloorRaster.new(image, 640, 640)

x = 0
z = 0
Window.loop do
  break if Input.keyPush?(K_ESCAPE)
  x -= 3
  z -= 2
  bg.draw(320, 240, x, z, :angle=>90, :y_diff=>0.05, :vflip=>false)
  bg.draw(320, 240, x, z, :angle=>90, :y_diff=>0.05, :vflip=>true)
end

こんなのとか。
壁として描画その2
floorraster_test3.rb
# 壁として表示その2。右から左へ壁が動いていく

require 'dxruby'
require_relative 'floorraster'

image = Image.load("wall2.png")
bg = FloorRaster.new(image, 640, 640)
draw_x = 0

Window.loop do
  break if Input.keyPush?(K_ESCAPE)
  draw_x -= 3
  draw_x = 640 if draw_x < 0 # 画面左端に来たら右端に戻す
  floor_dist = (draw_x - 320).quo(2000) # 床までの距離を変更
  vflip = (floor_dist < 0)? true : false # 画面右側と左側で、反転描画フラグを変更

  bg.draw(draw_x, 240, 0, 0,
          :angle=>270, # 反時計回りに90度回転
          :floor_dist=>floor_dist.abs, # 床までの距離
          :scr_dist=>0.5, # 画面までの距離
          :y_diff=>0,
          :vflip=>vflip)
end

こんなのとか。
壁として描画その3
floorraster_test4.rb
# 壁として表示その3。無限の平面ではなく、四角い壁として描画

require 'dxruby'
require_relative 'floorraster'

image = Image.load("wall1.png")

# uv値を繰り返さないモードを指定してる
bg = FloorRaster.new(image, 640, 640, false, false)

move_value = 0
move_value_max = 640
Window.loop do
  break if Input.keyPush?(K_ESCAPE)
  move_value -= 2
  move_value = move_value_max if move_value < 0
  floor_dist = (move_value - (move_value_max / 2)).quo(400)
  vflip = (floor_dist < 0)? true : false

  bg.draw(move_value, 240, 0, 0,
          :angle=>270,
          :floor_dist=>floor_dist.abs,
          :scr_dist=>0.3,
          :y_diff=>0.0,
          :v_offset=>1.1, # v値の参照位置を調整して、画面内に映るようにしてる
          :vflip=>vflip)
end

一応、ソースと画像の一式を、zipにして置いときます。Public Domain ってことで。

_floorraster.zip

フォグをかけたいのだけど。 :

遠いところがチラチラするのが気になるので、フォグもかけてみたいのだけど。

フォグってのは、遠いところは霧がかかってるような見え方にすることで、ちょっと誤魔化す(?)手法、という言い方でいいのだろうか。ググってみても分かりやすいページが出てこなくて。自分、呼称を間違えてるのかもしれず。

さておき。試しに、input.y の値を ―― 0.0〜1.0の値を使って、単純にフォグの色と差し替えたら、ほとんどの部分がフォグの色になってしまって失敗してしまった。うーん。

であれば、例えば0.1以下の場合だけフォグが働く、等の処理を書けばいいのかな。でも、HLSL内でif文を使うとめちゃくちゃ遅くなる、という話も見かけてしまったわけで。

要するに、0.0〜1.0の入力値に対して1.0〜0.0の値が出てくるけど、急激に値が減っていって、ほとんどの場合は出力値が0.0近辺でジリジリしてる、みたいな数式を書ければ良さそうだなと。でも、どう書けばいいのやら。たぶんコレ、中学生レベルの数学な気もしますけど。

GeoGebra を使ってグラフを作りつつ検討。GeoGebra の下の入力欄に、y=1/x などと打ち込むと、即座にグラフを描いてくれる。実に便利。例えば y=0.05/(0.05+x*16) と打ち込んだら、結構それっぽいグラフになってくれた。
GeoGebraでグラフ作成

一応HLSL内に、件の式を書いて、フォグを実装してみたり。結局、input.y の値でやっていて z値でやってるわけではないから、ちと不自然かもしれず。でも、多少は隠せてるから、これはこれで。

む。HLSLって、maxとかminとかそのあたりの関数もあるのか…。だったら、それを使って条件分岐に近いこともできるのかな。

一応本体部分も貼っておくです。 :

HLSL内で似たような記述がズラズラ並んでるあたり、気になるのですけど。たった1〜2行違うだけなのに、関数(?)をコピペしてるとか、なんだかアレだよなと。まとめて一つに書けないのかな…。

floorraster.rb
require 'dxruby'

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

  @@hlsl = <<EOS
  float floor_distance;
  float screen_distance;
  float x_diff;
  float y_diff;
  float alpha;
  float v_offset;
  float fog_d;
  float4 fogcol;
  texture tex0;

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

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

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

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

  float4 PS1(float2 input : TEXCOORD0) : COLOR0
  {
    float4 output;
    float pz;
    float fogv;
    fogv = fog_d / (fog_d + (input.y * 16));
    pz = screen_distance * floor_distance / -(input.y + y_diff);
    input.y = pz + v_offset;
    input.x = (input.x - x_diff) * -pz / screen_distance + 0.5;
    output = tex2D( Samp0, input );
    output *= (1.0 - fogv);
    output += (fogcol * fogv);
    output.a *= alpha;
    return output;
  }

  float4 PS2(float2 input : TEXCOORD0) : COLOR0
  {
    float4 output;
    float pz;
    float fogv;
    fogv = fog_d / (fog_d + (input.y * 16));
    pz = screen_distance * floor_distance / -(input.y + y_diff);
    input.y = pz + v_offset;
    input.x = (input.x - x_diff) * -pz / screen_distance + 0.5;
    output = tex2D( Samp1, input );
    output *= (1.0 - fogv);
    output += (fogcol * fogv);
    output.a = alpha;
    return output;
  }

  float4 PS3(float2 input : TEXCOORD0) : COLOR0
  {
    float4 output;
    float pz;
    float fogv;
    fogv = fog_d / (fog_d + (input.y * 16));
    pz = screen_distance * floor_distance / -(input.y + y_diff);
    input.y = pz + v_offset;
    input.x = (input.x - x_diff) * -pz / screen_distance + 0.5;
    output = tex2D( Samp2, input );
    output *= (1.0 - fogv);
    output += (fogcol * fogv);
    output.a = alpha;
    return output;
  }

  float4 PS4(float2 input : TEXCOORD0) : COLOR0
  {
    float4 output;
    float pz;
    float fogv;
    fogv = fog_d / (fog_d + (input.y * 16));
    pz = screen_distance * floor_distance / -(input.y + y_diff);
    input.y = pz + v_offset;
    input.x = (input.x - x_diff) * -pz / screen_distance + 0.5;
    output = tex2D( Samp3, input );
    output *= (1.0 - fogv);
    output += (fogcol * fogv);
    output.a = alpha;
    return output;
  }

  technique RasScrollWrapUV
  {
   pass
   {
    PixelShader = compile ps_2_0 PS1();
   }
  }

  technique RasScrollWrapU
  {
   pass
   {
    PixelShader = compile ps_2_0 PS2();
   }
  }

  technique RasScrollWrapV
  {
   pass
   {
    PixelShader = compile ps_2_0 PS3();
   }
  }

  technique RasScrollNotWrap
  {
   pass
   {
    PixelShader = compile ps_2_0 PS4();
   }
  }
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
  attr_accessor :fogcol, :fog_d
  attr_accessor :v_offset

  #
  # 初期化処理
  #
  # @param [Image] image 使用する画像
  # @param [int] w 描画横幅。nilならウインドウ横幅
  # @param [int] h 描画縦幅。nilならウインドウ縦幅
  # @param [Boolean] uwrap u値をwrapするか。falseならwrapしない
  # @param [Boolean] vwrap v値をwrapするか。falseならwrapしない
  # @param [float] floor_dist 床との距離
  # @param [float] scr_dist 画面との距離
  # @param [float] y_dist 視線方向を上下にずらす量。範囲は 0.0 - 1.0
  # @param [float] v_offset v値の参照をどのくらいずらすか
  # @param [float] alpha 透明度。0.0で透明。1.0で不透明
  # @param [float] fog_d フォグのかかり具合。値を大きくするとかかり具合が大きくなる
  # @param [Array] fogcol フォグの色。r,g,b,a の並びで、0-1.0の値
  #
  def initialize(image, w=nil, h=nil, uwrap=true, vwrap=true,
                 floor_dist=0.3, scr_dist=0.3, y_diff=0.1, v_offset=0.0,
                 alpha=1.0, fog_d=0.05, fogcol=[0,0,0,0])
    
    self.core = Shader::Core.new(@@hlsl,{
                                   :floor_distance=>:float,
                                   :screen_distance=>:float,
                                   :x_diff=>:float,
                                   :y_diff=>:float,
                                   :alpha=>:float,
                                   :v_offset=>:float,
                                   :fog_d=>:float,
                                   :fogcol=>:float
                                 })

    # wrap状態を変える
    mode_str = "RasScrollWrapUV"
    mode_str = "RasScrollWrapU" if uwrap and !vwrap
    mode_str = "RasScrollWrapV" if !uwrap and vwrap
    mode_str = "RasScrollNotWrap" if !uwrap and !vwrap

    self.shader = Shader.new(self.core, mode_str)

    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.v_offset = v_offset
    self.alpha = alpha
    self.fog_d = fog_d
    self.fogcol = fogcol

    # 画面を覆い隠すタイル数を求める
    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)

    # フォグのかかり具合と色を指定
    set_fog(fog_d, fogcol)
  end

  #
  # フォグのかかり具合と色を指定
  #
  # @param [float] fog_d フォグのかかり具合を指定。値が大きければかかり方が大きくなる
  # @param [Array] fogcol フォグの色。r,g,b,a の並びで、0-1.0の値
  #
  def set_fog(fog_d, fogcol)
    self.shader.fog_d = fog_d
    self.shader.fogcol = fogcol
  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 [float] v_offset v値の参照をどのくらいずらすか
  # @option opts [int] z 描画奥行情報
  # @option opts [float] alpha 透明度。0.0で透明。1.0で不透明
  # @option opts [float] angle 回転角度。単位は度
  # @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.v_offset = opts[:v_offset] if opts.has_key?(:v_offset)
    self.alpha = opts[:alpha] if opts.has_key?(:alpha)
    z = (opts.has_key?(:z))? opts[:z] : 0
    angle = (opts.has_key?(:angle))? opts[:angle] : 0
    vflip = 1
    if opts.has_key?(:vflip)
      vflip = (opts[:vflip])? -1 : 1
    end

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

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

    # Shader を使って透視変換しつつ描画
    wh = self.rt_w / 2
    Window.drawEx(x - wh, y, self.rt, :shader=>self.shader,
                  :scaley=>self.rt_scaley * vflip,
                  :centerx=>wh, :centery=>0,
                  :z=>z, :angle=>angle)
  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 __FILE__ == $0
  image = Image.load("lena.png")
  bg = FloorRaster.new(image)
  x = 0
  z = 0
  Window.loop do
    break if Input.keyPush?(K_ESCAPE)
    x += 8
    z += 2
    bg.draw(320, 240, x, z)
  end
end

以上です。

過去ログ表示

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