Radiosity - Die Lichtenergie gerät bei zu geringen Abstand aus dem Ruder



  • Hallo liebe Freunde der Grafikprogrammierung,

    mir ist beim Rendern einer Szene mit Radiosity aufgfallen, dass wenn zwei Patches (Flächen) zu nah zueinander sind, dann nimmt die Lichtenergie mit jeden Beleuchtungsschritt immer weiter zu bis ins unendliche.

    Um den Austausch der Lichtenergie zwischen zwei Flächen X und Y zu berechnen, verwende ich folgende Formel:

    1/(PI * r²) * cos wX * cos wY * dX * dY

    wX = Winkel zwischen Normale von X und Richtungsvektor zu Y
    dX = Flächeninhalt von X
    r = Abstand zwischen den Mittelpunkten zwischen X und Y

    Wenn nun r gegen 0 geht, da die beiden Flächen einen sehr geringen Abstand zueinander haben, dann teilt man also durch ein sehr geringen Wert und das Ergebnis ist eine große Zahl. Diese große Zahl dient dann als Faktor, womit die ausgesendete Energie von X multipliziert wird, und auf die eingehende Energie von Y addiert wird. Y bekommt somit sehr viel Energie.

    Diese viele Energie schießt es dann im nächsten Beleuchtungsschritt weiter in die Szene und es empfängt von X wieder eine große Packung Energie.

    Die Folge ist dann, das die Gesamtlichtmenge immer größer und größer wird.

    Hier ist ein Beispiel nach 6 Beleuchtungsschritten: Der Abstand zwischen der Schranktür und dem Schrank ist beim Schanier ganz klein:

    https://ibb.co/kfvCaQ

    Der rote Fleck an der Wand ist falsch.

    Wenn man sich die Gesamtlichmenge in der Szene als Zahl ansieht, dann sieht das so aus: (Jede Zeile ist ein Beleuchtungsschritt. Ich habe die Summe über alle Radiosity-Werte gebildet)

    1
    3
    39
    452
    5152
    59045
    671989
    7700635
    8,763998E+07
    1,004307E+09
    1,142989E+10
    1,309804E+11
    1,490672E+12
    1,708229E+13
    1,944114E+14
    2,227849E+15
    2,535487E+16
    2,905531E+17
    3,306749E+18
    3,789355E+19

    Bei einer anderen Szene, wo alle Patches genügend Abtand haben, sehen diese Zahlen so aus:

    19,03509
    19,35596
    19,92422
    20,06566
    20,13663
    20,16373
    20,1758
    20,18093
    20,18321
    20,18423
    20,18468
    20,18488
    20,18497
    20,18501
    20,18503
    20,18504
    20,18505
    20,18505
    20,18505
    20,18505

    Ich habe nun in mein Quellcode die Bedingung geschrieben, dass zwei Patches nicht näher als 0.01 sein dürfen. Nun sieht die Problemszene ok aus:

    https://ibb.co/gBpupk

    So. Das war die Einleitung. Kommen wir nun zu meiner Frage die sich daraus ergibt.

    Es erscheint mir physikalisch unlogisch, dass die Lichtenergie einfach nur deswegen ins unendlich geht, weil ich zwei Gegenstände ganz nah zueinander hinstelle. Wieso passiert das bei mein Algorithmus?

    Wenn ich den Lichtaustausch über das Hemicube-Verfahren berechne, dann fällt die dY und r²-Faktor weg und ich habe diese Problem erst garnicht. Nur leider hat das Verfahren wieder das Problem der begrenzenten Hemicube-Auflsöung:

    https://ibb.co/ezt9N5

    Hab ich ein Denkfehler, oder ist die Formel für das Fotometrische Grundgesetz

    https://de.wikipedia.org/wiki/Strahldichte#Fotometrisches_Grundgesetz

    einfach mal an der Stelle, wo durch r² geteilt wird etwas falsch bzw. hat Randbedingungen, die es in der Wirklichkeit nicht gibt?

    Vielleicht liegt der Fehler ja auch hier: Ich habe keine Prüfung, dass die Summe aller ausgesendeten Energie nicht größer sein darf, als die vorhandene Startenergie:

    foreach (var sender in patches)
                {
                    Vektor senderColor = sender.OutputRadiosity;
                    foreach (var empfänger in sender.ViewFaktors)
                    {
                        empfänger.Patch.InputRadiosity += senderColor * empfänger.Faktor * sender.SurvaceArea * empfänger.Patch.SurvaceArea;//Addiere Fluxwerte
                    }
                }
    

    Aber selbst wenn ich diese Bedingung hinschreiben wöllte. Ich wüßte garnicht, wie ich sie formulieren soll. Wenn das photometrische Grundgesetzt nunmal sagt, dass die Empfangsfläche 10 Photonen bekommen soll und ich habe aber nur noch 8, was mach ich dann?

    Ich verwende für den Sichbarkeitstest zwischen zwei Flächen immer genau 1 Sichtstrahl. Diese Information wird bestimmt für Rapso noch wichtig^^



  • Würde nicht einfach:

    if (r <= 1)
    {
    r= 1.0001;
    }

    das Problem lösen?



  • Ja dann ist das Problem weg. Das wäre ein ähnlicher Lösungsansatz wie bei mir gerade, wo ich nur den Austausch der Lichtenergie zwischen zwei Flächen betrachte, die weiter weg sind, als 0.001

    Die Sache ist nur, in der Wirklichkeit gibts doch kein Minimaldistanz, die ein Photon zurück legen muss.

    Was ich mir noch überlegt habe. Das fotometrische Grundgesetz ist ja so aufgebaut, dass man für eine Fläche sagt: Wie viel Prozent deiner Photonen sendest du zu einer anderen Fläche. Das Maximum wäre 100%.

    D.h. der Faktor 'empfänger.Faktor * sender.SurvaceArea * empfänger.Patch.SurvaceArea' darf nie größer als 1 werden.

    Nur was mach ich, wenn ich laut diesen Gesetz 60% zu Patch1 und 60% zu Patch2 senden soll? Die Summe aller Prozentwerte darf ja auch nicht 100% überschreiten.

    Ich glaube ich akzeptiere einfach, dass bei mir der Schrank in Flammen aufgeht, wenn er zur nah zur Wand steht^^ Ich erschaffe einfach meine eigene Physik^^


  • Mod

    XMAMan schrieb:

    mir ist beim Rendern einer Szene mit Radiosity aufgfallen, dass wenn zwei Patches (Flächen) zu nah zueinander sind, dann nimmt die Lichtenergie mit jeden Beleuchtungsschritt immer weiter zu bis ins unendliche.

    Das passiert bei der nährung mittels patches, jedoch bin ich mir nicht sicher, ob das schon an den stellen in deinem Bild passieren sollte.

    XMAMan schrieb:

    empfänger.Patch.InputRadiosity += senderColor * empfänger.Faktor * sender.SurvaceArea * empfänger.Patch.SurvaceArea;//Addiere Fluxwerte
    

    so wirkt das nicht richtig, wo ist r^2?

    XMAMan schrieb:

    Ich verwende für den Sichbarkeitstest zwischen zwei Flächen immer genau 1 Sichtstrahl. Diese Information wird bestimmt für Rapso noch wichtig^^

    das wird wichtig, aber erstmal schauen wir, ob deine grundlegenden berechnungen stimmen.



  • empfänger.Faktor berechnet sich wie folgt:

    Siehe unten:
    Faktor = geometryTerm * viewFactor

    Der 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.


  • Mod

    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.


  • Mod

    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).


  • Mod

    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.


  • Mod

    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) ?


Anmelden zum Antworten