#概要
【Ray tracing】Voxelを作ってみるの続き。
Ambient Occlusionを普通にかけると元の形が反映されるため特別なAOが必要。
#Ambient Occlusion
理想的なAO。これをやっていきたい。
(Voxel Edgesをいじったやつ)
コード見てもさっぱりわからないので作者の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も必要になるけど今回は使わない。)
イメージしやすく各面との差分を書いてみる。
va、vbも書いちゃったけど(0, 1, 0)を加えるだけでvc、vdになるので、
まずは簡単なvaとvbを考えてみる。
まず(0, 1, 0)はnormal方向なので普通にnormalだけ使って表せるように見えるけど、
もしこれが(0, -1, 0)方向の時は結果が反転してしまう。
なのでnormalに絶対値をとって面がx、y、z軸方向どっちに向いているかを考える。
normalはすでにこの記事で出してるのでこれをもとに周りのvoxelたちを得る。
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の数値が得られるはず。
// hitT、hitVoxelなどは
// https://qiita.com/misaki_mofu/items/026cd353054856921995参照
vec3 realPos = ro + hitT * rd;
vec3 uvw = realPos - hitVoxel;
dirで場合分けをしてuvwからuvを得る
// 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が取れたことが分かる。
step3: Ambient Occlusionを計算する
材料はそろったのでいよいよAmbient Occlusionを実装する。
分かりやすくするために最初に右側のオクルージョンだけ考えてみる。
式で表すと
// さっきやったようにvc.xyzwには1.0か0.0が入っている。
float rightOcclusion = uv.x * vc.x;
float frontRightOcclusion = uv.x * uv.y * vd.x * (1.0 - vc.x) * (1.0 - vc.z);
他のvoxelでも同じようにやる。
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にあてる。
float occ = calcOcc(uv, vc, vd);
// 必ず反転させる。
// お好みで滑らかにする。
occ = 1.0 - occ * occ / 12.0;
occ = occ*occ;
occ = occ*occ;
// colorにかける。
color *= occ;
前回やったtorusのvoxelでやってみる。
かかったけど、かかりすぎかな?
ちょっと自重させる。
ちょっとだけ自然になった気がする。
最後に
カメラを回転させると変になる。uvがずれる感じ。
なので、tMaxの初期値を変更させる。これでuvのずれが無くなってどの向きでもAOが正しくかかるようになる!
// vec3 tMax = (voxel - ro) / rd;
vec3 tMax = (voxel - ro + rayStep * 0.5 + 0.5) / rd;
以上がvoxelのためだけのアンビエントオクルージョンでした。おわり。