Ciel de diffusion atmosphérique des artefacts spatiaux

20

Je suis en train d'implémenter la diffusion atmosphérique d'une planète depuis l'espace. J'ai utilisé les shaders de Sean O'Neil de http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html comme point de départ.

J'ai à peu près le même problème lié à fCameraAngle sauf avec le shader SkyFromSpace par opposition au shader GroundFromSpace comme ici: http://www.gamedev.net/topic/621187-sean-oneils-atmospheric-scattering/

Je reçois des artefacts étranges avec le ciel du shader spatial lorsque je ne les utilise pas fCameraAngle = 1dans la boucle intérieure. Quelle est la cause de ces artefacts? Les artefacts disparaissent lorsque fCameraAngle est limité à 1. Il me semble également ne pas avoir la teinte présente dans le bac à sable d'O'Neil ( http://sponeil.net/downloads.htm )

Position de la caméra X = 0, Y = 0, Z = 500. GroundFromSpace à gauche, SkyFromSpace à droite. entrez la description de l'image ici

Position de la caméra X = 500, Y = 500, Z = 500. GroundFromSpace à gauche, SkyFromSpace à droite. entrez la description de l'image ici

J'ai trouvé que l'angle de la caméra semble être géré très différemment selon la source:

Dans les shaders d'origine, l'angle de la caméra dans SkyFromSpaceShader est calculé comme suit:

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

Alors que dans le sol depuis l'espace shader, l'angle de la caméra est calculé comme suit:

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

Cependant, diverses sources en ligne bricolent avec la négation du rayon. Pourquoi est-ce?

Voici un projet C # Windows.Forms qui illustre le problème et que j'ai utilisé pour générer les images: https://github.com/ollipekka/AtmosphericScatteringTest/

Mise à jour: J'ai découvert grâce au projet ScatterCPU trouvé sur le site d'O'Neil que le rayon de la caméra est annulé lorsque la caméra est au-dessus du point ombré afin que la diffusion soit calculée d'un point à la caméra.

La modification de la direction des rayons supprime en effet les artefacts, mais introduit d'autres problèmes comme illustré ici:

Rayon négatif pour l'angle de la caméra

De plus, dans le projet ScatterCPU, O'Neil protège contre les situations où la profondeur optique de la lumière est inférieure à zéro:

float fLightDepth = Scale(fLightAngle, fScaleDepth);

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

Comme souligné dans les commentaires, avec ces nouveaux artefacts, cela laisse toujours la question, qu'est-ce qui ne va pas avec les images où la caméra est positionnée à 500, 500, 500? On dirait que le halo est concentré sur une partie complètement mauvaise de la planète. On pourrait s'attendre à ce que la lumière soit plus proche de l'endroit où le soleil devrait frapper la planète, plutôt que de l'endroit où elle change du jour à la nuit.

Le projet github a été mis à jour pour refléter les changements dans cette mise à jour.

ollipekka
la source
1
J'aimerais fouiller votre code et essayer d'aider, mais il semble installer XNA pour VS 2012 j'ai besoin de VS 2010 ...
J'ai supprimé toutes les références externes au projet et le projet n'a plus besoin de XNA. Il s'agit d'un simple projet Windows.Forms et il ne devrait pas avoir besoin de quelque chose de spécial pour s'exécuter. Par conséquent, la conversion vers une ancienne version de Visual Studio devrait être assez banale.
ollipekka
Parlez-vous des artefacts de pixels vers le centre de la sphère dans votre première image? Cela ne devrait pas vraiment affecter l'image finale. Le shader SkyFromSpace est censé être appliqué à une sphère à l'envers, donc seule la partie de l'atmosphère qui s'étend au-delà de la planète sera visible, tandis que le centre avec les artefacts sera caché derrière la planète. Cependant, l'ombrage du sol et du ciel semble à la recherche de l'appareil photo à 500 500 500 ... hmm

Réponses:

1

Je n'ai pas de code de travail en ce moment, car je fais la transition de mon moteur mais ce sont mes réglages de paramètres de travail:

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

C'était le 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;
    }

Faites-moi savoir si cela fonctionne toujours. Si vous avez besoin d'aide, je vais essayer de fouiller mon code. Je pense que j'ai utilisé deux sphères pour faire le rendu: une pour la surface et une pour l'atmosphère.

Le vagabond
la source
0

quelques pistes de réflexion: vérifiez la précision de vos flotteurs. aux échelles spatiales, la plupart du temps, float32 ne suffit pas. Vérifiez le tampon dpeth si vous avez un rendu primitif, comme une sphère sous votre shader de diffusion.

Ces artefacts peuvent également être trouvés dans le lancer de rayons, ce sont généralement des rayons secondaires qui se croisent avec la surface primaire tremblant à cause de problèmes de précision du flotteur.

EDIT: à 1000 (tous les entiers sont entièrement représentables jusqu'à 16 millions en représentation float32, grâce à la mantisse 24 bits), le nombre suivant pour un float32 est 1000.00006103, donc votre précision est toujours assez bonne dans cette plage.

cependant, si vous utilisiez des gammes de mètres, voir une planète à cette distance signifierait des valeurs de 100 000 000 et la suivante est de 100000008: 8 mètres de précision à 100 000 km.

cela provoquerait des sauts de caméra si vous essayiez de vous déplacer sur un satellite par exemple, et le rendu du satellite lui-même serait tout cassé si le zéro de votre monde était le centre de la planète. si c'est le centre du système stellaire, c'est encore pire.

recherchez flavien brebion (Ysaneya) et le jeu infinity quest for earth. Il a un journal de développement intéressant de gamedev et son forum où il explique comment les distances du système stellaire sont impossibles à gérer en utilisant des absolus.

Il mentionne également le problème du tampon de profondeur dans ce type de plages et est l'un des premiers, sinon le premier, à introduire des échelles z logarithmiques. http://www.gamedev.net/blog/73/entry-2006307-tip-of-the-day-logarithmic-zbuffer-artifacts-fix/ un beaucoup plus complet ici: http://outerra.blogspot.jp/ 2012/11 / maximizing-depth-buffer-range-and.html

Banc d'essai logiciel: bonne idée, c'est un excellent moyen de créer des shaders afin que vous puissiez déboguer ce qui se passe pas à pas. vérifiez simplement vos valeurs ligne par ligne, et si quelque chose vous semble bizarre, vous pouvez enquêter. Je n'ai pas vu dans le code que vous avez publié la partie où l'angle de la caméra est utilisé dans le shader, donc je suis un peu perplexe à propos de cette partie.

v.oddou
la source
Pourriez-vous expliquer ce que vous entendez par précision flottante? Les échelles utilisées dans l'exemple vont de -1000 à 1000. L'exemple est purement une implémentation logicielle pour le moment, où le résultat du shader est rendu sur une image puis affiché à l'aide de l'API c # System.Drawing, qui signifie que l'exemple n'utilise pas de primitives.
ollipekka