====== Fragmenty kódu pro raytracer ======
**Disclaimer: nepoužívejte čemu nerozumíte, použití na vlastní nebezpečí, nemusí být správné, optimální, blablabla... ;)**
Z tohohle předmětu už je pomalu taky fraška, tak proč to dělat složitě... tady jsou hotový kusy kódu co se budou hodit. Vybral jsem to ze svojeho funkčního rendereru, takže by to mělo být korektní, ale za nic neručím ;). Nejasnosti a dotazy do diskuse prosím.
* Můj vektor se jmenuje Vector3d
* skalární a vektorový součin (dot & cross product) značím jako Vector3d::dot a Vector3d::cross
* předpokládám že jsme našli průsečík primárního paprsku s nejbližším objektem. Data o této intersekci jsou uložený ve struktuře hit
* integrator je objekt kterýmu dáte paprsek a on vám vrátí barvu
* __forceinline
je MSVC klíčový slovo co zajistí inlinování funkce (odstraní se overhead z function callu a jede to rychlej). Něco jak "inline", ale je to silnější.
==== Stínový paprsky ====
sledují se podobně jako primární, ale nestínují se. Origin je hit location, target je světlo (nebo naopak). Nehledá se nejbližší průsečík, ale libovolný. Musí se ověřit že průsečík je mezi bodem a světlem, ne až za ním. **Je velmi vhodný posunout origin po směru paprsku o nějaký epsilon (0.001 nebo tak), protože jinak se paprsek protne s polygonem ze kterého byl vystřelen (self-intersection).**
Pro toto se ve frameworku s uspechem vyuziji promenne tMin a tMax.
==== Ideální odraz ====
Vygenerujte paprsek, vystínujte jako primární (hlídat si hloubku zanoření, jinak 2 zrcadla proti sobě dají stack overflow). Opět to chce posunutí (bias), aby se paprsek neprotl sám s tím samým povrchem v nulové vzdálenosti.
Směr paprsku je:
( ray.direction - hit.normal * 2 * Vector3D::dot(ray.direction, hit.normal) ).normalize();
==== Ideální lom ====
Tohle je opruz. Zase se nageneruje paprsek, ale nestínuje se jako primární, hledá se průsečík se zadní stranou polygonu (pokud máte jednostranný polygony). Tento paprsek je potřeba v místě dopadu podruhý zalomit, aby vyletěl z tělesa. Při tomto druhém lomu se (imho) nepočítá vůbec shading, jen se zalomí a rekurzivně střelí dál.
Generování refraktivního paprsku:
if(refractAmount > TRESHOLD){ //refraction
// en.wikipedia.org/wiki/Snell%27s_law#Vector_form
float IORratio = 1/material.IOR;
float cos1 = Vector3D::dot(hit.normal, -ray.direction);
float cos2 = sqrt(1 - IORratio*IORratio*(1-cos1*cos1));
if(cos1 < 0)
cos2 = -cos2;
Vector3D direction = (IORratio*ray.direction +
(IORratio*cos1 -cos2)*hit.normal).normalize();
//parametry ray jsou origin, direction, typ, hloubka
Ray refracted(hit.position+direction*Vector3D(SHADOW_BIAS), direction, RAY_REFRACTED,
ray.depth);
refract = root.integrator->getColor(refracted);
}
Ošetření toho když vám přijde refraktivní paprsek: místo klasickýho stínování uděláte tohle:
if(ray.type == RAY_REFRACTED){
//ray just travelled through material and hit surface again
float IORratio = material.IOR;
float cos1 = Vector3d::dot(hit.normal, -ray.direction);
float cos2 = 1 - IORratio*IORratio*(1-cos1*cos1);
Vector3d direction;
if(cos2 < 0){ //total internal reflection
direction = (ray.direction + 2*cos1*hit.normal).normalize();
Ray resurfaced(hit.position+direction*Vector3d(SHADOW_BIAS), direction, RAY_REFRACTED, ray.depth );
return core->integrator->getColor(resurfaced);
} else {
cos2 = sqrt(cos2);
if(cos1 < 0)
cos2 = -cos2;
direction = (IORratio*ray.direction + (IORratio*cos1 - cos2)*hit.normal).normalize();
Ray resurfaced(hit.position+direction*Vector3d(SHADOW_BIAS), direction, RAY_PRIMARY, ray.depth);
return core->integrator->getColor(resurfaced);
}
}
==== global illumination ====
Z hemisféry vystřelíte několik (1-n) náhodných paprsků (co se stínují jako primární), zprůměrujete vrácené barvy, a připočítáte k osvětlení. Easy, ne? Potřebujete umět nějak paprsky zabíjet, buď ruskou ruletou (náhodně), nebo podle max hloubky. Osvětlení GI získané následujícím způsobem se násobí difůzní barvou.
GI paprsek:
VectorRotator rotator(hit.normal);
RandomCouple x = random.getCosineLobeSample();
Vector3d direction = rotator.rotateVector(x);
No, průser je ten random a vectorRotator.
Random generuje vzorky s cosinovou hustotou (viz. [[http://www.cs.kuleuven.ac.be/~phil/GI/TotalCompendium.pdf|Global Illumination Compendium]], str 19). Vzorek je vektor na hemisféře okolo y osy. Vytvoří se takhle:
RandomCouple random(this->uniformCouple()); //2 random čísla 0-1
return Vector3d (cos(2 * PI*random.v2)* sqrt(random.v1),
sqrt(1 - random.v1),
sin(2 * PI*random.v2)* sqrt(random.v1));
VectorRotator rotuje vektory z hemisféry okolo y osy do hemisféry okolo hit normály. Je to vlastně ortonormální báze, kde y souřadnice je ve směru vaší normály. Tahle báze se vyrobí takhle:
__forceinline VectorRotator(const Vector3d& normal){
//musim vytvorit dalsi 2 vektory kolmy na normal.
_ASSERTE(normal.isNormalized());
collumn2 = normal;
Vector3d temp = normal;
if(fabs(temp.x) < fabs(temp.y)) {
//tahle šaškárna je o tom že potřebuju vyrobit jakejkoliv vektor kterej bude zaručeně nerovnoběžnej s normálou
temp.x = 2;
} else {
temp.y = 2;
}
collumn3 = Vector3d::cross(temp, collumn2).normalize();
collumn1 = Vector3d::cross(collumn2, collumn3).normalize();
}
A pak se používá takto:
__forceinline Vector3d rotateVector(const Vector3d& vect) const{
return Vector3d(
collumn1.x * vect.x + collumn2.x * vect.y + collumn3.x * vect.z,
collumn1.y * vect.x + collumn2.y * vect.y + collumn3.y * vect.z,
collumn1.z * vect.x + collumn2.z * vect.y + collumn3.z * vect.z
);
}
==== Glossy reflexe ====
Zase se vygenerují samply na y-hemisféře, tentokrát nejsou difůzní ale jsou soustředěný hodně okolo osy y. Jak moc určuje reflectionBlur. Tyhle samply se orotují přes rotator, kterej tentokrát **je vytvořenej podle směru ideálního odrazu, ne podle normály.** Protože to bude zase šumět, tak je fajn udělat víc samplů a výsledky zprůměrovat.
Vector3d direction = random.getPhongDensitySample(material.reflectionBlur);
direction = rotator.rotateVector(direction);
Ty samply dostanu touhle funkcí
__forceinline Vector3d getPhongDensitySample(const float blur){
RandomCouple random(this->uniformCouple());
float cosElevation = pow(random.v1, 1/(1.0f + blur));
float sinElevation = sqrt(1 - cosElevation*cosElevation);
random.v2 *= (2 * PI);
return Vector3d(cos(random.v2)*sinElevation, cosElevation, sin(random.v2)*sinElevation);
}
Možná to není úplně to co oni chcou, nemachruju tam s tou BRDF funkcí, ale je to rychlý a kompaktní, tak snad se nehraje na city ;)
--- Nejsem si jistý, ale tohle podle mě je BRDF, ne? --- //[[r.polasek@seznam.cz|Roman Polášek]] 2009/12/04 18:01//
==== Area Lights ====
Je potřeba zvolit pokaždý jinej náhodnej sample na světle a udělat na něj klasickej shadowray. IMplementovaný zatím mám jen triangle světla. Docela lahůdka je ale s intenzitama, sám v tom mám bordel, možná je tam chyba.
Možná budete chtít si ten kód upravit pro ty obdélníkový světla, mohlo by to taky být docela podezřelý. Úprava spočívá pouze ve změně generováním randomPoint na nějaký triviální point + random1*direction1 + random2*direction2
RgbColor __forceinline getIrradianceSecondary(const Hit& hit,
Abstract::AccelerationStructure* structure){
RandomCouple randomCouple(global->random->uniformCouple());
float sqrtRandom = sqrt(randomCouple.v1);
//tohle je to klíčový a nejdůležitější: samplování trianglu
Vector3d randomPoint = triangle->point +
triangle->direction1 * randomCouple.v2 * sqrtRandom +
triangle->direction2 * (1 - sqrtRandom);
Vector3d direction = randomPoint - hit.position;
Ray shadowRay(hit.position, direction.normalize());
float cosineSurface = Vector3d::dot(hit.normal, shadowRay.direction);
if(cosineSurface < TRESHOLD)
return RgbColor();
float cosineLight = Vector3d::dot(inverseNormal, shadowRay.direction);
if(cosineLight < TRESHOLD)
return RgbColor();
float distance = direction.x/shadowRay.direction.x;
shadowRay.origin = hit.position + SHADOW_BIAS*shadowRay.direction;
if(scene->castsShadow(shadowRay, distance - (2*SHADOW_BIAS)))
return RgbColor();
//druhá důležitá věc: násobí se dvěma cosiny. Koukněte do shirleyho kdyžtak
//jo a je tam útlum podle těch 3 koeficientů viz zadání
return this->color * cosineSurface * cosineLight / (distance*distance * quadraticAtten + distance * linearAtten + constantAtten);
}
Ještě bych dodal že je docela logický aby intenzita světla závisela na jeho velikosti, proto jsem si předvynásobil intenzitu světla jeho plochou. Plocha se určí takhle:
float area = Vector3d::cross(triangle->direction1, triangle->direction2).size() / 2;
==== Environment lighting ====
nejjednodušší bod, jen se změní vrácená barva paprsku co vyletěl mimo scénu. Kód na mapování je na webu
~~DISCUSSION~~