Подвійні параболоїдні точкові світлові тіні в режимі відкладеного освітлення


10

Я розігрувався з цим навчальним кодом / зразком коду, який демонструє просту реалізацію світлопрограми, яка є типом відкладеної настройки освітлення.

Я перебуваю в процесі впровадження точкових світлових тіней, використовуючи подвійні параболоїдні тіньові карти. Я стежу за цим описом DPM: http://gamedevelop.eu/en/tutorials/dual-paraboloid-shadow-mapping.htm

Я вмію створювати тіньові карти, і вони здаються прекрасними.

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

Ось мій точковий шейдер для світла: http://olhovsky.com/shadow_mapping/PointLight.fx

Піксельна шейдерна функція, яка вас цікавить PointLightMeshShadowPS.

Хтось бачить очевидну помилку в цій функції?

Сподіваємось, хтось вирішив цю проблему раніше :)

введіть тут опис зображення

введіть тут опис зображення

Як видно з зображень вище, тіні публікації не співпадають з позиціями публікацій, тому дещо перетворення десь неправильне ...

Так виглядає, коли точкове світло знаходиться дуже близько до землі (майже торкаючись землі).

введіть тут опис зображення

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


Редагувати:

Додаткова інформація:

введіть тут опис зображення

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


Редагувати: Щоб зробити це питання більш зрозумілим, ось кілька фрагментів коду.

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

VertexShaderOutputMeshBased SpotLightMeshVS(VertexShaderInput input)
{
    VertexShaderOutputMeshBased output = (VertexShaderOutputMeshBased)0;    
    output.Position = mul(input.Position, WorldViewProjection);

    //we will compute our texture coords based on pixel position further
    output.TexCoordScreenSpace = output.Position;
    return output;
}

//////////////////////////////////////////////////////
// Pixel shader to compute spot lights with shadows
//////////////////////////////////////////////////////
float4 SpotLightMeshShadowPS(VertexShaderOutputMeshBased input) : COLOR0
{
    //as we are using a sphere mesh, we need to recompute each pixel position into texture space coords
    float2 screenPos = PostProjectionSpaceToScreenSpace(input.TexCoordScreenSpace) + GBufferPixelSize;
    //read the depth value
    float depthValue = tex2D(depthSampler, screenPos).r;

    //if depth value == 1, we can assume its a background value, so skip it
    //we need this only if we are using back-face culling on our light volumes. Otherwise, our z-buffer
    //will reject this pixel anyway

    //if depth value == 1, we can assume its a background value, so skip it
    clip(-depthValue + 0.9999f);

    // Reconstruct position from the depth value, the FOV, aspect and pixel position
    depthValue*=FarClip;

    //convert screenPos to [-1..1] range
    float3 pos = float3(TanAspect*(screenPos*2 - 1)*depthValue, -depthValue);

    //light direction from current pixel to current light
    float3 lDir = LightPosition - pos;

    //compute attenuation, 1 - saturate(d2/r2)
    float atten = ComputeAttenuation(lDir);

    // Convert normal back with the decoding function
    float4 normalMap = tex2D(normalSampler, screenPos);
    float3 normal = DecodeNormal(normalMap);

    lDir = normalize(lDir);

    // N dot L lighting term, attenuated
    float nl = saturate(dot(normal, lDir))*atten;

    //spot light cone
    half spotAtten = min(1,max(0,dot(lDir,LightDir) - SpotAngle)*SpotExponent);
    nl *= spotAtten;

    //reject pixels outside our radius or that are not facing the light
    clip(nl -0.00001f);

    //compute shadow attenuation

    float4 lightPosition = mul(mul(float4(pos,1),CameraTransform), MatLightViewProjSpot);

    // Find the position in the shadow map for this pixel
    float2 shadowTexCoord = 0.5 * lightPosition.xy / 
                            lightPosition.w + float2( 0.5, 0.5 );
    shadowTexCoord.y = 1.0f - shadowTexCoord.y;
    //offset by the texel size
    shadowTexCoord += ShadowMapPixelSize;

    // Calculate the current pixel depth
    // The bias is used to prevent floating point errors 
    float ourdepth = (lightPosition.z / lightPosition.w) - DepthBias;

    nl = ComputeShadowPCF7Linear(nl, shadowTexCoord, ourdepth);

    float4 finalColor;

    //As our position is relative to camera position, we dont need to use (ViewPosition - pos) here
    float3 camDir = normalize(pos);

    // Calculate specular term
    float3 h = normalize(reflect(lDir, normal));
    float spec = nl*pow(saturate(dot(camDir, h)), normalMap.b*50);
    finalColor = float4(LightColor * nl, spec); 

    //output light
    return finalColor * LightBufferScale;
}

