Atmosphärischer zerstreuender Himmel von den Raumartefakten

20

Ich bin dabei, die atmosphärische Streuung eines Planeten aus dem Weltraum zu implementieren. Ich habe Sean O'Neils Shader von http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html als Ausgangspunkt verwendet.

Ich habe so ziemlich das gleiche Problem mit fCameraAngle, außer mit SkyFromSpace-Shader im Gegensatz zu GroundFromSpace-Shader wie hier: http://www.gamedev.net/topic/621187-sean-oneils-atmospheric-scattering/

Ich bekomme seltsame Artefakte mit Sky vom Space Shader, wenn ich sie nicht fCameraAngle = 1in der inneren Schleife benutze . Was ist die Ursache für diese Artefakte? Die Artefakte verschwinden, wenn fCameraAngle auf 1 begrenzt ist. Mir scheint auch der Farbton zu fehlen, der in O'Neils Sandbox vorhanden ist ( http://sponeil.net/downloads.htm ).

Kameraposition X = 0, Y = 0, Z = 500. GroundFromSpace links, SkyFromSpace rechts. Bildbeschreibung hier eingeben

Kameraposition X = 500, Y = 500, Z = 500. GroundFromSpace links, SkyFromSpace rechts. Bildbeschreibung hier eingeben

Ich habe festgestellt, dass der Kamerawinkel je nach Quelle sehr unterschiedlich zu sein scheint:

In den ursprünglichen Shadern wird der Kamerawinkel in SkyFromSpaceShader wie folgt berechnet:

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

Während im Boden vom Space Shader der Kamerawinkel wie folgt berechnet wird:

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

Allerdings basteln verschiedene Quellen online am Negieren des Strahls. Warum ist das?

Hier ist ein C # Windows.Forms-Projekt, das das Problem veranschaulicht und mit dem ich die Bilder erstellt habe: https://github.com/ollipekka/AtmosphericScatteringTest/

Update: Ich habe aus dem ScatterCPU-Projekt auf O'Neils Site herausgefunden, dass der Kamerastrahl negiert wird, wenn sich die Kamera über dem zu schattierenden Punkt befindet, sodass die Streuung von Punkt zu Kamera berechnet wird.

Durch Ändern der Strahlrichtung werden zwar Artefakte entfernt, es treten jedoch andere Probleme auf, wie hier dargestellt:

Negierender Strahl für Kamerawinkel

Darüber hinaus schützt O'Neil im ScatterCPU-Projekt vor Situationen, in denen die optische Tiefe des Lichts unter Null liegt:

float fLightDepth = Scale(fLightAngle, fScaleDepth);

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

Wie in den Kommentaren erwähnt, bleibt neben diesen neuen Artefakten die Frage, was mit den Bildern, bei denen die Kamera auf 500, 500, 500 positioniert ist, nicht stimmt. Es fühlt sich an, als ob der Heiligenschein auf einen völlig falschen Teil des Planeten fokussiert ist. Man würde erwarten, dass das Licht näher an der Stelle ist, an der die Sonne auf den Planeten treffen sollte, als dort, wo es sich von Tag zu Nacht ändert.

Das Github-Projekt wurde aktualisiert, um Änderungen in diesem Update widerzuspiegeln.

ollipekka
quelle
1
Ich würde gerne Ihren Code
Ich habe alle externen Verweise auf das Projekt entfernt und das Projekt benötigt keine XNA mehr. Es handelt sich um ein einfaches Windows.Forms-Projekt, für dessen Ausführung nichts Besonderes erforderlich sein sollte. Daher sollte es ziemlich trivial sein, auf eine ältere Visual Studio-Version zu konvertieren.
ollipekka
Sprechen Sie über die Pixelartefakte in Richtung der Kugelmitte in Ihrem ersten Bild? Diese sollten das endgültige Image nicht wirklich beeinträchtigen. Der SkyFromSpace-Shader soll auf eine Inside-Out-Kugel angewendet werden, sodass nur der Teil der Atmosphäre sichtbar ist, der sich über den Planeten hinaus erstreckt, während das Zentrum mit den Artefakten hinter dem Planeten verborgen bleibt. Doch sowohl der Boden und Himmel Shading Look off für die Kamera auf 500.500.500 ..... hmm

