Langit berhamburan atmosfer dari artefak ruang angkasa

20

Saya sedang dalam proses mengimplementasikan hamburan planet-planet dari ruang angkasa. Saya telah menggunakan shader Sean O'Neil dari http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html sebagai titik awal.

Saya memiliki masalah yang hampir sama terkait dengan fCameraAngle kecuali dengan SkyFromSpace shader sebagai lawan dari GroundFromSpace shader seperti di sini: http://www.gamedev.net/topic/621187-sean-oneils-atmospheric-scat/

Saya mendapatkan artefak aneh dengan langit dari luar angkasa jika tidak digunakan fCameraAngle = 1dalam lingkaran dalam. Apa penyebab artefak ini? Artefak menghilang ketika fCameraAngle dibatasi menjadi 1. Saya juga tampaknya tidak memiliki rona yang ada di kotak pasir O'Neil ( http://sponeil.net/downloads.htm )

Posisi kamera X = 0, Y = 0, Z = 500. GroundFromSpace di sebelah kiri, SkyFromSpace di sebelah kanan. masukkan deskripsi gambar di sini

Posisi kamera X = 500, Y = 500, Z = 500. GroundFromSpace di sebelah kiri, SkyFromSpace di sebelah kanan. masukkan deskripsi gambar di sini

Saya telah menemukan bahwa sudut kamera tampaknya ditangani sangat berbeda tergantung sumbernya:

Dalam bayangan asli, sudut kamera di SkyFromSpaceShader dihitung sebagai:

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

Sedangkan di darat dari shader ruang, sudut kamera dihitung sebagai:

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

Namun, berbagai sumber bermain-main online dengan meniadakan ray. Kenapa ini?

Berikut adalah proyek C # Windows.Forms yang menunjukkan masalah dan yang saya gunakan untuk menghasilkan gambar: https://github.com/ollipekka/AtmosphericScatteringTest/

Pembaruan: Saya telah menemukan dari proyek ScatterCPU yang ditemukan di situs O'Neil bahwa sinar kamera dinegasikan ketika kamera berada di atas titik yang diarsir sehingga hamburan dihitung dari titik ke kamera.

Mengubah arah sinar memang menghilangkan artefak, tetapi menimbulkan masalah lain seperti diilustrasikan di sini:

Sinar negasi untuk sudut kamera

Selain itu, dalam proyek ScatterCPU, O'Neil menjaga terhadap situasi di mana kedalaman optik untuk cahaya kurang dari nol:

float fLightDepth = Scale(fLightAngle, fScaleDepth);

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

Seperti yang ditunjukkan dalam komentar, bersama dengan artefak baru ini masih menyisakan pertanyaan, apa yang salah dengan gambar di mana kamera diposisikan pada 500, 500, 500? Rasanya seperti halo terfokus pada bagian planet yang sepenuhnya salah. Orang akan berharap bahwa cahaya akan lebih dekat ke tempat di mana matahari seharusnya mengenai planet, daripada di mana ia berubah dari hari ke malam.

Proyek github telah diperbarui untuk mencerminkan perubahan dalam pembaruan ini.

ollipekka
sumber
1
Saya ingin menyodok kode Anda dan mencoba membantu, tetapi tampaknya menginstal XNA untuk VS 2012 saya memerlukan VS 2010 ...
Saya menghapus semua referensi eksternal ke proyek dan proyek tidak perlu XNA lagi. Ini adalah proyek Windows.Forms sederhana dan seharusnya tidak perlu sesuatu yang istimewa untuk dijalankan. Oleh karena itu harus cukup sepele untuk mengkonversi ke versi Visual Studio yang lebih lama.
ollipekka
Apakah Anda berbicara tentang artefak piksel menuju pusat bola di gambar pertama Anda? Itu seharusnya tidak benar-benar mempengaruhi gambar akhir. Shader SkyFromSpace seharusnya diterapkan pada bola dalam-luar, sehingga hanya sedikit atmosfer yang melampaui planet ini yang akan terlihat, sedangkan pusat dengan artefak akan disembunyikan di belakang planet ini. Namun baik bayangan tanah dan langit terlihat pada kamera di 500.500.500 ..... hmm

Jawaban:

1

Saya tidak memiliki kode kerja sekarang, karena saya sedang mentransisikan mesin saya tetapi ini adalah pengaturan parameter kerja saya:

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

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

Beri tahu saya jika masih berfungsi. Jika Anda membutuhkan bantuan lain, saya akan mencoba menggali kode saya. Saya pikir saya menggunakan dua bidang untuk melakukan rendering: satu untuk permukaan dan satu untuk atmosfer.

Petualang
sumber
0

beberapa jalur pemikiran: periksa ketepatan float Anda pada skala ruang, sebagian besar waktu float32 tidak cukup. Periksa dpeth buffer jika Anda memiliki rendering primitif, seperti bola di bawah shader hamburan Anda.

Artefak ini, dapat ditemukan dalam raytracing juga, ini biasanya persimpangan sinar sekunder dengan permukaan primer yang naik-turun karena masalah presisi apung.

EDIT: pada 1000 (semua bilangan bulat sepenuhnya mewakili hingga 16 juta dalam representasi float32, berkat mantissa 24 bit), angka berikutnya untuk float32 adalah 1000.00006103 sehingga presisi Anda masih cukup bagus pada kisaran ini.

namun jika Anda menggunakan rentang meter, untuk melihat sebuah planet jarak ini akan berarti nilai 100.000.000 dan selanjutnya adalah 100000008: 8 meter presisi pada 100.000 km.

ini akan menyebabkan kamera melompat jika Anda mencoba untuk bergerak di sekitar satelit misalnya, dan render satelit itu sendiri akan rusak semua jika nol dunia Anda adalah pusat dari planet ini. jika itu adalah pusat dari sistem bintang maka itu bahkan lebih buruk.

mencari flavien brebion (Ysaneya) dan game infinity quest untuk bumi. Dia memiliki jurnal dev yang menarik tentang gamedev dan forumnya di mana dia menjelaskan bagaimana jarak sistem bintang tidak mungkin dikelola dengan menggunakan absolut.

Dia juga menyebutkan masalah penyangga kedalaman pada rentang semacam itu, dan merupakan salah satu yang pertama, jika bukan yang pertama, untuk memperkenalkan skala z logaritmik. http://www.gamedev.net/blog/73/entry-2006307-tip-of-the-day-logarithmic-zbuffer-artifacts-fix/ yang jauh lebih lengkap di sini: http://outerra.blogspot.jp/ 2012/11 / memaksimalkan-kedalaman-buffer-range-and.html

Ranjang uji perangkat lunak: ide bagus, ini adalah cara terbaik untuk membuat shader sehingga Anda dapat men-debug apa yang terjadi langkah demi langkah. cukup periksa nilai-nilai Anda baris demi baris, dan jika ada sesuatu yang aneh Anda bisa selidiki. Saya tidak melihat dalam kode yang Anda posting bagian di mana sudut kamera digunakan dalam shader, jadi saya agak bingung tentang bagian ini.

v.oddou
sumber
Bisakah Anda menguraikan apa yang Anda maksud dengan presisi float? Timbangan yang digunakan dalam contoh adalah dari -1000 hingga 1000. Contohnya adalah murni implementasi perangkat lunak saat ini, di mana ada hasil shader dirender ke gambar dan kemudian ditampilkan menggunakan c # System.Drawing API, yang berarti bahwa contoh tersebut tidak menggunakan primitif.
ollipekka