Тепер ось точковий світловий код, який я використовую, який має певну помилку при перетворенні у світлий простір при використанні тіньових карт:

VertexShaderOutputMeshBased PointLightMeshVS(VertexShaderInput input)
{
    VertexShaderOutputMeshBased output = (VertexShaderOutputMeshBased)0;    
    output.Position = mul(input.Position, WorldViewProjection);

    //we will compute our texture coords based on pixel position further
    output.TexCoordScreenSpace = output.Position;
    return output;
}

float4 PointLightMeshShadowPS(VertexShaderOutputMeshBased input) : COLOR0
{
    // as we are using a sphere mesh, we need to recompute each pixel position 
    // into texture space coords
    float2 screenPos = 
        PostProjectionSpaceToScreenSpace(input.TexCoordScreenSpace) + GBufferPixelSize;

    // read the depth value
    float depthValue = tex2D(depthSampler, screenPos).r;

    // if depth value == 1, we can assume its a background value, so skip it
    // we need this only if we are using back-face culling on our light volumes. 
    // Otherwise, our z-buffer will reject this pixel anyway
    clip(-depthValue + 0.9999f);

    // Reconstruct position from the depth value, the FOV, aspect and pixel position
    depthValue *= FarClip;

    // convert screenPos to [-1..1] range
    float3 pos = float3(TanAspect*(screenPos*2 - 1)*depthValue, -depthValue);

    // light direction from current pixel to current light
    float3 lDir = LightPosition - pos;

    // compute attenuation, 1 - saturate(d2/r2)
    float atten = ComputeAttenuation(lDir);

    // Convert normal back with the decoding function
    float4 normalMap = tex2D(normalSampler, screenPos);
    float3 normal = DecodeNormal(normalMap);

    lDir = normalize(lDir);

    // N dot L lighting term, attenuated
    float nl = saturate(dot(normal, lDir))*atten;

    /* shadow stuff */

    float4 lightPosition = mul(mul(float4(pos,1),CameraTransform), LightViewProj);

    //float4 lightPosition = mul(float4(pos,1), LightViewProj);
    float posLength = length(lightPosition);
    lightPosition /= posLength;

    float ourdepth = (posLength - NearClip) / (FarClip - NearClip) - DepthBias;
    //float ourdepth = (lightPosition.z / lightPosition.w) - DepthBias;

    if(lightPosition.z > 0.0f)
    {
        float2 vTexFront;
        vTexFront.x =  (lightPosition.x /  (1.0f + lightPosition.z)) * 0.5f + 0.5f; 
        vTexFront.y =  1.0f - ((lightPosition.y /  (1.0f + lightPosition.z)) * 0.5f + 0.5f);    

        nl = ComputeShadow(FrontShadowMapSampler, nl, vTexFront, ourdepth);
    }
    else
    {
        // for the back the z has to be inverted        
        float2 vTexBack;
        vTexBack.x =  (lightPosition.x /  (1.0f - lightPosition.z)) * 0.5f + 0.5f; 
        vTexBack.y =  1.0f - ((lightPosition.y /  (1.0f - lightPosition.z)) * 0.5f + 0.5f); 

        nl = ComputeShadow(BackShadowMapSampler, nl, vTexBack, ourdepth);
    }

    /* shadow stuff */

    // reject pixels outside our radius or that are not facing the light
    clip(nl - 0.00001f);

    float4 finalColor;
    //As our position is relative to camera position, we dont need to use (ViewPosition - pos) here
    float3 camDir = normalize(pos);

    // Calculate specular term
    float3 h = normalize(reflect(lDir, normal));
    float spec = nl*pow(saturate(dot(camDir, h)), normalMap.b*100);
    finalColor = float4(LightColor * nl, spec);

    return finalColor * LightBufferScale;
}

