Atmosferyczne niebo rozpraszające z kosmicznych artefaktów


20

Jestem w trakcie wdrażania rozpraszania atmosferycznego planet z kosmosu. Korzystam z shaderów Seana O'Neila z http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html jako punktu wyjścia.

Mam prawie ten sam problem związany z fCameraAngle, z wyjątkiem modułu cieniującego SkyFromSpace w przeciwieństwie do modułu cieniującego GroundFromSpace, jak tutaj: http://www.gamedev.net/topic/621187-sean-oneils-atmospheric-scattering/

Dziwne artefakty z nieba pojawiają się z modułu cieniującego, gdy nie są używane fCameraAngle = 1w wewnętrznej pętli. Co jest przyczyną tych artefaktów? Artefakty znikają, gdy fCameraAngle zostanie ograniczone do 1. Wydaje mi się, że brakuje mi też odcienia obecnego w piaskownicy O'Neila ( http://sponeil.net/downloads.htm )

Pozycja kamery X = 0, Y = 0, Z = 500. GroundFromSpace po lewej, SkyFromSpace po prawej. wprowadź opis zdjęcia tutaj

Pozycja kamery X = 500, Y = 500, Z = 500. GroundFromSpace po lewej, SkyFromSpace po prawej. wprowadź opis zdjęcia tutaj

Przekonałem się, że kąt kamery wydaje się różnie traktowany w zależności od źródła:

W oryginalnych modułach cieniujących kąt kamery w SkyFromSpaceShader jest obliczany jako:

float fCameraAngle = dot(v3Ray, v3SamplePoint) / fHeight;

Natomiast w ziemi z modułu cieniującego kąt kamery oblicza się jako:

float fCameraAngle = dot(-v3Ray, v3Pos) / length(v3Pos);

Jednak różne źródła online majstrują przy negacji promienia. Dlaczego to?

Oto projekt C # Windows.Forms, który demonstruje problem i użyłem go do generowania obrazów: https://github.com/ollipekka/AtmosphericScatteringTest/

Aktualizacja: dowiedziałem się z projektu ScatterCPU znalezionego na stronie O'Neila, że ​​promień kamery jest negowany, gdy kamera znajduje się powyżej punktu cieniowanego, dzięki czemu rozproszenie jest obliczane od punktu do kamery.

Zmiana kierunku promienia rzeczywiście usuwa artefakty, ale wprowadza inne problemy, jak pokazano tutaj:

Promień negujący kąt kamery

Ponadto w projekcie ScatterCPU O'Neil chroni przed sytuacjami, w których głębokość optyczna światła jest mniejsza niż zero:

float fLightDepth = Scale(fLightAngle, fScaleDepth);

if (fLightDepth < float.Epsilon)
{
    continue;
}

Jak wskazano w komentarzach, wraz z tymi nowymi artefaktami wciąż pozostaje pytanie, co jest nie tak z obrazami, na których aparat jest ustawiony na 500, 500, 500? Wydaje się, że halo koncentruje się na całkowicie niewłaściwej części planety. Można się spodziewać, że światło będzie bliżej miejsca, w którym słońce powinno uderzyć w planetę, a nie w miejsce, w którym zmienia się z dnia na noc.

Projekt github został zaktualizowany, aby odzwierciedlić zmiany w tej aktualizacji.


1
Chciałbym wbić kod i spróbować pomóc, ale wygląda na to, że instaluję XNA dla VS 2012 Potrzebuję VS 2010 ...

Usunąłem wszystkie zewnętrzne odniesienia do projektu i projekt nie potrzebuje już XNA. Jest to prosty projekt Windows.Forms i nie powinien wymagać niczego specjalnego do uruchomienia. Dlatego konwersja do starszej wersji programu Visual Studio powinna być dość trywialna.
ollipekka

Czy na pierwszym zdjęciu mówisz o artefaktach pikseli w kierunku środka kuli? Nie powinny one tak naprawdę wpływać na ostateczny obraz. Moduł cieniujący SkyFromSpace ma zostać nałożony na wewnętrzną kulę, więc widoczny będzie tylko fragment atmosfery rozciągający się poza planetę, podczas gdy centrum z artefaktami będzie ukryte za planetą. Jednak zarówno cieniowanie ziemi, jak i nieba wygląda na 500 500 500 ..... hmm

Odpowiedzi:


1

Obecnie nie mam działającego kodu, ponieważ zmieniam silnik, ale były to moje parametry pracy:

// Inited in code
float innerRadius = sphere.Radius;
float outerRadius = innerRadius*1.025f;
float scale = 1.0f/(outerRadius - innerRadius);
float scaleDepth = outerRadius - innerRadius;
float scaleOverScaleDepth = scale/scaleDepth;

