Cielo di dispersione atmosferica da artefatti spaziali


20

Sto implementando lo scattering atmosferico di un pianeta dallo spazio. Ho usato gli shader di Sean O'Neil da http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html come punto di partenza.

Ho praticamente lo stesso problema relativo a fCameraAngle tranne che con lo shader SkyFromSpace rispetto allo shader GroundFromSpace come qui: http://www.gamedev.net/topic/621187-sean-oneils-atmospher-scattering/

Ottengo strani artefatti con il cielo dallo space shader quando non lo uso fCameraAngle = 1nel circuito interno. Qual è la causa di questi artefatti? Gli artefatti scompaiono quando fCameraAngle è limitato a 1. Mi sembra anche che manchi la tonalità presente nella sandbox di O'Neil ( http://sponeil.net/downloads.htm )

Posizione della telecamera X = 0, Y = 0, Z = 500. GroundFromSpace a sinistra, SkyFromSpace a destra. inserisci qui la descrizione dell'immagine

Posizione della telecamera X = 500, Y = 500, Z = 500. GroundFromSpace a sinistra, SkyFromSpace a destra. inserisci qui la descrizione dell'immagine

Ho scoperto che l'angolazione della telecamera sembra gestita in modo molto diverso a seconda della fonte:

Negli shader originali l'angolo della telecamera in SkyFromSpaceShader viene calcolato come:

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

Mentre nel terreno dallo space shader l'angolo della telecamera viene calcolato come:

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

Tuttavia, varie fonti online armeggiano con la negazione del raggio. Perchè è questo?

Ecco un progetto Windows.Forms in C # che dimostra il problema e che ho usato per generare le immagini: https://github.com/ollipekka/AtmospherScatteringTest/

Aggiornamento: ho scoperto dal progetto ScatterCPU trovato sul sito di O'Neil che il raggio della videocamera viene negato quando la videocamera si trova sopra il punto in ombra, in modo che la dispersione venga calcolata dal punto alla videocamera.

La modifica della direzione del raggio rimuove effettivamente gli artefatti, ma introduce altri problemi come illustrato qui:

Raggio negativo per l'angolazione della telecamera

Inoltre, nel progetto ScatterCPU, O'Neil protegge da situazioni in cui la profondità ottica della luce è inferiore a zero:

float fLightDepth = Scale(fLightAngle, fScaleDepth);

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

Come sottolineato nei commenti, insieme a questi nuovi artefatti questo lascia ancora la domanda, cosa c'è di sbagliato con le immagini in cui la fotocamera è posizionata a 500, 500, 500? Sembra che l'aureola sia focalizzata su una parte del pianeta completamente sbagliata. Ci si aspetterebbe che la luce sarebbe più vicina al punto in cui il sole dovrebbe colpire il pianeta, piuttosto che dove cambia da giorno a notte.

Il progetto github è stato aggiornato per riflettere le modifiche in questo aggiornamento.


1
Mi piacerebbe inserire il tuo codice e provare ad aiutarti, ma sembra che per installare XNA per VS 2012 richiedo VS 2010 ...

Ho rimosso tutti i riferimenti esterni al progetto e il progetto non ha più bisogno di XNA. È un semplice progetto Windows.Forms e non dovrebbe aver bisogno di nulla di speciale per essere eseguito. Quindi dovrebbe essere abbastanza banale convertire nella versione precedente di Visual Studio.
ollipekka,

Stai parlando degli artefatti pixel verso il centro della sfera nella tua prima immagine? Questi non dovrebbero davvero influire sull'immagine finale. Lo shader SkyFromSpace dovrebbe essere applicato a una sfera dentro e fuori, quindi sarà visibile solo un po 'dell'atmosfera che si estende oltre il pianeta, mentre il centro con gli artefatti sarà nascosto dietro il pianeta. Comunque sia il ground che il sky shading guardano fuori per la fotocamera a 500.500.500 ..... hmm

Risposte:


1

Non ho un codice funzionante in questo momento, poiché sto passando il mio motore ma queste erano le mie impostazioni dei parametri di lavoro:

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

Questo era lo 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;
    }

Fammi sapere se funziona ancora. Se hai bisogno di altro aiuto, proverò a cercare il mio codice. Penso di aver usato due sfere per eseguire il rendering: una per la superficie e una per l'atmosfera.


0

alcune tracce pensiero: controlla la precisione dei tuoi galleggianti. alle scale spaziali, la maggior parte delle volte float32 non è sufficiente. Controlla dpeth buffer se hai un rendering primitivo, come una sfera sotto il tuo shader di scattering.

Questi artefatti possono essere trovati anche nel raytracing, in genere si tratta di intersezioni di raggi secondari con il jitter della superficie primaria dovuto a problemi di precisione del galleggiante.

EDIT: a 1000 (tutti i numeri interi sono rappresentabili fino a 16 milioni nella rappresentazione float32, grazie alla mantissa a 24 bit), il numero successivo per un float32 è 1000.00006103, quindi la tua precisione è ancora abbastanza buona in questo intervallo.

tuttavia, se si dovessero usare intervalli di metri, vedere un pianeta a questa distanza significherebbe valori di 100.000.000 e il prossimo è 100000008: 8 metri di precisione a 100.000 km.

ciò provocherebbe salti della telecamera se, ad esempio, provassi a muoverti attorno a un satellite e il rendering del satellite stesso si spezzerebbe se lo zero del tuo mondo è il centro del pianeta. se è il centro del sistema stellare, allora è anche peggio.

cercare flavien brebion (Ysaneya) e la ricerca infinita del gioco per la terra. Ha un interessante diario di sviluppo di gamedev e del suo forum in cui spiega come le distanze del sistema stellare siano impossibili da gestire usando gli assoluti.

Cita anche il problema del buffer di profondità in quel tipo di intervalli ed è uno dei primi, se non il primo, a introdurre scale z logaritmiche. http://www.gamedev.net/blog/73/entry-2006307-tip-of-the-day-logarithmic-zbuffer-artifacts-fix/ molto più completo qui: http://outerra.blogspot.jp/ 2012/11 / massimizzando approfondita-tampone-gamma-and.html

Banco di prova software: buona idea, è un modo eccellente di creare shader in modo da poter eseguire il debug di ciò che sta accadendo passo dopo passo. controlla i tuoi valori riga per riga e se qualcosa sembra strano puoi indagare. Non ho visto nel codice che hai pubblicato la parte in cui viene utilizzato l'angolazione della telecamera nello shader, quindi sono un po 'perplesso su questa parte.


Potresti elaborare cosa intendi per precisione del galleggiante? Le scale che vengono utilizzate nell'esempio vanno da -1000 a 1000. L'esempio è puramente un'implementazione software al momento, in cui il risultato dello shader viene renderizzato su un'immagine e quindi visualizzato utilizzando l'API System # c.D Systeming, che significa che l'esempio non utilizza primitivi.
ollipekka,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.