レイトレーシングでNonPhotoRealisticなレンダリングをしたい - Physically-based Feature Line Rendering

この記事は [レイトレアドベントカレンダー 2022](https://qiita.com/advent-calendar/2022/raytracing) の記事として書かれました。

# クールジャパン

アニメいいですよね。クールですよね。ということで、PhotoRealisticなレンダリングを実現するためのレイトレーシングであえてNonPhotoRealisticの実現に挑戦したいと思います。

# FeatureLine

NonPhotoRealisticと一口でいっても、色々な要素があります。今回は以下のようなFeatureLineをレイトレーシングでどのように実現するのかについて、１つの論文を簡単に紹介しつつ、実際にどのような実装をしたのかについても簡単に紹介したいと思います。

![example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/03b0af34-838e-554d-2cf2-4b1d5dae6bec.png)

今回紹介する論文の中で例として多く挙げられていた参考論文として、以下の二つがあります。

- [Choudhury and Parker2009] "Ray Tracing NPR-Style Feature Lines"
- [Ogaki and Georgiev2018] "Production Ray Tracing of Feature Lines"

ここでは、上記の論文については掘り下げませんが、後者はポスト処理でスクリーンスペースでFeatureLineを描画する手法になります。
前者についてはスクリーンスペースでの実現に関して記述はありませんが、見る限り、後者と同様にスクリーンスペースでFeatureLineを描画するのが実装的には簡単そうに見えました。
ということで、両者ともポスト処理でスクリーンスペースでFeatureLineを描画する手法となります。しかし、ポスト処理でスクリーンスペースでFeatureLineを描画する手法自体は通常のラスタライズレンダリングでもよく行われている手法であり、これらの手法は既存のレンダラーに容易に組み込みやすいというメリットもあり、実際に[Choudhury and Parker2009]を実装したところ容易に組み込むことができました。
ここまでだと、タイトルで**レイトレーシング**と強調していることと若干乖離しているようにも聞こえてしまうかもしれません。
一方で、ラスタライズレンダリングに対して、レイトレーシングで優っている（？）点として反射の実現がありますが、上記の手法はスクリーンスペースである以上、反射したところまでFeatureLineを描画するのは難しいという問題があります。反射まで考慮にいれるとなると、レイトラバース中にFeatureLine描画の処理をする必要があります。
[Ogaki and Georgiev2018]では、反射も考慮に入れていて、鏡面反射はうまくいくのですが、roughnessな反射については、きれいに描画されないという問題があり、さらに１回の反射しか対応できない、と今回紹介する論文は指摘しています。そこで、レイトレーシングのレイトラバースに統合された手法としてのFeatureLine描画に関する論文について、簡単に紹介したいと思います。

# Physically-based Feature Line Rendering

今回紹介させていただく論文は

[Rex West2021] "Physically-based Feature Line Rendering"

となります。

こちらの論文は、レイトレーシングのレイトラバースに統合された手法としてのFeatureLine描画についての論文となります。

![main_exsample.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/613ea112-c6b3-56a8-56ef-d1ad75d6e550.png)

今回は、論文全体の雑訳ではなく、理論部分に関してだけピックアップして、私の理解を紹介していこうと思います。理解が怪しいところもあり、誤りがあるかもしれませんが、ご容赦ください。

## 2.2 Ray-based feature line rendering

最初に紹介した[Choudhury and Parker2009]、[Ogaki and Georgiev2018]のどちらもレイを飛ばした周囲をスクリーンスペースでサンプリングし、レイがヒットして取得したオブジェクトの法線、深度等の情報とスクリーンスペースでサンプリングした場所の法線、深度等の情報を比較することで、その差分により、FeatureLineの描画について判断しています。
しかし、こちらの論文では、そのサンプリングをスクリーンスペースで行うのではなく、サンプルレイという形で実際にレイを飛ばすことで情報を取得します。ここでは、本来のレイをquery ray、FeatureLine用のサンプリングレイをsample rayとします。では、query rayとsample rayからどのようにFeatureLineを判断するのかというと、概要は以下の図になります。

![fig3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/2d51a33e-6407-e025-23bd-765a16e35616.png)

上図の(c)で水色の丸がquery hitがヒットした場所で、赤い点線がsample rayになります。sample rayがヒットした点の中で、最もレイの原点に近い点がFeatureLineの対象となります。

これをquery rayのバウンスごとに終了するまで繰り返していきます。そうすることで、反射も考慮したFeatureLineの描画ができるとのことです。以下は、[Choudhury and Parker2009]、[Ogaki and Georgiev2018]、Ray-based feature line renderingでの反射の様子を図示したものとなります。

![fig4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/9b27d1db-b395-564c-1154-de6af45cd256.png)

[Choudhury and Parker2009]は最初のヒットのみ、[Ogaki and Georgiev2018]は１回の反射のみしか実現できませんが、こちらの論文ではレイベースで処理するための何回のバウンスでも考慮に入れることができます。

## 3 PATH-BASED FEATURE LINE RENDERING

ということでパスベースでのFeatureLineの描画を実現するために、以下のポイント掲げています。

- 既存手法に適合するためにFeatureLineを光源として扱う
- FeatureLineとの交差はどのedge（=レイ、と考えて良さそう）に対しても実現でき、スクリーンスペースでなくてもいい

それにより、以下を実現するポイントとして挙げています

- パスのedge（=レイ）をセンサーからスタートし、FeatureLineにヒットするまでテストする
- FeatureLineは理想的な光源として扱い、すべての入ってくる光を吸収し、ユーザー指定の色（=ラインの色）を発光する

これにより、通常のモンテカルロ法のレンダリングに組み込むことができるようになるそうです。

![edge.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/aae1e8c2-0183-2af4-3caf-af575bfd9a2f.png)

## 3.1 Implementation

ここからは実装の解説です。論文にはラフな疑似コードで全体の流れを説明していますが、ちょっとここで私の理解が怪しいところがいくつかありますので、ご承知おきを。

### 3.1.1 Sampling feature line-aware paths

まず、前提として、こちらの論文では、最初にパスのトラバースを終了し、各レイごとの結果（e.g. ヒットした点の法線等の情報）を保存し、それに対して後からFeatureLineの処理を実行するというものです。個人的には、そこまでしなくても実装できるのでは、と思っています・・・

![alg1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/b4437502-6d42-c2a4-12f4-004cfaab8129.png)

上述したように、各ピクセルごとにパスのトラバースを行い、それに対しFeatureLine判定を行います。レイベースで判定するので、スクリーンスペースのように周囲のピクセルの状況を取得する必要はありません。

![fig6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/e9985e72-32e3-b206-1a0e-542815adb5f4.png)

上の左図では、青い線がedge（=query ray）で、赤い点線がsample rayが飛ぶ範囲です。複数飛ばすので、コーン状になっています。赤い点がsannple rayがヒットした点をedge上にプロジェクトした点になります。そして、右図では、プロジェクトした点をFeatureLineとして光源として扱っています。

実際の論文は、Algorithm.1には続きがあり、キャッシュを利用する方法に触れられています。しかし、詳細は、[Ogaki and Georgiev2018]とのことで、私も内容を確認していないので、ここでは触れません。

![alg2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/3c3ffd99-5573-d272-0613-095ca3be1a97.png)

### 3.1.2 Computing the PDF of a modified path.

パスの結果を変える以上、PDFも変わるので、それを考慮にいれる必要があります。前述のようにFeatureLineを光源として扱うので、FeatureLineと判定された処理の時のPDFを置き換える必要があります。例えば、FeatureLineにヒットするまでの処理回数が$k'$の変更されたパス$\bar{x}'$のPDFは以下のように、計算されます。

```math
p(\bar{x}')=p(x_0,x_1,...)=p(x_0)p(x_0|x_1)\prod_{i=2}^{k'}p(x_i|x_{i-1},x_{i-2})
```

まず、そもそもとして、FeatureLineにヒットしたときのPDFをどうするべきかについては、この論文では語られていません。そのため、私は単純に飛ばすサンプルレイの数の逆数としています。つまり、飛ばすサンプルレイの数が$N$なら、$1/N$としています。

### 3.1.3 Feature line width

![alg3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/35869419-3cf7-7ae6-fa8e-bdb5ed6b92fc.png)

スクリーンスペースでやらない以上、FeatureLineの幅をどうするのかについては、単純に計算できません。そこで、スクリーンスペース上でのFeatureLineの幅を3D空間にプロジェクトします。ただし、球のようなカーブしているオブジェクトで以下の図のように線が細くなってしまいます。後述しますが、サンプルレイの飛ぶ範囲を本来なら下図のグレーの範囲で考えなくてはいけないところ、赤線の範囲内でしか計算しないため、線の太さに関して低く見積もられてしまうためです。

![fig7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/468faa27-02d6-d9c5-a946-1b2073ae43a8.png)

では、どのようにプロジェクトするのかというと、正確にプロジェクトするのではなく、計算コストがリーズナブルになるようにheuristicな方法を提案しています。ここまででいくつかの図でコーン形状を見てきていますが、論文には、このコーン形状はスクリーン-スペースの線幅の一貫性をほぼ維持する、と書かれています。

```math
w_{scaled}=d \cdot p_{width} \cdot w_{screen}
```

各記号の意味は以下となります：
- $w_{scaled}$：オブジェクトスペースにプロジェクトされたライン幅
- $d$：パスをたどった時のその点までのedgeの長さの総和
- $p_{width}$：オブジェクトスペースにおけるカメラから１の距離でのピクセル幅
- $w_{width}$：スクリーンスペースでのライン幅

$d$については、以下のようになります。

![whatisd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/f7bde525-c80d-0215-6695-2df5c2e04e25.png)

では、上式がどのように導出されているのかについては論文内では触れられていないのですが、私の見解は以下となります。

![w_scaled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/e8ce5bf9-5e69-9463-2f27-f6e2eba49f0b.png)

上の図より、以下のような比を考えることができます。

```math
d : 1 = w_{scaled} : w'_{screen} \\
\Rightarrow w_{scaled} = d \cdot w'_{screen}
```

$w_{screen}^{\prime}$はカメラから１の距離の面に$w_{scaled}$を投影したものになります。本来計算したい$w_{screen}$は本当のピクセルサイズ(=1)上でのFeatureLineの幅なので、以下のように考えることができます。

```math
p_{width} : 1 = w'_{screen} : w_{screen} \\
\Rightarrow w'_{screen} = p_{width} \cdot w_{screen}
```

以上から、

```math
w_{scaled} = d \cdot w'_{screen} = d \cdot p_{width} \cdot w_{screen}
```

と導出されると思われます。

では、なぜこれが最初のヒットだけでなく、その後のバウンスでも成り立つのかというと、以下の図をご覧ください。

![fig4_bottom.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/91118fa8-1f2b-220d-2b95-8d23ba11e6c5.png)

上図について、赤い点線で囲まれたところについて、反射したコーンを反射させずに一直線になるようにしてみると、１つの大きなコーンとみなすことができます。

![cone.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/7b1ee8a1-9db2-f91c-e66c-3863f563916f.png)

これにより、常にコーン形状の範囲内で処理していると考えることができます。

また、この章では、重要なポイントが数行でさらっと記述されています。それは、sample rayのバウンスをどのように計算するのかです。query rayは本来のレイでsample rayはFeatureLine判定用になります。そのため、diifuse面だからといって、個別に半球上に飛んで行かれては、FeatureLineを判定できません。そこで、query rayの方向に沿う形で常に飛んでいく必要があります。

そこで、再度、sample rayの計算について私の見解を紹介したいと思います。rayという以上は最低限始点と方向が必要となります。方向の計算は始点と終点が決まれば決まります。sample rayについては、基本的にはquery rayの情報のみから導出します。

まず、前述したようにカメラから１の距離のピクセル幅を計算する必要がありますが、その際に、そのピクセル幅×ライン幅を半径とするdiscを考えます。そして、そのdiscを基準にquery rayのヒット位置に仮想のdiscを配置していきます。query rayの始点に配置するdiscをopening disc、ヒット位置（終点）に配置するdiscをclosing discと呼びます。

![disc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/e904b5e0-00ba-4382-a399-cb6368afc711.png)

closing discの半径は、上図では$d_0$、$d_1$の比とopening discの半径から計算できます。

sample rayは最初のdisc内での始点をランダムに決めた後は、以降はランダム要素は出てこずに、disc内での位置をdiscの半径に応じて変化させた位置をたどるようにします。つまり、sample rayの終端はclosing disc上でopening discでのsample rayの始点と同じ位置をopening discとの半径の比率に応じて大きくすることで決めることができます。そして、opening disc上の始点とclosing disc上の終点をつなげることでsample rayを決定できます。これをquery rayのヒットした点ごとに仮想discを配置し、繰り返していきます。

![dsic_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/0b33b367-b625-6ee2-ad1a-89d5619d049e.png)

そして、下図のようにsample rayがヒットした点は複数存在するので、その中から$w_{scaled}$の式で算出されたライン幅の条件に合うものを選択することになります。

![fig3_c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/416e97a5-5b19-82a7-9305-3bade2d85950.png)

ここまで、Algorith.3におけるcomputeTestRigionからsamplerRegionの流れだという認識です。

### 3.1.4 Feature line metrics.

ここでは、Algorithm 3の下半分lineMetricについて見ていきます。

![alg3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/ae138bf0-c9cc-9516-1a6c-51094d6bf71a.png)


まず、上の疑似コードで出てくる$p$、$q$、$s$の関係は以下となります。

![alg3_pqs.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/5dd60fa7-cb7b-871f-6ff4-d851497b7d8e.png)

$p$がquery rayの始点、$q$がquery rayのヒットした点（終点）、$s$がsample rayがヒットして点、となります。

以下の項目のメトリクスを計算し、どれか１つでもtrueであれば、FeatureLineとして判定します。

```math
\begin{align}
MeshID &: id_q \neq id_s \\
Albedo &: |a_q - a_s| > t_{albedo} \\
Normal &: (1.0 - \vec{n_q} \cdot \vec{n_s}) > t_{normal} \\
Depth  &: |d_q - d_s| > t_{depth}
\end{align}
```

$t_{albedo}$、 $t_{normal}$、$t_{depth}$はユーザー指定の任意の値を使えるとのことですが、$t_{depth}$に関しては、以下のheuristicな計算で算出しています。こちらの論文では、以下の式において$\beta=2$で取り扱ったそうです。

```math
t_{depth} = \beta min(d_p,d_q)||\vec{ps}-\vec{pq}||/|\vec{pq} \cdot \vec{n}_{closest}|
```
上式の各記号の意味は以下となります：
* $\beta$：ユーザー定義のスケールファクター
* $\vec{n}_{closest}$：$q$か$s$どちらか$p$に近いほうの点の法線

上式は、query rayの原点からの距離と、入射角度の両方に対応しており、遠くて細かいジオメトリやquery rayと平行に近い面での過度に密な線を減らすことができるとのことです。
このあたり、直観的にそうなのかな、というのはわかるのですが、導出について自信がないので、あまり触れません・・・。

sample rayは複数飛ばすので、$s$は当然複数となり、$v \in \lbrace q,s \rbrace$の中でquery rayにプロジェクトした点が$p$に最も近い点をFeatureLineの光源とします。

![pick_point.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/db337e7a-fa2b-275b-3012-895859cc3cb7.png)

### 3.1.5 Approximating the feature line test

FeatureLineの判定をsample rayで行っているため、sample rayの数により、その判定が変わってしまいます。例えば、sample rayの数が少ないと、FeatureLineが半透明になったり、線が細くなったりします。
論文では、sample rayが少ないと偏りがでてbiasになり、数を増やすほど偏りが減りunbiasになると書かれています。

一方で無限に増やすことはできないので、性能と品質のバランスになると思われます。

### 3.1.6 Selective reflection of feature lines

FeatureLineを光源として扱うので、特定の場所、特にdiffuse面では、FeatureLineの色でシェーディングされてしまうことがあると書かれています。そのため、どういったsurfaceやmaterialがFeatureLineの反射を受け入れるかを調整できるようにしたほうがいい旨が書かれれています。

私が実装したときは、上記のようにFeatureLineは光源として扱われるので、反射が続く限りどこかでFeatureLine判定される可能性があり、lambert面にも関わらず、FeatureLineの影響がでてしまい、最終的に、FeatureLine色の光源の影響を受けてしまいました。そこで、lambert面では映り込みもないので、FeatrueLineの判定を打ち切るようにしました。

## 4 RESULTS AND DISCUSSION

既存手法との比較については、以下のようになります。

![result.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/5136ea02-cd7f-69e0-1f2d-edcc4be72068.png)

FeatureLineがDoF等の物理効果の影響を受けるように描画することが可能になっています。

![effect.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/fd60aa5b-8f85-b7a5-6362-f2c38bdb46f6.png)

## 5 LIMITATIONS AND FUTURE WORK

以下に関してはチャレンジだと書かれています。
- BDPTなどのstraight forwardなPath Tracingの実装でないものへの適用
- 曲面の反射面上で線の太さの維持
- heuristicな手法なのでFeatureLineの探索処理が深くなるほど、線が細くなる
- 処理コストが高い
- オクルードされた面に対するFeatureLineの正しいレンダリング
- FeatureLineを光源として扱っているので、線を半透明したり、線自身が光を反射するなど、アーティスティックな効果を入れること

# 実装結果

ということで、自分が実装した結果は、以下となりました。

![sc_40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128377/2005e19f-4100-bdfb-dfad-fa7151eae66b.png)

ノイズがかなり残っていますが、FeatureLineの描画は十分確認できると思います。手前のshort boxは鏡面マテリアルです。反射の映り込み部分に描画されているFeatureLineも反射面のマテリアルに応じた描画になっているのが、わかると思います。それっぽくFeatureLineの描画ができていると思います。ラインの色は適当です。特に深い意味はありません。

## 気になるところ

まずは、LIMITITATIONSでも触れられていますが、性能面です。sample rayを複数飛ばすので、その分だけ単純に性能が落ちます。ここはどうあがいても一定時間で処理できるスクリーンスペースでの描画には及びません。

そして、既存のレンダラーへの組み込みやすさですが、私の場合は大改造でした。「大」は言い過ぎかもしれませんが、それなりの変更を行いました。[Ogaki and Georgiev2018]でも既存レンダラーへの統合のしやすさからスクリーンスペースがいいと書かれているので、そこもスクリーンスペース手法に対して大きなdisadvantageになります。

# 最後に

とはいうものの、レイトレーシングの強みである反射の描画に物理的に正しく対応できる点は魅力的ではありますので、既存レンダラーへの統合、性能について許容できるのであれば、この論文の手法は一考の価値はあると思いました。