半透明と半透明の合成の結論
RenderTargetの半透明合成は結局DirectX9のせいでできないということであった。そこでシェーダを使ってムリヤリにでもどうにかならないだろうか、とちょっといじってみた。
まずDXRubyの開発中最新版でWindow.draw_exに:blend=>:noneの指定を追加。透明色も半透明色もそのままピクセルを置く指定だ。Imageにはcopy_rectというメソッドがあるのだが、画面に描く場合にこれをする手段が無かった。
これでどのように描画するのかというと、以下のようにする。描画先のRenderTargetをAとし、描画元の画像をBとする。draw_exしたいという要望だったので対応したやりかたとなっている。
(1) Aと同じサイズの透明なRenderTargetを作る。これをCとする。
(2) もうひとつAと同じサイズのRenderTargetを作る。Dとする。
(3) Cに対してdraw_exでBを描画する。ここで:blend=>:noneする。:blend以外の指定はそのまま使う(noneなのでalphaは無視される)。
(4) シェーダに追加テクスチャとしてCを渡し、D.draw_ex(x, y, A, :shader=>shader, :blend=>:none)とする。このシェーダが理想的な合成をした結果を出力するため、DにAとCの合成結果が描画される。
(5) Dをスクリーンに描画する。
DをまたRenderTargetに描画したいという場合はまた同じことを繰り返す必要がある。あくまでもスクリーン描画は普通なのだ。RenderTargetに描画するときだけ問題になる。
とはいえこんな処理をいちいち書いていては大変なので、AlphaBlendクラスを作ってみた。
とりあえず実行結果はこんな感じ。掲示板にアップいただいた検証コードの改造版。
コードは以下に置いておくが、とりあえずDXRuby1.3.3devをリリースするまで実行するのはちょっと待ってね。
require 'dxruby' class AlphaBlend hlsl = <<EOS texture tex0; texture tex1; float alpha; sampler Samp0 = sampler_state {Texture =<tex0>;}; sampler Samp1 = sampler_state {Texture =<tex1>;}; struct PixelIn {float2 UV : TEXCOORD0;}; struct PixelOut {float4 Color : COLOR0;}; PixelOut PS(PixelIn input) { PixelOut output; float4 cd = tex2D( Samp0, input.UV ); float4 cs = tex2D( Samp1, input.UV ); cs.a *= alpha; output.Color.a = (1.0 - cs.a) * cd.a + cs.a; output.Color.rgb = ((cs.rgb * cs.a) + (cd.rgb * cd.a) * (1.0 - cs.a)) / output.Color.a; return output; } technique AlphaBlend {pass P0 {PixelShader = compile ps_2_0 PS();}} EOS @@core = Shader::Core.new( hlsl, { :tex1 => :texture, :alpha => :float } ) def initialize(bg) @shader = Shader.new(@@core, "AlphaBlend") @result = RenderTarget.new(bg.width, bg.height) @rt_image = RenderTarget.new(bg.width, bg.height) @bg = bg @shader.alpha = 1.0 end def draw_ex(x, y, image, param = {}) temp = param.dup temp[:blend] = :none @rt_image.draw_ex(x, y, image, temp) @rt_image.update @shader.tex1 = @rt_image if param[:alpha] == nil @shader.alpha = 1.0 else @shader.alpha = param[:alpha] / 255.0 end @result.draw_ex(0, 0, @bg, :shader=>@shader, :blend=>:none) @result.update return @result end end image_bg = [] image_bg[0] = Image.load("./sozai/bg_test3.jpg") #背景画像 imaga_0 = Image.new(200, 200, [128, 255, 0, 0]) #半透明レイヤ imaga_1 = Image.new(200, 200, [255, 0, 255, 0]) #不透明レイヤ target = RenderTarget.new(800, 600) map = [[0,0],[0,0]] Window.resize(800, 600) target.draw(500, 150, imaga_1, 500) target.update ab = AlphaBlend.new(target.to_image) # 引数は背景にしたい絵。 # この例では毎フレーム描くわけじゃないから念のためImageにしとく #ゲームループ Window.loop do #タイルを敷き詰めて背景を作成 Window.draw_tile(0, 0, map, image_bg, 0, 0, 4, 3, 0) #Windowに直接描画(A) Window.draw( 50, 50, imaga_0, 1000) Window.draw(150, 150, imaga_1, 500) #RenderTargetを経由しての描画(B)←Aと同じ様に描画される事を期待している rt = ab.draw_ex(400, 50, imaga_0) # ←合成したRenderTargetが返ってくるのでそれを描画する Window.draw(0, 0, rt) end