Vector4 invWavelength = new Vector4(
    (float) (1.0/Math.Pow(wavelength.X, 4.0)),
    (float) (1.0/Math.Pow(wavelength.Y, 4.0)),
    (float) (1.0/Math.Pow(wavelength.Z, 4.0)),
    1);

float ESun = 15.0f;
float kr = 0.0025f;
float km = 0.0015f;
float g = -0.95f;
float g2 = g * g;
float krESun = kr * ESun;
float kmESun = km * ESun;
float epkr4Pi = epkr4Pi = (float)(kr * 4 * Math.PI)
float epkm4Pi = epkr4Pi = (float)(kr * 4 * Math.PI)

To był moduł cieniujący:

struct AtmosphereVSOut
{
    float4 Position : POSITION;
    float3 t0 : TEXCOORD0;
    float3 c0 : TEXCOORD1; // The Rayleigh color
    float3 c1 : TEXCOORD2; // The Mie color
    float4 LightDirection : TEXCOORD3;
};

// The scale equation calculated by Vernier's Graphical Analysis
float expScale (float fCos)
{
    //float x = 1.0 - fCos;
    float x = 1 - fCos;
    return scaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));

}
// Calculates the Mie phase function
float getMiePhase(float fCos, float fCos2, float g, float g2)
{
    return 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + fCos2) / pow(1.0 + g2 - 2.0*g*fCos, 1.5);
}

// Calculates the Rayleigh phase function
float getRayleighPhase(float fCos2)
{
    return 0.75 + (1.0 + fCos2);
}

// Returns the near intersection point of a line and a sphere
float getNearIntersection(float3 vPos, float3 vRay, float fDistance2, float fRadius2)
{
    float B = 2.0 * dot(vPos, vRay);
    float C = fDistance2 - fRadius2;
    float fDet = max(0.0, B*B - 4.0 * C);
    return 0.5 * (-B - sqrt(fDet));
}

AtmosphereVSOut
AtmosphereFromSpaceVS(float4 vPos : POSITION )
{
    // Multiply the camera position vector in world space by the 
    // World Inverse matrix so that it gets transformed to
    // object space coordinates
    float4 vEyePosInv = mul(vEyePos, mWorldInverse);

    // Compute a ray from the vertex to the camera position
    float3 vRay = vPos - vEyePosInv.xyz;

    // Transform the Light Position to object space and use
    // the result to get a ray from the position of the light
    // to the vertex. This is our light direction vector
    // which has to be normalized.
    float4 vLightDir = mul(vLightPosition,mWorldInverse) - vPos;
    vLightDir.xyz = normalize(vLightDir.xyz);
    vLightDir.w = 1.0;

    // From the vRay vector we can calculate the 
    // "far" intersection with the sphere
    float fFar = length (vRay);
    vRay /= fFar;

    // But we have to check if this point is obscured by the planet
    float B = 2.0 * dot(vEyePosInv, vRay);
    float C = cameraHeight2 - (innerRadius*innerRadius);
    float fDet = (B*B - 4.0 * C);

    if (fDet >= 0)
    {
        // compute the intersection if so
        fFar = 0.5 * (-B - sqrt(fDet));
    }

    // Compute the near intersection with the outer sphere
    float fNear = getNearIntersection (vEyePosInv, vRay, cameraHeight2, outerRadius2);

    // This is the start position from which to compute how
    // the light is scattered
    float3 vStart = vEyePosInv + vRay * fNear;
    fFar -= fNear;

    float fStartAngle = dot (vRay, vStart) / outerRadius;
    float fStartDepth = exp (scaleOverScaleDepth * (innerRadius - cameraHeight));
    float fStartOffset = fStartDepth * expScale (fStartAngle);
    float fSampleLength = fFar / samples;
    float fScaledLength = fSampleLength * scale;
    float3 vSampleRay = vRay * fSampleLength;
    float3 vSamplePoint = vStart + vSampleRay * 0.5f;

    // Now we have to compute each point in the path of the
    // ray for which scattering occurs. The higher the number
    // of samples the more accurate the result.
    float3 cFrontColor = float3 (0,0,0);
    for (int i = 0; i < samples; i++)
    {
        float fHeight = length (vSamplePoint);
        float fDepth = exp (scaleOverScaleDepth * (innerRadius - fHeight));
        float fLightAngle = dot (vLightDir, vSamplePoint) / fHeight;
        float fCameraAngle = dot(-vRay, vSamplePoint) / fHeight;
        float fScatter = (fStartOffset + fDepth * (expScale (fLightAngle) - expScale (fCameraAngle)));

        float3 cAttenuate = exp (-fScatter * (vInvWavelength.xyz * kr4PI + km4PI));

        cFrontColor += cAttenuate * (fDepth * fScaledLength);
        vSamplePoint += vSampleRay;
    }

    // Compute output values
    AtmosphereVSOut Out;

    // Compute a ray from the camera position to the vertex
    Out.t0 = vEyePos.xyz - vPos.xyz;

    // Compute the position in clip space
    Out.Position = mul(vPos, mWorldViewProj);

    // Compute final Rayleigh and Mie colors
    Out.c0.xyz = cFrontColor * (vInvWavelength.xyz * krESun);
    Out.c1.xyz = cFrontColor * kmESun;

    // Pass the light direction vector along to the pixel shader
    Out.LightDirection = vLightDir;

    return Out;
}

