頂点シェーダを使う

DXRuby1.3devではシェーダをサポートしていて、主にピクセルシェーダを扱うサンプルを作っているが、仕組み的には頂点シェーダを使うこともできる。ただ、DXRubyが2D専用として開発されている都合上、ちょっと使いにくい。

頂点シェーダは最低限、頂点の座標とテクスチャ座標を受け取り、出力する必要がある。
入力の頂点座標はfloat4で表され、xyzwが入る。DXRubyでは描画は必ず矩形で、三角形2つの組み合わせだから、頂点は6個。うち2つは同じ点を表すことになる。draw系メソッドで渡した座標にスケーリングや回転の処理を行った結果の座標となる。概念としてはこれがワールド座標系で、ローカル→ワールドの変換はソフトウェアで行っている。
テクスチャ座標はそれぞれの頂点に位置するテクスチャ座標である。
出力の頂点座標は入力に対してビュー変換、透視座標変換した結果だ。DirectXの座標系だから、右に+x、上に+y、奥に+zという左手座標系で、中心が(0,0,0)、それぞれ-1.0〜+1.0の値になる。スクリーンにあわせるビューポート変換はDirectXが自動で行う。

上記の仕掛けであるから、頂点シェーダは受け取った座標にたいして、ビュー変換と透視座標変換をして結果を出力してやればよい。DXRubyも中身は3Dレベルの処理をしていて、この変換を真面目にやっているので、それと同じ変換行列を作ってやれば、通常と同じ出力を得ることができるはずだ。
このような感じになる。

require 'dxruby'

hlsl = <<EOS
float4x4 g_view, g_proj;
texture tex0;

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

struct VS_OUTPUT
{
  float4 pos : POSITION;
  float2 tex : TEXCOORD0;
};

VS_OUTPUT VS(float4 pos: POSITION, float2 tex: TEXCOORD0)
{
  VS_OUTPUT output;

  output.pos = mul(mul(pos, g_view), g_proj);
  output.tex = tex;

  return output;
}

float4 PS(float2 input : TEXCOORD0) : COLOR0
{
  return tex2D( Samp, input );
}

technique
{
 pass
 {
  VertexShader = compile vs_2_0 VS();
  PixelShader = compile ps_2_0 PS();
 }
}
EOS

core = Shader::Core.new(hlsl, {:g_view=>:float, :g_proj=>:float})
shader = Shader.new(core)

shader.g_view = [1, 0, 0, 0,
                 0, -1, 0, 0,
                 0, 0, 1, 0,
                 -Window.width / 2.0, Window.height / 2.0, 0, 1]
shader.g_proj = [2.0 / Window.width, 0, 0, 0,
                 0, 2.0 / Window.height, 0, 0,
                 0, 0, 0, 0,
                 0, 0, 1, 1]

image = Image.load("../logo.png")

Window.loop do
  Window.draw_shader(100, 100, image, shader)
  break if Input.key_push?(K_ESCAPE)
end