大气伪影对大气的散射


20

我正在实现行星从太空的大气散射。我一直使用http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html中的Sean O'Neil的着色器作为起点。

除了SkyFromSpace着色器与GroundFromSpace着色器不同之外,我与fCameraAngle有几乎相同的问题,如下所示:http ://www.gamedev.net/topic/621187-sean-oneils-atmospheric-scattering/

当不在fCameraAngle = 1内部循环中使用时,我从太空着色器中获得了天空的奇异伪像。这些伪影的原因是什么?当fCameraAngle限制为1时,伪像消失。我似乎也缺少O'Neil沙箱(http://sponeil.net/downloads.htm)中的色相。

相机位置X = 0,Y = 0,Z = 500。左侧为GroundFromSpace,右侧为SkyFromSpace。 在此处输入图片说明

相机位置X = 500,Y = 500,Z = 500。左侧为GroundFromSpace,右侧为SkyFromSpace。 在此处输入图片说明

我发现,根据来源,相机角度似乎处理起来非常不同:

在原始着色器中,SkyFromSpaceShader中的摄影机角度计算为:

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

而在空间着色器的地面中,摄影机角度的计算公式为:

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

但是,在线上的各种消息来源都在修补射线。为什么是这样?

这是一个C#Windows.Forms项目,该项目演示了该问题,并且我已使用该项目来生成图像:https : //github.com/ollipekka/AtmosphericScatteringTest/

更新:我从O'Neil站点上的ScatterCPU项目中发现,当摄影机位于阴影点上方时,摄影机射线被否定,以便计算从点到摄影机的散射。

更改射线方向的确确实消除了伪影,但引入了其他问题,如下所示:

镜头角度的负射线

此外,在ScatterCPU项目中,O'Neil可以防止光线的光学深度小于零的情况:

float fLightDepth = Scale(fLightAngle, fScaleDepth);

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

正如评论中指出的那样,再加上这些新的伪像,这仍然留下了一个问题:将相机定位在500、500、500处的图像出了什么问题?感觉就像光环聚焦在地球上完全错误的部分上。人们会期望,光会更靠近太阳照射到地球的位置,而不是白天到黑夜变化的地方。

github项目已更新,以反映此更新中的更改。


1
我很想戳您的代码并尝试提供帮助,但似乎要为VS 2012安装XNA,我需要VS 2010 ...

我删除了对该项目的所有外部引用,并且该项目不再需要XNA。这是一个简单的Windows.Forms项目,不需要任何特殊的运行。因此,转换为较旧的Visual Studio版本应该相当简单。
ollipekka 2013年

您是在谈论第一张图像中朝向球心的像素伪像吗?这些不应该真正影响最终的形象。应该将SkyFromSpace着色器应用于由内而外的球体,因此只有延伸到地球之外的大气部分才可见,而带有伪像的中心将隐藏在行星的后面。但是,摄像机的地面和天空阴影看起来都在500,500,500 ..... hmm

Answers:


1

我正在转换引擎,目前没有工作代码,但这是我的工作参数设置:

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

这是着色器:

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

让我知道它是否仍然有效。如果您需要任何其他帮助,我将尝试深入研究我的代码。我想我使用了两个球体进行渲染:一个用于表面,一个用于大气。


0

一些思路:检查您的浮标的精度。在空间尺度上,大多数时候float32是不够的。检查dpeth缓冲区是否具有原始渲染,例如散射着色器下的球体。

这些伪影也可以在光线跟踪中找到,它们通常是辅助光线与浮点精度问题引起的主表面抖动相交。

编辑:在1000(float32表示形式中,所有整数都可以完全表示直到1600万,这要归功于24位尾数),float32的下一个数字是1000.00006103,因此您的精度在这个范围内仍然相当不错。

但是,如果要使用米范围,要看到一个行星,那么这个距离将意味着100,000,000,然后是100000008:在100,000 km处有8米的精度。

例如,如果您尝试绕卫星移动,这将导致相机跳动;如果您的世界的零是行星的中心,那么卫星本身的渲染将全部破裂。如果它是恒星系统的中心,那就更糟了。

查找黄蜂(Ysaneya)和对地球的游戏无限追求。他在gamedev的开发杂志上很有趣,在他的论坛上他解释了如何使用绝对值无法控制星系距离。

他还提到了在这种范围内的深度缓冲问题,并且是第一个(即使不是第一个)引入对数z尺度的人。 http://www.gamedev.net/blog/73/entry-2006307-tip-of-the-day-logarithmic-zbuffer-artifacts-fix/ 在此更为完整:http : //outerra.blogspot.jp/ 2012/11 / maximizing-depth-buffer-range-and.html

软件测试平台:好主意,这是编写着色器的绝佳方法,因此您可以逐步调试正在进行的工作。只需逐行检查您的价值观,如果看起来有些奇怪,您可以进行调查。我没有在代码中看到您在着色器中使用了相机角度的零件,因此我对此部分感到困惑。


您能详细说明一下浮动精度吗?该示例中使用的比例从-1000到1000。该示例目前仅是软件实现,其中将着色器的结果渲染到图像,然后使用c#System.Drawing API进行显示。表示该示例未使用原语。
ollipekka 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.