以下の内容はhttps://knasa.hateblo.jp/entry/2025/12/13/000000より取得しました。


【Unity】良い感じに被写界深度を調整できるカメラを作る

この記事は Akatsuki Games Advent Calendar 2025 の13日目の記事です。

はじめに

レコルト良いですよね、具材入れて待っていたら料理が良い感じに出来が上がっている。最高。
持っているのは炊飯器と兼用のやつなので、別にしっかりしたのが欲しいなと思ってきた今日この頃。
こんにちは、奈茶です。

被写界深度(Depth of Field, DOF)、3Dではよく用いられるカメラ演出かと思います。
これが入ることで、画がグッと良くなりますよね。

マグカップにピントが合っている

そんなDOF、URPにも用意されているのですが、これがそのままではちょっと使いづらい
というわけで今回はUnityで被写界深度を良い感じに調整できる機能を作成していきます。
スクリプトも共有しますので、皆さんの環境に合わせて組み込んでみていただけるかと思います。

今回使用したUnityとURPのバージョンは以下です。

  • Unity 6002.10f1
  • Universal Render Pipeline 17.2.0

※今回試してみたくてUnity6を使用していますが、今回の記事の内容はもっと前のUnityとURPのバージョンでも問題ないはずです。

被写界深度

いきなり本題に入る前に、事前の用語の確認です。
用語自体は、物理カメラでも用いられているものと同じです

被写界深度とは、ピントが合っている範囲のことです。 なので、

  • 被写界深度浅い(ピントが合う範囲が狭い)、とボケやすい
  • 被写界深度深い(ピントが合う範囲が広い)とボケにくい

被写界深度が浅い
被写界深度が深い

となります。
そしてこれにはいくつかの要素が関係しており、今回必要な部分をかいつまむと以下の表の様な関係になります。

要素 ボケる ボケない
被写界深度 浅い 深い
撮影距離(被写体とカメラの距離) 近い 遠い
絞り値(F値) 小さい(絞りを開ける) 大きい(絞りを絞る)
焦点距離 長い 短い

URPのDepth of Field ポストプロセス

では、用語の確認をしたところで、URPのDOFの項目を見ていきましょう → 公式ドキュメントはこちら

Bokehモードは実際のカメラ挙動を模して造られているため、
Focus Distance(撮影距離)Focal Lenght(焦点距離)Aperture(絞り値)のパラメータが調整できるようになっています。
Bladeの項目はボケの見た目に影響し、被写界深度とはちょっと違うので省略します。

つまり、良い感じにボケた被写界深度を設定するためには
撮影距離をピントを合わせたい距離にして、絞り値と焦点距離をそれに合わせて大きくしたり、小さくして調整すればいいのです。
対象が移動したら、撮影距離が変わるので被写界深度も変わります、そうなるとボケ具合が変わるので絞り値と焦点距離も......めっちゃ大変!!

実際どうなの

という画像を用意しました。 以下のシーンでは3つの写真立てが並んでいますが、それぞれカメラから5m, 7m, 9mと離れた位置に置いてあります。
そして背景の壁は23m程離れています。

DOFなし

これにDOFを設定して、「隣の写真は見えるけど、2つ隣はボケて、背景もボケる」被写界深度前後2mずつくらいの良い感じに設定したいとします。
5mを基準とすると、焦点距離 60, 絞り値 1.4 が良さそうです。
これで他の撮影距離でも確認してみると以下のようになります。

5m(基準) 7m 9m

遠景を見ていただければわかりやすいですが、なーんかボケ具合違くない?となってしまいます。
しかしそれは当然で、撮影距離が遠くなった分被写界深度が深くなり、ボケにくくなるからですね。
なのでその分焦点距離を長くするか、絞り値を小さくする必要があります。
写真数枚なら頑張れるかもしれないですが、動画でキャラクターが移動したりする中で手動はまず無理な上、インタラクティブにフォーカスを変えられそうにはないことが分かります。

Depth of Fieldの実装を見る

そこで、先ほどのパラメーターからどのような計算を経てボケを作成しているのかを見ていきます。
詳しい実装の解説に関しては Colorful Paletteさんの記事が非常に参考になりました。ありがとうございました!

media.colorfulpalette.co.jp

この記事では詳しい実装の解説は行わず、必要な部分だけを見ていきます。

DOFの実装は2か所に分かれます、1つは実際にポストプロセスとして処理を行うshaderと、そこに必要なパラメーターを事前に計算して渡すスクリプトです。

以下は BokehDepthOfFiled.shader内のFragCoC関数で、CoC(Circle of Confuse, 錯乱円)を求める部分になります。

half FragCoC(Varyings input) : SV_Target
{
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

    float2 uv = UnityStereoTransformScreenSpaceTex(input.texcoord);
    float depth = LOAD_TEXTURE2D_X(_CameraDepthTexture, _SourceSize.xy * uv).x;
    float linearEyeDepth = LinearEyeDepth(depth, _ZBufferParams);

    half coc = (1.0 - FocusDist / linearEyeDepth) * MaxCoC;
    half nearCoC = clamp(coc, -1.0, 0.0);
    half farCoC = saturate(coc);

    return saturate((farCoC + nearCoC + 1.0) * 0.5);
}

錯乱円とはざっくりとボケのサイズのことになります。
ここの処理ではDepthデータから値を変換して、0~1の範囲で値が正規化されますが、0.5が一番ピントが合っており、0.5より大きいと遠景ボケ、0.5より小さいと前景ボケとなっていきます。
つまりできるだけ広い範囲を0.5に近い数値にすることでボケにくく、逆に0や1に持っていくことでボケやすくなります。

ここで重要なのがMaxCoCの値になります。MaxCoCが大きい値ならボケやすく被写界深度が浅く)、小さい値ならボケにくく被写界深度が深く)することができ、この値を固定すれば、同じようなボケ具合にできそうです。

そしてMaxCoCはPostProcessPass.cs 内のDoBokehDepthOfField関数内で以下の様に求められています。

// "A Lens and Aperture Camera Model for Synthetic Image Generation" [Potmesil81]
float F = m_DepthOfField.focalLength.value / 1000f;
float A = m_DepthOfField.focalLength.value / m_DepthOfField.aperture.value;
float P = m_DepthOfField.focusDistance.value;
float maxCoC = (A * F) / (P - F);

maxCoCを求めるためにfocusDistance, focalLength, apertureが使用されています。
maxCoCから逆算してこれらのパラメーターを決めるようにしてみましょう。

focusDistanceは被写体によって決まるので固定値となります。
apertureの方が幅が狭い(0~32)となっている都合で、apertureも固定して、focalLengthを求めたいと思います。

ソースコードから

maxCoC=(A*F)/(P-F)

これらを各パラメーターに戻すと

maxCoC=\frac{(focalLength/aperture)(focalLength/1000)}{(focusDistance- focalLength/1000)}

focalLengthをx、apertureをa、focusDistanceをd、maxCoCをmとすると

m=\frac{(\frac{x^2}{1000a})}{(d-\frac{x}{1000})}

この式をなんやかんやして

x^2+amx-1000amd = 0

二次方程式で表せます。

2つの解の内、負の値は不要なのでfocalLengthは

focalLength=\frac{-aperture \cdot maxCoC+\sqrt{aperture^2maxCoC^2+4000aperture \cdot maxCoC \cdot focusDistance}}{2}

で求めることが出来ます。

実装

先ほどの式をもとに以下の様な関数を作成しました。 この記事で一番重要なのがここです。

static float CalcFocalLength(float focusDistance, float aperture, float maxCoc)
{
    var A = aperture * maxCoc; 
    return (-A + Mathf.Sqrt(A*A + 4000.0f * A * focusDistance)) / 2.0f;
}

これを組み込んだコンポーネントを作成しました。

アタッチするとこんな感じです。 MaxCoCの値と、Apertureを決めればFocusTargetにピントが合うように良い感じにしてくれます。

結果

では、先ほどの関数を使用するとどうなるのか見ていきましょう。 また撮影距離5m、焦点距離 60, 絞り値 1.4 を基準に、他の距離でどうなるか見てみます。 maxCoCの値は0.5206に設定しています。

5m(基準) 7m 9m
焦点距離は約60
焦点距離は約71
焦点距離は約81

どうでしょう?背景のボケ具合はそのままに、写真だけピントの合い方が変わっているように見えないでしょうか?

maxCoCの値を変えることでボケの強さを変えることが出来ます。
以下のGIFはマグカップにフォーカスを合わせて、maxCoCの値だけ変更しています。

オートフォーカスみたいなことも簡単にできます

終わりに

maxCoCの値自体を見てもボケ具合は想像できないので、見た目ありきの調整方法ではあります。
それでも素のパラメーター調整よりはかなり直感的に調整できるようになったのではないでしょうか。

今回は紹介しきれなかったのですが、解像度が変わると被写界深度のボケ具合も変わってしまいます。
基準解像度を設定して、それに合わせる等何かしら解像度に基づいた対応を入れる必要があります。
書けたら来年その記事も書くかもしれません、来年に期待。

ここまで書いてRAYNOSちゃんでポートレート写真みたいなのを撮ればよかったなーと思いました。

参考




以上の内容はhttps://knasa.hateblo.jp/entry/2025/12/13/000000より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14