🎨DirectX

[DX] 음영 효과 (Light Direction)

공대 컴린이 2023. 2. 5. 15:29
728x90

💡 음영

음영을 구현하기 위해선 어느 방향에서 빛이 내리쬐고 있는지 Light Direction이 필요하다.

음영을 구현하는데 노멀벡터가 필요했던 이유는 Light Direction과 내적하기 위함이다.

📐 내적의 성질

내적은 "|A| |B| cosθ"를 적용하는데, 방향 벡터의 크기를 1로 잡는다고 하면 결과는 cosθ 만 나오게 된다.

즉, 벡터 A와 벡터 B가 이루는 내각이 cosθ 이다.

이러한 내각의 크기를 이용하여 태양이 위치한 각도와 사람이 위치한 각도에 따라 내적값이 변화하는 원리를 적용시켜보았다.

 

내적의 성질에 따르면 태양과 사람이 수직일수록 밝은(1에 가까운)값이 나오고, 태양이 땅과 가까울수록 사람과 직각이되어 가장 어두운(0에 가까운)값이 나온다.

즉, 태양을 Light Direction으로 계산하고 사람을 노멀벡터로 계산하여 내적하면 내적값에 따라 음영이 어느정도의 수치로 적용되는지 구할 수 있다.

📐 빛 방향 (Light Direction) 뒤집기

 

이때 빛 방향은 하늘에서 땅으로 내려오는 방향벡터일것인데, 빛 방향을 뒤집지 않으면 노멀 벡터와 내적한 값이 땅 아래를 향하기 때문에 이를 뒤집어서 사용해야 한다.

🌎 Normal 벡터의 World 변환

Normal 벡터를 Pixel Shader로 전달하기 전에, 항상 World 변환을 하고 전달해야 한다.

그 이유는, World가 카메라에 의해 회전되어 기울어진다면 음영을 구현하는 Normal 벡터 또한 함께 기울어져야 정상적인 조명 연산이 적용되기 때문이다.

💡 벡터 w값에 따른 위치벡터/방향벡터

벡터 float4는 x,y,z,w를 나타낸다.
w가 1일때는 4x4 행렬이 되어 위치까지 모두 포함된 위치벡터라고 하고, w가 0일때는 3x3 행렬이 되어 위치값이 사라지고 방향만 포함되어 있기 때문에 방향벡터라고 한다.
float3는 w가 0인것과 마찬가지이므로 방향벡터라고 생각하고 쓴다.

따라서 쉐이더 소스코드에서 Position은 위치값을 포함하는 float4를 사용하고, Normal은 위치값 필요없이 방향만 나타내므로 float3을 사용한다.


📄 Terrain.fx

matrix World;
matrix View;
matrix Projection;
float3 Direction; // 빛 방향

struct VertexInput
{
    float4 Position : Position;
    float3 Normal : Normal;
};

struct VertexOutput
{
    float4 Position : SV_Position;
    float3 Normal : Normal;
};

VertexOutput VS(VertexInput input)
{
    VertexOutput output;
    output.Position = mul(input.Position, World);
    output.Position = mul(output.Position, View);
    output.Position = mul(output.Position, Projection);

    // 중요 - World로 공간 변환 후 반환해야 한다. (위에 설명 작성함)
    // Normal은 float3이고, World는 4x4행렬이기 떄문에 (float3x3)으로 형변환
    output.Normal = mul(input.Normal, (float3x3) World); 
    
    return output;
}

float4 PS(VertexOutput input) : SV_Target
{
    float3 normal = normalize(input.Normal);
    float3 light = -Direction; // 빛 방향 뒤집기

    // dot(light, normal) = cosθ 이기 때문에 dot결과가 1이면 float4(1,1,1,1)값이 그대로 나오고
    // dot 결과가 0에 가까울수록 어두워짐
    return float4(1, 1, 1, 1) * dot(light, normal);
}

RasterizerState FillMode_Wireframe
{
    FillMode = Wireframe;
};

technique11 T0
{
    pass P0
    {
        SetVertexShader(CompileShader(vs_5_0, VS()));
        SetPixelShader(CompileShader(ps_5_0, PS()));
    }

    pass P1
    {
        SetRasterizerState(FillMode_Wireframe);

        SetVertexShader(CompileShader(vs_5_0, VS()));
        SetPixelShader(CompileShader(ps_5_0, PS()));
    }
}

[ 실행 결과 ]

728x90