RGSSに近い色調整機能を持たせるSprite拡張

DXRuby1.3.1devのSpriteに載せる予定はなく、RGSSにあってFreeRGSSでも作りこんである機能として、tone=、color=、flashといったメソッドがある。
これらは従来のDXRubyでは実現が不可能であり、1.3系ならShaderクラスを使うことで可能となる。実はFreeRGSSでも内部でシェーダを使っている。
それぞれの意味としては
・toneはrgbとgrayからなる配列で、画像にたいしてrgbを-255〜+255で調整しつつ、grayの値(0〜255)に応じてグレイスケール化する。
・colorはargbの色配列で、画像にその色を半透明合成する。
flashは色配列と時間を引数で渡し、画像を指定色で塗りつぶし、時間の間で元の画像まで減衰させる。updateメソッドで1フレーム進める。
RGSSの仕様として、colorとflashの同時適用はできない。flashの色がある程度弱くなったところでcolorの色に切り替わるような動きをする。
これを実現させるコードは以下。Spriteクラスを継承してRgssSpriteを作っている。HLSLはFreeRGSSで使ったものだ。

class SpriteShader < Shader
  hlsl = <<EOS
  float4 g_blend;
  float4 g_tone;

  texture tex0;
  sampler Samp = sampler_state
  {
   Texture =<tex0>;
  };

  struct PixelIn
  {
    float2 UV : TEXCOORD0;
  };
  struct PixelOut
  {
    float4 Color : COLOR0;
  };

  PixelOut PS(PixelIn input)
  {
    PixelOut output;
    float3 temp;
    float3 ntsc = {0.298912, 0.586611, 0.114478};
    float ntscy;
    output.Color = tex2D( Samp, input.UV );

    ntsc = ntsc * output.Color.rgb;
    ntscy = ntsc.r + ntsc.g + ntsc.b;
    temp.r = output.Color.r + ((ntscy - output.Color.r) * g_tone.a);
    temp.g = output.Color.g + ((ntscy - output.Color.g) * g_tone.a);
    temp.b = output.Color.b + ((ntscy - output.Color.b) * g_tone.a);

    output.Color.rgb = min(1.0, temp + g_tone.rgb) * (1.0 - g_blend.a) + g_blend.rgb * g_blend.a;

    return output;
  }

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

  @@core = Shader::Core.new(hlsl, 
    {
      :g_blend => :float,
      :g_tone => :float,
    }
  )

  def initialize
    super(@@core, "TShader")
    self.g_blend = [0.0,0.0,0.0,0.0]
    self.g_tone = [0.0,0.0,0.0,0.0]
  end

  def tone=(ary)
    self.g_tone = [ary[0] / 255.0, ary[1] / 255.0, ary[2] / 255.0, ary[3] / 255.0]
  end

  def color=(ary)
    self.g_blend = [ary[1] / 255.0, ary[2] / 255.0, ary[3] / 255.0, ary[0] / 255.0]
  end
end

class RgssSprite < Sprite
  def initialize(x=0, y=0, image=nil)
    super
    self.shader = SpriteShader.new
    @rgss_sprite_color = nil
    @rgss_sprite_flash_color = nil
    @rgss_sprite_flash_duration = 1
    @rgss_sprite_flash_count = 0
  end

  def tone=(ary)
    if ary == nil
      self.shader.tone = [0,0,0,0]
    else
      self.shader.tone = ary
    end
  end

  def color=(ary)
    @rgss_sprite_color = ary
  end

  def flash(color=[255,255,255,255], duration=30)
    @rgss_sprite_flash_color = color
    @rgss_sprite_flash_duration = duration
    @rgss_sprite_flash_count = duration
  end

  def update
    @rgss_sprite_flash_count -= 1 if @rgss_sprite_flash_count > 0
  end

  def draw
    if @rgss_sprite_color != nil and @rgss_sprite_flash_count > 0
      if @rgss_sprite_color[0] > @rgss_sprite_flash_color[0] * @rgss_sprite_flash_count / @rgss_sprite_flash_duration
        self.shader.color = @rgss_sprite_color
      else
        self.shader.color = [@rgss_sprite_flash_color[0] * @rgss_sprite_flash_count / @rgss_sprite_flash_duration,
                             @rgss_sprite_flash_color[1],
                             @rgss_sprite_flash_color[2],
                             @rgss_sprite_flash_color[3]
                            ]
      end
    elsif @rgss_sprite_flash_count > 0
      self.shader.color = [@rgss_sprite_flash_color[0] * @rgss_sprite_flash_count / @rgss_sprite_flash_duration,
                           @rgss_sprite_flash_color[1],
                           @rgss_sprite_flash_color[2],
                           @rgss_sprite_flash_color[3]
                          ]
    elsif @rgss_sprite_color != nil
      self.shader.color = @rgss_sprite_color
    else
      self.shader.color = [0,0,0,0]
    end
    super
  end
end

で、これを使うコード。

#!ruby -Ks
require 'dxruby'
require './shader/sprite'

Window.width = 800
Window.height = 600

s = RgssSprite.new
s.image = Image.load('./bgimage/BG10a_80.jpg')

Window.loop do

  # Zでフラッシュさせる
  s.flash if Input.key_push?(K_Z)

  # Xを押している間グレイスケールになる
  if Input.key_down?(K_X)
    s.tone = [0,0,0,255]

  # Cを押している間、赤色が無くなる
  elsif Input.key_down?(K_C)
    s.tone = [-255,0,0,0]
  else
    s.tone = nil
  end

  # Vを押している間、青をブレンドする
  if Input.key_down?(K_V)
    s.color = [60,0,0,255]
  else
    s.color = nil
  end

  s.update
  s.draw
  break if Input.key_push?(K_ESCAPE)
end

すっかり書き忘れていたが、DXRubyFwのSprite機能を統合する(かもしれない)のでDXRuby1.3.1devではためしにSpriteクラスを実装し始めてみている。
モノはWikiに置いてある。