대부분의 게임에서 표준으로 사용되는 조명셰이더 기법을 알아보도록 하겠다.
빛을 구성하는 요소로 크게 난반사광diffuse light과 정반사광specular light가 있다.
난반사광diffuse light이란?
발산하는 빛이 물체의 표면에 반사될때 여러방향으로 고르게 반사되는 빛을 가리킨다.
이때문에 어느방향에서 바라봐도 물체의 명암이나 색조가 크게 변하지 않는다.
게임에 주로 사용하는 람베르트lambert 모델을 살펴보겠다.
람베르트 모델은 표면법선과 입사광이 이루는 각의 코사인 값을 구하면 그게 바로 난반사광의 양이라고 한다.
람베르트 모델은 코사인 함수를 내적dot projuct연산으로 대신해 난반사광의 값을 구한다.
※ 두 벡터가 이루는 각의 코사인 값은 그 둘의 내적을 구한 뒤 두 벡터의 길이를 곱한 결과로 나눈 것과 같다.
두 벡터의 길이를 1로 만들면 공식을 더 간단히 만들 수 있다.
A ⋅ B = cosθ | A || B |
렌더몽키에서 이를 확인해보자.
먼저 난반사광을 계산하기 위해 입사광의 벡터와 표면법선벡터가 필요하다.
입사광 벡터 Float4 전역변수 gWorldLightPosition(500, 500, -500, 1)을 추가한다.
정점셰이더 Vertex Shader
float4x4 gWorldMatrix;
float4x4 gViewMatrix;
float4x4 gProjectionMatrix;
float4 gWorldLightPosition;
struct VS_INPUT
{
float4 Position : POSITION0;
// 정점버퍼에서 법선을 가리키는 시맨틱 NORMAL
float3 Normal : NORMAL;
};
struct VS_OUTPUT
{
float4 Position : POSITION0;
float3 Diffuse : TEXCOORD1;
};
VS_OUTPUT vs_main( VS_INPUT Input )
{
VS_OUTPUT Output;
Output.Position = mul( Input.Position, gWorldMatrix );
// 현재 위치에서 광원의 위치를 빼면 입사광의 벡터를 구할 수 있다.
float3 lightDir = Output.Position.xyz - gWorldLightPosition.xyz;
lightDir = normalize(lightDir); // 벡터의 길이를 1로 만든다.
Output.Position = mul(Output.Position, gViewMatrix);
Output.Position = mul(Output.Position, gProjectionMatrix);
// 입사광의 벡터와 같은 월드공간이 되도록
// 정점버퍼의 법선을 월드공간으로 변환해준다.
float3 worldNormal = mul(Input.Normal, (float3x3)gWorldMatrix);
worldNormal = normalize(worldNormal);
// N ⋅ L을 하여 난반사광의 값을 구한다.
Output.Diffuse = dot(-lightDir, worldNormal);
return( Output );
}
픽셀셰이더 Pixel Shader
struct PS_INPUT
{
float3 Diffuse : TEXCOORD1;
};
float4 ps_main(PS_INPUT Input) : COLOR0
{
// 내적은 코사인 함수이므로 -1 ~ 1 사이의 결과 값을 가지므로
// -1 이하인 값을 0으로 바꾼다.
// saturate() 함수는 0이하의 값을 0으로 1 이상의 값을 1로 변경한다.
float3 diffuse = saturate(Input.Diffuse);
return float4( diffuse, 1.0f );
}
출처 : 셰이더 프로그래밍 입문
'Shader & 포스트 프로세싱' 카테고리의 다른 글
디퓨즈/스페큘러 매핑 (0) | 2022.02.23 |
---|---|
기초적인 조명셰이더(2)_정반사광specular light (0) | 2022.02.23 |
텍스처매핑 (0) | 2022.02.15 |
빨강셰이더 (0) | 2022.02.15 |
Shader란? (0) | 2022.02.14 |
댓글