レンズシェーダ

レンズっぽい球面収差つきの拡大シェーダ。毎度のことながら計算は適当にそれっぽく。レンズの屈折の計算は難しくてよくわからん。
画像はきまぐれアフター様
http://gakaiblog.at.webry.info/

require 'dxruby'

hlsl = <<EOS
texture tex0;
texture tex1;
float2 scale;
float2 point;
float r, distance;

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

float4 PS(float2 input : TEXCOORD0) : COLOR0
{
  float4 output;
  float d;

  clip(tex2D( Samp0, input ).r - 1.0);

  d = sqrt((input.x-0.5)*(input.x-0.5) + (input.y-0.5)*(input.y-0.5))+1;
  output = tex2D( Samp1, float2( point.x + (input.x-0.5) / scale.x * distance / r * d, point.y + (input.y-0.5) / scale.y * distance / r * d));
  return output;
}

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

Window.width, Window.height = 800, 600
bgimage = Image.load("bgimage/BG42a.jpg")
image = Image.new(200,200).circle_fill(100,100,100,C_WHITE)
loupeimage = Image.new(200,200).circle(100,100,100,C_WHITE)

core = DXRuby::Shader::Core.new(hlsl, {:scale=>:float, :r=>:float, :distance=>:float, :tex1=>:texture, :point=>:float})
shader = Shader.new(core, "Lens")
shader.scale = [bgimage.width.quo(image.width), bgimage.height.quo(image.height)]
shader.r = 1000
shader.distance = 500
shader.tex1 = bgimage

Window.loop do
  shader.point = [Input.mouse_pos_x.quo(bgimage.width), Input.mouse_pos_y.quo(bgimage.height)]
  shader.distance += Input.y*10
  shader.distance = 0 if shader.distance < 0
  shader.distance = shader.r if shader.distance > shader.r

  Window.draw(0,0,bgimage)
  Window.draw_shader(Input.mouse_pos_x-image.width/2, Input.mouse_pos_y-image.width/2, image, shader)
  Window.draw(Input.mouse_pos_x-image.width/2, Input.mouse_pos_y-image.width/2, loupeimage)
  break if Input.key_push?(K_ESCAPE)
end