Radiosity - Die Lichtenergie gerät bei zu geringen Abstand aus dem Ruder
-
empfänger.Faktor berechnet sich wie folgt:
Siehe unten:
Faktor = geometryTerm * viewFactorDer GeometryTerm enthält dann das r²
private void AddAllViewFaktorsWithSolidAngle(IPatch patch, Random rand) { foreach (var otherPatch in this.patches) { if (patch == otherPatch) continue; //Keine Beleuchtung mit sich selbst if (otherPatch.IsLightSource) continue; //Lichtquellen dürfen nicht beleuchtet werden float distanceSqrt = (patch.CenterOfGravity - otherPatch.CenterOfGravity).QuadratBetrag(); if (distanceSqrt <= 0.01f) continue; //Wenn der Abstand zwischen zwei Objekten zu gering ist, dann erhält man ein hohen ViewFaktor (Zahl >> 10), was dann dazu führt, dass mit jeden Beleuchtungsstep die Lichtenergie immer mehr wird Ray ray = new Ray(patch.CenterOfGravity, Vektor.Normiere(otherPatch.CenterOfGravity - patch.CenterOfGravity)); float lambda1 = patch.Normale * ray.Direction; if (lambda1 <= 0) continue; //float lambda2 = otherPatch.Normale * (-ray.Direction); //if (lambda2 <= 0) continue; if (patch.IsLightSource && patch.IsInSpotDirection(otherPatch.CenterOfGravity) == false) continue; //Der andere liegt außerhalb meiens Spotcutoff var point = this.intersectionFinder.GetIntersectionPoint(ray, patch, 0); if (point == null || point.IntersectedObject != otherPatch) continue; //Sichtbarkeitstest float lambda2 = point.Normale * (-ray.Direction); if (lambda2 <= 0) continue; float geometryTerm = lambda1 * lambda2 / distanceSqrt; float viewFactor = 1; //Diese Zahl soll später mal mit ein Rasterizer berechnet werden if (lambda1 > 0 && lambda2 > 0 && distanceSqrt > 0.0001f) { patch.AddViewFaktor(new ViewFaktor() { Patch = otherPatch, Faktor = geometryTerm * viewFactor }); } } }
-
XMAMan schrieb:
var point = this.intersectionFinder.GetIntersectionPoint(ray, patch, 0); if (point == null || point.IntersectedObject != otherPatch) continue; //Sichtbarkeitstest
Behandelt dieser Test auch Teilüberdeckung? Wenn nicht, würde ja ein Patch der teilweise von einem Anderen verdeckt ist, trotzdem die vollen Lichtmenge äquivalent zu seiner Größe erhalten - was etwa zu einem 60% + 60% Fall führen würde.
-
Nein ich versende nur ein Sichtstrahl vom Mittelpunkt zum anderen Mittelpunkt. D.h. teilverdeckte Patches bekommen zu viel Energie ab. Das alleine darf aber kein Grund dafür sein, dass die Gesamtlichtmenge nach 20 Beleuchtungsschritten gegen unendlich geht.
-
float lambda1 = patch.Normale * ray.Direction; float lambda2 = point.Normale * (-ray.Direction); float geometryTerm = lambda1 * lambda2 / distanceSqrt; Faktor = geometryTerm * viewFactor empfänger.Patch.InputRadiosity += senderColor * empfänger.Faktor * sender.SurvaceArea * empfänger.Patch.SurvaceArea;
Hier nimmst du an, dass jeder Lichtstrahl von Quelle zu Ziel jeweils denselben Winkel zu den Normalen hat und auch die Länge des Lichtstrahls mit dem Abstand übereinstimmt.
Das trifft aber (näherungsweise) nur zu, wenn der Abstand deutlich größer als die Ausdehnung der Patches ist.
Bei exakter Rechnung müsstest du über beide Flächen integrieren.Klar ist, dass bei der Rechnung wie sie hier ausgeführt ist, das empfangene Licht gegen unendlich geht, wenn man der Abstand gegen Null geht.
-
Nehmen wir mal an, ich würde über beide Patch-Oberflächen integrieren und somit für jeden Punkt auf Patch I zu jeden Punkt auf Patch J ein eigenen GeometryTerm berechnen. Wäre dann das r²-Problem weg? Wenn ja warum?
Die wichtigere Frage. Wie soll die Lösung aussehen?
Da ich den Algorithmus in endlicher Zeit laufen lassen will, wie soll dann der Lichtaustauschen zwischen zwei Flächen vonstatten gehen? Soll ich das Integral Numerisch in Abhängigkeit vom Verhältnis Patch-Größe zu Patchabstand in N (N berechnet sich aus diesem Verhältnis) Schritten berechnen?
-
Ich denke mal, dass das Integral für einfache Formen ein relativ einfacher geschlossener Ausdruck sein dürfte. Evtl. im Matheforum nachfragen? Meine eigenen Fähigkeiten diesbezgl. sind da etwas eingerostet.
-
Verstehe. Anstatt eine numerische Lösung sollte ich mir das Problem lieber in IntegralSchreibweise auf dem Papier Lösen und die so ermittelte Funktion dann in meine Beleuchungsfunktion einsetzen.
Mein Gedanke, warum ich 'glaube' dass das Interpolieren des Abstandwertes ok ist es folgender Grundgedanke:
Gegeben ist die Schräge 2D-Line / und ein Punkt X daneben
/---X
Wenn ich nun über die Linie entlanglaufe und den Abstand zu X berechne, dann ist er im Mittel so groß, wie der Abstand des Linien-Mittelpunktes zu X. Das was die Linie oben Näher ist, ist sie unten weiter weg.
So war mein Gedankengang zu dem Thema und warum der GeometryTerm im Mittel über die Fläche gleichgroß ist. Vielleicht ist dort einfach noch ein Denkfehler drin den ich (mal wieder) übersehen habe.
-
XMAMan schrieb:
Gegeben ist die Schräge 2D-Line / und ein Punkt X daneben
/---X
Wenn ich nun über die Linie entlanglaufe und den Abstand zu X berechne, dann ist er im Mittel so groß, wie der Abstand des Linien-Mittelpunktes zu X. Das was die Linie oben Näher ist, ist sie unten weiter weg.
Der Winkel, unter dem die Strecke vom X aus gesehen wird, bestimmt die Menge an Licht, die die Strecke von der Lichtquelle X erhält. Betrachte nun alle Punkte (den Kreis) mit dem selben Abstand wie X vom Streckenmittelpunkt. Offensichtlich, ist der betreffende Winkel in der Regel nicht gleich für alle Punkte auf dem Kreis, sondern:
- ist 0, wenn der Abstand gößer als die halbe Streckenlänge ist und wir einen Punkt nehmen, in dem der Kreis die Gerade schneidet, auf der die Strecke liegt, und größer (aber stets kleiner als 90°) für andere Punkte
- ist exakt 90°, wenn Abstand gleich halbe Streckenlänge (Satz des Thales)
- ist 180°, wenn der Abstand kleiner als die halbe Streckenlänge ist und wir einen Punkt nehmen, in dem der Kreis die Strecke liegt, und kleiner (aber stets größer als 90°) für andere Punkte.
-
Teilabdeckung ist erstmal nicht so sehr wichtig.
Die energie in der Scene wird natuerlich "steigen" wenn du es iterativ machst, da du nirgenswo energie abziehst. Du berechnest die akkumulierte energie pro patch, nicht den verteilungsfaktor, denn dafuer muestest du alle patches in ein gleichungssystem stecken und es dann z.B. mit Gaus loesen.
am anfang hast du
Energie = Lightquellen + 0.0 * Scene
und bei jeder iteration steigt die Scenenenergie
Energie = Lightquellen + 0.5 * Scene
Energie = Lightquellen + 0.9 * Scene
Energie = Lightquellen + 1.1 * Scene
usw.Die "echte" energie steigt dabei natuerlich nicht, du naeherst dich nur bei jeder iteration dem echten wert.
Ein sehr kleiner Abstand zwischen patches ist auch nicht problematisch, da mit sinkendem Abstand, auch immer weniger licht von aussen zwischen die beiden patches kommt (wenn wir erstmal Teilabdeckung usw. ignorieren).
Wenn du erstmal eine konvergierte scene bekommst, kannst du die spezialfaelle angehen.
-
rapso schrieb:
Die energie in der Scene wird natuerlich "steigen" wenn du es iterativ machst, da du nirgenswo energie abziehst.
Wenn ich es richtig verstehe, wird dem System in N Iterationsschritten N-mal Energie über die Primärlichtquellen hinzugefügt. Also teilt man vermutlich am Ende alle Werte durch N, um eine brauchbare Verteilung zu erhalten. Das setzt aber voraus, dass Sekundärlicht keine extra Energie (insbesondere nicht mit superlinearem Wachstum) erzeugt.
rapso schrieb:
Ein sehr kleiner Abstand zwischen patches ist auch nicht problematisch, da mit sinkendem Abstand, auch immer weniger licht von aussen zwischen die beiden patches kommt (wenn wir erstmal Teilabdeckung usw. ignorieren).
Hm. Das Problem ist ja, dass gegenseitige Beleuchtung abgesehen vom Farb-/Albedofaktor symmetrisch ist. Wenn Fläche eins in einem Iterationsschritt 10% mehr Energie an Fläche zwei gleicher Farbe abgibt, als sie erhalten hat, bekommt sie im folgenden Schritt nochmals 10% mehr von Fläche zwei zurück. Das führt unweigerlich zu exponentiellem Wachstum.
-
Die Energie schrumpft bei mir bei der Brdf.
In folgender Zeile wärend der Beleuchtung:
patch.OutputRadiosity = Vektor.Mult(patch.ColorOnCenterPoint, patch.InputRadiosity) * lichtdurchlassKoeffizient / (float)Math.PI;
Input ist ja erstmal nur die Summe. Output ist dann die Energie, die Reflektiert wird. Da ich ja nur 0.8 / PI in den Output lege, wird dort ja nicht alles ungefilter weiter gegebben.
Da ich ja momentan kein Lichtaustausch zwischen zwei Patches berechne, die näher als 0,01 sind, konvergiert die Szene ja bereits. Ich erhalte jetzt auch bei 20 Beleuchtungsschritten das richtige Ergebnis. Die Frage ist ja eher, wie bekomme ich diese Einschränkung mit der 0,01 weg.
private static void RadiosityStepSolidAngle(List<IPatch> patches) { foreach (var patch in patches) { float lichtdurchlassKoeffizient = 0.8f; patch.OutputRadiosity = Vektor.Mult(patch.ColorOnCenterPoint, patch.InputRadiosity) * lichtdurchlassKoeffizient / (float)Math.PI; //Beim Reflektieren der Input-Zu-Output-Energie kommt die Brdf ins Spiel if (patch.IsLightSource) patch.OutputRadiosity += new Vektor(1, 1, 1) * patch.EmissionPerPatch / patch.SurvaceArea; patch.InputRadiosity = new Vektor(0, 0, 0); } foreach (var sender in patches) { Vektor senderColor = sender.OutputRadiosity;//Das hier ist die Photonencount pro Fläche. foreach (var empfänger in sender.ViewFaktors) { empfänger.Patch.InputRadiosity += senderColor * empfänger.Faktor * sender.SurvaceArea * empfänger.Patch.SurvaceArea;//Addiere Fluxwerte } } foreach (var patch in patches) { patch.InputRadiosity /= patch.SurvaceArea; //Umrechnen von Flux in Radiosity } }
-
Evtl. reicht es ja schon, einfach den Empfangsstrom zu begrenzen.
Statt:empfänger.Patch.InputRadiosity += senderColor * empfänger.Faktor * sender.SurvaceArea * empfänger.Patch.SurvaceArea;
empfänger.Patch.InputRadiosity += senderColor * sender.SurvaceArea * min( empfänger.Faktor * empfänger.Patch.SurvaceArea, 1 );
o.ä. Der Empfänger kann ja nicht mehr Licht empfangen, als ausgesendet wird. Immer noch ein Hack, aber wenigstens keine magische Zahl mehr.
-
camper schrieb:
empfänger.Patch.InputRadiosity += senderColor * sender.SurvaceArea * min( empfänger.Faktor * empfänger.Patch.SurvaceArea, 1] );
o.ä. Der Empfänger kann ja nicht mehr Licht empfangen, als ausgesendet wird. Immer noch ein Hack, aber wenigstens keine magische Zahl mehr.
Auch 1 ist eine magic number, was ist wenn die patches 100 mal kleiner oder groesser waeren? -> Das ist nicht zielführend.
Für den Anfang, erstell eine Cornell Box, ohne inhalt, nur waende, boden, decke und ein patch oben ist das licht. dann kannst du ohne tracen trivial die formfaktoren errechnen und die sache sollte problemfrei konvergieren. (ohne jegliche magic numbers).
-
XMAMan schrieb:
Die Energie schrumpft bei mir bei der Brdf.
im ersten post schriebst du die energie wird groesser. Schumpfen sollte es nirgendwo, sonst waere es falsch.
https://www.siggraph.org/education/materials/HyperGraph/radiosity/images/slide13.jpg
-
rapso schrieb:
Auch 1 ist eine magic number, was ist wenn die patches 100 mal kleiner oder groesser waeren? -> Das ist nicht zielführend.
Die 1 hier repräsentiert einen qualitativen Unterschied, im Gegensatz zu einer willkürlichen Quantität wie 0.01 die wahrscheinlich nur zufällig in einer bestimmten Szene so passt. Ziel war dabei nur, einen möglichst einfache Ansatz zu finden, der Beleuchtung ohne Lichtquellen verhindert. Die Ursache (falscher Geometrieterm) und der richtige Lösungsansatz dazu wurden ja bereits weiter oben diskutiert.
100 mal größerer patches bedeutet eben nicht, dass das Produkt
empfänger.Faktor * empfänger.Patch.SurvaceArea größer als 1 werden kann wenn empfänger.Faktor korrekt berechnet wird. Schließlich ist der Raumwinkel, der durch eine Fläche abgedeckt werden kann, durch 2π begrenzt.
-
camper schrieb:
rapso schrieb:
Auch 1 ist eine magic number, was ist wenn die patches 100 mal kleiner oder groesser waeren? -> Das ist nicht zielführend.
Die 1 hier repräsentiert einen qualitativen Unterschied, im Gegensatz zu einer willkürlichen Quantität wie 0.01 die wahrscheinlich nur zufällig in einer bestimmten Szene so passt. Ziel war dabei nur, einen möglichst einfache Ansatz zu finden, der Beleuchtung ohne Lichtquellen verhindert. Die Ursache und der richtige Lösungsansatz dazu wurden ja bereits weiter oben diskutiert.
was passiert wenn die surfacearea 100 mal groesser skaliert waere? weiterhin 1 beibehalten oder dann min(...,100) ?
-
rapso schrieb:
was passiert wenn die surfacearea 100 mal groesser skaliert waere? weiterhin 1 beibehalten oder dann min(...,100) ?
Hab meine Antwort oben nochmal editiert, bevor ich die Frage gesehen habe. Problem ist, das empfänger.Faktor falsch berechnet wird.
-
camper schrieb:
100 mal größerer patches bedeutet eben nicht, dass das Produkt
empfänger.Faktor * empfänger.Patch.SurvaceArea größer als 1 werden kann wenn empfänger.Faktor korrekt berechnet wird. Schließlich ist der Raumwinkel, der durch eine Fläche abgedeckt werden kann, durch 2π begrenzt.dieser faktor zwischen zwei patches aendert sich nicht. Patch groesse ist nur eine granularitaet. du kannst z.b. eine wand als 1 patch nutzen oder in millionen tesselieren. Raumwinkel spielt da auch ab einer gewissen distanz zwischen patches keine roller (deswegen wird es so approximiert). 1 ist deswegen eine magic number die mal funktionieren kann, an anderer stelle ganz falsche resultate liefert.
-
camper schrieb:
rapso schrieb:
was passiert wenn die surfacearea 100 mal groesser skaliert waere? weiterhin 1 beibehalten oder dann min(...,100) ?
Hab meine Antwort oben nochmal editiert, bevor ich die Frage gesehen habe. Problem ist, das empfänger.Faktor falsch berechnet wird.
Hab ich angenommen, deswegne die 2te antwort
Es kann einiges falsch sein, deswegen mein vorschlag mit einer trivialen testszene, das kann ihm beim bug suchen helfen.
-
ach ja
if (patch.IsLightSource && patch.IsInSpotDirection(otherPatch.CenterOfGravity) == false) continue; //Der andere liegt außerhalb meiens Spotcutoff
var point = this.intersectionFinder.GetIntersectionPoint(ray, patch, 0);
if (point == null || point.IntersectedObject != otherPatch) continue; //Sichtbarkeitstestfloat lambda2 = point.Normale * (-ray.Direction);
if (lambda2 <= 0) continue;1. mach erst den lambda2 test, dann IsInSpot, dann intersection, das kann dir bis 50% rechenzeit sparen.
2. Speicher dir irgendwo als bitmaske die resultate der intersections ab.
3. n->Intersect->m ist wie m->Intersect->n, nochmal 50%