生成物は以下。
GLSLをAWKで実装したらどんな感じになるのか、以下の点から気になったためやってみる。
- ピクセル単位の処理をするGLSLが、行単位の処理に特化したAWKにどう書き換わるか?
- レイマーチングはシンプルな考えで実現されているが、GLSL以外でもシンプルに実装できるか?
- AWKは制約が大きい言語だが、自然に実装できるか?
- どれほどの速度が出るか
GLSLのレイマーチング自体は、
を参考にさせて頂く。
全貌として、
- ImageMagickのテキストから画像データへの変換を利用する
- Bashで1行にピクセルでのx、y値を指定したデータをAWKに渡す
- AWKでは、1行に対応して、ImageMagickが解釈できる1ピクセルを出力していく
- 1フレ分の画像データだけを出力する(動画を生成したかったが遅すぎて断念)
#!bash
size=256
{
echo "# ImageMagick pixel enumeration: $size,$size,255,srgb"
for y in $(seq 0 $((size - 1))); do
for x in $(seq 0 $((size - 1))); do
echo $x $y;
done;
done | awk -v size=$size -f ray_marching.awk
} | convert - ray_marching.png
AWKで気を付ける必要がある点は以下。
- ローカル変数は、引数の最後に複数スペースを置いて引数として同様に並べる
- returnで配列は返せない、なので第一引数を利用する
function vec2(v, x, y) {
v[0] = x;
v[1] = (y == "") ? x : y;
}
function vec3(v, x, y, z) {
v[0] = x;
v[1] = (y == "") ? x : y;
v[2] = (z == "") ? x : z;
}
function vec3Clone(v, v0) {
vec3(v, v0[0], v0[1], v0[2]);
}
function vec3Length(v) {
return sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}
function vec3Add(v, v0) {
vec3(v, v[0] + v0[0], v[1] + v0[1], v[2] + v0[2]);
}
function vec3Sub(v, v0) {
vec3(v, v[0] - v0[0], v[1] - v0[1], v[2] - v0[2]);
}
function vec3Mul(v, s) {
vec3(v, v[0] * s, v[1] * s, v[2] * s);
}
function vec3Mod(v, s) {
vec3(v,
v[0] % s + (v[0] < 0 ? s : 0),
v[1] % s + (v[1] < 0 ? s : 0),
v[2] % s + (v[2] < 0 ? s : 0));
}
function vec3Dot(v0, v1) {
return v0[0] * v1[0] + v0[1] * v1[1] + v0[2] * v1[2];
}
function vec3Clamp(v, min, max) {
vec3(v,
(v[0] < min) ? 0 : ((v[0] > max) ? max : v[0]),
(v[1] < min) ? 0 : ((v[1] > max) ? max : v[1]),
(v[2] < min) ? 0 : ((v[2] > max) ? max : v[2]));
}
function vec3Normalize(v, l) {
l = vec3Length(v);
if (l == 0.0) {
vec3(v, 0.0);
return;
}
vec3(v, v[0] / l, v[1] / l, v[2] / l);
}
function getDistance(pos, size, p, vs) {
vec3Clone(p, pos);
vec3Mod(p, 6);
vec3(vs, 2.0);
vec3Sub(p, vs);
return vec3Length(p) - size;
}
function getNormal(v, pos, size, ep, v0, v1, v2, baseDistance) {
ep = 0.0001;
vec3(v0, pos[0] - ep, pos[1], pos[2]);
vec3(v1, pos[0], pos[1] - ep, pos[2]);
vec3(v2, pos[0], pos[1], pos[2] - ep);
baseDistance = getDistance(pos, size);
vec3(v,
baseDistance - getDistance(v0, size),
baseDistance - getDistance(v1, size),
baseDistance - getDistance(v2, size));
vec3Normalize(v);
}
BEGIN {
vec3(lightDir, 1.0);
vec2(resolution, size);
}
{
vec2(fragCoord, $2, $1);
vec2(pos,
(fragCoord[0] * 2.0 - resolution[0]) / resolution[1],
(fragCoord[1] * 2.0 - resolution[1]) / resolution[1]);
vec3(col, 0);
vec3(cameraPos, 0.0, 0.0, 10.0);
vec3(ray, pos[0], pos[1], 0.0);
vec3Sub(ray, cameraPos);
vec3Normalize(ray);
vec3Clone(cur, cameraPos);
size = 0.75;
for (i = 0; i < 99; ++i) {
d = getDistance(cur, size);
if (d < 0.0001) {
getNormal(normal, cur, size);
dot = vec3Dot(normal, lightDir);
vec3(col, dot);
vec3(ambient, 1.0, 0.7, 0.2);
vec3Add(col, ambient);
break;
}
vec3Clone(delta, ray);
vec3Mul(delta, d);
vec3Add(cur, delta);
}
vec3Clamp(col, 0.0, 1.0);
printf "%d,%d: (%d,%d,%d)\n",
$2, $1,
int(col[0] * 255.99), int(col[1] * 255.99), int(col[2] * 255.99)
}
感想は以下。
- レイマーチングはGLSL以外でもシンプルに実現できる
- AWKの制約はあるものの、それでもある程度自然なコードで実現できる
- 1フレ256x256を生成するだけでも数分掛かり非常に遅い