Двойные параболоидные точечные световые тени в режиме отложенного освещения

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 рендеринга камеры с позиции источника света правильное?
Рой Т.
Рендеринг камеры из положения источника света не имеет проекционной матрицы, потому что проекция выполняется вручную, чтобы получить деформацию параболоида. Я проверю этот код, хорошая идея, Рой Т.
Ольховский
Gajet: «Я имею в виду, что если вы запишете карты теней в карту текстур, они будут затемнять правильные пятна?» Карты теней хранят тени в светлом пространстве, если я смотрю на карту, то нет простого способа узнать наверняка, что они верны, потому что я вижу их в пространстве экрана. Что такое «текстуракарта» - вы имеете в виду текстуру? Карты теней - это текстуры.
Ольховский
Рой Т .: Перемещение света показывает, что карта теней обрезается, поэтому существует проблема с преобразованием при использовании тени, а не только при ее создании.
Ольховский

Ответы:

2

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

Christoph
источник
вы также можете попробовать отладку с помощью NVidia-fxComposer
Ali1S232
Я не думаю, что на этом этапе мне очень поможет рассмотрение значений кода сборки, потому что у меня возникают проблемы с пониманием того, как преобразование должно быть выполнено в первую очередь. Таким образом, поиск значения в регистре 10 или где-то еще не поможет.
Ольховский
"Или ты забыл трансформацию мира ?!" Я действительно забыл применить преобразование мира при создании карт теней - дох! Теперь это работает, оставляя все шейдеры такими, какими они были у меня.
Ольховский
1

Привет Ольховский, хороший сложный вопрос. Я знаю вашу боль, я использовал Отложенное затенение, Inferred Lighting и тени в моей последней работе. Это было действительно очень весело, но также было очень больно, когда это не сработало.

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

Основные приемы отладки для таких ситуаций включают в себя упрощение сцены.

Вот что мне приходит в голову: поставьте камеру в то же положение, что и источник света, с такими же качественными и другими атрибутами, что и в проходе освещения. Теперь вы можете легко сравнивать значения в пиксель-шейдере. Пиксель-ху в обычном проходе рендеринга для вашего текущего объекта должен совпадать с вычисленным пикселем-ху для поиска в карте теней, если он имеет такое же разрешение.

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

Кроме этого, можете ли вы показать, как вы создаете матрицу, которая вычисляет положение на карте теней для текущего пикселя, то есть преобразование из экранного пространства в световое пространство?

Майк Земдер
источник
Просто видеть карту теней - это Parabloid, что делает его еще более трудным для отладки, и идея поместить камеру в положение источников света, чтобы сравнить текущее положение пикселей и положение в карте теней, не будет работать, неважно :)
Maik Semder
Если вам интересно, отправьте мне письмо на kris@olhovsky.com, и я отвечу с копией моего проекта. В противном случае: CameraTransformматрица на самом деле является мировой матрицей камеры, которая в данный момент просматривает сцену. LightViewProjМатрица на самом деле только мир матрица света, как вид матрица света просто единичная матрица.
Ольховский
Можете ли вы сделать простой проект C ++ с ним? Там также должно быть участие параблоидной трансформации правильно?
Майк Земдер
Параболоидное преобразование находится в пиксельном шейдере, с которым я связан в вопросе. Мои навыки C ++ слишком ограничены, чтобы запустить быстрый C ++ проект, который инкапсулирует весь конвейер отложенного рендеринга, я думаю :) Однако, если вы разбираетесь в C ++, то я думаю, что не должно быть слишком сложно читать мой код C #. Тем более, что основная проблема на самом деле связана с пиксельным шейдером и, возможно, с передачей ему матриц.
Ольховский
Я ссылался на g_mDPView и g_mDPWorldView. Можете ли вы показать, как они рассчитываются.
Майк Земдер