#はじめに
- Part1(基礎編)
- Part2(円柱編)
- Part3(角柱編)(ここ)
- Part4(SDFの加工編)
こんにちは、アドベントカレンダー11日目担当の避雷です。今回は前回に引き続き、レイマーチングのSDFを読み解いていこうと思います。今回は多角形について扱います。
#解説
サンプルはこちら
https://www.shadertoy.com/view/WdKXzd
##三角柱
iq氏のサイトに書かれているSDFはこちら。
float sdTriPrism( vec3 p, vec2 h )
{
vec3 q = abs(p);
return max(q.z-h.y,max(q.x*0.866025+p.y*0.5,-p.y)-h.x*0.5);
}
なんだか良くわからない数字がハードコーディングされています。
良く観察するとh.yが三角柱の高さ、h.xが三角柱の底辺のサイズを制御しているみたいです。
また、0.866025
について、生成物が正三角形であること、かなりの精度で記述されていることなどを考えれば、これは$\frac{\sqrt{3}}{2}$であると予測できます。実際に$\sqrt{3} = 1.73...$なのでこの予測は正しそうです。
数式を観察してみます。実装の内容は、
- 底辺の生成($-y < 0$)
- 斜辺の生成($0.886x + p.y * 0.5 < 0$)
- 絶対値で折り返す($q = abs(p)$)(この折り返しによって斜辺が左右両方になる)
- $(x,y)$を$h.x * 0.5$(h.xは三角形の一辺の長さとして定義されている)だけ右下にずらす
###TIPS
これは当然のことではありますが、三角柱2つの和を取ると六芒星柱になります。
float sdHexStar(vec3 p, vec2 h){
float result = sdTriPrism(p,h);
p.y = -p.y;
result = min(result,sdTriPrism(p,h));
return result;
}
このぐらいになると情報量が増えてだいぶ魅せられる画になってくるような気がしますね。
さらに奥行き方向の座標による捻りや太さにブレを持たせるとだいぶ良くなってくるような気がします。
##六角柱
iq氏のサイトに書かれているSDFはこちら。
float sdHexPrism( vec3 p, vec2 h )
{
const vec3 k = vec3(-0.8660254, 0.5, 0.57735);
p = abs(p);
p.xy -= 2.0*min(dot(k.xy, p.xy), 0.0)*k.xy;//A
vec2 d = vec2(
length(p.xy-vec2(clamp(p.x,-k.z*h.x,k.z*h.x), h.x))*sign(p.y-h.x),//B
p.z-h.y );//C
return min(max(d.x,d.y),0.0) + length(max(d,0.0));//D
}
これもなんだか良くわからないベクトルがハードコーディングされています。
h.x…1辺の長さ h.y…柱の高さ
$k = (\frac{\sqrt{3}}{2},\frac{1}{2},\frac{1}{\sqrt{3}})$
行われている処理を分解してみましょう。
- 座標の絶対値を取る $p = abs(p)$ これによって第一象限($x > 0,y > 0$)にのみ注目すればよくなる
- 内積を利用して線Aを折り線としてAの左側の点を右側に移す($A$)
- xy平面での六角形までの距離を取得して、符号をつける($B$)
- 六角柱の高さの部分を切り取る($C$)
- xy平面とz軸での長さを取得する($D$)
##TIPS
上記がExactな六角柱のSDFですが、実際にライブコーディング等でこんな式を暗記するのはあまり現実的ではありません。
代わりに上下反転させた三角柱を二つ重ねて共通部分を取ることでも似たような図形を作ることは可能です。
float sdHexPrismAlter(vec3 p, vec2 h){
float result = sdTriPrism(p,h);
p.y = -p.y;
result = max(result,sdTriPrism(p,h));
return result;
}
正八面体です。実はPart1の距離についての解説でも同等な図形を紹介してはいるのですが、iq氏による実装をみてみましょう
float sdOctahedron( vec3 p, float s)
{
p = abs(p);
float m = p.x+p.y+p.z-s;
vec3 q;
if( 3.0*p.x < m ) q = p.xyz;
else if( 3.0*p.y < m ) q = p.yzx;
else if( 3.0*p.z < m ) q = p.zxy;
else return m*0.57735027;
float k = clamp(0.5*(q.z-q.y+s),0.0,s);
return length(vec3(q.x,q.y-s+k,q.z-k));
}
8面のうち、いずれかの面の真上に存在するときは$m * \frac{1}{\sqrt{3}}$(mはL1ノルムによる距離を測ったもの)を、それ以外の時はちゃんと計測して返しています。
#おわりに
今回は角柱系の実装を紹介しました。ここら辺は一見するとわかりづらいマジックナンバーが大量に出現しがちなので読解に根気が必要になってしまいます。
次回はレイマーチング解釈シリーズのラスト、SDFの加工処理についてやって行こうと思います。