2011-12-09

Tłumienie światła w ośrodku

Promień światła rozchodzący się w danym ośrodku traci swoją energię poprzez pochłanianie i rozproszenie. Możliwa jest także reemisja którą tutaj się nie zajmujemy. Rozproszeniem w sensie pojawianiem się promieni rozproszenia i oświetlaniem przez nie sceny też tutaj się nie zajmujemy. Spróbujemy zasymulować utratę energii przez promień biegnący przez dany ośrodek poprzez jego pochłanianie.

Jeśli założymy że energia promienia na każdy przebyty odcinek dystansu maleje zawsze o tyle samo, niezależnie od wartości tej energii to energia maleje w sposób ekspotencjalny. Inaczej mówiąc poniższe równanie jest rozwiązaniem równania różniczkowego $\displaystyle\frac{dC_{out}}{dx}=a$.

$C_{OUT} = C*e^{-ax}$, gdzie x to dystans, a to współczynnik tłumienia ośrodka.

Latwo zauważyć, że kolor dla $x=0$ nie jest zmieniamy i jego energia maleje do zera wraz z przebytym dystansem. Dla RGB współczynnik a jest trójką liczb.

Inne podejście zastosowano w OpenGL. Tam wartość tłumienia określa się jako:

$T=1/(Ax^2+Bx+C)$

Jeśli chcemy by dla $x=1$ T wynosiło 1 to $C=1$. Jeśli $C>1$ to definiujemy coś co możemy nazwać energią pochłoniętą zaraz na dzień dobry. W sytuacjach odbicia albo refleksji możemy uznać to za energie pochłoniętą podczas tych zjawisk. Ja generalnie energię absorbowaną podczas odbicia albo załamania określam gdzie indziej. Dalej chcemy by $T<1$ dla $X>0$, czyli $A>0$. Poza tym raczej nie chcemy by dla $X>0$ T w jakimkolwiek przedziale malało. Czyli biorąc pod uwagę przesuniętą o -C parabole $Ax^2+Bx$, jedno miejsce zerowe wypadnie w $X=0$, drugie zaś powinno być po ujemnej stronie X, czyli $B>0$. Takie warunki zapewniają nam, że energia z biegiem promienia nigdy nie rośnie.

Powyższym współczynnikom także możemy nadać sens fizyczny. A to energia tracona przez promień na skutek zmniejszającego się strumienia energii wraz ze wzrostem promienia sfery, której środek to punkt emisji. Energia zsumowana po takiej sferze w najlepszym wypadku powinna pozostać bez zmian. B to liniowa utrata energii przez promień, np. na skutek rozproszenia, pochłaniania. Zaś możliwe znaczenie C zostało podane wcześniej.

Ważna cechą obu metod jest to, że jeśli światło przechodzi przez wiele ośrodków, to nie ma znaczenia od której strony liczymy końcową wartość energii jaka dociera do kamery. Innymi słowy możemy liczyć bieg promienia od kamery do światła i po drodze kalkulować efektywny współczynnik tłumienia.

W przypadku gdy nasza scena może znacznie różnić się wymiarami, tzn może być bardzo duża albo bardzo mała we znaki daje się kwestia błędów numerycznych. Najprostszym rozwiązaniem jest normalizacja sceny, a także wyliczanie intersekcji z obiektami w lokalnym znormalizowanym układzie współrzędnych. Kiedy normalizujemy scenę zmieniamy jej wymiary, zmienią się więc odległości przebywane przez promienie i ich kolory - jeśli podlegają one tłumieniu. Musimy wprowadzić dodatkowy czynnik kompensujący. Najlepiej nazwać go prędkością światła. Wraz z zmniejszeniem sceny, chcemy by światło biegło wolniej - wtedy w tej samej jednostce czasu światło przebędzie taką samą drogę jak w scenie powiększonej. Czyli prędkość światła jest proporcjonalna do skali. W naszych równaniach tłumienia X zastąpmy czasem $t=X/V$. Jeśli nasza scena zmalała dwukrotnie, prędkość światła podobnie widzimy, że czas przebycia danego dystansu jest niezmienny.

Takie równania tłumienia są tylko pewnym przybliżeniem rzeczywistości, ale pozwalają dość realistycznie oddać efekt przechodzenia światła przez kolorowe ośrodki, rozpraszanie energii na dalekich odległościach w powietrzu.

Przykład implementacji:
public abstract class Attenuation
{
    public abstract void CalculateAttenuation(float a_light_speed);
    public abstract ColorFloat Attenuate(ColorFloat a_light, float a_dist);
}

public class PolynomialAttenuation : Attenuation
{
    public ColorFloat A;
    public ColorFloat B;
    public ColorFloat C;
    private ColorFloat m_real_a;
    private ColorFloat m_real_b;

    public override ColorFloat Attenuate(ColorFloat a_light, float a_dist)
    {
        return a_light / (m_real_a * a_dist * a_dist + m_real_b * a_dist + C);
    }

    public override void CalculateAttenuation(float a_light_speed)
    {
        Debug.Assert(A >= ColorFloat.Black);
        Debug.Assert(B >= ColorFloat.Black);
        Debug.Assert(C >= ColorFloat.White);

        m_real_a = A / a_light_speed / a_light_speed;
        m_real_b = B / a_light_speed;
    }
}

public class ExponentalAttenuation : Attenuation
{
    private ColorFloat m_real_alpha;
    public ColorFloat Alpha;

    public override void CalculateAttenuation(float a_light_speed)
    {
        m_real_alpha = Alpha / a_light_speed;
    }

    public override ColorFloat Attenuate(ColorFloat a_light, float a_dist)
    {
        return new ColorFloat(
            a_light.R * (float)Math.Exp(-a_dist * m_real_alpha.R),
            a_light.G * (float)Math.Exp(-a_dist * m_real_alpha.G),
            a_light.B * (float)Math.Exp(-a_dist * m_real_alpha.B));
    }
}

Brak komentarzy:

Prześlij komentarz