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 :)
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).
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:
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;
}
źródło
Odpowiedzi:
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 ?!
źródło
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?
źródło
CameraTransform
Matryca jest w rzeczywistości matrycą światową kamery aktualnie oglądającej scenę.LightViewProj
Matryca jest właściwie tylko matryca świat świetle, jako macierz widoku światła jest po prostu macierzą jednostkową.