Podwójne paraboloidalne cienie świetlne w odroczonym ustawieniu oświetlenia

10

Bawiłem się z tym samouczkiem / przykładowym kodem który demonstruje prostą implementację light-pre-pass, która jest rodzajem odroczonej konfiguracji oświetlenia.

Jestem w trakcie implementacji punktowych cieni światła, używając podwójnych paraboloidalnych map cieni. Postępuję zgodnie z tym opisem DPM: http://gamedevelop.eu/en/tutorials/dual-paraboloid-shadow-mapping.htm

Jestem w stanie stworzyć mapy cienia i wyglądają one dobrze.

Uważam, że obecny problem dotyczy mojego modułu cieniującego piksele, który wyszukuje wartość głębokości na mapie cienia podczas renderowania świateł punktowych.

Oto mój kod modułu cieniującego światła: http://olhovsky.com/shadow_mapping/PointLight.fx

Ciekawą funkcją cieniowania pikseli jest PointLightMeshShadowPS.

Czy ktoś widzi rażący błąd w tej funkcji?

Mam nadzieję, że ktoś już poradził sobie z tym problemem :)

wprowadź opis zdjęcia tutaj

wprowadź opis zdjęcia tutaj

Jak widać na powyższych zdjęciach, cienie postu nie pasują do pozycji postów, więc niektóre transformacje są gdzieś niewłaściwe ...

Tak to wygląda, gdy światło punktowe znajduje się bardzo blisko ziemi (prawie dotykając ziemi).

wprowadź opis zdjęcia tutaj

Gdy światło punktowe przesuwa się bliżej ziemi, cienie łączą się i dotykają wzdłuż linii, w której spotykają się dwie mapy cienia (to znaczy wzdłuż płaszczyzny, na której kamera światła została obrócona, aby uchwycić dwie mapy cienia).


Edytować:

Dalsza informacja:

wprowadź opis zdjęcia tutaj

Kiedy odsuwam światło punktowe od źródła, linia równoległa do „prawego” wektora kamery światła przycina cień. Powyższe zdjęcie pokazuje efekt przesunięcia światła punktowego w lewo. Jeśli przesunę światło punktowe w prawo, po prawej stronie pojawi się odpowiednia linia odcinania. Myślę więc, że to oznacza, że ​​przekształcam coś nieprawidłowo w module cieniującym piksele, tak jak myślałem.


Edycja: Aby wyjaśnić to pytanie, oto kilka fragmentów kodu.

Oto kod, którego obecnie używam do narysowania zaciemnionego światła punktowego . Działa i używa mapowania cieni, jak można się spodziewać.

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

