レイマーチングで使われるSDFを解釈する~Part4~

#初めに

- [Part1(基礎編)](https://qiita.com/Hirai0827/private/dbc24dbae35dc885d55b)
- [Part2(円柱編)](https://qiita.com/Hirai0827/private/1258f6417c8e1a367f80)
- [Part3(角柱編)](https://qiita.com/Hirai0827/private/92a49f4a992c69ede27a)
- Part4(SDFの加工編)(ここ)

こんにちは、アドベントカレンダー12日目担当の避雷です。今回はレイマーチング解釈シリーズ最終回として、既存のSDFの加工処理について読み解いて行こうと思います。
#本編
本編の解読に入る前に今まで出てきたSDFについて一つ確認しましょう。
##Exactの真意
iq氏のサイトに書いてある直方体のSDFをみてみましょう。

```glsl
float sdBox( vec3 p, vec3 b )
{
  vec3 q = abs(p) - b;
  return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
}
```

ん？Part1で紹介した立方体のSDFはこんな複雑じゃなかったはず…

```glsl
float map(vec3 p,float size){
    return max(abs(p) - size / 2.);
}
```

なぜこの二つは同じ立方体を描画しているにも関わらず違いが生じるのでしょうか？辺の長さのほかにも`length(max(q,0.0))`という謎の項が生えています。
Part1で「この立方体のSDFは近似である」といった解説をしていたのを覚えているでしょうか。
Part1のSDFでは面の真上以外にある点の場合、立方体上でもっともその点に近い点は立方体の辺上にあることを理由に、結構大雑把な近似を行っていたのでした。
その近似が悪さをするせいで簡略化されたSDFの方では今回紹介するroundingのような加工がうまく動かないのです。
https://www.shadertoy.com/view/tsKSRt
↓サンプル画像、左…近似SDF、右…ExactなSDF（片方は近似されていないことが分かる）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/91ccf7b4-335a-7e6d-8b08-5e15e1734880.png)

これがiq氏のSDFが複雑になっている理由であり、複雑になっている意味であり、図形の隣に添えられた"-Exact"の真意でもあります。
今回はiq氏版の立方体のSDFを使って、立体の加工関数について読み解いて行こうと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/85154e50-6aaf-8394-fbef-6066e8fbed8b.png)

コイツはz=0がxy平面が作る断面図です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/1cdf6819-2f8f-7b98-0116-903a873bf617.png)
元のSDFを横軸に、加工後のSDFの値を縦軸にしたグラフです。下図の場合は
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/614c63b5-ccdd-0d11-70b6-b223a7d70f7c.png)

サンプルは
https://www.shadertoy.com/view/wdKXRt
## Rounding（丸める）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/5c079cd5-eb60-0cb3-aa87-f106fc46c4e9.png)

```glsl

float opRound( float sdf, float rad )
{
    return sdf - rad;
}

```

上からエビフライの衣を被せるような感じで丸みを持たせます。距離関数の0になる境界面をrad分外に押し出すイメージです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/40290fae-6744-0462-ff4f-ca1885477b81.png)

↓y = x - 1.0のグラフ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/59205127-8f11-81f0-df88-ed08bb51a701.png)
元のsdf = 0になる部分の外側にsdf < 0となる部分が発生します。この部分がRoundingによって付与される部分です。

##Onion (皮だけにする)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/e13fc2cd-fcf7-6472-95f5-c8b831bb3762.png)


```glsl

float opOnion( float sdf, float thickness )
{
    return abs(sdf)-thickness;
}
```

絶対値をとることによって本来なら内面（SDFがマイナス）になる部分を外面(SDFがプラス)にします。thickness分引くことで、本来のSDF =　0になる部分の周辺にだけSDF < 0の空間を作ることが出来ます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/ccfd2e94-bebf-087f-8e46-702b1d4d06e6.png)

↓y = |x - 1.0|のグラフ　
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/d9857436-2a4f-c879-b934-9d8371c8b993.png)

元のsdf = 0になる境界で折り返しが発生し、そこを中心に皮が描画されます。

##DoubleSurface(二重壁)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/76b2d6a0-366c-7dee-62eb-406cec122421.png)

```glsl
float opDoubleSurface(float sdf, float thickness){
	return abs((abs(sdf)-thickness * 1.5))- thickness * 0.5;
}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/6d36edc5-ca05-3d5d-ce7f-3157325dc510.png)

絶対値を複数噛ませることによって二枚分の皮を描画します。
↓y = ||x| - 1.5| - 0.5のグラフ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/256231/f30b22d9-6c2d-3761-f150-d6c44402c7c4.png)


生成されるSDFが負となる部分がx = 0(元のSDF = 0)の周りに2つ分生成されているのがわかるでしょうか。これによって元の立体の境界の内側と外側に一枚ずつの表面を生成することが出来ます。

#終わりに
このように、SDFで吐き出された値に後付けで計算を行うことによってより多様な表現を付与することが出来ます。
これにてSDF解釈シリーズは終わりになります。再度紹介しますが、iq氏によって書かれたこの記事には、SDFによる描画処理のノウハウが詰まっているので是非参考にしてください。
https://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm