Vulkan Spec のさまざまな箇所で depth
という用語が使われています。この章では、Vulkan で使われているさまざまな「深度」の概要を説明します。またこの章では、3Dグラフィックスの基礎知識が必要です。
Note
|
ステンシルは深度と密接に関係していますが、本章では API の領域外はカバーしません。 |
「深度」の概念は Vulkan のグラフィックスパイプラインでのみ使用され、ドローコールが送信されるまでは有効ではありません。
VkGraphicsPipelineCreateInfo
の中には、深度に関連するさまざまな制御可能な値があります。いくつかの状態は動的です。
いくつかの異なる深度フォーマットがあり、実装によって Vulkan でのサポートが公開されます。
深度イメージからの読み取りに必要なフォーマットは、サンプリングやブリット操作による読み取りをサポートするための VK_FORMAT_D16_UNORM
と VK_FORMAT_D32_SFLOAT
だけです。
深度イメージへの書き込みには、VK_FORMAT_D16_UNORM
がサポートされている必要があります。ここから、(VK_FORMAT_X8_D24_UNORM_PACK32
または VK_FORMAT_D32_SFLOAT
)かつ(VK_FORMAT_D24_UNORM_S8_UINT
または VK_FORMAT_D32_SFLOAT_S8_UINT
)の少なくとも1つをサポートする必要があります。このため、深度とステンシルの両方が同じフォーマットで必要な場合に、使用するフォーマットを見つけるために、余分なロジックを必要とします。
// クエリロジックの例
VkFormatProperties properties;
vkGetPhysicalDeviceFormatProperties(physicalDevice, VK_FORMAT_D24_UNORM_S8_UINT, &properties);
bool d24s8_support = (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
vkGetPhysicalDeviceFormatProperties(physicalDevice, VK_FORMAT_D32_SFLOAT_S8_UINT, &properties);
bool d32s8_support = (properties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT);
assert(d24s8_support | d32s8_support); // 常に少なくとも1つサポートする
「深度バッファ」という言葉はグラフィックの話をするときによく使われますが、Vulkanでは、VkFramebuffer
から描画時に参照されるる VkImage
/ VkImageView
に過ぎません。VkRenderPass
を作成する際、pDepthStencilAttachment
値はフレームバッファ内の深度アタッチメントを指します。
pDepthStencilAttachment
を使用するには、その VkImage
は VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT
で作成されている必要があります。
VkImageAspectFlags
が必要なイメージバリアやクリアなどの操作を行う際には、VK_IMAGE_ASPECT_DEPTH_BIT
を使って深度メモリを参照します。
VkImageLayout
を選択する際、イメージの読み取りと書き込みの両方を可能にするレイアウトがあります。
-
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
-
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL
-
VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_OPTIMAL
また、イメージへの読み取りのみを可能にするレイアウトもあります。
-
VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL
-
VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL
-
VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_OPTIMAL
レイアウトの移行の際には、深度イメージの読み書きに必要な深度アクセスマスクの設定を確認してください。
// 未定義のレイアウトから、読み書き可能な深度アタッチメントに変更する例
// コア Vulkan の例
srcAccessMask = 0;
dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
// VK_KHR_synchronization2
srcAccessMask = VK_ACCESS_2_NONE_KHR;
dstAccessMask = VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_READ_BIT_KHR | VK_ACCESS_2_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT_KHR;
sourceStage = VK_PIPELINE_STAGE_2_NONE_KHR;
destinationStage = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT_KHR | VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT_KHR;
Note
|
アプリケーションに初期フラグメントテストと後期フラグメントテストのみを使用するかどうか分からない場合は、両方使ってください。 |
グラフィックスパイプラインには、ラスタライズされるべきプリミティブを生成する一連のラスタライズ前のシェーダステージがあります。ラスタライズステップに到達する前に、ラスタライズ前の最後のステージの最終的な vec4
型の位置(gl_Position
)は、Fixed-Function Vertex Post-Processing を実行します。
以下は、ラスタライズの前に行われるさまざまな座標名と操作についての高レベルの概要です。
VK_EXT_depth_clip_enable の depthClipEnable
を使用しない限り、プリミティブが view volume
の外にある場合、常にクリッピングが発生します。Vulkan では、これは深度に対して次のように表現されます。
0 <= Zc <= Wc
正規化デバイス座標(NDC)を計算する際に、[0, 1]
の外側にあるものはクリップされます。
Zd
が Zc
/ Wc
の結果であるいくつかの例。
-
vec4(1.0, 1.0, 2.0, 2.0)
- クリップされない (Zd
==1.0
) -
vec4(1.0, 1.0, 0.0, 2.0)
- クリップされない (Zd
==0.0
) -
vec4(1.0, 1.0, -1.0, 2.0)
- クリップされる (Zd
==-0.5
) -
vec4(1.0, 1.0, -1.0, -2.0)
- クリップされない (Zd
==0.5
)
ラスタライズ前のシェーダステージでは、ClipDistance
と CullDistance
の組み込み配列を使って、ユーザー定義のクリッピングとカリングを設定することができます。
ラスタライズ前の最後のシェーダステージでは、これらの値はプリミティブ全体で線形補間され、補間された距離が 0
よりも小さいプリミティブの部分はクリップボリュームの外側とみなされます。フラグメントシェーダで ClipDistance
や CullDistance
が使用される場合、これらの線形補間された値が含まれます。
Note
|
|
OpenGLでは、view volume
は次のように表されます。
-Wc <= Zc <= Wc
[-1, 1]
の外側にあるものはクリップされます。
VK_EXT_depth_clip_control 拡張機能は、Vulkan 上で OpenGL を効率的にレイヤ化するために追加されました。VkPipeline
の作成時に VkPipelineViewportDepthClipControlCreateInfoEXT::negativeOneToOne
を VK_TRUE
に設定すると、OpenGL [-1, 1]
ビューボリュームを使用するようになります。
VK_EXT_depth_clip_control
が利用できない場合、現在の回避策はラスタライズ前のシェーダで変換を実行することです。
// [-1,1] から [0,1]
position.z = (position.z + position.w) * 0.5;
ビューポート変換とは、ビューポート矩形と深度範囲に基づいて、正規化デバイス座標からフレームバッファ座標に変換することです。
パイプラインで使われているビューポートのリストは VkPipelineViewportStateCreateInfo::pViewports
で表され、 VkPipelineViewportStateCreateInfo::viewportCount
は使われているビューポートの数を設定します。VkPhysicalDeviceFeatures::multiViewport
が有効でない場合は、ビューポートは1つだけでなければなりません。
Note
|
ビューポートの値は、 |
各ビューポートは、ビューポートの「深度範囲」を設定する VkViewport::minDepth
および VkViewport::maxDepth
の値を持ちます。
Note
|
名前に反して、 |
minDepth
と maxDepth
は、0.0
から 1.0
に含まれるように制限されています。VK_EXT_depth_range_unrestricted が有効な場合、この制限はなくなります。
フレームバッファの深度座標 Zf
は次のように表される。
Zf = Pz * Zd + Oz
-
Zd
=Zc
/Wc
(プリミティブクリッピングをご覧ください) -
Oz
=minDepth
-
Pz
=maxDepth
-minDepth
ポリゴンのラスタライズによって生成されたすべてのフラグメントの深度値は、そのポリゴンに対して計算された単一の値によってオフセットすることができます。描画時に VkPipelineRasterizationStateCreateInfo::depthBiasEnable
が VK_FALSE
である場合、深度バイアスは適用されません。
VkPipelineRasterizationStateCreateInfo
の depthBiasConstantFactor
、depthBiasClamp
、depthBiasSlopeFactor
を使って、深度バイアスを算出することができます。
Note
|
|
Note
|
深度バイアス値は、 |
入力ビルトインの FragCoord
はフレームバッファの座標です。Z
成分はプリミティブの補間された深度値です。この Z
成分の値は FragDepth
に書き込まれます。シェーダが動的に FragDepth
に書き込む場合は、DepthReplacing
実行モードを宣言する必要があります(これは glslang などのツールで行います)。
Note
|
|
Note
|
SPIR-V で |
DepthGreater
、DepthLess
、DepthUnchanged
の各実行モードでは、フラグメントの前に実行される初期の深度テストに依存する実装の最適化が可能になります。GLSLでは、gl_FragDepth
を適切なレイアウト修飾子で宣言することで簡単に実現できます。
// どのような方法でも変更可能であると仮定する
layout(depth_any) out float gl_FragDepth;
// 値が増加可能であると仮定する
layout(depth_greater) out float gl_FragDepth;
// 値が減少可能であると仮定する
layout(depth_less) out float gl_FragDepth;
// 値が変更不可能であると仮定する
layout(depth_unchanged) out float gl_FragDepth;
この条件に違反すると、未定義の動作となります。
次のラスタライズ後の処理は、「サンプルごと」に行われます。つまり、カラーアタッチメントを使用してマルチサンプリングを行う場合、同様に使用される「深度バッファ」 VkImage
も、同じ VkSampleCountFlagBits
値で作成されていなければなりません。
各フラグメントには、そのフラグメント内のサンプルが、そのフラグメントを生成したプリミティブの領域内にあると判断されるカバレッジマスクが設定されています。フラグメント操作の結果、カバレッジマスクのすべてのビットが 0
になった場合、そのフラグメントは破棄されます。
VK_KHR_depth_stencil_resolve 拡張機能(Vulkan 1.2でコアに昇格)を使って、マルチサンプリングされた深度/ステンシルのアタッチメントを、カラーのアタッチメントと同様にサブパスで解決することが可能です。
Note
|
|
VkPipelineDepthStencilStateCreateInfo::depthBoundsTestEnable
が使用されると、深度アタッチメントの各 Za
を取り、それが VkPipelineDepthStencilStateCreateInfo::minDepthBounds
および VkPipelineDepthStencilStateCreateInfo::maxDepthBounds
によって設定された範囲内にあるかどうかをチェックします。値が境界内にない場合は、カバレッジマスクはゼロに設定されます。
Note
|
深度境界値は、 |
深度テストでは、フレームバッファの深度座標 Zf
と深度アタッチメントの深度値 Za
を比較します。テストに失敗すると、そのフラグメントは破棄されます。テストに合格した場合、深度アタッチメントはフラグメントの出力深度で更新されます。VkPipelineDepthStencilStateCreateInfo::depthTestEnable
は、パイプラインでのテストを有効/無効にするために使用されます。
以下に、深度テストの概要を説明します。
VkPipelineDepthStencilStateCreateInfo::depthCompareOp
は深度テストに使われる比較関数を提供します。
depthCompareOp
== VK_COMPARE_OP_LESS
(Zf
< Za
)の例
-
Zf
= 1.0 |Za
= 2.0 | テスト合格 -
Zf
= 1.0 |Za
= 1.0 | テスト失敗 -
Zf
= 1.0 |Za
= 0.0 | テスト失敗
Note
|
VK_EXT_extended_dynamic_state の |
深度テストに合格しても、VkPiplineDexpersStencilStateCreateInfo::depthWriteEnable
が VK_FALSE
に設定されていると、深度アタッチメントに値が書き込まれません。この主な理由は、深度テスト自体が、特定のレンダリング技術に使用できるカバレッジマスクを設定するためです。
Note
|
|