20
\$\begingroup\$

I am in the process of implementing atmospheric scattering of a planets from space. I have been using Sean O'Neil's shaders from http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html as a starting point.

I have pretty much the same problem related to fCameraAngle except with SkyFromSpace shader as opposed to GroundFromSpace shader as here: http://www.gamedev.net/topic/621187-sean-oneils-atmospheric-scattering/

I get strange artifacts with sky from space shader when not using fCameraAngle = 1 in the inner loop. What is the cause of these artifacts? The artifacts disappear when fCameraAngle is limtied to 1. I also seem to lack the hue that is present in O'Neil's sandbox (http://sponeil.net/downloads.htm)

Camera position X=0, Y=0, Z=500. GroundFromSpace on the left, SkyFromSpace on the right. enter image description here

Camera position X=500, Y=500, Z=500. GroundFromSpace on the left, SkyFromSpace on the right. enter image description here

I've found that the camera angle seems to handled very differently depending the source:

In the original shaders the camera angle in SkyFromSpaceShader is calculated as:

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

Whereas in ground from space shader the camera angle is calculated as:

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

However, various sources online tinker with negating the ray. Why is this?

Here is a C# Windows.Forms project that demonstrates the problem and that I've used to generate the images: https://github.com/ollipekka/AtmosphericScatteringTest/

Update: I have found out from the ScatterCPU project found on O'Neil's site that the camera ray is negated when the camera is above the point being shaded so that the scattering is calculated from point to the camera.

Changing the ray direction indeed does remove artifacts, but introduces other problems as illustrated here:

Negating ray for camera angle

Furthermore, in the ScatterCPU project, O'Neil guards against situations where optical depth for light is less than zero:

float fLightDepth = Scale(fLightAngle, fScaleDepth);

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

As pointed out in the comments, along with these new artifacts this still leaves the question, what is wrong with the images where camera is positioned at 500, 500, 500? It feels like the halo is focused on completely wrong part of the planet. One would expect that the light would be closer to the spot where the sun should hits the planet, rather than where it changes from day to night.

The github project has been updated to reflect changes in this update.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ I'd love to poke your code and try to help, but it appears to install XNA for VS 2012 I require VS 2010... \$\endgroup\$
    – user13213
    Mar 5, 2013 at 14:25
  • \$\begingroup\$ I removed all the external references to the project and the project does not need XNA anymore. It is a simple Windows.Forms project and it shouldn't need anything special to run. Hence it should be fairly trivial to convert to older Visual Studio version. \$\endgroup\$
    – ollipekka
    Mar 5, 2013 at 15:27
  • \$\begingroup\$ Are you talking about the pixel artifacts towards the center of the sphere in your first image? Those shouldn't really affect the final image. The SkyFromSpace shader is supposed to be applied to an inside-out sphere, so only the bit of the atmosphere that extends beyond the planet will be visible, while the center with the artifacts will be hidden behind the planet. However both the ground and sky shading look off for the camera at 500,500,500.....hmm \$\endgroup\$
    – user13213
    Mar 5, 2013 at 16:29

2 Answers 2

1
\$\begingroup\$

I don't have working code right now, as I am transitioning my engine but these were my working parameter settings:

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

This was the 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;
    }

Let me know if it still does work. If you need any other help I'll try to dig around my code. I think I used two spheres to do the rendering: one for the surface and one for the atmosphere.

\$\endgroup\$
0
\$\begingroup\$

some thought tracks : check the precision of your floats. at space scales, most all the times float32 is not enough. Check dpeth buffer if you have primitive rendering, like a sphere under your scattering shader.

These artifacts, can be found in raytracing as well, these are usually secondary rays intersection with the primary surface jittering from float precision issues.

EDIT: at 1000 (all integers are representible fully until 16 million in float32 representation, thanks to the 24 bits mantissa), the next number for a float32 is 1000.00006103 so your precision is still pretty good at this range.

however if you were to use meter ranges, to see a planet a this distance would mean values of 100,000,000 and the next is 100000008 : 8 meters of precision at 100,000km.

this would cause camera jumps if you were to try to move around a satelite for example, and the rendering of the satellite itself would bo all broken if the zero of your world is the center of the planet. if it is the center of the star system then it is even worse.

look up flavien brebion (Ysaneya) and the game infinity quest for earth. He has interesting dev journal of gamedev and his forum where he explains how star system distances are impossible to manage using absolutes.

He also mentions the depth buffer problem at those kind of ranges, and is one of the first, if not the first, to introduce logarithmic z scales. http://www.gamedev.net/blog/73/entry-2006307-tip-of-the-day-logarithmic-zbuffer-artifacts-fix/ a much more complete here: http://outerra.blogspot.jp/2012/11/maximizing-depth-buffer-range-and.html

Software test bed: good idea, it is a excellent way of authoring shaders so you can debug what is going on step by step. just check your values lines by lines, and if something look weird you can investigate. I didn't see in the code you posted the part where the camera angle is used in the shader, so I'm a bit puzzled about this part though.

\$\endgroup\$
1
  • \$\begingroup\$ Could you elaborate what you mean by the float precision? The scales that are being used in the example are from -1000 to 1000. The example is purely a software implementation at the moment, where there result of the shader is rendered to an image and then displayed using the c# System.Drawing API, which means that the example doesn't utilise primitives. \$\endgroup\$
    – ollipekka
    Oct 2, 2013 at 15:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .