기본 조명

게임상의 기본적인 조명 공식은 다음과 같다.

글로벌 일루미네이션 = 앰비언트 라이트 + 디퓨즈 라이트 + 스펙큘러 라이트 + 에미션(emission) 라이트

에미션 라이트는 빛의 자체 발광색으로 여기서는 무시한다.

앰비언트 라이트 : 씬의 기본 색깔

디퓨즈 라이트 :
가장 흔히 사용되는 방법은 램버트의 법칙( Lambert's law )이다.
모든 방향에 같은 강도로 반사되며, 빛은 카메라의 방향에 무관하다.

램버트의 법칙은 다음과 같다.

max(NL, 0)   

N : 버텍스 노멀값
L : 라이팅 벡터

버텍스 노멀값과 라이팅 벡터가  θ >= 90이면 빛의 세기는 0이다.
θ = 0이면 버텍스 노멀값과 라이팅 벡터가 동일하며 빛의 세기는 가장 강하다.

float3 diffuse = dot(-lightDir, normal);
diffuse = saturate(diffuse);

 

스펙큘러 라이트 :

가장 흔히 사용되는 방법은 퐁 모델이다.
시선 벡터( view vector)와 반사 벡터( reflection vector )를 이용해 빛의 양을 구한다.
반사 벡터는 빛이 물체의 노멀값에 대하여 반사 되는 벡터이다.

max(RV, 0)^α

R : 반사 벡터
V : 시선 벡터
α : pow의 승으로 하이라이트를 범위를 좁게 모을려면 20이상 클수록 하이라이트 범위가 좁다.

float3 viewDir = output.pos.xyz - gCameraPos.xyz;
viewDir = normalize(viewDir); 

float3 reflection = reflect(lightDir, normal);
if(diffuse.x > 0)
{
    specular = saturate(dot(reflection, -viewDir));
    specular = pow(specular, 20);
}

 

struct VS_INPUT

{

    float4 pos : POSITION;

    float3 normal : NORMAL;

};

 

struct VS_OUTPUT

{

    float4 pos : POSITION;

    float3 diffuse : TEXCOORD1;

    float3 viewDir : TEXCOORD2;

    float3 reflection : TEXCOORD3;

};

 

float4x4 gWorldMat;

float4x4 gViewMat;

float4x4 gProjMat;

 

float4 gLightPos;

float4 gCameraPos;

 

VS_OUTPUT vs_main(VS_INPUT input)

{

    VS_OUTPUT output;

 

    output.pos = mul(input.pos, gWorldMat);

 

    float3 lightDir = output.pos.xyz - gLightPos.xyz;

    lightDir = normalize(lightDir);

 

    float3 viewDir = output.pos.xyz - gCameraPos.xyz;

    viewDir = normalize(viewDir);

    output.viewDir = viewDir;

 

    output.pos = mul(output.pos, gViewMat);

    output.pos = mul(output.pos, gProjMat);

 

    float3 normal = mul(input.normal, (float3x3)gWorldMat);

    normal = normalize(normal);

 

    output.diffuse = dot(-lightDir, normal);

    output.reflection = reflect(lightDir, normal);

 

    return output;

}

gLightPos 값은 ( 500, 500, -500, 1)이다.
gCameraPos 값은 (0, 0, -200, 1)이다.

struct PS_INPUT

{

    float3 diffuse : TEXCOORD1;

    float3 viewDir : TEXCOORD2;

    float3 reflection : TEXCOORD3;

};

 

float4 ps_main(PS_INPUT input) : COLOR

{

    float3 diffuse = saturate(input.diffuse);

 

    float3 reflection = normalize(input.reflection);

    float3 viewDir = normalize(input.viewDir);

    float3 specular = 0;

 

    if(diffuse.x > 0)

    {

        specular = saturate(dot(reflection, -viewDir));

        specular = pow(specular, 20);

    }

 

    float3 ambient = float3(0.1f, 0.1f, 0.1f);

 

    return float4(ambient + diffuse + specular, 1);

}

reflection, viewDir을 픽셀 셰이더에서 다시 정규화 해주고 있다.
버텍스 셰이더와 픽셀 셰이더 사이의 보간기로 인해 노멀값이 보간되어 다시 정규화 시켜준다.

if(diffuse.x > 0)
{
    specular = saturate(dot(reflection, -viewDir));
    specular = pow(specular, 20);
}

디퓨즈 라이트가 0이상일때 스펙큘러 라이트를 계산합니다.

참조 : 포프 김님의 셰이더 프로그래밍 입문 책 참조

렌더 몽키 파일 : light.rfx
다이렉트 X 파일 : DxDemo_03_diffuselight.zip
                        DxDemo_04_phong_arcball.zip