Antworten:

1

Ich habe momentan keinen Arbeitscode, da ich meinen Motor umstelle, aber dies waren meine Arbeitsparametereinstellungen:

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

Das war der Shader:

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;
    }

Lassen Sie mich wissen, ob es noch funktioniert. Wenn Sie weitere Hilfe benötigen, versuche ich, meinen Code zu durchsuchen. Ich glaube, ich habe für das Rendern zwei Kugeln verwendet: eine für die Oberfläche und eine für die Atmosphäre.

Der Wanderer
quelle
0

einige gedankenspuren: überprüfe die präzision deiner schwimmer. Bei Weltraumskalen reicht float32 meist nicht aus. Überprüfen Sie dpeth buffer, wenn Sie primitives Rendering haben, wie eine Kugel unter Ihrem Scattering-Shader.

Diese Artefakte können auch im Raytracing gefunden werden. Dabei handelt es sich normalerweise um Sekundärstrahlen, die sich mit dem primären Oberflächenjitter aufgrund von Problemen mit der Genauigkeit des Floats schneiden.

BEARBEITEN: bei 1000 (alle ganzen Zahlen sind dank der 24-Bit-Mantisse bis 16 Millionen in float32-Darstellung vollständig darstellbar) lautet die nächste Zahl für float32 1000.00006103, sodass Ihre Genauigkeit in diesem Bereich immer noch ziemlich gut ist.

Wenn Sie jedoch Messbereiche verwenden würden, würde ein Planet in dieser Entfernung Werte von 100.000.000 und der nächste von 100.000.0008 bedeuten: 8 Meter Genauigkeit bei 100.000 km.

Dies würde zu Kamerasprüngen führen, wenn Sie beispielsweise versuchen, sich auf einem Satelliten zu bewegen, und das Rendering des Satelliten selbst würde völlig kaputt gehen, wenn die Null Ihrer Welt das Zentrum des Planeten ist. Wenn es das Zentrum des Sternensystems ist, ist es noch schlimmer.

Suche nach Flavien Brebion (Ysaneya) und dem Spiel Infinity Quest for Earth. Er hat ein interessantes Entwicklerjournal von Gamedev und sein Forum, in dem er erklärt, wie Sternensystementfernungen mit Absoluten unmöglich zu bewältigen sind.

Er erwähnt auch das Tiefenpufferproblem in solchen Bereichen und ist einer der ersten, wenn nicht der erste, der logarithmische Z-Skalen einführt. http://www.gamedev.net/blog/73/entry-2006307-tip-of-the-day-logarithmic-zbuffer-artifacts-fix/ viel vollständiger hier: http://outerra.blogspot.jp/ 2012/11 / Maximierung-Tiefe-Puffer-Bereich-and.html

Softwaretestumgebung: Gute Idee, es ist eine hervorragende Möglichkeit, Shader zu erstellen, damit Sie Schritt für Schritt debuggen können, was gerade vor sich geht. Überprüfen Sie einfach Ihre Werte Zeile für Zeile, und wenn etwas komisch aussieht, können Sie nachforschen. Ich habe in dem Code, den Sie gepostet haben, den Teil nicht gesehen, in dem der Kamerawinkel im Shader verwendet wird, daher bin ich über diesen Teil ein bisschen verwirrt.

v.oddou
quelle
Könnten Sie näher erläutern, was Sie mit der Präzision des Schwimmers meinen? Die Skalen, die im Beispiel verwendet werden, reichen von -1000 bis 1000. Das Beispiel ist derzeit nur eine Softwareimplementierung, bei der das Ergebnis des Shaders in ein Bild gerendert und dann mit der c # System.Drawing-API angezeigt wird bedeutet, dass im Beispiel keine Grundelemente verwendet werden.
ollipekka