イメージベースドライティングを試してみる

2009-11-16

smallptにイメージベースドライティング(Image Based Lighting)を組み込んでみる。 データはLight Probe Image Galleryの.hdr。 これはRGBEフォーマットらしい。読み込みはにソースがある。 しかし一部おかしなところがあって、ヘッダの読み込みRGBE_ReadHeader()でフォーマットの指定子ががきたらループを抜けるようになってるけど、データはその後にEXPOSUREがきたりしていて読み込みに失敗するのでちょっと変更:

int RGBE_ReadHeader(FILE *fp, int *width, int *height, rgbe_header_info *info)
{
...
for(;;) {
if ((buf[0] == 0)||(buf[0] == '\n'))
// return rgbe_error(rgbe_format_error,"no FORMAT specifier found");
break;
else if (strcmp(buf,"FORMAT=32-bit_rle_rgbe\n") == 0)
// break; /* format found so break out of loop */
/*nothing*/;
else if (info && (sscanf(buf,"GAMMA=%g",&tempf) == 1)) {
info->gamma = tempf;
info->valid |= RGBE_VALID_GAMMA;
}
else if (info && (sscanf(buf,"EXPOSURE=%g",&tempf) == 1)) {
info->exposure = tempf;
info->valid |= RGBE_VALID_EXPOSURE;
}
if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
return rgbe_error(rgbe_read_error,NULL);
}
// if (fgets(buf,sizeof(buf)/sizeof(buf[0]),fp) == 0)
// return rgbe_error(rgbe_read_error,NULL);
if (strcmp(buf,"\n") != 0)
...

これを使って、環境マップの読み込み:

float* envmap;
int env_width, env_height;
bool read_envmap(const char* fn) {
FILE* f = fopen(fn, "rb");
if (f != NULL) {
rgbe_header_info info;
RGBE_ReadHeader(f, &env_width, &env_height, &info);
envmap = (float*)malloc(sizeof(float) * 3 * env_width * env_height);
RGBE_ReadPixels_RLE(f, envmap, env_width, env_height);
fclose(f);
return true;
} else {
return false;
}
}

あとはパストレーシングで、レイの交差判定でなにとも当たらなかったらレイの方向で環境マップからひっぱってくるようにする。球マップのほうはベクトルからuvの求め方がわからなかったので(「(Dxr,Dyr) where r=(1/pi)*acos(Dz)/sqrt(Dx^2 + Dy^2)」と書いてあったけどなぜかうまくいかなかった)、十字になってるキューブマップのほうで:

Vec env(const Ray& r) {
static const int tbl[][2] = {
{ 0, 1 }, // 0: -X
{ 2, 1 }, // 1: +X
{ 1, 2 }, // 2: -Y
{ 1, 0 }, // 3: +Y
{ 1, 1 }, // 4: -Z
{ 1, 3 }, // 5: +Z
};
int dir = find_dir(r.d);
const Vec& d = r.d;
double u, v;
switch (dir) {
default:
case 0: u = -d.z / -d.x; v = -d.y / -d.x; break;
case 1: u = d.z / d.x; v = -d.y / d.x; break;
case 2: u = d.x / -d.y; v = d.z / -d.y; break;
case 3: u = d.x / d.y; v = -d.z / d.y; break;
case 4: u = d.x / -d.z; v = -d.y / -d.z; break;
case 5: u = d.x / d.z; v = d.y / d.z; break;
}
int s0 = env_width * tbl[dir][0] / 3;
int t0 = env_height * tbl[dir][1] / 4;
int s = (int)((u * 0.5 + 0.5) * env_width/3) + s0;
int t = (int)((v * 0.5 + 0.5) * env_height/4) + t0;
float* p = &envmap[(s + t * env_width) * 3];
return Vec(p[0], p[1], p[2]);
}

// ベクトルが立方体のどちらを向いているか
int find_dir(const Vec& d) {
double x = fabs(d.x), y = fabs(d.y), z = fabs(d.z);
if (x > y) {
if (x > z) return d.x < 0 ? 0 : 1;
else return d.z < 0 ? 4 : 5;
} else {
if (y > z) return d.y < 0 ? 2 : 3;
else return d.z < 0 ? 4 : 5;
}
}

結果

IBL1 IBL2

  • ピクセルあたり1000サンプルで、1024x768で描画したものを256x192に縮小
  • 環境マップからのサンプルは、キューブマップの角のときのフィルタの仕方がわからなかったので、ポイントサンプル
  • smallptのシーンは、でかい球の内部に構成されているとは予想外だったので、天井や壁を取っ払っても環境が描画されなくてハマッた
  • 環境マップの放射輝度そのままだと明るすぎたので、適当にスケールしてる
  • フォトンマッピングに組み込むにはどうしたらいいのかな

追記

球のLightProbeのときうまくいかないと思ったのは単に上下が逆転していただけだった…。継ぎ目もほとんどわからない。 球のほうが計算式は簡単になるけど、ファイルサイズが2倍くらいデカい。