PSOut
AtmosphereFromSpacePS(AtmosphereVSOut In)
{
    PSOut Out;

    float cos = saturate(dot (In.LightDirection, In.t0) / length (In.t0));
    float cos2 = cos*cos;

    float fMiePhase = getMiePhase(cos,cos2,g,g2);
    float fRayleighPhase = getRayleighPhase(cos2);

    float exposure = 2.0;
    Out.color.rgb = 1.0 - exp(-exposure * (fRayleighPhase * In.c0 + fMiePhase * In.c1));
    Out.color.a = Out.color.b;

    return Out;
    }

Daj mi znać, jeśli nadal działa. Jeśli potrzebujesz innej pomocy, postaram się przekopać mój kod. Wydaje mi się, że do renderowania użyłem dwóch sfer: jednej dla powierzchni i jednej dla atmosfery.


0

kilka pomysłów: sprawdź dokładność swoich pływaków. w skalach kosmicznych większość czasu float32 to za mało. Sprawdź bufor dpeth, jeśli masz prymitywne renderowanie, takie jak kula pod shaderem rozpraszającym.

Te artefakty można również znaleźć w śledzeniu promieni, są to zwykle przecięcia promieni wtórnych z drżeniem pierwotnej powierzchni z powodu problemów z precyzją ruchu.

EDYCJA: przy 1000 (wszystkie liczby całkowite są w pełni reprezentatywne do 16 milionów w reprezentacji float32, dzięki 24-bitowej mantysie), kolejna liczba dla float32 to 1000.00006103, więc twoja precyzja jest nadal całkiem dobra w tym zakresie.

jeśli jednak użyjesz zasięgów metrów, aby zobaczyć planetę, odległość ta oznaczałaby wartości 100 000 000, a następna to 100000008: 8 metrów precyzji na 100 000 km.

spowodowałoby to skoki kamery, gdybyś próbował na przykład poruszać się po satelicie, a rendering samego satelity zostałby przerwany, jeśli zero twojego świata jest centrum planety. jeśli jest to środek układu gwiezdnego, jest jeszcze gorzej.

wyszukaj Flavien Brebion (Ysaneya) i poszukiwanie nieskończoności gry dla Ziemi. Ma ciekawy dziennik deweloperów gamedev i swoje forum, na których wyjaśnia, w jaki sposób nie można zarządzać odległościami między systemami gwiezdnymi za pomocą absolutów.

Wspomina także o problemie bufora głębokości przy tego rodzaju zakresach i jest jednym z pierwszych, jeśli nie pierwszym, który wprowadza logarytmiczne skale z. http://www.gamedev.net/blog/73/entry-2006307-tip-of-the-day-logarithmic-zbuffer-artifacts-fix/ wiele więcej tutaj: http://outerra.blogspot.jp/ 2012/11 / maksymalizacja-głębokość-bufor-zakres-i.html

Oprogramowanie testowe: dobry pomysł, jest to doskonały sposób na tworzenie shaderów, dzięki czemu możesz debugować to, co dzieje się krok po kroku. po prostu sprawdzaj wartości po linii, a jeśli coś wygląda dziwnie, możesz to sprawdzić. Nie widziałem w kodzie, w którym opublikowałeś część, w której kąt cieniowania jest używany w module cieniującym, więc jestem nieco zdziwiony tą częścią.


Czy mógłbyś wyjaśnić, co rozumiesz przez precyzję pływaka? Skale używane w tym przykładzie wynoszą od -1000 do 1000. Przykład jest w tej chwili czysto implementacją oprogramowania, w której wynik modułu cieniującego jest renderowany na obraz, a następnie wyświetlany za pomocą interfejsu API systemu c # System.Drawing, który oznacza, że ​​w przykładzie nie są używane elementy pierwotne.
ollipekka
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.