はじめに
沖縄での開催でしたが、台風の影響で雨風がひどく、欠航となった便もあったようで、大変だった方も多かったのではないでしょうか?参加者の皆さま、まずはお疲れさまでした。3年ぶりの開催となる今回は、画像をいかにきれいにデノイズするかを競う会と、アニメーションのレンダリングを行う本選との2本立てでしたが、私はデノイズ部門の参加は見送り、本選のみ参加しました。
本選
言い訳になりますが、年明けから母の体調が悪く、東京と実家を行ったり来たりしていて、今年はほとんど準備ができませんでした。というより、生まれて初めて何かを作りたいと思えなくなって、戸惑ったというのが正直なところでしょうか。前回も書いたような気がしますが、健康が何よりも大切です。それでも、なんとか海の中にいそうな生き物をイメージして2日ほどprocessingと戯れてアセットを準備しました。提出した作品は以下のようなもので、3位となりました。生き物の動きや、地面などすべてプロシージャラルに生成しています。床は1枚板で、あとは全てカーブです。強めのDOFとけられを加えました。動画はそのうち追加しようと思います。プレゼンの時には触れませんでしたが、解像度は1280x720、24fps、40sppです。1フレーム当たり2~3秒で描画しています。デノイズはしていません。
「デノイズは甘え」自分への戒めです。
以下が上のぐにゃぐにゃのカーブを書き出すぐちゃぐちゃのコード。実行画面を見ながら書き殴ったので、もはや何をしたか全く覚えていません。
int N = 1024; // foot positions
int Y = 4; // ply
int S = 192; //segments
float[] x = new float[N];
float[] y = new float[N];
float[] z = new float[N];
float[] w = new float[N];
boolean[] in = new boolean[N];
float radius = 640;
float range = 128;
int time = 0;
int start_time = 0;
PrintWriter output;
boolean export = true;
class Curve {
PVector C0, C1, C2, C3; // control points
PVector P0, P1, P2, P3; // control points
Curve(PVector p0, PVector p1, PVector p2, PVector p3)
{
P0 = p0;
P1 = p1;
P2 = p2;
P3 = p3;
}
PVector eval(int id, float yarn, float fiber, float t, float offset) {
float theta = noise(id, yarn) * 2.0f * PI;
PVector o0 = new PVector(cos(-offset * 2 * PI) * cos(theta) * 5, sin(theta) * 5, sin(-offset * 2 * PI) * cos(theta) * 5);
PVector Px = PVector.add(P0, o0);
C0 = PVector.add(PVector.add(PVector.add(PVector.mult(Px, -1), PVector.mult(P1, 3)), PVector.mult(P2, -3)), P3);
C1 = PVector.add(PVector.add(PVector.mult(Px, 3), PVector.mult(P1, -6)), PVector.mult(P2, 3));
C2 = PVector.add(PVector.mult(Px, -3), PVector.mult(P1, 3));
C3 = Px;
PVector p0 = PVector.mult(C0, t * t * t);
PVector p1 = PVector.mult(C1, t * t);
PVector p2 = PVector.mult(C2, t);
PVector p3 = C3;
PVector position = PVector.add(PVector.add(PVector.add(p0, p1), p2), p3);
PVector t0 = PVector.mult(C0, 3 * t * t);
PVector t1 = PVector.mult(C1, 2 * t);
PVector t2 = C2;
PVector tangent = PVector.add(PVector.add(t0, t1), t2).normalize();
PVector upvector = new PVector(0, 1, 0);
PVector binormal = tangent.cross(upvector).normalize();
PVector normal = binormal.cross(tangent).normalize();
// yarn
float yarn_p = PI * 2 * yarn;
float yarn_r = noise(t*31) + 19.0 * sqrt(sin(PI * t)) * pow(noise(t*7, id), 2.2);
float yarn_t = PI * 2 * noise(t * 2, id) * 4 + yarn_p;
float yarn_u = cos(yarn_t) * yarn_r;
float yarn_v = sin(yarn_t) * yarn_r;
// fiber
float sign = ((id+(int)(yarn * Y)) % 2) * 2 - 1;
float fiber_p = PI * 2 * fiber;
float fiber_r = sqrt(sqrt(sin(PI * t))) * sq(noise(t * 4, yarn)) * 4 + 0.5;
float fiber_t = sign * PI * 2 * noise(t * 5, yarn) * 4 + fiber_p + offset * 400 + noise(id);
float fiber_u = yarn_u + cos(fiber_t) * fiber_r;
float fiber_v = yarn_v + sin(fiber_t) * fiber_r;
PVector noise = PVector.add(PVector.mult(normal, fiber_v), PVector.mult(binormal, fiber_u));
return PVector.add(position, noise);
}
void display(int id, float t, float offset) {
for(int y = 0; y < Y; ++y) {
int F = 1 + int(6*noise(y, id)); // # fibers
for(int f = 0; f < F; ++f) {
stroke((y * 17 + 3) % 255, ((f + 1) * 31) % 255, 255, 255);
for(int i = 0; i < S; ++i) {
PVector p0 = eval(id, y/(float)Y, f/(float)F, t * float(i + 0) / S * sqrt(noise(y, f, id)), offset);
PVector p1 = eval(id, y/(float)Y, f/(float)F, t * float(i + 1) / S * sqrt(noise(y, f, id)), offset);
line(p0.x, p0.y, p0.z, p1.x, p1.y, p1.z);
}
// save
if(export) {
output.println("s " + S);
output.println("id " + id * N + y * 32 + f);
for(int i = 0; i <= S; ++i) {
PVector p0 = eval(id, y/(float)Y, f/(float)F, t * i / S, offset);
output.println("p " + p0.x + " " + p0.y + " " + p0.z);
}
}
}
}
}
}
void setup() {
size(1920, 1080, P3D);
randomSeed(1999);
smooth(4);
time = start_time;
int i = 0;
for(int v = 0; v < 32; ++v) {
for(int u = 0; u < 32; ++u) {
x[i] = (u + random(0,1))/32 * 2048-1024;
y[i] = 0.0;
z[i] = (v + random(0,1))/32 * 2048-1024;
if(i % 5==0) {
y[i] = 150.0 + random(0, 30);
}
in[i] = false;
w[i] = 1000000000;
++i;
}
}
}
void grid()
{
stroke(64, 64, 64, 255);
for(int x = -32; x <= 32; ++x) {
line(x * 64, 0, 2048, x * 64, 0, -2048);
}
stroke(64, 64, 64, 255);
for(int x = -32; x <= 32; ++x) {
line(2048, 0, x * 64, -2048, 0, x * 64);
}
}
float cy = 55;
void draw()
{
background(0);
if(export) {
output = createWriter("curves"+ nf(time-start_time, 4) +".txt");
output.println("gr 0.5");
}
// frame to time
float t = time * 0.001;
//if(1032 <= time)
//noLoop();
strokeWeight(2);
grid();
// center
float cx = cos(-t * 2 * PI) * radius;
float cz = sin(-t * 2 * PI) * radius;
camera(100, 300, -1100, cx*0.8, 50, cz*0.8, 0, -1, 0);
frustum(-40, 40, -40 * 1080/1920, 40 * 1080/1920, 120, 8000);
for(int i = 0; i < N; ++i) {
float length = dist(x[i], y[i], z[i], cx, cy, cz);
if(range > length) {
if(!in[i]) {
in[i] = true;
w [i] = t;
cy += 0.25;
}
}
if(range * 1.7 < length) {
if(in[i]) {
in[i] = false;
w [i] = t;
cy-=0.25;
}
}
float ellapsed = t - w[i];
if(0 <= ellapsed) {
float mx = lerp(cx, x[i], 0.64) + cos(-t * 2 * PI) * 100 * (noise(i*10, 1) - 0.5);
float my = cy + 65 + 120 * (noise(i*10, 3) - 0.5);
float mz = lerp(cz, z[i], 0.64) + sin(-t * 2 * PI) * 100 * (noise(i*10, 2) - 0.5);
float off = 45;
if(10 < y[i]) {
off = -45;
}
PVector p0 = new PVector(cos(-(t+0.01) * 2 * PI) * radius, cy, sin(-(t+0.01) * 2 * PI) * radius);
PVector p1 = new PVector(mx, my, mz);
PVector p2 = new PVector(lerp(cx, x[i], 0.95), lerp(cy, y[i], 0.95) + off, lerp(cz, z[i], 0.95));
PVector p3 = new PVector(x[i], y[i], z[i]);
Curve curve = new Curve(p0, p1, p2, p3);
if(in[i]) {
float tmp = 0.0025 + 0.01 * noise(i);
if(tmp > ellapsed) {
curve.display(i, ellapsed / tmp, t);
}
else {
curve.display(i, 1.0, t);
}
}
else {
if(0.02 > ellapsed) {
curve.display(i, sqrt(1.0f - ellapsed / 0.02), t);
}
}
}
}
if(export) {
output.flush();
output.close();
}
//if(32<time)
//saveFrame("line-######.png");
time++;
}
本選では、参加者は自身を含め全ての作品に点をつけ、順位を決定します。個人的に特に興味深く、高得点を入れたのは、@UshioさんのNeRFを使った作品と、@h013さんのコースティクスの作品です。
最近はOptixやEmbree、OIDN、TBB、tiny-cuda-nnなどといった便利なライブラリが数多く公開されており、これらを結合させることで、昔と比べるとはるかに容易にレンダラを組み上げることができます。そういったアプローチも戦略として非常に有効だと思いますし、仕事をする上では開発速度と信頼性の面で最適解だと思います。いろいろな意見があるとは思いますが、私はレンダラに限って言うと、交差判定などを含め、基本的に全てを作るよう心がけています。これは、プロはそういった著名なライブラリを競争相手と見なすべきであることと、スクラッチから作っていく過程において、既存手法の問題点を見つけたり、全く新しいアイデアを思い付いたりすることがあるためです。私が高い得点を入れた2作品も、短期間でスクラッチから書き上げていたものでした。
さて、注目の1位はきれいな雲のレンダリングを行った@Shocker_0x15さんでした。おめでとうございます!
全ての結果はこちらから見られます。
セミナー
セミナーは@Ushioさんや@yumcyawizさんをはじめ力作ぞろいでした。こういったレベルの高い話はいまの職場ですることが皆無なので、ありがたいですね。私は、セミナーもろくに準備できなかったので、前回の合宿と今回の合宿の間に何をやったかを簡単に振り返ってみました。
SSSとメニーライトとの組み合わせ
複数の機能を併用してもプロダクション向けのレンダラは正しく動くべし
コースティクスのリファクタリング
メニーライトのサンプリングを応用して、フォトンの射出方向を変える
布のレンダリング
フライアウェイファイバーの生成と交差判定の精度が肝
HPGの論文
ネガティブな空間に対してBVHを構築し、サンプリング
voronoiのライブラリ
オープンソースなので使ってみてください
BVHのSTAR
とても勉強になったプロジェクト
リザバーのSIMD化
Ratio Estimatorの例がおススメ
ノイズの生成
周波数特性と輝度値のヒストグラム両方に制約を課す
あまり見向きをされなくなったノイズなどもいろいろな論文を見返してみると、案外解決されていないことがあったりします。これからも流行にとらわれないで何か面白いものを作っていきたいと思います。
さいごに
悪天候の中準備してくださった運営の皆さん(実行結果を夜な夜な送ってくれたtomohiroさんも)と、合宿に合わせてハネムーンの行き先を変えてくれた妻に感謝したいと思います。母の体調も良くなってきたので、次回は頑張ります!