ひとことで
画像の各画素に、色と不透明度(アルファ)を別々に持つ代わりに、あらかじめ色に不透明度を掛けた値を持っておく方式です。 の代わりに を保存します。こうすると、テクスチャの補間やミップマップで縁に黒や白の線が出る問題が消え、さらに加算(光)と通常の半透明(煙)を 1 つの合成方法で扱えるようになります。
ゲームでの使いどころ
- 葉・草・髪・柵などの切り抜きテクスチャ:縁の黒ずみ(ダークフリンジ)や白い縁取りを防ぐ。
- パーティクル:炎が煙に変わる粒子を、1 回の描画で表す。加算用と半透明用で描画を分ける必要がない。
- UI・文字:拡大縮小される半透明の UI 画像の縁をきれいにする。
- ポストプロセスと合成:レンダーターゲット同士の合成、フィルタ処理(ぼかしなど)を正しく行う。
しくみ
ストレート(通常の)アルファの問題:半透明の画像では、完全に透明な画素()の色は見えないので、画像ツールはそこを黒(0, 0, 0)で保存しがちです。テクスチャを拡大してバイリニア補間すると、縁の画素は「葉の緑」と「透明部分の黒」の中間になり、不透明度は中間なのに色は暗くなります。その結果、縁に黒い線が出ます。
乗算済みアルファでは、透明な画素は で、色にすでに不透明度が掛かっているので、補間しても
となり、「半分の不透明度の緑」として正しく合成されます。
合成の式:
乗算済みの式では、 でも に値を入れておけば、それがそのまま足されます。つまり、
- :奥を完全に隠す(通常の不透明)
- :通常の半透明
- で色だけある:加算合成(光る)
が 1 つの合成方法で表せます。若いうちは光る炎()、年を取ると暗い煙()に変わる粒子を、途中で合成方法を切り替えずに描けます。
実装のポイント
- どこで乗算するかを統一する:画像を読み込むときに乗算する(three.js の
texture.premultiplyAlpha、ツールでの書き出し)か、シェーダーで乗算するかを決めて、二重に掛けないようにします。 - 対策としての色の拡張:ストレートアルファのまま使う場合は、透明部分にも近くの色を広げておく(エッジパディング、ダイレーション)と縁の黒ずみを防げます(デモの中央)。
- 線形空間で:ガンマ補正された色に不透明度を掛けると、わずかに誤差が出ます。可能なら線形の値で扱います。
つまずきやすい点
- 二重の乗算:乗算済みのテクスチャを、ストレートの合成(SrcAlpha, OneMinusSrcAlpha)で描くと、色が二重に暗くなります。逆に、ストレートのテクスチャを乗算済みの合成で描くと、透明なはずの部分が加算されて光ります。
- 画像ツールとの食い違い:PNG は規格上ストレートアルファです。ツールやエンジンが読み込み時に自動で変換するかを確認する必要があります。
- 色を取り出すとき:乗算済みの色から元の色を取り出すには で割る必要があり、 が小さいと精度が落ちます。
デモで試すこと
- 上の 3 枚は、同じ 24×24 画素の葉の画像を大きく拡大したものです。左(透明部分が黒のストレート)は、縁に黒い線が出ています。中央(色を外へ広げたストレート)と右(乗算済み)は、縁がきれいです。「葉をさらに拡大」で違いがはっきりします。
- 下の粒子は、若いうちは光る炎(不透明度 0 の加算)、年を取ると暗い煙(不透明度 1)に変わります。乗算済みアルファなら、これを 1 回の描画で表せます。
- 「粒子を乗算済みアルファで合成」をオフにすると、通常のアルファ合成になり、不透明度の低い炎はほとんど見えなくなります。