Я играл с этим учебным / примерным кодом, который демонстрирует простую реализацию предварительного прохождения света, которая является типом отложенного освещения.
Я нахожусь в процессе реализации точечных светлых теней, используя двойные параболоидные карты теней. Я следую этому описанию 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;
}
источник
Ответы:
С PIX вы можете отлаживать пиксели изолированно, возможно, вы найдете ошибку таким образом. FOV или ошибка проекции - горячая подсказка. Или ты забыл трансформацию мира ?!
источник
Привет Ольховский, хороший сложный вопрос. Я знаю вашу боль, я использовал Отложенное затенение, Inferred Lighting и тени в моей последней работе. Это было действительно очень весело, но также было очень больно, когда это не сработало.
Я думаю, что совет с PIX на самом деле хороший. Вам не нужно связываться с инструкциями ассемблера шейдера, но вы можете посмотреть карты теней и другие цели рендеринга, выбрать пиксель и вызвать его пиксельный шейдер и пройти через него, а также его вершинный шейдер.
Основные приемы отладки для таких ситуаций включают в себя упрощение сцены.
Вот что мне приходит в голову: поставьте камеру в то же положение, что и источник света, с такими же качественными и другими атрибутами, что и в проходе освещения. Теперь вы можете легко сравнивать значения в пиксель-шейдере. Пиксель-ху в обычном проходе рендеринга для вашего текущего объекта должен совпадать с вычисленным пикселем-ху для поиска в карте теней, если он имеет такое же разрешение.
Другой - переключиться на ортографическую проекцию, сделать что-то легким, предсказуемым и проверяемым. Чем проще, тем лучше вы можете проверить каждый шаг расчета.
Кроме этого, можете ли вы показать, как вы создаете матрицу, которая вычисляет положение на карте теней для текущего пикселя, то есть преобразование из экранного пространства в световое пространство?
источник
CameraTransform
матрица на самом деле является мировой матрицей камеры, которая в данный момент просматривает сцену.LightViewProj
Матрица на самом деле только мир матрица света, как вид матрица света просто единичная матрица.