« 色ビット数拡張 | メイン | pee 更新 演算誤差の件 »

2012年12月20日

パーティクル/吉里吉里 その他の開発日誌:: α付き画像にα付き画像を重ねた時の誤差

    

パーティクルで、不透明画像にパーティクルを重ねていくものと、α付き画像に重ねていくものを作っているが、α付き画像どうしのブレンドは複雑で割り算が入り誤差が発生しやすい。
合成方法は不透明度付きピクセルのブレンドモード付き合成方法 に書かれている。
こんな感じ。

Ca = Fa+(1-Fa)*Ba
= 1-((1-Fa)*(1-Ba))
= Fa*Ba + Fa*(1-Ba) + (1-Fa)*Ba
Cc =( Fa * Ba * func(Bc,Fc)
+ Fa *(1-Ba) * Fc
+ (1-Fa)*Ba * Bc
) / Ca

単純な合成ならリンクの通りだいぶ省略できるけど、加算合成の場合は複雑。
吉里吉里2だと、α付き画像とα付き画像の通常アルファ合成だけがある模様。
後、MMX版は見当たらず、C版のみのよう。
C版は割り算等の部分をあらかじめ計算してテーブルに持っている。

通常合成のC版はこれでいいとして、加算合成の方は他のアルファ部分の乗数をテーブルで持って、除算が必要。
テーブル部分にあらかじめ除算値を入れていてもいいが、そうしたら誤差の影響かうまくいかなかった。
除算は、0~255 で割るのを加算、乗算、シフト にしてテーブルで持った。
SIMDで整数除算 に書かれている方法。
以下のメソッドで rcp, shift, bias のテーブルを作る。
divが割る値。

void short_rcp( unsigned short div, unsigned short& rcp, unsigned char& shift, unsigned char& bias ) {
  int b = 0;
  for( int i = 0; i < 16; i++ ) {
    if( ( ( div >> ( 15 - i ) ) & 0x1 ) == 1 ) {
      b = 15 - i;
      break;
    }
  }
  unsigned int r = 16 + b;
  unsigned int r2 = 1 << r;
  double f = (double)r2 / div;
  double fm = fmod( f, 1.0 );
  if( fm == 0.0 ) {
    shift = (unsigned char)b;
    rcp = 1;
    bias = 0;
  } else if( fm < 0.5 ) {
    shift = (unsigned char)b + 16;
    rcp = (unsigned short)f;
    bias = 1;
  } else {
    shift = (unsigned char)b + 16;
    rcp = (unsigned short)( f + 0.5 );
    bias = 0;
  }
}

求めた値は、以下の式で使う。
x = (((x + bias)*rcp)>>shift)&0xff;

C版の加算合成は上記の方法で求められ、不透明画像に重ねていく方法と比較して違いがわからない程度になった。
α付き画像への通常アルファ合成と加算合成のC版は出来て、α付きのまま出力できるようになった。
次の問題はSSE2版。
テーブルが使えないので何とか精度を上げて変なところを解消したい。
変なところとは何個も重ねていると一部黒くなって線が見えてしまうところ。

精度を上げるため 255で割る処理は、>>8 で代用していたのを a = (a * 0x8081) >> (7+16) といった処理にするのと、128を加算して、四捨五入した。
割り算部分は、SSE を使い浮動小数点で逆数計算し、SSE2 でかける時は 128を加算し四捨五入。
ニュートン-ラフソン法 を使った逆数の精度向上は余り影響しないようなので止めた。
16bit固定少数での演算になるので、11bit精度のものを22bitに上げてもさほど影響しないのかもしれない。
SSE2版はこのように処理したら、一部黒くなって線が見えてしまうのは回避出来たが、加算合成時は四捨五入の128加算が多すぎるためかC版や不透明版の演算結果と比べて少し明るくなってしまう。

α付き画像にα付き画像を重ねる処理は重いので、あまり使わない方がいいが、状況によっては使いたい。
重ねる数が多いので、SSE2版が明るくなってしまうのはC版と比較すると目立つ。
何とかしたいところではあるが、SSE2版だけなら気にすることもないので、とりあえずはこのままで他の部分を作り込む。



投稿者 Takenori : 2012年12月20日 01:13




comments powered by Disqus
Total : Today : Yesterday : なかのひと