球体レンダリング

ずいぶんまえにTwitterで@terushowさんが3D機能の使い道について説明していたことがあって、そのひとつに例えば球体にテクスチャマッピングをして2D画像を生成する、というのがあったと記憶している。
3Dとはポリゴンのことで、球体をポリゴンで作るとすると平面を組み合わせた擬似球体にならざるをえない。ピクセルシェーダで球体を計算してレンダリングしてやれば手軽に綺麗にできるのではないかと考えてやってみた。
まあ、マイクロポリゴンとかまですれば綺麗になりそうだがそれはまた別の話として。

計算が難しくてうまくいかなかったから2パス化した。
とりあえず影はついてないが、回転させているので動かすと球体っぽいのがわかるのではないかと。
描画アルゴリズムメルカトル図法の地図を地球儀に貼り付けるようなイメージになっているので、そのような画像を持ってきて貼れば地球儀みたいになるのではないかと思う。
球体だから計算でできるのであって、複雑な形状にテクスチャを貼る場合は3D機能が必要だろう。頑張ればこの手でできるのかもしれないが。

require 'dxruby'

hlsl = <<EOS
texture tex0;
float2 raito;

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

float4 PS1(float2 input : TEXCOORD0) : COLOR0
{
  return tex2D( Samp0, 
                    float2( ((1 - (acos((input.x - 0.5) * 2) / acos(-1))) - 0.5) + 0.5, input.y) );
}

float4 PS2(float2 input : TEXCOORD0) : COLOR0
{
  return tex2D( Samp0, 
                  float2((input.x - 0.5) / cos(asin((input.y - 0.5)*2)) / raito.x + 0.5
                       , (input.y - 0.5) / raito.y + 0.5) );
}

technique Sphere1
{
 pass P0
 {
  PixelShader = compile ps_2_0 PS1();
 }
}
technique Sphere2
{
 pass P0
 {
  PixelShader = compile ps_2_0 PS2();
 }
}
EOS

Window.width, Window.height = 800, 600
image = Image.load("bgimage/BG42a.jpg")

core = DXRuby::Shader::Core.new(hlsl, {:raito=>:float})
shader1 = Shader.new(core, "Sphere1")
shader2 = Shader.new(core, "Sphere2")
shader1.raito = shader2.raito = [600.quo(image.width), 600.quo(image.height)]

rt1 = RenderTarget.new(800, 600)
rt2 = RenderTarget.new(800, 600)
x = 0

Window.loop do
  x -= 1
  rt1.draw_tile(0, 0, [[0]], [image], x, 0, 2, 1).update
  rt2.draw_shader(0, 0, rt1, shader1).update
  Window.draw_shader(0, 0, rt2, shader2)
  break if Input.key_push?(K_ESCAPE)
end