透明度
PlayCanvasには、完全に不透明ではないサーフェスをレンダリングする方法がいくつかあります。それぞれコスト、描画順への依存度、発生するアーティファクトの種類が異なるため、何をレンダリングするかによって適切な選択が変わります。
いずれの方法も、マテリアルの不透明度によって制御されます。不透明度はStandardMaterial#opacity、opacityMap、または頂点カラーから取得されます。
アルファブレンディング
blendTypeにBLEND_NORMALなどのブレンドモードを設定すると、サーフェスはフレームバッファに既に存在する内容と混合されます。
material.blendType = pc.BLEND_NORMAL;
material.opacity = 0.5;
material.update();
これは最も滑らかな結果が得られ、任意の不透明度の値をサポートしますが、描画順に依存します。ブレンドされるジオメトリは不透明なジオメトリの後の透明パスで描画され、Layer#transparentSortModeに従ってレイヤーごとに奥から手前へソートされます。ソートはメッシュインスタンス単位で行われるため、自身と重なる単一のメッシュを正しく解決することはできません。これは、植生、髪、ガラスでアーティファクトが発生する一般的な原因です。また、ブレンドされるマテリアルは通常デプス書き込みを無効にするため、互いを遮蔽しません。
ブレンドステート
blendTypeは、よく使われるいくつかの設定に対する便利な短縮形です。完全に制御するには、BlendStateをMaterial#blendStateに割り当てます。BlendStateでは、ブレンド式と、カラーおよびアルファのソース係数とデスティネーション係数をそれぞれ個別に指定でき、さらにチャンネルごとのカラー書き込みマスクも指定できます。ブレンドステートを割り当てると、blendTypeで以前に設定した内容は上書きされます。
// BLEND_NORMAL と同等の設定を明示的に記述したもの
material.blendState = new pc.BlendState(
true,
pc.BLENDEQUATION_ADD, pc.BLENDMODE_SRC_ALPHA, pc.BLENDMODE_ONE_MINUS_SRC_ALPHA
);
material.update();
よく使われるステートは定数として用意されています(BlendState.NOBLEND、BlendState.ALPHABLEND、BlendState.ADDBLEND、BlendState.NOWRITE)。パフォーマンスを最大限に高めるには、作成後にステートを変更するのではなく、必要なブレンドステートを事前に作成して必要に応じて割り当ててください。
なお、ゲッターは読み取り専用のビューを返すため、ブレンディングを変更するにはセッターを使用する必要があります。これにより、マテリアルの透明度とソートの状態が同期されます。
const state = material.blendState.clone();
state.setColorWrite(true, true, true, false);
material.blendState = state;
material.update();
カラーアタッチメントごとのブレンディング
ブレンドステートは、デフォルトではレンダーターゲットのすべてのカラーアタッチメントに適用されます。複数のレンダーターゲットにレンダリングする場合、BlendState#setAttachmentを使用して、インデックス1から7のアタッチメントに個別のブレンドステートと書き込みマスクを設定できます。アタッチメント0はクラスの他のプロパティで設定し、個別のステートが設定されていないアタッチメントはアタッチメント0に従います。
// アタッチメント1はアタッチメント0のブレンディングを維持しますが、どのチャンネルも書き込みません
const state = material.blendState.clone();
const noWrite = state.clone();
noWrite.setColorWrite(false, false, false, false);
state.setAttachment(1, noWrite);
material.blendState = state;
material.update();
これにはGraphicsDevice#supportsIndependentBlendingが必要です。サポートされていないデバイスでは、アタッチメント0のステートがすべてのアタッチメントに使用されます。
ブレンドステートで2つ目のソースを参照する係数を使用すると、デュアルソースブレンディングも有効になります。これにより、フラグメントシェーダーがブレンド係数として使用される2つ目のカラーを出力できます。
アルファテスト
alphaTestは、不透明度がしきい値を下回るフラグメントを破棄します。
material.alphaTest = 0.5;
material.update();
結果は二値になります。つまりフラグメントは完全に不透明か、破棄されるかのどちらかです。そのためソートが不要で、マテリアルは不透明パスに留まり、通常どおりデプスに書き込みます。これにより低コストで描画順に完全に依存しなくなりますが、切り抜きのエッジは硬くエイリアスが目立ちます。部分的な不透明度が不要な、密度の高い植生などの切り抜き表現で通常選ばれる方法です。
不透明度のディザリング
opacityDitherは、不透明度をブレンドする代わりに、一定の割合のフラグメントを破棄するスクリーンスペースのディザパターンに変換します。
material.blendType = pc.BLEND_NONE;
material.opacity = 0.5;
material.opacityDither = pc.DITHER_BAYER8;
material.update();
使用できるパターンはDITHER_BAYER2、DITHER_BAYER4、DITHER_BAYER8、DITHER_BAYER16、DITHER_BLUENOISE、DITHER_IGNNOISEです。アルファテストと同様に描画順に依存せず不透明パスに留まりますが、連続的な不透明度をサポートします。その代償としてノイズが見えますが、テンポラルアンチエイリアシングや高い出力解像度と組み合わせることで滑らかな透明表現に解消されます。opacityShadowDitherは、オブジェクトが落とすシャドウに同じ手法を適用します。
アルファトゥカバレッジ
alphaToCoverageは、フラグメントのアルファ値を使用してMSAAのサンプルカバレッジマスクを構築します。ブレンドの代わりに、ハードウェアがアルファ値に応じた割合のマルチサンプルカバレッジを保持します。
material.blendType = pc.BLEND_NONE;
material.opacity = 0.5;
material.alphaToCoverage = true;
material.update();
ブレンドを有効にする必要はありません。アルファテストと同様に、アルファ値はカバレッジマスクによって消費されます。マテリアルは不透明パスに留まりデプスに書き込むため、結果は描画順に依存しません。
品質はレンダーターゲットのサンプル数によって制限されます。4x MSAAの場合、不透明度は0%、25%、50%、75%、100%に量子化されます。そのため、アルファトゥカバレッジはアルファによる切り抜きの硬いエッジを滑らかにするのには適していますが、量子化が目立つ広い面積の半透明表現には適していません。
要件
アルファトゥカバレッジにはマルチサンプルのレンダーターゲットが必要で、シングルサンプルのレンダーターゲットにレンダリングする場合は何も通知されずに無視されます。リリースビルドではログも出力されず、エラーも発生せず、サーフェスは単に完全に不透明としてレンダリングされます。このフラグを有効にしても変化が見られない場合は、アンチエイリアシングが実際に有効になっているか確認してください。
const device = await pc.createGraphicsDevice(canvas, {
deviceTypes: [deviceType],
antialias: true
});
WebGPUではさらに要件があります。レンダーターゲットの最初のカラーアタッチメントが、アルファチャンネルを持つブレンド可能なフォーマットを使用している必要があります。これは実際に問題になります。CameraFrameはHDRレンダーターゲットにPIXELFORMAT_111110Fを優先しますが、このフォーマットにはアルファチャンネルがありません。そのため、デフォルトのフォーマットのCameraFrameを通してレンダリングされるジオメトリではアルファトゥカバレッジは無視され、デバッグビルドでは警告が出力されます。アルファチャンネルを持つフォーマットを要求すれば解決します。
cameraFrame.rendering.renderFormats = [pc.PIXELFORMAT_RGBA16F];
cameraFrame.update();
WebGLには同等の制限はありません。レンダーターゲットがアルファチャンネルを格納しているかどうかに関係なく、シェーダーが出力したアルファ値を使用するためです。そのためWebGLではPIXELFORMAT_111110Fのようなフォーマットでもアルファトゥカバレッジが適用されます。これは2つのバックエンド間の意図的な違いであり、バグではありません。
手法の選択
| 手法 | 不透明度 | 描画順への依存 | パス | 主な欠点 |
|---|---|---|---|---|
| アルファブレンディング | 連続的 | あり | 透明 | ソートのアーティファクト、自己ソート不可 |
| アルファテスト | 二値 | なし | 不透明 | 硬くエイリアスの目立つエッジ |
| 不透明度のディザリング | 連続的 | なし | 不透明 | TAAがない場合にノイズが見える |
| アルファトゥカバレッジ | サンプル数に量子化 | なし | 不透明 | MSAAが必要、段階が粗い |
大まかな指針としては、ガラスなど実際に透けて見えるサーフェスで描画順よりも品質が重要な場合はアルファブレンディング、密度の高い切り抜きにはアルファテスト、フェードやLODの遷移(特にテンポラルアンチエイリアシングが既に有効な場合)には不透明度のディザリング、MSAAのコストを既に支払っている場合に切り抜きのエッジを滑らかにするにはアルファトゥカバレッジを使用してください。