【Ray Tracing】Voxelのためだけのアンビエントオクルージョン

#概要
[【Ray tracing】Voxelを作ってみる](https://qiita.com/misaki_mofu/items/026cd353054856921995)の続き。
Ambient Occlusionを普通にかけると元の形が反映されるため特別なAOが必要。
#Ambient Occlusion
理想的なAO。これをやっていきたい。
（[Voxel Edges](https://www.shadertoy.com/view/4dfGzs)をいじったやつ）
![Voxel Edges - Google Chrome 2019_11_26 18_20_54 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128017/61b7c4b4-58d5-e460-a7e0-c47e25aeddd6.png)
コード見てもさっぱりわからないので作者のiqさんの記事を参考にする。
http://www.iquilezles.org/www/articles/voxellines/voxellines.htm
記事を見るとAOするにはいくつか手順があるらしい。
記事の内容とコードを見比べて整理してみる。
## step1: 周りに他のvoxelがあるか調べる。
具体的に考えてみる。たとえば現在のvoxelの面がy軸方向にあるとする（図中の斜線部分）。
そしてその周りのvoxelの面をそれぞれva、vb、vc、vdに分類してみる、けどAOを加えるかどうかを判断するのは上のvc、vdだけしか使わないらしい。
（Edgeもやりたい場合はva、vbも必要になるけど今回は使わない。）
![ao1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128017/0113ae65-7fea-8327-787a-ad4fc382074f.jpeg)

イメージしやすく各面との差分を書いてみる。
va、vbも書いちゃったけど(0, 1, 0)を加えるだけでvc、vdになるので、
まずは簡単なvaとvbを考えてみる。
![ao2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128017/fcc5961b-df57-492f-8e80-595a8285bf17.jpeg)
まず(0, 1, 0)はnormal方向なので普通にnormalだけ使って表せるように見えるけど、
もしこれが(0, -1, 0)方向の時は結果が反転してしまう。
なのでnormalに絶対値をとって面がx、y、z軸方向どっちに向いているかを考える。
normalはすでに[この記事](https://qiita.com/misaki_mofu/items/026cd353054856921995)で出してるのでこれをもとに周りのvoxelたちを得る。

```glsl
vec3 dir = abs(normal);
// voxelは現在のvoxel
// 現在のvoxelから見て右の面
vec3 va_x = voxel + dir.yzx;
// 左の面
vec3 va_y = voxel - dir.yzx;
// 前の面
vec3 va_z = voxel + dir.zxy;
// 後の面
vec3 va_w = voxel - dir.zxy;

// 右前の面
// ちょっと難しいけどzにも(0, 1, 0)の値が入ってるのを考える & xyzとかぶらない並びにしてあげる。
vec3 vb_x = voxel + dir.yzx + dir.zxy;
// 左前の面
vec3 vb_y = voxel - dir.yzx + dir.zxy;
// 左後ろの面
vec3 vb_z = voxel - dir.yzx - dir.zxy;
// 右後ろの面
vec3 vb_w = voxel + dir.yzx - dir.zxy;

// vc、vdはそれぞれva、vbにnormalを足せばいい（例えばnormalが(0, -1, 0)の場合、vc、vdは一個下のvoxelであるため）
vec3 vc_x = va_x + normal;
vec3 vc_y = va_y + normal;
vec3 vc_z = va_z + normal;
vec3 vc_w = va_w + normal;

vec3 vd_x = vb_x + normal;
vec3 vd_y = vb_y + normal;
vec3 vd_z = vb_z + normal;
vec3 vd_w = vb_w + normal;

// 各voxelでオブジェクトにhitしているか判定する
// hitしてたら1.0を、してなかったら0.0を返す
vec4 vc = vec4(
  step(mapTheWorld(vc_x), 0.0),
  step(mapTheWorld(vc_y), 0.0),
  step(mapTheWorld(vc_z), 0.0),
  step(mapTheWorld(vc_w), 0.0)
);

vec4 vd = vec4(
  step(mapTheWorld(vd_x), 0.0),
  step(mapTheWorld(vd_y), 0.0),
  step(mapTheWorld(vd_z), 0.0),
  step(mapTheWorld(vd_w), 0.0)
);
```
これで周りのvoxelがあるか把握できた。

## step2: voxelのuvを作る。
uvを作る前にuvwを作る。
普通に実際のpositionからvoxelの位置を引くことによって0から1の数値が得られるはず。

```glsl
// hitT、hitVoxelなどは
// https://qiita.com/misaki_mofu/items/026cd353054856921995参照
vec3 realPos = ro + hitT * rd;
vec3 uvw = realPos - hitVoxel;
```
gl_FragColorにuvwを渡すとこんな感じになる。
![07_voxel2 - Google Chrome 2019_11_27 0_39_24 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128017/cbb66a4e-686f-ab47-54ea-18976a97dd98.png)

dirで場合分けをしてuvwからuvを得る

```glsl
// uvの向きは基本的にdirの向きから見て横軸縦軸の順番にすると良さそう。
vec2 uv = dir.x > 0.0 ? uvw.zy : dir.y > 0.0 ? uvw.xz : uvw.yx;
// iqさんのコードでは下の方法で出してる。こっちのほうがすっきり見えるけど個人的にわかりずらい。
vec2 uv = vec2( dot(dir.yzx, uvw), dot(dir.zxy, uvw) );
```
uvをgl_FragColorに入れるとこんな感じでuvが取れたことが分かる。
![07_voxel2 - Google Chrome 2019_11_27 1_24_28 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128017/f6ac1971-7595-e7b0-ddac-8ffabfc79c38.png)

## step3: Ambient Occlusionを計算する
材料はそろったのでいよいよAmbient Occlusionを実装する。
分かりやすくするために最初に右側のオクルージョンだけ考えてみる。
![ao3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128017/73bbb17f-c5e6-2601-91fa-2590a97cd907.jpeg)
![ao4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128017/c016fa98-728f-6c37-72b5-d5b937c10ce0.jpeg)
式で表すと

```glsl
// さっきやったようにvc.xyzwには1.0か0.0が入っている。
float rightOcclusion = uv.x * vc.x;
```
![ao5.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128017/0b14e851-a4ce-668b-d2b8-6c88a4e72202.jpeg)
式で表すと

```glsl
float frontRightOcclusion = uv.x * uv.y * vd.x * (1.0 - vc.x) * (1.0 - vc.z);
```

他のvoxelでも同じようにやる。

```glsl
float calcOcc( in vec2 uv, vec4 vc, vec4 vd )
{
    vec2 st = 1.0 - uv;

    // vc.xyzwがあるとき
    vec4 wa = vec4( uv.x, st.x, uv.y, st.y ) * vc;

   // vd.xyzwがあるとき（そして両脇のvcにvoxelがないとき）
    vec4 wb = vec4(uv.x*uv.y,
                   st.x*uv.y,
                   st.x*st.y,
                   uv.x*st.y) * vd * (1.0-vc.xzyw) * (1.0-vc.zywx);
    
    // 最後に足し合わせる。voxelがあるほどaoが濃くなる。
    return wa.x + wa.y + wa.z + wa.w +
           wb.x + wb.y + wb.z + wb.w;
}
```

あとはcolorにあてる。

```glsl
float occ = calcOcc(uv, vc, vd);
// 必ず反転させる。
// お好みで滑らかにする。
occ = 1.0 - occ * occ / 12.0;
occ = occ*occ;
occ = occ*occ;
// colorにかける。
color *= occ;
```

#実践してみる！
[デモ](https://mnmxmx.github.io/raymarching-study/dist/07_voxel_ao/)
[ソースコード](https://github.com/mnmxmx/raymarching-study/blob/master/js/07_voxel_ao/modules/glsl/output.frag)

前回やったtorusのvoxelでやってみる。
![07_voxel2 - Google Chrome 2019_11_28 0_22_14 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128017/4f35d2ce-01ae-bcf2-28a9-5b799ebb987f.png)
かかったけど、かかりすぎかな？
ちょっと自重させる。
![07_voxel2 - Google Chrome 2019_11_28 0_30_37 (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128017/1ee6a7c9-23b5-ba01-b060-f9a89e4a4430.png)
ちょっとだけ自然になった気がする。

# 最後に
カメラを回転させると変になる。uvがずれる感じ。
なので、tMaxの初期値を変更させる。これでuvのずれが無くなってどの向きでもAOが正しくかかるようになる！

```glsl
// vec3 tMax = (voxel - ro) / rd;
vec3 tMax = (voxel - ro + rayStep * 0.5 + 0.5) / rd;
```
以上がvoxelのためだけのアンビエントオクルージョンでした。おわり。