본문 바로가기
ComputerGraphics/HLSL

난반사 HLSL (람베르트 반사) 쉐이더 / 렌더몽키(RenderMonkey)

by lucidmaj7 2020. 5. 28.
728x90
반응형

0. 람베르트 난반사

오늘 공부해볼 HLSL은 조명 구현 중에서 난반사에 대한 것입니다. 난반사는 어느 각도에서 보던 고르게 반사되는 반사를 말합니다. 예를 들면 나무로된 탁자의 표면, 흰종이를 들을 수 있겠습니다.

컴퓨터 그래픽에서도 이러한 난반사를 계산하여 표현하고 있는데 일반적으로 람베르트 코사인 법칙( Lambert's cosine law)을 이용하고 있습니다.

https://en.wikipedia.org/wiki/Diffuse_reflection

람베르트의 난반사 법칙은 표면의 법선 벡터와 입사광이 이루는 벡터의 코사인 값이 반사되는 광량이라고 정의를 내리고 있습니다.

https://ko.wikipedia.org/wiki/%EC%82%BC%EA%B0%81%ED%95%A8%EC%88%98

코사인 그래프를 보면 0에서 1 90도에서 0을 나타내고 있습니다. 즉, 빛을 수직으로 비추면 가장 밝은 빛이 난반사 된다는 것을 나타내며 0도에 가까워 질수록 반사되는 빛의 양이 감소합니다.

https://www.4physics.com/tn3/lambertian.htm

그럼 이러한 개념을 가지고 쉐이더를 작성해 보겠습니다.

 

1. 렌더몽키 설정

Effect Workspace를 우클릭하여 Add Default Effect > DirectX > DirectX를 클릭하여 Effect를 추가해줍니다.

다음 각각 View, World, Projection Matrix를 추가해주고 float4 gWorldLightPosition을 추가해줍니다. gWorldLightPosition는 빛의 입사되는 방향을 가리키는 벡터가 됩니다.

Stream Mapping을 더블클릭하여 인풋에 NORMAL을 flaot3로 추가해 줍니다.

2. 정점 쉐이더 작성

이전과 같이 Vertex Shader의 내용을 다 지우고 시작합니다. 다음은 람베르트 난반사를 구현한 정점 쉐이더입니다.

float4x4 gViewMatrix;
float4x4 gWorldMatrix;
float4x4 gProjectionMatrix;

float4 gWorldLightPosition;

struct VS_INPUT{
   float4 mPosition: POSITION;  //입력 로컬 좌표 
   float3 mNormal: NORMAL;  // 법선 벡터 
};


struct VS_OUTPUT{
   float4 mPosition: POSITION; // 최종 변환 좌표 
   float3 mDiffuse: TEXCOORD1; // 반사광 

};

VS_OUTPUT vs_main(VS_INPUT input)
{

   VS_OUTPUT output;
   
   output.mPosition = mul(input.mPosition, gWorldMatrix);
   
   float3 lightDirection = output.mPosition.xyz - gWorldLightPosition.xyz;
  
   
   output.mPosition = mul(output.mPosition, gViewMatrix);
   output.mPosition = mul(output.mPosition, gProjectionMatrix);
   
   lightDirection = normalize(lightDirection);
   float3 worldNormal = mul(input.mNormal , (float3x3)gWorldMatrix );
   worldNormal= normalize( worldNormal );
   
   output.mDiffuse = dot(-lightDirection , worldNormal);
   
   return output;


}

입사광의 방향을 나타내는 벡터인 gWorldLightPosition 벡터가 전역으로 설정되어 있습니다. 

VS_INPUT에는 각 정점의 좌표와 정점의 Normal 벡터가 입력버퍼로 되어있으며 VS_OUTPUT는 월드 변환을 마친 정점좌표와 반사광의 양을 나타내는 mDiffuse가 들어있습니다.

람베르트 난반사에서 필요한 것은 입사광의 방향 벡터와 정점의 법선 벡터입니다. gWorldLightPosition에서 나온 빛이 정점위치에 부딧치는 곳까지의 벡터를 구하게 되는데 이때 빛이 부딧치는 정점은 월드 좌표로 변환이된 곳이어야 합니다. 때문에 lightDirection을 구하기 전에 input.mPosition을 gWorldMatrix와 곱하여 월드 좌표로 변환을 먼저 합니다.

다음 output.mPosition.xyz - gWorldLightPosition.xyz를 하여 빛이 광원에서 나와 표면까지의 벡터를 구해줍니다. 이렇게 구한 빛의 벡터를 normalize하여 길이가 1인 벡터로 만들어줍니다.(방향만 중요하기 때문에 길이는 필요 없습니다.)

이제 입력으로 주어진 법선 벡터를 월드 변환하여 월드 변환된 좌표의 법선벡터로 바꿉니다. mul(input.normal, gWorldMatrix) 이렇게 구한 월드 노멀 벡터도 역시 normalize하여 방향만 남겨 둡니다.

이렇게 구해진 worldNormal과 lightDirection을 내적하면 바로 두 벡터의 코사인 값이 구해집니다. 이때 빛의 방향 벡터를 반대로 바꾼 -lightDirection와 내적합니다. (두벡터벡터를 내적할때에는 시작점이 같게..) 결과값을 mDiffuse에 넣어 반환하여 픽셀 쉐이더로 전달하게 됩니다.

 

3. 픽셀 쉐이더 작성

픽쉘 쉐이더도 마찬가지로 모두 다 지워주고 다음과 같이 작성합니다.

struct PS_INPUT
{
   float3 mDiffuse : TEXCOORD1;
};


float4 ps_main(PS_INPUT Input) : COLOR
{
   float3 diffuse = saturate(Input.mDiffuse);
   return float4(diffuse, 1);
}

입력에는 앞서 정점 쉐이더에서 넘어온 mDiffuse를 받아서 뿌려주면됩니다. 이때 saturate 함수로 cos 값으로 넘어온 mDiffuse에서 0이하는 0으로 처리해줍니다. 그리고 float3인 mDiffuse를 float4로 변환하여 리턴합니다. 

4. 결과

5. 정리

  • 길이가 1인 두 벡터의 내적은 두 벡터가 이루는 cos값이다. 
  • 람베르트 난반사의 반사량은 빛의 각도와 표면의 법선벡터가 이루는 각도의 코사인값이다.
  • saturate함수는 0이하 의 값은 0으로 1이상의 값은 1로 바꿔준다.

6. 참고

 

람베르트난반사.rfx
0.04MB

728x90
반응형

댓글