DirectX11 Работа с светом

Проблема с освещением в моей сцене остается нерешенной. Несмотря на то, что нормали в модели корректны, и освещение в других программах (как в 3D редакторах, так и в Unity) выглядит безупречно, мой собственный шейдер выдает непредсказуемые результаты.

Я перепробовал множество подходов, но ни один из них не дал желаемого эффекта. Модель демонстрирует резкие перепады освещенности, причем свет неожиданно фокусируется на одной стороне, в то время как другие стороны остаются в тени. Это особенно заметно, когда модель вращается, и свет резко "прыгает" на новую сторону. К тому же, спереди и сзади модели практически отсутствует свет, что делает ее внешний вид полностью черным.

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

cbuffer ConstantBuffer : register(b0)
{
    matrix World;
    matrix View;
    matrix Projection;
    float4 lightDirection;
    float4 lightColor;
    float4 ambientColor;
}

struct VS_INPUT
{
    float4 Pos : POSITION;
    float2 Tex : TEXCOORD0;
    float3 Normal : NORMAL;
};

struct PS_INPUT
{
    float4 Pos : SV_POSITION;
    float2 Tex : TEXCOORD0;
    float3 Normal : TEXCOORD1;
};

PS_INPUT VS(VS_INPUT input)
{
    PS_INPUT output;
    output.Pos = mul(input.Pos, World);
    output.Pos = mul(output.Pos, View);
    output.Pos = mul(output.Pos, Projection);
    output.Tex = float2(input.Tex.x, -input.Tex.y);
    output.Normal = mul(float4(input.Normal, 1), World).xyz;
    
    return output;
}

Texture2D txDiffuse : register(t0);
SamplerState samLinear : register(s0);

float4 PS(PS_INPUT input) : SV_Target
{
    float4 finalColor = 0;
    finalColor += saturate(dot((float3) -lightDirection, input.Normal) * lightColor);
    finalColor.a = 1;
    return finalColor;
}

введите сюда описание изображения


Ответы (2 шт):

Автор решения: Monster

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

finalColor += saturate(dot(-lightDirection.xyz, normalize(input.Normal)) * lightColor);

Кроме того, если матрица World в вершинном шейдере имеет Translation (т.е. 4 строка имеет ненулевые компоненты xyz), то нормали будут не просто повернуты, а сдвинуты и повернуты, и это их сломает. Нужно умножать нормали на матрицу с нулевым сдвигом, либо сделать новую матрицу:

float3x3 WorldNoTranslation = (float3x3)World;
output.Normal = mul(float4(input.Normal, 1), WorldNoTranslation).xyz;

Это медленнее, чем просто передать в константу еще одну матрицу мирового преобразования, без сдвига, но для проверки сойдет.

→ Ссылка
Автор решения: Алексей Морозов

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

D3D11_INPUT_ELEMENT_DESC layout[] = {
        { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 }
    };

Сейчас вертексы, нормали и текстурные координаты. А были сначала вертексы, текстурные координаты и нормали. Я поменял их местами, и всё заработало. Освещение корректно показывает тени и модель.

→ Ссылка