砂サンプルのアレンジ

サンプルに入れてあるdot.rbをシェーダ使ってどうにかできないかと試行錯誤していた。

まだちょっとおかしいが、なんとなく動く。落ちた砂が横に薄く広がるようになっている。
問題点は、薄く広がった砂が薄すぎて見えないというところだ。
あと、そのあたりの処理が間違ってるようでたまに変なことになる。
Rubyで書いたらとてもじゃないけど遅すぎて話にならない処理だが、HLSLで強引に書けばそれなりの速度で動くらしい。
ただ、概念が違いすぎてこういうものを作るにはかなりの修行が必要なようだ。
コードは長いので続きにおいといた。

#!ruby -Ks
# DXRuby サンプル
# マウス左クリックで描いて、右クリックで消せます。
require 'dxruby'

hlsl = <<EOS
  float width;
  float height;

  texture tex0;

  sampler Samp0 = sampler_state
  {
   Texture =<tex0>;
   AddressU=BORDER;
   AddressV=BORDER;
  };

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

  // 左右への伝播処理
  PixelOut PS1(PixelIn input)
  {
    PixelOut output;
    float4 u = tex2D( Samp0, input.UV + float2(0.0, -1.0 / height) ); // 上
    float4 c = tex2D( Samp0, input.UV );                              // ここ
    float4 d = tex2D( Samp0, input.UV + float2(0.0,  1.0 / height) ); // 下
    float4 l = tex2D( Samp0, input.UV + float2(-1.0 / width, 0.0) );  // 左
    float4 r = tex2D( Samp0, input.UV + float2( 1.0 / width, 0.0) );  // 右
    float4 ld = tex2D( Samp0, input.UV + float2(-1.0 / width, 1.0 / height) );  // 左下
    float4 rd = tex2D( Samp0, input.UV + float2( 1.0 / width, 1.0 / height) );  // 右下

    if( any( d.g ) ) // 下になにかある
    {
      if( any( c.g ) && !any( c.r ) ) // ここが緑
      {
        output.Color = c;
      }
      else // ここが緑以外
      {
        float temp = 0;
        int count = 0;
        float t;
        if( !any( l.g ) || any( l.r ) ) // 左が緑以外だった
        {
          temp += l.r;
          count += 1;
        }
        if( !any( r.g ) || any( r.r ) ) // 右が緑以外だった
        {
          temp += r.r;
          count += 1;
        }

        t = (c.r * (3 - count) + temp) / 3;
        if( any ( t ) )
        {
          output.Color = float4(t,t,t,1.0);
        }
        else
        {
          output.Color = c;
        }
      }
    }
    else // 下に何もない
    {
      if( any( c.g ) && !any( c.r ) ) // ここが緑だった
      {
        output.Color = c;
      }
      else // 緑以外だった。(なにもない含む)
      {
        // 他から伝播する
        float temp = 0;
        int count = 0;
        if( any( ld ) ) // 左下に何かある。左が伝播してくる
        {
          if( !any( l.g ) || any( l.r ) ) // 左が緑以外だった
          {
            temp += l.r;
            count += 1;
          }
        }

        if( any( rd ) ) // 右下に何かある。右が伝播してくる
        {
          if( !any( r.g ) || any( r.r ) ) // 右が緑以外だった
          {
            temp += r.r;
            count += 1;
          }
        }

        if( count == 0 )
        {
          output.Color = c;
        }
        else
        {
          float t = (c.r * (3 - count) + temp) / 3;
          if( any ( t ) )
          {
            output.Color = float4(t,t,t,1.0);
          }
          else
          {
            output.Color = c;
          }
        }
      }
    }

    return output;
  }

  // 下へ落ちる処理
  PixelOut PS2(PixelIn input)
  {
    PixelOut output;
    float2 f2 = float2(0.0, 1.0 / height);
    float4 u = tex2D( Samp0, input.UV - f2 ); // 上
    float4 c = tex2D( Samp0, input.UV );      // ここ
    float4 d = tex2D( Samp0, input.UV + f2 ); // 下

    if( !any( d.g ) ) // 下に何もない
    {
      if( !any( c.g ) ) // ここに何もない
      {
        if( any( u.g ) && !any(u.r) )// 上が緑
        {
          output.Color = c;
        }
        else
        {
          output.Color = u; // 上の色をもってくる
        }
      }
      else // ここに何かある
      {
        if( !any(c.r) )// ここが緑
        {
          output.Color = c; // ここの色そのまま
        }
        else
        {
          if( any( u.g ) && !any(u.r) )// 上が緑
          {
            output.Color = 0;
          }
          else
          {
            float temp = min(1.0 - c.r, u.r);
            if( any( temp ) )
            {
              output.Color = float4(temp,temp,temp,1.0); // 上とここの合成
            }
            else
            {
              output.Color = 0;
            }
          }
        }
      }
    }
    else // 下に何かがある
    {
      if( !any( c.g ) ) // ここに何もない
      {
        if( any(u.g) && !any(u.r) )// 上が緑
        {
          output.Color = c;
        }
        else
        {
          output.Color = u; // 上の色をもってくる
        }
      }
      else // ここに何かある
      {
        if( any(c.g) && !any(c.r) )// ここが緑
        {
          output.Color = c;
        }
        else
        {
          if( any(d.g) && !any(d.r) )// 下が緑
          {
            float temp = min(1.0, c.r+u.r);
            output.Color = float4(temp,temp,temp,1.0); // 上とここの合成
          }
          else // 下に白
          {
            float temp = min(1.0, c.r+u.r) - (1.0 - d.r);
            if( any( temp ) )
            {
              output.Color = float4(temp,temp,temp,1.0); // 上とここの合成
            }
            else
            {
              output.Color = 0;
            }
          }
        }
      }
    }

    return output;
  }

  technique First
  {
   pass P0 // パス
   {
    PixelShader = compile ps_2_0 PS1();
   }
  }
  technique Second
  {
   pass P0 // パス
   {
    PixelShader = compile ps_2_0 PS2();
   }
  }
