Céu de dispersão atmosférica de artefatos espaciais


20

Estou no processo de implementar a dispersão atmosférica de planetas do espaço. Eu tenho usado os shaders de Sean O'Neil em http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html como ponto de partida.

Eu tenho praticamente o mesmo problema relacionado ao fCameraAngle, exceto com o sombreador SkyFromSpace, em oposição ao sombreador GroundFromSpace, como aqui: http://www.gamedev.net/topic/621187-sean-oneils-atmospheric-scattering/

Recebo artefatos estranhos com o céu do sombreador do espaço quando não uso fCameraAngle = 1no loop interno. Qual é a causa desses artefatos? Os artefatos desaparecem quando o fCameraAngle é limitado a 1. Parece que também não tenho o tom presente na caixa de areia de O'Neil ( http://sponeil.net/downloads.htm )

Posição da câmera X = 0, Y = 0, Z = 500. GroundFromSpace à esquerda, SkyFromSpace à direita. insira a descrição da imagem aqui

Posição da câmera X = 500, Y = 500, Z = 500. GroundFromSpace à esquerda, SkyFromSpace à direita. insira a descrição da imagem aqui

Descobri que o ângulo da câmera parece ser tratado de maneira muito diferente, dependendo da fonte:

Nos shaders originais, o ângulo da câmera no SkyFromSpaceShader é calculado como:

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

Enquanto no solo, a partir do espaço shader, o ângulo da câmera é calculado como:

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

No entanto, várias fontes online mexem com a negação do raio. Por que é isso?

Aqui está um projeto C # Windows.Forms que demonstra o problema e que eu usei para gerar as imagens: https://github.com/ollipekka/AtmosphericScatteringTest/

Atualização: Descobri no projeto ScatterCPU encontrado no site de O'Neil que o raio da câmera é negado quando a câmera está acima do ponto que está sendo sombreado, para que a dispersão seja calculada do ponto para a câmera.

Alterar a direção do raio de fato remove os artefatos, mas introduz outros problemas, conforme ilustrado aqui:

Raio negativo para o ângulo da câmera

Além disso, no projeto ScatterCPU, O'Neil protege contra situações em que a profundidade óptica da luz é menor que zero:

float fLightDepth = Scale(fLightAngle, fScaleDepth);

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

Como apontado nos comentários, junto com esses novos artefatos, isso ainda deixa a questão: o que há de errado com as imagens em que a câmera está posicionada em 500, 500, 500? Parece que o halo está focado na parte completamente errada do planeta. Seria de esperar que a luz estivesse mais próxima do ponto em que o sol deve atingir o planeta, e não de onde muda de dia para noite.

O projeto github foi atualizado para refletir as alterações nesta atualização.


11
Eu adoraria para picar o seu código e tentar ajudar, mas parece instalar XNA para VS 2012 I exigem VS 2010 ...

Eu removi todas as referências externas ao projeto e o projeto não precisa mais do XNA. É um projeto Windows.Forms simples e não precisa de nada de especial para ser executado. Portanto, deve ser bastante trivial converter para a versão mais antiga do Visual Studio.
Ollipekka

Você está falando sobre os artefatos de pixel em direção ao centro da esfera em sua primeira imagem? Isso realmente não deve afetar a imagem final. O sombreador SkyFromSpace deve ser aplicado a uma esfera de dentro para fora, de modo que apenas a parte da atmosfera que se estende além do planeta seja visível, enquanto o centro com os artefatos ficará oculto atrás do planeta. No entanto , tanto o sombreamento olhar chão e céu fora para a câmera em 500.500.500 ..... hmm

Respostas:


1

No momento, não tenho código de trabalho, pois estou fazendo a transição do mecanismo, mas essas foram as configurações dos parâmetros de trabalho:

// 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 era o 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;
    }

Deixe-me saber se ainda funciona. Se você precisar de outra ajuda, tentarei pesquisar meu código. Acho que usei duas esferas para fazer a renderização: uma para a superfície e outra para a atmosfera.


0

algumas trilhas de pensamento: verifique a precisão de seus carros alegóricos. em escalas espaciais, na maioria das vezes float32 não é suficiente. Marque dpeth buffer se você tiver renderização primitiva, como uma esfera sob o shader de dispersão.

Esses artefatos também podem ser encontrados no traçado de raios, geralmente são interseções de raios secundários com a superfície primária tremendo devido a problemas de precisão do flutuador.

EDIT: em 1000 (todos os números inteiros são totalmente representáveis ​​até 16 milhões na representação float32, graças à mantissa de 24 bits), o próximo número para um float32 é 1000.00006103, portanto sua precisão ainda é muito boa nesse intervalo.

no entanto, se você usar faixas de medidores, ver um planeta a essa distância significaria valores de 100.000.000 e o próximo é 100000008: 8 metros de precisão a 100.000 km.

isso causaria pulos de câmera se você tentasse se mover por um satélite, por exemplo, e a renderização do satélite em si ficaria quebrada se o zero do seu mundo fosse o centro do planeta. se é o centro do sistema estelar, é ainda pior.

procure o flavien brebion (Ysaneya) e a busca infinita do jogo pela terra. Ele tem um interessante diário de desenvolvimento de gamedev e seu fórum, onde explica como é impossível gerenciar distâncias do sistema estelar usando absolutos.

Ele também menciona o problema do buffer de profundidade nesses tipos de faixas e é um dos primeiros, se não o primeiro, a introduzir escalas z logarítmicas. http://www.gamedev.net/blog/73/entry-2006307-tip-of-the-day-logarithmic-zbuffer-artifacts-fix/ muito mais completo aqui: http://outerra.blogspot.jp/ 2012/11 / maximizing-depth-buffer-range-and.html

Cama de teste de software: boa ideia, é uma excelente maneira de criar shaders para que você possa depurar o que está acontecendo passo a passo. basta verificar seus valores, linhas por linhas, e se algo parecer estranho, você poderá investigar. Eu não vi no código que você postou a parte em que o ângulo da câmera é usado no sombreador, então estou um pouco intrigado com essa parte.


Você poderia elaborar o que você quer dizer com precisão de flutuação? As escalas que estão sendo usadas no exemplo são de -1000 a 1000. O exemplo é puramente uma implementação de software no momento, em que o resultado do shader é renderizado em uma imagem e, em seguida, exibido usando a c # System.Drawing API, que significa que o exemplo não utiliza primitivas.
Ollipekka #
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.