Oto punktowy kod świetlny , którego używam, który ma jakiś błąd w transformacji w jasną przestrzeń podczas korzystania z map cienia:

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;
}
Olhovsky
źródło
i mówisz, że same mapy cieni nie mają problemu / (Mam na myśli, że jeśli
wypalisz
Czy masz 100% pewności, że pole widzenia kamery renderowanej z pozycji źródła światła jest prawidłowe?
Roy T.
Renderowanie z kamery ze źródła źródła światła nie ma matrycy projekcyjnej, ponieważ projekcja odbywa się ręcznie w celu uzyskania paraboloidalnej wypaczenia. Sprawdzę jednak ten kod, dobry pomysł Roy T.
Olhovsky
Gajet: „Mam na myśli to, że jeśli wypalisz mapy cienia do mapy tekstur, przyciemnią one właściwe miejsca?” Mapy cieni przechowują cienie w jasnej przestrzeni, jeśli spojrzę na mapę, nie ma łatwego sposobu, aby się upewnić, że są poprawne, ponieważ widzę je w przestrzeni ekranu. Co to jest „mapa tekstur” - masz na myśli teksturę? Mapy cienia to tekstury.
Olhovsky
Roy T .: Przesunięcie światła ujawnia, że ​​mapa cienia zostaje przycięta, więc istnieje problem z transformacją podczas faktycznego korzystania z cienia, nie tylko podczas jego tworzenia.
Olhovsky

Odpowiedzi:

2

Dzięki PIX możesz debugować pojedyncze piksele, być może znajdziesz błąd w ten sposób. FOV lub błąd projekcji to gorąca wskazówka. A może zapomniałeś o transformacji świata ?!

Christoph
źródło
możesz także spróbować debugowania za pomocą NVidia-fxComposer
Ali1S232
Nie sądzę, że wpatrywanie się w wartości kodu asemblera bardzo mi w tym pomoże, ponieważ mam problem ze zrozumieniem, w jaki sposób należy przeprowadzić transformację. Więc zobaczenie, jaka wartość jest w rejestrze 10 lub gdziekolwiek, tak naprawdę nie pomoże.
Olhovsky
„A może zapomniałeś o transformacji świata ?!” Właściwie zapomniałem zastosować transformację świata podczas tworzenia map cienia - doh! To działa teraz, pozostawiając wszystkie shadery takie, jakie miałem.
Olhovsky
1

Hej Olhovsky, fajne, trudne pytanie. Znam twój ból, wdrożyłem Deferred Shading, Wnioskowane oświetlenie i cienie w mojej ostatniej pracy. To była naprawdę świetna zabawa, ale także dużo bólu, gdy nie działała zgodnie z oczekiwaniami.

Myślę, że rada z PIX jest właściwie dobra. Nie musisz zadzierać z instrukcjami asemblera modułu cieniującego, ale możesz spojrzeć na mapy cienia i inne cele renderowania, wybrać piksel i wywołać jego moduł cieniujący i przejść przez niego, a także jego moduł cieniujący wierzchołki.

Ogólne sztuczki debugowania w tego rodzaju sytuacjach obejmują uproszczenie sceny.

Przychodzi mi na myśl: ustaw aparat w tej samej pozycji co źródło światła z tymi samymi cechami fovy i innymi atrybutami jak w przepustce oświetleniowej. Teraz możesz łatwo porównywać wartości w module cieniującym piksele. Piksel-xy w normalnym przepuszczeniu renderowania dla bieżącego obiektu powinien być taki sam, jak obliczony piksel-xy dla wyszukiwania w mapie cienia, o ile ma taką samą rozdzielczość.

Kolejnym jest przejście na rzut ortograficzny, uczynienie czegoś łatwym, przewidywalnym i możliwym do sprawdzenia. Im prościej, tym lepiej można sprawdzić każdy krok obliczeniowy.

Poza tym, czy możesz pokazać, w jaki sposób tworzysz macierz, która oblicza pozycję na mapie cienia dla bieżącego piksela, czyli transformację z przestrzeni ekranu na przestrzeń światła?

Maik Semder
źródło
Samo zobaczenie mapy cienia jest Parabloidą, co sprawia, że ​​debugowanie jest jeszcze trudniejsze, a pomysł ustawienia kamery w pozycji świateł w celu porównania aktualnej pozycji pikseli i pozycji na mapie cienia nie zadziała, nieważne :)
Maik Semder
Jeśli jesteś zainteresowany, wyślij mi e-mail na [email protected], a ja odpowiem kopią mojego projektu. W przeciwnym razie: CameraTransformMatryca jest w rzeczywistości matrycą światową kamery aktualnie oglądającej scenę. LightViewProjMatryca jest właściwie tylko matryca świat świetle, jako macierz widoku światła jest po prostu macierzą jednostkową.
Olhovsky
Czy możesz zrobić z nim prosty projekt w C ++? Powinna być także zaangażowana transformacja parabloidalna, prawda?
Maik Semder
Transformacja paraboloidalna znajduje się w module cieniującym pikseli, który powiązałem w pytaniu. Moje umiejętności w C ++ są zbyt ograniczone, aby opracować szybki projekt w C ++, który obejmuje cały odroczony proces renderowania. Myślę jednak, że jeśli jesteś biegły w C ++, to myślę, że nie powinno być zbyt trudne czytanie mojego kodu w C #. Zwłaszcza, że ​​większość zmartwień dotyczy naprawdę modułu cieniującego piksele i być może związanej z nim matematyki.
Olhovsky
Miałem na myśli g_mDPView i g_mDPWorldView. Czy możesz pokazać, w jaki sposób są obliczane.
Maik Semder