EOS

core = Shader::Core.new(
  hlsl,
  {
    :width => :float,
    :height => :float,
  }
)

shader = Shader.new(core, "First")
shader.width = 320
shader.height = 240


# ウィンドウ設定
Window.width = 640
Window.height = 480
Window.mag_filter = TEXF_POINT
# 画面の画像データ
$screen = Image.new(320, 240)

BLACK = [0, 0, 0]
WHITE = [255, 255, 255]

rt = RenderTarget.new(320, 240)
font = Font.new(32)

Window.loop do
  # ドット生成
  $screen[rand(100)+75, 1] = [255,255,255,255]

  # マウスの座標取得
  mx = Input.mousePosX/2
  my = Input.mousePosY/2

  # 左クリックで描画
  if Input.mouseDown?(M_LBUTTON) then
    $screen.boxFill(mx - 1, my - 1, mx + 1, my + 1, [0, 255, 0])
  end

  # 右クリックで消去
  if Input.mouseDown?(M_RBUTTON) then
    $screen.boxFill(mx - 1, my - 1, mx + 1, my + 1, [0, 0, 0, 0])
  end

  # 画面描画

  shader.technique = "First"
  rt.draw_ex(0, 0, $screen, :shader=>shader)
  rt.update
  $screen = rt.to_image
  shader.technique = "Second"
  rt.draw_ex(0, 0, $screen, :shader=>shader)
  rt.update
  $screen = rt.to_image
  Window.draw_scale(0, 0, $screen, 2, 2,0,0)

  Window.drawFont(32,32,Window.fps.to_s + " fps", font)
  Window.drawFont(32,64,Window.getLoad.to_i.to_s + " %", font)
  break if Input.keyPush?(K_ESCAPE)
end