diff --git a/docs/user-manual/graphics/transparency.md b/docs/user-manual/graphics/transparency.md new file mode 100644 index 00000000000..44214c3dbbc --- /dev/null +++ b/docs/user-manual/graphics/transparency.md @@ -0,0 +1,132 @@ +--- +title: Transparency +description: "Compare the ways PlayCanvas renders transparent surfaces: alpha blending, alpha test, opacity dithering and alpha to coverage, and when to use each." +--- + +PlayCanvas offers several ways to render a surface that is not fully opaque. They differ in cost, in how much they depend on draw order, and in the kind of artifacts they produce, so the right choice depends on what you are rendering. + +All of them are driven by the material's opacity, which comes from [`StandardMaterial#opacity`](https://api.playcanvas.com/engine/classes/StandardMaterial.html#opacity), an [`opacityMap`](https://api.playcanvas.com/engine/classes/StandardMaterial.html#opacitymap), or vertex colors. + +## Alpha Blending + +Setting [`blendType`](https://api.playcanvas.com/engine/classes/Material.html#blendtype) to a blending mode such as `BLEND_NORMAL` mixes the surface with whatever is already in the frame buffer. + +```javascript +material.blendType = pc.BLEND_NORMAL; +material.opacity = 0.5; +material.update(); +``` + +This gives the smoothest result and supports any opacity value, but it is order dependent. Blended geometry is drawn in the transparent pass, after opaque geometry, and is sorted back to front per layer according to [`Layer#transparentSortMode`](https://api.playcanvas.com/engine/classes/Layer.html#transparentsortmode). Sorting happens per mesh instance, so it cannot resolve a single mesh that overlaps itself - a common source of artifacts on foliage, hair and glass. Blended materials also normally disable depth writes, so they do not occlude each other. + +### Blend State + +`blendType` is a convenient shorthand for a handful of common configurations. For full control, assign a [`BlendState`](https://api.playcanvas.com/engine/classes/BlendState.html) to [`Material#blendState`](https://api.playcanvas.com/engine/classes/Material.html#blendstate), which specifies the blend equation and the source and destination factors for color and alpha independently, along with a per-channel color write mask. Assigning a blend state overwrites anything previously set through `blendType`. + +```javascript +// equivalent to BLEND_NORMAL, written out in full +material.blendState = new pc.BlendState( + true, + pc.BLENDEQUATION_ADD, pc.BLENDMODE_SRC_ALPHA, pc.BLENDMODE_ONE_MINUS_SRC_ALPHA +); +material.update(); +``` + +Several ready-made states are available as constants - `BlendState.NOBLEND`, `BlendState.ALPHABLEND`, `BlendState.ADDBLEND` and `BlendState.NOWRITE`. For best performance, create the blend states you need up front and assign them as required, rather than modifying a state after creation. + +Note that the getter returns a read-only view, so the setter must be used to change blending - this is what keeps the material's transparency and sorting state in sync: + +```javascript +const state = material.blendState.clone(); +state.setColorWrite(true, true, true, false); +material.blendState = state; +material.update(); +``` + +#### Per-attachment blending + +By default a blend state applies to every color attachment of the render target. When rendering to [Multiple Render Targets](/user-manual/graphics/advanced-rendering/multiple-render-targets), individual attachments can be given their own blend state and write mask using [`BlendState#setAttachment`](https://api.playcanvas.com/engine/classes/BlendState.html#setattachment), for attachment indices 1 to 7. Attachment 0 is configured through the other properties of the class, and any attachment without an independent state follows attachment 0. + +```javascript +// attachment 1 keeps the blending of attachment 0, but writes no channels +const state = material.blendState.clone(); +const noWrite = state.clone(); +noWrite.setColorWrite(false, false, false, false); +state.setAttachment(1, noWrite); +material.blendState = state; +material.update(); +``` + +This requires [`GraphicsDevice#supportsIndependentBlending`](https://api.playcanvas.com/engine/classes/GraphicsDevice.html#supportsindependentblending). On devices without support, the state of attachment 0 is used for all attachments. + +Using one of the secondary source blend factors in a blend state additionally enables dual-source blending, which lets a fragment shader output a second color used as a blend factor. + +## Alpha Test + +[`alphaTest`](https://api.playcanvas.com/engine/classes/Material.html#alphatest) discards any fragment whose opacity falls below a threshold. + +```javascript +material.alphaTest = 0.5; +material.update(); +``` + +The result is binary - a fragment is either fully opaque or gone - so there is nothing to sort and the material stays in the opaque pass, writing depth normally. That makes it cheap and completely order independent, at the cost of hard, aliased cutout edges. It is the usual choice for dense foliage and other cutouts where partial opacity is not needed. + +## Opacity Dithering + +[`opacityDither`](https://api.playcanvas.com/engine/classes/StandardMaterial.html#opacitydither) converts opacity into a screen-space dither pattern, discarding a proportion of fragments instead of blending them. + +```javascript +material.blendType = pc.BLEND_NONE; +material.opacity = 0.5; +material.opacityDither = pc.DITHER_BAYER8; +material.update(); +``` + +Available patterns are `DITHER_BAYER2`, `DITHER_BAYER4`, `DITHER_BAYER8`, `DITHER_BAYER16`, `DITHER_BLUENOISE` and `DITHER_IGNNOISE`. Like alpha test this is order independent and stays in the opaque pass, but it supports continuous opacity. The trade-off is visible noise, which resolves into smooth transparency when combined with temporal antialiasing or a high output resolution. [`opacityShadowDither`](https://api.playcanvas.com/engine/classes/StandardMaterial.html#opacityshadowdither) applies the same technique to the shadow the object casts. + +## Alpha To Coverage + +[`alphaToCoverage`](https://api.playcanvas.com/engine/classes/Material.html#alphatocoverage) uses the fragment's alpha to build an MSAA sample coverage mask. Instead of blending, the hardware keeps a proportion of the multi-sample coverage matching the alpha value. + +```javascript +material.blendType = pc.BLEND_NONE; +material.opacity = 0.5; +material.alphaToCoverage = true; +material.update(); +``` + +Blending does not need to be enabled - the alpha is consumed by the coverage mask, much like alpha test. The material stays in the opaque pass and writes depth, which makes the result order independent. + +Quality is bounded by the sample count of the render target. With 4x MSAA, opacity is quantized to 0%, 25%, 50%, 75% and 100%, which is why alpha to coverage works well for softening the sharp edges of an alpha cutout, but is a poor choice for large areas of even semi-transparency, where the quantization is obvious. + +### Requirements + +Alpha to coverage requires a multi-sampled render target and is **silently ignored** when rendering into a single-sampled one. Nothing is logged in release builds and no error is raised - the surface simply renders as fully opaque. If you enable the flag and see no change, check that antialiasing is actually on: + +```javascript +const device = await pc.createGraphicsDevice(canvas, { + deviceTypes: [deviceType], + antialias: true +}); +``` + +On WebGPU there is an additional requirement: the first color attachment of the render target must use a blendable format that has an alpha channel. This matters in practice because [`CameraFrame`](https://api.playcanvas.com/engine/classes/CameraFrame.html) prefers `PIXELFORMAT_111110F` for its HDR render target, and that format has no alpha channel. Alpha to coverage is therefore ignored for geometry rendered through `CameraFrame` with its default formats, and a warning is logged in debug builds. Requesting a format with an alpha channel resolves it: + +```javascript +cameraFrame.rendering.renderFormats = [pc.PIXELFORMAT_RGBA16F]; +cameraFrame.update(); +``` + +WebGL has no equivalent restriction, as it uses the alpha the shader outputs regardless of whether the render target stores an alpha channel. Alpha to coverage therefore still applies on WebGL with formats such as `PIXELFORMAT_111110F`, which is a deliberate difference between the two backends rather than a bug. + +## Choosing an Approach + +| Technique | Opacity | Order dependent | Pass | Main drawback | +|-----------|---------|-----------------|------|---------------| +| Alpha blending | Continuous | Yes | Transparent | Sorting artifacts, no self-sorting | +| Alpha test | Binary | No | Opaque | Hard, aliased edges | +| Opacity dithering | Continuous | No | Opaque | Visible noise without TAA | +| Alpha to coverage | Quantized to sample count | No | Opaque | Needs MSAA, coarse steps | + +As a rough guide, use alpha blending for glass and other genuinely see-through surfaces where quality matters more than ordering; alpha test for dense cutouts; opacity dithering for fades and level-of-detail transitions, especially when temporal antialiasing is already enabled; and alpha to coverage to soften cutout edges when MSAA is already being paid for. diff --git a/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/transparency.md b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/transparency.md new file mode 100644 index 00000000000..041db3f42e0 --- /dev/null +++ b/i18n/ja/docusaurus-plugin-content-docs/current/user-manual/graphics/transparency.md @@ -0,0 +1,132 @@ +--- +title: 透明度 +description: "PlayCanvasで透明なサーフェスをレンダリングする方法(アルファブレンディング、アルファテスト、不透明度のディザリング、アルファトゥカバレッジ)を比較し、それぞれの使いどころを説明します。" +--- + +PlayCanvasには、完全に不透明ではないサーフェスをレンダリングする方法がいくつかあります。それぞれコスト、描画順への依存度、発生するアーティファクトの種類が異なるため、何をレンダリングするかによって適切な選択が変わります。 + +いずれの方法も、マテリアルの不透明度によって制御されます。不透明度は[`StandardMaterial#opacity`](https://api.playcanvas.com/engine/classes/StandardMaterial.html#opacity)、[`opacityMap`](https://api.playcanvas.com/engine/classes/StandardMaterial.html#opacitymap)、または頂点カラーから取得されます。 + +## アルファブレンディング + +[`blendType`](https://api.playcanvas.com/engine/classes/Material.html#blendtype)に`BLEND_NORMAL`などのブレンドモードを設定すると、サーフェスはフレームバッファに既に存在する内容と混合されます。 + +```javascript +material.blendType = pc.BLEND_NORMAL; +material.opacity = 0.5; +material.update(); +``` + +これは最も滑らかな結果が得られ、任意の不透明度の値をサポートしますが、描画順に依存します。ブレンドされるジオメトリは不透明なジオメトリの後の透明パスで描画され、[`Layer#transparentSortMode`](https://api.playcanvas.com/engine/classes/Layer.html#transparentsortmode)に従ってレイヤーごとに奥から手前へソートされます。ソートはメッシュインスタンス単位で行われるため、自身と重なる単一のメッシュを正しく解決することはできません。これは、植生、髪、ガラスでアーティファクトが発生する一般的な原因です。また、ブレンドされるマテリアルは通常デプス書き込みを無効にするため、互いを遮蔽しません。 + +### ブレンドステート + +`blendType`は、よく使われるいくつかの設定に対する便利な短縮形です。完全に制御するには、[`BlendState`](https://api.playcanvas.com/engine/classes/BlendState.html)を[`Material#blendState`](https://api.playcanvas.com/engine/classes/Material.html#blendstate)に割り当てます。BlendStateでは、ブレンド式と、カラーおよびアルファのソース係数とデスティネーション係数をそれぞれ個別に指定でき、さらにチャンネルごとのカラー書き込みマスクも指定できます。ブレンドステートを割り当てると、`blendType`で以前に設定した内容は上書きされます。 + +```javascript +// 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`)。パフォーマンスを最大限に高めるには、作成後にステートを変更するのではなく、必要なブレンドステートを事前に作成して必要に応じて割り当ててください。 + +なお、ゲッターは読み取り専用のビューを返すため、ブレンディングを変更するにはセッターを使用する必要があります。これにより、マテリアルの透明度とソートの状態が同期されます。 + +```javascript +const state = material.blendState.clone(); +state.setColorWrite(true, true, true, false); +material.blendState = state; +material.update(); +``` + +#### カラーアタッチメントごとのブレンディング + +ブレンドステートは、デフォルトではレンダーターゲットのすべてのカラーアタッチメントに適用されます。[複数のレンダーターゲット](/user-manual/graphics/advanced-rendering/multiple-render-targets)にレンダリングする場合、[`BlendState#setAttachment`](https://api.playcanvas.com/engine/classes/BlendState.html#setattachment)を使用して、インデックス1から7のアタッチメントに個別のブレンドステートと書き込みマスクを設定できます。アタッチメント0はクラスの他のプロパティで設定し、個別のステートが設定されていないアタッチメントはアタッチメント0に従います。 + +```javascript +// アタッチメント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`](https://api.playcanvas.com/engine/classes/GraphicsDevice.html#supportsindependentblending)が必要です。サポートされていないデバイスでは、アタッチメント0のステートがすべてのアタッチメントに使用されます。 + +ブレンドステートで2つ目のソースを参照する係数を使用すると、デュアルソースブレンディングも有効になります。これにより、フラグメントシェーダーがブレンド係数として使用される2つ目のカラーを出力できます。 + +## アルファテスト + +[`alphaTest`](https://api.playcanvas.com/engine/classes/Material.html#alphatest)は、不透明度がしきい値を下回るフラグメントを破棄します。 + +```javascript +material.alphaTest = 0.5; +material.update(); +``` + +結果は二値になります。つまりフラグメントは完全に不透明か、破棄されるかのどちらかです。そのためソートが不要で、マテリアルは不透明パスに留まり、通常どおりデプスに書き込みます。これにより低コストで描画順に完全に依存しなくなりますが、切り抜きのエッジは硬くエイリアスが目立ちます。部分的な不透明度が不要な、密度の高い植生などの切り抜き表現で通常選ばれる方法です。 + +## 不透明度のディザリング + +[`opacityDither`](https://api.playcanvas.com/engine/classes/StandardMaterial.html#opacitydither)は、不透明度をブレンドする代わりに、一定の割合のフラグメントを破棄するスクリーンスペースのディザパターンに変換します。 + +```javascript +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`](https://api.playcanvas.com/engine/classes/StandardMaterial.html#opacityshadowdither)は、オブジェクトが落とすシャドウに同じ手法を適用します。 + +## アルファトゥカバレッジ + +[`alphaToCoverage`](https://api.playcanvas.com/engine/classes/Material.html#alphatocoverage)は、フラグメントのアルファ値を使用してMSAAのサンプルカバレッジマスクを構築します。ブレンドの代わりに、ハードウェアがアルファ値に応じた割合のマルチサンプルカバレッジを保持します。 + +```javascript +material.blendType = pc.BLEND_NONE; +material.opacity = 0.5; +material.alphaToCoverage = true; +material.update(); +``` + +ブレンドを有効にする必要はありません。アルファテストと同様に、アルファ値はカバレッジマスクによって消費されます。マテリアルは不透明パスに留まりデプスに書き込むため、結果は描画順に依存しません。 + +品質はレンダーターゲットのサンプル数によって制限されます。4x MSAAの場合、不透明度は0%、25%、50%、75%、100%に量子化されます。そのため、アルファトゥカバレッジはアルファによる切り抜きの硬いエッジを滑らかにするのには適していますが、量子化が目立つ広い面積の半透明表現には適していません。 + +### 要件 + +アルファトゥカバレッジにはマルチサンプルのレンダーターゲットが必要で、シングルサンプルのレンダーターゲットにレンダリングする場合は**何も通知されずに無視されます**。リリースビルドではログも出力されず、エラーも発生せず、サーフェスは単に完全に不透明としてレンダリングされます。このフラグを有効にしても変化が見られない場合は、アンチエイリアシングが実際に有効になっているか確認してください。 + +```javascript +const device = await pc.createGraphicsDevice(canvas, { + deviceTypes: [deviceType], + antialias: true +}); +``` + +WebGPUではさらに要件があります。レンダーターゲットの最初のカラーアタッチメントが、アルファチャンネルを持つブレンド可能なフォーマットを使用している必要があります。これは実際に問題になります。[`CameraFrame`](https://api.playcanvas.com/engine/classes/CameraFrame.html)はHDRレンダーターゲットに`PIXELFORMAT_111110F`を優先しますが、このフォーマットにはアルファチャンネルがありません。そのため、デフォルトのフォーマットの`CameraFrame`を通してレンダリングされるジオメトリではアルファトゥカバレッジは無視され、デバッグビルドでは警告が出力されます。アルファチャンネルを持つフォーマットを要求すれば解決します。 + +```javascript +cameraFrame.rendering.renderFormats = [pc.PIXELFORMAT_RGBA16F]; +cameraFrame.update(); +``` + +WebGLには同等の制限はありません。レンダーターゲットがアルファチャンネルを格納しているかどうかに関係なく、シェーダーが出力したアルファ値を使用するためです。そのためWebGLでは`PIXELFORMAT_111110F`のようなフォーマットでもアルファトゥカバレッジが適用されます。これは2つのバックエンド間の意図的な違いであり、バグではありません。 + +## 手法の選択 + +| 手法 | 不透明度 | 描画順への依存 | パス | 主な欠点 | +|------|----------|----------------|------|----------| +| アルファブレンディング | 連続的 | あり | 透明 | ソートのアーティファクト、自己ソート不可 | +| アルファテスト | 二値 | なし | 不透明 | 硬くエイリアスの目立つエッジ | +| 不透明度のディザリング | 連続的 | なし | 不透明 | TAAがない場合にノイズが見える | +| アルファトゥカバレッジ | サンプル数に量子化 | なし | 不透明 | MSAAが必要、段階が粗い | + +大まかな指針としては、ガラスなど実際に透けて見えるサーフェスで描画順よりも品質が重要な場合はアルファブレンディング、密度の高い切り抜きにはアルファテスト、フェードやLODの遷移(特にテンポラルアンチエイリアシングが既に有効な場合)には不透明度のディザリング、MSAAのコストを既に支払っている場合に切り抜きのエッジを滑らかにするにはアルファトゥカバレッジを使用してください。 diff --git a/sidebars.js b/sidebars.js index 5adf30e1fae..b1074c1f0a2 100644 --- a/sidebars.js +++ b/sidebars.js @@ -780,6 +780,7 @@ const sidebars = { 'user-manual/graphics/physical-rendering/image-based-lighting', ], }, + 'user-manual/graphics/transparency', { type: 'category', label: 'Linear Workflow',