Cielo disperso atmosférico de artefactos espaciales

20

Estoy en el proceso de implementar la dispersión atmosférica de planetas desde el espacio. He estado usando los sombreadores de Sean O'Neil de http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html como punto de partida.

Tengo casi el mismo problema relacionado con fCameraAngle, excepto con el sombreador SkyFromSpace en lugar del sombreador GroundFromSpace que aquí: http://www.gamedev.net/topic/621187-sean-oneils-atmospheric-scattering/

Obtengo artefactos extraños con el cielo del sombreador espacial cuando no lo uso fCameraAngle = 1en el bucle interno. ¿Cuál es la causa de estos artefactos? Los artefactos desaparecen cuando fCameraAngle se limita a 1. También parece que me falta el tono que está presente en la caja de arena de O'Neil ( http://sponeil.net/downloads.htm )

Posición de la cámara X = 0, Y = 0, Z = 500. GroundFromSpace a la izquierda, SkyFromSpace a la derecha. ingrese la descripción de la imagen aquí

Posición de la cámara X = 500, Y = 500, Z = 500. GroundFromSpace a la izquierda, SkyFromSpace a la derecha. ingrese la descripción de la imagen aquí

Descubrí que el ángulo de la cámara parece manejarse de manera muy diferente dependiendo de la fuente:

En los sombreadores originales, el ángulo de la cámara en SkyFromSpaceShader se calcula como:

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

Mientras que en el suelo desde el sombreador espacial, el ángulo de la cámara se calcula como:

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

Sin embargo, varias fuentes en línea juegan con negar el rayo. ¿Por qué es esto?

Aquí hay un proyecto de C # Windows.Forms que demuestra el problema y que he utilizado para generar las imágenes: https://github.com/ollipekka/AtmosphericScatteringTest/

Actualización: descubrí por el proyecto ScatterCPU encontrado en el sitio de O'Neil que el rayo de la cámara se niega cuando la cámara está por encima del punto sombreado, de modo que la dispersión se calcula de un punto a la cámara.

Cambiar la dirección del rayo de hecho elimina los artefactos, pero introduce otros problemas como se ilustra aquí:

Rayo de negación para ángulo de cámara

Además, en el proyecto ScatterCPU, O'Neil protege contra situaciones en las que la profundidad óptica de la luz es inferior a cero:

float fLightDepth = Scale(fLightAngle, fScaleDepth);

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

Como se señaló en los comentarios, junto con estos nuevos artefactos esto todavía deja la pregunta, ¿qué hay de malo en las imágenes donde la cámara está posicionada en 500, 500, 500? Parece que el halo está enfocado en una parte completamente equivocada del planeta. Uno esperaría que la luz estuviera más cerca del lugar donde el sol debería golpear el planeta, en lugar de donde cambia de día a noche.

El proyecto github se ha actualizado para reflejar los cambios en esta actualización.

ollipekka
fuente
1
Me encantaría meter tu código e intentar ayudar, pero parece instalar XNA para VS 2012.
Eliminé todas las referencias externas al proyecto y el proyecto ya no necesita XNA. Es un proyecto simple de Windows.Forms y no debería necesitar nada especial para ejecutarse. Por lo tanto, debería ser bastante trivial convertir a una versión anterior de Visual Studio.
ollipekka
¿Estás hablando de los artefactos de píxeles hacia el centro de la esfera en tu primera imagen? Esos realmente no deberían afectar la imagen final. Se supone que el sombreador SkyFromSpace se aplica a una esfera de adentro hacia afuera, por lo que solo se verá la parte de la atmósfera que se extiende más allá del planeta, mientras que el centro con los artefactos se ocultará detrás del planeta. Sin embargo, tanto el sombreado del suelo como el del cielo miran hacia la cámara a 500,500,500 ..... hmm

Respuestas:

1

No tengo código de trabajo en este momento, ya que estoy haciendo la transición de mi motor, pero estas fueron mis configuraciones de parámetros de trabajo:

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

Este fue el sombreador:

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

Avísame si aún funciona. Si necesita alguna otra ayuda, intentaré buscar mi código. Creo que usé dos esferas para hacer el renderizado: una para la superficie y otra para la atmósfera.

El vagabundo
fuente
0

algunas pistas de pensamiento: verifique la precisión de sus carrozas. a escalas espaciales, la mayoría de las veces float32 no es suficiente. Verifique el búfer dpeth si tiene una representación primitiva, como una esfera debajo de su sombreador de dispersión.

Estos artefactos también se pueden encontrar en el trazado de rayos, generalmente son intersecciones de rayos secundarios con la fluctuación de la superficie primaria debido a problemas de precisión de flotación.

EDITAR: a 1000 (todos los enteros son totalmente representables hasta 16 millones en representación float32, gracias a la mantisa de 24 bits), el siguiente número para un float32 es 1000.00006103, por lo que su precisión sigue siendo bastante buena en este rango.

sin embargo, si tuviera que usar rangos de medidores, ver un planeta a esta distancia significaría valores de 100,000,000 y el siguiente es 100000008: 8 metros de precisión a 100,000km.

esto provocaría saltos en la cámara si trataras de moverte alrededor de un satélite, por ejemplo, y la representación del satélite en sí se rompería si el cero de tu mundo es el centro del planeta. si es el centro del sistema estelar, entonces es aún peor.

busca flavien brebion (Ysaneya) y el juego de búsqueda infinita de la tierra. Tiene un interesante diario de desarrollo de gamedev y su foro donde explica cómo las distancias del sistema estelar son imposibles de manejar usando absolutos.

También menciona el problema del búfer de profundidad en ese tipo de rangos, y es uno de los primeros, si no el primero, en introducir escalas z logarítmicas. http://www.gamedev.net/blog/73/entry-2006307-tip-of-the-day-logarithmic-zbuffer-artifacts-fix/ un mucho más completo aquí: http://outerra.blogspot.jp/ 2012/11 / maximizing-depth-buffer-range-and.html

Banco de pruebas de software: buena idea, es una excelente manera de crear sombreadores para que pueda depurar lo que está sucediendo paso a paso. simplemente verifique sus valores líneas por líneas, y si algo parece extraño, puede investigar. No vi en el código que publicó la parte donde se usa el ángulo de la cámara en el sombreador, por lo que estoy un poco desconcertado sobre esta parte.

v.oddou
fuente
¿Podría explicar qué quiere decir con precisión de flotación? Las escalas que se están utilizando en el ejemplo son de -1000 a 1000. El ejemplo es puramente una implementación de software en este momento, donde el resultado del sombreador se procesa en una imagen y luego se muestra usando la API de dibujo del sistema c #. significa que el ejemplo no utiliza primitivas.
ollipekka