WWDC Quick Look 💓 By SwiftGGTeam
Display EDR content with Core Image, Metal, and SwiftUI

Display EDR content with Core Image, Metal, and SwiftUI

元の動画を見る

ハイライト

Core Image チームの David Hayward 氏は、Core Image での EDR (Extended Dynamic Range) の使用法を体系的に説明しました。 EDR では、ディスプレイのヘッドルームを超えない限り、標準の 0 ~ 1 の範囲外のピクセル値を許可し、1 より大きい値を使用して SDR 白よりも明るいピクセルを表します。

主要内容

多くの画像アプリはすでに HDR データにアクセスできます。 TIFF、OpenEXR、HDR ビデオ フレーム、Metal レンダリング結果、ProRAW DNG にはすべて、SDR ホワイトを超える輝度値が含まれる可能性があります。多くの場合、問題は表示リンクにあります。Core Image は浮動小数点ピクセルを生成しますが、最終ビューは SDR パイプラインに従って表示され、1 を超えるハイライトはクリップされます。

このセッションでは、完全な SwiftUI パスが提供されます。それは最初に使用されますViewRepresentableパッケージMTKView、SwiftUI アプリに Metal レンダー ターゲットを持たせます。それから使用しますRenderer各フレームに置くCIImageにレンダリングするCIRenderDestination; 最後に、EDR に必要なレイヤー、ピクセル形式、色空間、ヘッドルームをこのパイプラインに接続します (03:39)。

EDR の重要なルールは短いです。SDR の白は 1 のまま、黒は 0 のままで、1 を超える値は明るいピクセルを表します。現在のディスプレイのヘッドルームを超える値はクリップされます。ヘッドルームはモニター、周囲光、画面の明るさによって変化するため、レンダリング コードはヘッドルームを定数としてキャッシュできません (00:47)。

Apple は実装手順を 3 つの手順に圧縮しました。EDR をサポートするために View を初期化します。各レンダリングの前に現在のヘッドルームを読み取ります。させてContentViewimageProviderこのヘッドルームを使用して生成しますCIImage08:50)。

詳細

MTKView を使用してコア イメージのレンダリングを実行する

(05:17) サンプル アプリの最初のレイヤーは次のとおりです。MetalView。これは SwiftUI View ですが、内部的に作成されていますMTKView

struct MetalView: ViewRepresentable {
    
    @StateObject var renderer: Renderer
    
    func makeView(context: Context) -> MTKView {
        let view = MTKView(frame: .zero, device: renderer.device)
       
        view.delegate = renderer

        // Suggest to Core Animation, through MetalKit, how often to redraw the view.
        view.preferredFramesPerSecond = 30
       
        // Allow Core Image to render to the view using Metal's compute pipeline.
        view.framebufferOnly = false
        
       return view
    }

キーポイント:

  • ViewRepresentableプラットフォーム ビューを SwiftUI に接続します。 macOSではそうですNSView、他のプラットフォームはUIView
  • rendererはい@StateObject、Metal コマンド キューと Core Image コンテキストの保持を担当します。 -view.delegate = rendererさせてRenderer引き継ぐdraw()電話。 -preferredFramesPerSecond = 30アニメーション アプリを活用しましょうMTKView定期的に描画をトリガーします。 -framebufferOnly = falseCore Image が Metal コンピューティング パイプライン経由でこのビューにレンダリングできるようにします。

画像エディターの場合は、代わりにセッションを使用することをお勧めしますenableSetNeedsDisplay。このようにして、スライダー、ボタン、またはビデオ フレームが到着すると、無意味な定期的な更新を避けるために別の描画がトリガーされます (06:18)。

レンダラーはフレームごとに CIRenderDestination を作成します

07:12Rendererdraw(in:)現在の表示比率を決定し、Core Image レンダー ターゲットを作成し、imageProvider現在時刻のコピーが必要ですCIImage

func draw(in view: MTKView) {
  if let commandBuffer = commandQueue.makeCommandBuffer(),
     let drawable = view.currentDrawable {
      // Calculate content scale factor so CI can render at Retina resolution.
  #if os(macOS)
      var contentScale = view.convertToBacking(CGSize(width: 1.0, height: 1.0)).width
  #else
      var contentScale = view.contentScaleFactor
  #endif

      let destination = CIRenderDestination(width: Int(view.drawableSize.width),
                          height: Int(view.drawableSize.height), 
                          pixelFormat: view.colorPixelFormat,
                          commandBuffer: commandBuffer,
                          mtlTextureProvider: { () -> MTLTexture in
                                   return drawable.texture
                          })
       
      let time = CFTimeInterval(CFAbsoluteTimeGetCurrent() - self.startTime)

      // Create a displayable image for the current time.
      var image = self.imageProvider(time, contentScaleFactor)

      image = image.transformed(by: CGAffineTransform(translationX: shiftX, y: shiftY))
      image = image.composited(over: self.opaqueBackground)
                
      _ = try? self.cicontext.startTask(toRender: image, from: backBounds,
                                             to: destination, at: CGPoint.zero)

キーポイント:

  • makeCommandBuffer()このフレームのメタル コマンド バッファを作成します。 -view.currentDrawable最終表示用のテクスチャを提供します。
  • macOSの場合convertToBackingポイント対ピクセル比を計算します。 iOS および iPadOS で使用されるcontentScaleFactor
  • CIRenderDestinationコアイメージ出力をに接続しますdrawable.texture
  • imageProvider時間と比率で表示されるようにこのフレームを生成しますCIImage
  • startTask(toRender:)Core Image に結果を Metal ターゲットにレンダリングさせます。

ビューは別のモニターにドラッグされる可能性があり、CIImage はピクセル単位で測定されるため、コンテンツ スケールはここでフレームごとに計算されます (07:12)。

Open EDR: レイヤー、ピクセル形式、色空間

(09:17)MTKViewEDR サポートを追加する場合、最初のステップは次のとおりです。makeView()

if let caMtlLayer = view.layer as? CAMetalLayer  {
    caMtlLayer.wantsExtendedDynamicRangeContent = true
    view.colorPixelFormat = MTLPixelFormat.rgba16Float
    view.colorspace = CGColorSpace(name: CGColorSpace.extendedLinearDisplayP3)
}

キーポイント:

  • wantsExtendedDynamicRangeContent = true教えてCAMetalLayerこのレイヤーは EDR コンテンツを表示したいと考えています。 -.rgba16Float16ビット浮動小数点ピクセル形式を採用し、1を超えるカラー値を表現可能。 -extendedLinearDisplayP3拡張されたリニア Display P3 カラー スペースを使用します。
  • このコードは表示ターゲットを初期化するだけであり、各ピクセルの明るさはまだ決定していません。

このステップでは、コンテナーの機能に対処します。これがないと、1 を超える値を使用しても、後続の Core Image コンテンツは EDR パスに従って表示できません。

各ドローの前にヘッドルームを読む

(09:35) 2 番目のステップは、Renderer.draw()。レンダリング前に、ヘッドルームを現在の画面から読み取って、コンテンツ生成ロジックに渡す必要があります。

let screen = view.window?.screen;
#if os(macOS)
     let headroom = screen?.maximumExtendedDynamicRangeColorComponentValue ?? 1.0
#else
     let headroom = screen?.currentEDRHeadroom ?? 1.0
#endif
     var image = self.imageProvider(time, contentScaleFactor, headroom)

キーポイント:

  • view.window?.screenこのビューが現在表示されている画面を見つけます。
  • macOSの使用maximumExtendedDynamicRangeColorComponentValue
  • iOS および iPadOS で使用currentEDRHeadroom
  • 読み取り失敗時のフォールバック1.0これは、SDR 範囲のみを使用することと同じです。 -headroom渡されましたimageProvider、画像生成コードが現在の表示機能に応じてハイライトを制御できるようにします。

session は、ヘッドルームが動的属性であることを明確に強調しています。周囲の光、画面の明るさ、モニターの変化はすべて影響するため、適切である必要があります。draw()(09:49) を読み込みます。

CIFilter を使用して EDR ハイライトを生成する

(12:42) アプリが EDR をサポートした後、この例ではチェッカーボードにリップル トランジションを追加し、shadingImage明るい鏡面ハイライトを作成します。

let ripple = CIFilter.rippleTransition()
ripple.inputImage = image
ripple.targetImage = image
ripple.center = CGPoint(x: 512.0, y: 384.0)
ripple.time = Float(fmod(time*0.25, 1.0))
ripple.shadingImage = shading
image = ripple.outputImage

キーポイント:

  • CIFilter.rippleTransition()リップル遷移フィルターを作成します。 -inputImageそしてtargetImageどちらも現在のチェッカー イメージを指しており、エフェクトは同じピクチャのリップル変更に使用されます。 -centerリップルの中心を設定します。 -time現在のアニメーション時間を使用して、トランジションの進行を推進します。 -shadingImage鏡面ハイライト マップを提供します。 -outputImageリップルとハイライトを重ね合わせた後のコア イメージ出力です。

スペキュラー マップは、別の Core Image フィルター (13:34) から取得されます。ここからヘッドルームの実際の活用が始まります。

let gradient = CIFilter.linearGradient()
let w = min( headroom, 8.0 )
gradient.color0 = CIColor(red: w, green: w, blue: w, 
                          colorSpace: CGColorSpace(name: CGColorSpace.extendedLinearSRGB)!)!
gradient.color1 = CIColor.clear
gradient.point0 = CGPoint(x: sin(angle)*90.0 + 100.0, y: cos(angle)*90.0 + 100.0)
gradient.point1 = CGPoint(x: sin(angle)*85.0 + 100.0, y: cos(angle)*85.0 + 100.0)
let shading = gradient.outputImage?.cropped(to: CGRect(x: 0, y: 0, width: 200, height: 200))

キーポイント:

  • linearGradient()2 つのポイントと 2 つの色を含むプロシージャル イメージを生成します。ビットマップ データは必要ありません。 -min(headroom, 8.0)白の明るさを現在のヘッドルームとサンプル選択の間の最大値に制限します。 -CIColor(red: w, green: w, blue: w, ...)RGB コンポーネントが 1 より大きいことを許可します。 -extendedLinearSRGBクランプされていない線形色空間を提供します。 -color1 = .clearハイライトを明るい白から透明に移行させます。 -cropped(to:)無限範囲の勾配をリップル フィルターに必要なサイズにカットします。

Core Image には、EDR をサポートする 150 を超えるフィルターが組み込まれています。セッションで挙げられた例としては、CIColorControlsCIExposureAdjust、gradient filters、CIAreaLogarithmicHistogramおよび CIColorCube シリーズ (10:46)。

SDR カラー キューブに EDR 入力を処理させます

(16:13) 従来のカラー キューブ データは通常、0 ~ 1 のみをカバーします。WWDC 2022 GiveCIColorCubeWithColorSpace追加したextrapolate、既存の SDR キューブ データを EDR 入力に使用できるようにします。

let f = CIFilter.colorCubeWithColorSpace()
f.cubeDimension = 32
f.cubeData = sdrData
f.extrapolate = true
f.inputImage = edrImage
let edrResult = f.outputImage

キーポイント:

  • colorCubeWithColorSpace()色空間を使用してカラー キューブ フィルターを作成します。 -cubeDimension = 32キューブのサイズを設定します。 -cubeData = sdrData元の SDR ルック データを引き続き使用します。 -extrapolate = trueフィルターが SDR キューブ データを EDR 入力に外挿できるようにします。 -inputImage = edrImage1 より大きい輝度値を含む画像を渡します。 -outputImageEDR 出力機能は維持されます。

EDR により適した結果を取得したい場合、セッションでは、HLG や PQ などの EDR カラー スペースを使用して新しいキューブ データを作成し、場合によってはキューブの次元を増やすという別の方法についても言及されました (15:32)。

CIKernel の境界をカスタマイズする

(16:25) カスタム CIKernel を EDR に移行する場合は、最初に RGB 計算を確認してください。コード内で使用する場合clampminmaxRGB を 0 ~ 1 に制限すると、多くの場合、これらの制限を取り除くことができます。

アルファは 0 から 1 の間に維持する必要があります。セッションで示された反例は、カーネルが誤ってアルファを 5 倍にしてしまったことです。正しいアプローチは、アルファではなく RGB のみを増幅することです。そうしないと、ミキシングまたは表示時に未定義の動作が発生します (16:50)。

重要ポイント

  • EDR ハイライターを作成: ProRAW、OpenEXR、または HDR ビデオ フレームをロードし、複数のオーバーレイ領域を表示します。これを実行する価値がある理由は、EDR ハイライトが SDR プレビューでは簡単に見逃される可能性があるためです。から入手できますCIImage入力を開始し、Core Image フィルターを使用して疑似カラー オーバーレイを生成し、MTKViewヘッドルームを押してレンダリングします。

  • 強度を調整できる鏡面ハイライト フィルターを作成します:linearGradient()そしてrippleTransition()インタラクティブなエフェクトに変更されました。ユーザーがスライダーをドラッグすると、ハイライトの方向、サイズ、キャップが更新されます。実装エントリはenableSetNeedsDisplayimageProviderそしてCIColor(red: w, green: w, blue: w, colorSpace:)

  • 画像エディターに EDR ルック プレビューを追加: 既存の SDR カラー キューブを再利用し、extrapolateオンにすると、まず古い外観で EDR 入力を処理できるようになります。次に、HLG または PQ 用の新しいキューブ データを作成します。実装エントリはCIFilter.colorCubeWithColorSpace()cubeDataextrapolateそしてinputImage

  • ヘッドルーム デバッグ HUD を作成: 現在の画面のヘッドルームをレンダリング画面の隅に表示し、明るさ、周囲光、外部モニターによって変化するその曲線を記録します。実装エントリはcurrentEDRHeadroommaximumExtendedDynamicRangeColorComponentValueそしてすべてのフレームdraw(in:)の読み取りロジック。

関連セッション

  • iOS での EDR の探索 — iOS および iPadOS での EDR、ヘッドルーム、トーン マッピング、リファレンス モードについて説明します。
  • AVFoundation と Metal を使用して EDR で HDR ビデオを表示する — AVFoundation と Metal を使用して HDR ビデオの EDR 再生リンクを構築する方法を示します。
  • Discover Metal 3 - Metal 3 のレンダリング、パフォーマンス、ツールの更新について紹介しており、MTKView レンダリング パイプラインを理解するための背景情報です。
  • SwiftUI の新機能 — SwiftUI 2022 アップデートの概要。このセッションではマルチプラットフォーム SwiftUI アプリ構造にコンテキストを追加します。

コメント

GitHub Issues · utterances