і ви кажете, що самі тіньові карти не мають жодних проблем / (я маю на увазі, якщо ви спалите тіньові карти, щоб перетворити на карту текстури, вони затьмарять правильні плями?)
Ali1S232

Ви на 100% впевнені, що FOV відтворення камери з положення джерела світла є правильним?
Рой Т.

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

Гаджет: "Я маю на увазі, якщо ви спалите тіньові карти, щоб зробити їх текстурними картами, вони почнуть затемнювати правильні плями?" Тіньові карти зберігають тіні у світлому просторі, якщо я дивлюся на карту, немає простого способу точно зрозуміти, що вони правильні, оскільки я бачу їх у просторі екрану. Що таке "текстурна карта" - ви маєте на увазі текстуру? Карти тіней - це текстури.
Ольховський

Рой Т.: Переміщення світла виявляє, що тіньова карта відсікається, тому виникає проблема з перетворенням, коли насправді використовується тінь, а не лише при її створенні.
Ольховський

Відповіді:


2

За допомогою PIX ви можете налагоджувати пікселі, ізольовані, можливо, ви знайдете помилку таким чином. FOV або помилка проекції - гаряча підказка. Або ви забули світову трансформацію ?!


ви також можете спробувати налагодження за допомогою NVidia-fxComposer
Ali1S232

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

"Або ви забули світову трансформацію ?!" Я фактично забув застосувати трансформацію світу, створюючи тіньові карти - так! Це працює зараз, залишаючи всі шейдери, як у мене.
Ольховський

1

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

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

Загальні хитрощі налагодження для подібних ситуацій включають спрощення сцени.

Одне, що мені спадає на думку, - поставити камеру в таке ж положення, що і джерело світла, з тими ж фокусами та іншими атрибутами, що і в освітлювальному проході. Тепер ви можете легко порівняти значення в піксельному шейдері. Піксель-xy у звичайному рендерінгу для вашого поточного об'єкта повинен бути таким самим, як обчислений піксель-xy для пошуку в тіньовій карті, доки він має однакову роздільну здатність.

Ще один варіант - перейти на ортографічну проекцію, зробити щось легким, передбачуваним і перевірити. Чим простіше, тим краще ви можете перевірити кожен крок розрахунку.

Окрім цього, чи можете ви показати, як ви створюєте матрицю, яка обчислює положення в тіньовій карті для поточного пікселя, тобто перетворення з простору екрана в світло-простір?


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

Якщо вам цікаво, надішліть мені електронний лист на адресу kris@olhovsky.com, і я відповім копію свого проекту. Інакше: CameraTransformматриця - це фактично світова матриця камери, яка зараз переглядає сцену. LightViewProjМатриця насправді тільки світ матриця світла, як вид матриця світла просто одинична матриця.
Ольховський

Чи можете ви зробити з ним простий проект C ++? Також має відбутися параблоїдна трансформація?
Майк Семдер

Параболоїдна трансформація знаходиться в піксельній шейдері, про яку я пов’язаний у запитанні. Мої навички C ++ занадто обмежені, щоб створити швидкий проект C ++, який включає в себе весь відкладений конвеєр рендерінгу, я думаю :) Однак, якщо ви знаєте C ++, я думаю, що читати мій код C # не повинно. Тим більше, що більша частина проблем викликає піксельний шейдер, і, можливо, матриці, передані йому.
Ольховський

Я переглядав g_mDPView та g_mDPWorldView. Чи можете ви показати, як вони обчислюються.
Майк Семдер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.