// フォトン追跡法によりフォトンマップ構築 void create_photon_map(const int shoot_photon_num, PhotonMap *photon_map) { std::cout << "Shooting photons... (" << shoot_photon_num << " photons)" << std::endl; for (int i = 0; i < shoot_photon_num; i ++) { // 光源からフォトンを発射する // 光源上の一点をサンプリングする const double r1 = 2 * PI * rand01(); const double r2 = 1.0 - 2.0 * rand01() ; const Vec light_pos = spheres[LightID].position + ((spheres[LightID].radius + EPS) * Vec(sqrt(1.0 - r2*r2) * cos(r1), sqrt(1.0 - r2*r2) * sin(r1), r2)); const Vec normal = Normalize(light_pos - spheres[LightID].position); // 光源上の点から半球サンプリングする Vec w, u, v; w = normal; if (fabs(w.x) > 0.1) u = Normalize(Cross(Vec(0.0, 1.0, 0.0), w)); else u = Normalize(Cross(Vec(1.0, 0.0, 0.0), w)); v = Cross(w, u); // コサイン項に比例させる。フォトンが運ぶのが放射輝度ではなく放射束であるため。 const double u1 = 2 * PI * rand01(); const double u2 = rand01(), u2s = sqrt(u2); Vec light_dir = Normalize((u * cos(u1) * u2s + v * sin(u1) * u2s + w * sqrt(1.0 - u2))); Ray now_ray(light_pos, light_dir); // emissionの値は放射輝度だが、フォトンが運ぶのは放射束なので変換する必要がある。 // L(放射輝度)= dΦ/(cosθdωdA)なので、光源の放射束はΦ = ∫∫L・cosθdωdAになる。今回は球光源で完全拡散光源であることから // 球上の任意の場所、任意の方向に等しい放射輝度Leを持つ。(これがemissionの値)よって、 // Φ = Le・∫∫cosθdωdAで、Le・∫dA∫cosθdωとなり、∫dAは球の面積なので4πr^2、∫cosθdωは立体角の積分なのでπとなる。 // よって、Φ = Le・4πr^2・πとなる。この値を光源から発射するフォトン数で割ってやれば一つのフォトンが運ぶ放射束が求まる。 Color now_flux = spheres[LightID].emission * 4.0 * PI * pow(spheres[LightID].radius, 2.0) * PI / shoot_photon_num; // フォトンがシーンを飛ぶ bool trace_end = false; for (;!trace_end;) { // 放射束が0.0なフォトンを追跡してもしょうがないので打ち切る if (std::max(now_flux.x, std::max(now_flux.y, now_flux.z)) <= 0.0) break; double t; // レイからシーンの交差位置までの距離 int id; // 交差したシーン内オブジェクトのID if (!intersect_scene(now_ray, &t, &id)) break; const Sphere &obj = spheres[id]; const Vec hitpoint = now_ray.org + t * now_ray.dir; // 交差位置 const Vec normal = Normalize(hitpoint - obj.position); // 交差位置の法線 const Vec orienting_normal = Dot(normal, now_ray.dir) < 0.0 ? normal : (-1.0 * normal); // 交差位置の法線(物体からのレイの入出を考慮) switch (obj.ref_type) { case DIFFUSE: { // 拡散面なのでフォトンをフォトンマップに格納する photon_map->AddPoint(Photon(hitpoint, now_flux, now_ray.dir)); // 反射するかどうかをロシアンルーレットで決める // 例によって確率は任意。今回はフォトンマップ本に従ってRGBの反射率の平均を使う const double probability = (obj.color.x + obj.color.y + obj.color.z) / 3; if (probability > rand01()) { // 反射 // orienting_normalの方向を基準とした正規直交基底(w, u, v)を作る。この基底に対する半球内で次のレイを飛ばす。 Vec w, u, v; w = orienting_normal; if (fabs(w.x) > 0.1) u = Normalize(Cross(Vec(0.0, 1.0, 0.0), w)); else u = Normalize(Cross(Vec(1.0, 0.0, 0.0), w)); v = Cross(w, u); // コサイン項を使った重点的サンプリング const double r1 = 2 * PI * rand01(); const double r2 = rand01(), r2s = sqrt(r2); Vec dir = Normalize((u * cos(r1) * r2s + v * sin(r1) * r2s + w * sqrt(1.0 - r2))); now_ray = Ray(hitpoint, dir); now_flux = Multiply(now_flux, obj.color) / probability; continue; } else { // 吸収(すなわちここで追跡終了) trace_end = true; continue; } } break; } } } std::cout << "Done. (" << photon_map->Size() << " photons are stored)" << std::endl; std::cout << "Creating KD-tree..." << std::endl; photon_map->CreateKDtree(); std::cout << "Done." << std::endl; }
void preprocess(const Scene *scene) { /* Create a sample generator for the preprocess step */ Sampler *sampler = static_cast<Sampler *>( NoriObjectFactory::createInstance("independent", PropertyList())); Emitter* distantsDisk = scene->getDistantEmitter(); if(distantsDisk != nullptr ) { float lngstDir = scene->getBoundingBox().getLongestDirection(); distantsDisk->setMaxRadius(lngstDir); } /* Allocate memory for the photon map */ m_photonMap = std::unique_ptr<PhotonMap>(new PhotonMap()); m_photonMap->reserve(m_photonCount); /* Estimate a default photon radius */ if (m_photonRadius == 0) m_photonRadius = scene->getBoundingBox().getExtents().norm() / 500.0f; int storedPhotons = 0; const std::vector<Emitter *> lights = scene->getEmitters(); int nLights = lights.size(); Color3f tp(1.0f, 1.0f, 1.0f); cout << "Starting to create "<< m_photonCount << " photons!" << endl; int percentDone= 0; int onePercent = int(floor(m_photonCount / 100.0)); // create the expected number of photons while(storedPhotons < m_photonCount) { //uniformly sample 1 light (assuming that we only have area lights) int var = int(std::min(sampler->next1D()*nLights, float(nLights) - 1.0f)); const areaLight* curLight = static_cast<const areaLight *> (lights[var]); //sample a photon Photon curPhoton; Vector3f unQuantDir(0.0f,0.0f,0.0f); curLight->samplePhoton(sampler, curPhoton, 1, nLights, unQuantDir); Color3f alpha = curPhoton.getPower(); Color3f tp(1.0f, 1.0f, 1.0f); //trace the photon Intersection its; Ray3f photonRay(curPhoton.getPosition(), unQuantDir); m_shootedRays++; if (scene->rayIntersect(photonRay, its)) { while(true) { const BSDF* curBSDF = its.mesh->getBSDF(); if (curBSDF->isDiffuse()) { //store the photon m_photonMap->push_back(Photon( its.p /* Position */, -photonRay.d /* Direction*/, tp * alpha /* Power */ )); storedPhotons++; } if(!(storedPhotons < m_photonCount)) break; BSDFQueryRecord query = BSDFQueryRecord(its.toLocal(-photonRay.d), Vector3f(0.0f), EMeasure::ESolidAngle); Color3f fi = curBSDF->sample(query, sampler->next2D()); if(fi.maxCoeff() == 0.0f) break; tp *= fi; Vector3f wo = its.toWorld(query.wo); photonRay = Ray3f(its.p, wo); //ray escapes the scene if (!scene->rayIntersect(photonRay, its)) break; //stop critirium russian roulette float q = tp.maxCoeff(); if(q < sampler->next1D()) break; tp /= q; } } if(onePercent != 0) { if(storedPhotons % onePercent == 0){ int percent = int(floor(storedPhotons / onePercent)); if(percent % 10 == 0 && percentDone != percent){ percentDone = percent; cout << percent << "%" << endl; } } } } /* Build the photon map */ m_photonMap->build(); }