Атмосферне розсіяне небо від космічних артефактів


20

Я перебуваю в процесі здійснення атмосферного розсіювання планет з космосу. Я використовував шейдери Шона О'Ніла від http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html в якості вихідної точки.

У мене майже однакова проблема, пов'язана з fCameraAngle, за винятком шейдера SkyFromSpace, на відміну від шейдера GroundFromSpace, як тут: 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/

Оновлення: У проекті ScatterCPU, знайденому на сайті O'Neil, я з’ясував, що променевий знімок камери відміняється, коли камера знаходиться над точкою затінення, так що розсіювання обчислюється від точки до камери.

Зміна напрямку променя дійсно видаляє артефакти, але створює інші проблеми, як показано тут:

Негативний промінь для кута камери

Крім того, у проекті ScatterCPU O'Neil захищає від ситуацій, коли оптична глибина світла менше нуля:

float fLightDepth = Scale(fLightAngle, fScaleDepth);

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

Як зазначається в коментарях, поряд із цими новими артефактами все ще залишається питання, що не так із зображеннями, де камера розміщена у 500, 500, 500? Відчувається, що ореол зосереджений на абсолютно неправильній частині планети. Можна було б очікувати, що світло буде ближче до того місця, де сонце повинно потрапити на планету, а не там, де воно змінюється з дня на ніч.

Проект github було оновлено, щоб відобразити зміни цього оновлення.


1
Я хотів би ткнути ваш код і спробувати допомогти, але, здається, для встановлення XNA для VS 2012 мені потрібен VS 2010 ...

Я видалив усі зовнішні посилання на проект, і проект більше не потребує XNA. Це простий проект Windows.Forms, і він не повинен мати нічого особливого для запуску. Отже, переходити на старішу версію Visual Studio слід досить тривіально.
ollipekka

Ви в першому зображенні говорите про артефакти пікселів у напрямку до центру сфери? Вони не повинні впливати на остаточний образ. Шейдер SkyFromSpace повинен бути застосований до внутрішньої сфери, тому буде видно лише шматочок атмосфери, який виходить за межі планети, а центр з артефактами буде приховано за планетою. Однак і затінення землі, і небо виглядають на камеру в 500 500 500 ..... хм

Відповіді:


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, якщо у вас є примітивне візуалізація, як сфера під вашим шейдером розсіювання.

Ці артефакти можуть бути виявлені і при просвітленні променів, зазвичай це перетинання вторинних променів з первинним тремтінням поверхні від питань точності поплавця.

EDIT: при 1000 (всі цілі числа представлені повністю до 16 мільйонів у представленні float32, завдяки 24-бітовій мантісі), наступне число для float32 становить 1000.00006103, тому ваша точність все ще досить хороша в цьому діапазоні.

однак якщо ви використовували метри діапазону, побачити планету на цій відстані буде означати значення 100 000 000, а наступне - 100000008: 8 метрів точності на 100 000 км.

це може спричинити стрибки камери, якби, наприклад, ви намагалися рухатись по супутнику, а сам супутник буде порушений, якщо нуль вашого світу є центром планети. якщо це центр зоряної системи, то це ще гірше.

шукати flavien brebion (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 / максимізація глибини-буфер-діапазон-і.html

Пробне програмне забезпечення: хороша ідея, це відмінний спосіб створення шейдерів, щоб ви могли налагоджувати те, що відбувається покроково. просто перевірте рядки значень за рядками, і якщо щось виглядає дивно, ви можете дослідити. Я не бачив у коді, який ви розмістили, ту частину, де використовується кут камери в шейдері, тому я трохи спантеличений цією частиною.


Не могли б ви пояснити, що ви маєте на увазі під поплавковою точністю? Масштаби, які використовуються у прикладі, становлять від -1000 до 1000. Приклад - це суто програмна реалізація на даний момент, коли результат шейдера відображається на зображенні, а потім відображається за допомогою API # # System.Drawing, який означає, що в прикладі не використовуються примітиви.
ollipekka
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.