頂点シェーダで画像のX軸回転

昨日のVectorクラスをいじりつつ、昔のコードを引っ張り出してMatrixを部分的に実装。これを使って行列を生成して頂点シェーダに食わせれば、使いやすいかどうかはともかく、とりあえず3D描画が可能なはずだ。
やってみた。

require 'dxruby'

class Vector
  def initialize(*v)
    @vec = v
  end

  def rotate(angle)
    x = @vec[0] * Math.cos(Math::PI / 180 * angle) - @vec[1] * Math.sin(Math::PI / 180 * angle)
    y = @vec[0] * Math.sin(Math::PI / 180 * angle) + @vec[1] * Math.cos(Math::PI / 180 * angle)
    temp = @vec.dup
    temp[0] = x
    temp[1] = y
    Vector.new(*temp)
  end

  def +(v)
    case v
    when Vector
      Vector.new(*@vec.map.with_index{|s,i|s+v[i]})
    when Array
      Vector.new(*@vec.map.with_index{|s,i|s+v[i]})
    when Numeric
      Vector.new(*@vec.map{|s|s+v})
    else
      nil
    end
  end

  def *(matrix)
    result = []
    for i in 0..(matrix.size-1)
      data = 0
      for j in 0..(@vec.size-1)
        data += @vec[j] * matrix[j][i]
      end
      result.push(data)
    end
    return Vector.new(*result)
  end

  def [](i)
    @vec[i]
  end

  def size
    @vec.size
  end

  def to_a
    @vec
  end
end

class Matrix
  def initialize(*arr)
    @arr = Array.new(4) {|i| Vector.new(*arr[i])}
  end

  def *(a)
    result = []
    for i in 0..(a.size-1)
      result.push(@arr[i] * a)
    end
    return Matrix.new(*result)
  end

  def [](i)
    @arr[i]
  end

  def size
    @arr.size
  end

  def self.create_rotation_x(angle)
    cos = Math.cos(Math::PI/180 * angle)
    sin = Math.sin(Math::PI/180 * angle)
    return Matrix.new(
     [   1,   0,   0, 0],
     [   0, cos, sin, 0],
     [   0,-sin, cos, 0],
     [   0,   0,   0, 1]
    )
  end

  def self.create_transration(x, y, z)
    return Matrix.new(
     [   1,   0,   0,   0],
     [   0,   1,   0,   0],
     [   0,   0,   1,   0],
     [   x,   y,   z,   1]
    )
  end

  def to_a
    @arr.map {|v|v.to_a}.flatten
  end
end


hlsl = <<EOS
float4x4 g_world, 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(mul(pos, g_world), 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_world=>:float, :g_view=>:float, :g_proj=>:float})
shader = Shader.new(core)

shader.g_view = Matrix.new([1, 0, 0, 0],
                           [0, -1, 0, 0],
                           [0, 0, 1, 0],
                           [-Window.width/2, Window.height/2, 0, 1]
                          ).to_a

zn = 150.0
zf = 2000.0
sw = 640.0
sh = 480.0
shader.g_proj = Matrix.new([2.0 * zn / sw,              0,                    0, 0],
                           [             0, 2.0 * zn / sh,                    0, 0],
                           [             0,             0,       zf / (zf - zn), 1],
                           [             0,             0, -zn * zf / (zf - zn), 0]
                          ).to_a

image = Image.load("../logo.png")
a=0
Window.loop do
  a += 1
  shader.g_world = ( Matrix.create_rotation_x(a) * Matrix.create_transration(300,300,300)
                   ).to_a

  Window.draw_shader(-image.width/2, -image.height/2, image, shader)
  break if Input.key_push?(K_ESCAPE)
end