그림자 매핑1

이번장에서는 렌더타겟으로 담을 그림자 맵을 만들어보자.

렌더몽키의 셰이더 Pass 이름은 CreateShadow이다.

그림자 매핑은 광원을 중심으로 한 뷰 공간이다. 카메라 뷰 행렬과 구하는 것 처럼 광원-뷰 공간 행렬을 이용해서 그림자를 만든다.

 

1. 정점 셰이더

그림자맵을 만들 때 필요한 입력 정보는 정점 위치만 필요하다.

struct VS_INPUT
{
   float4 pos : POSITION;   
};

출력은 위치값과 깊이정보를 넘겨 준다.
픽셀 셰이더에 깊이값을 계산하기 위해 위치 정보를 넘겨줘야 한다.  픽셀셰이더에서는 POSITION 시맨티을 직접 바로 접근 할 수 없다. TEXCOORD 관련 시맨틱으로 깊이값을 전달해줄 수밖에 없다.
레스터라이저가 픽셀을 찾기 위해 기본적으로 위치값은 넘겨준다.

clipPos를 통해 깊이정보를 넘겨준다.

struct VS_OUTPUT
{
   float4 pos : POSITION;
   float4 clipPos : TEXCOORD1;
};

 

//Vertex Shader

struct VS_INPUT
{
   float4 pos : POSITION;   
};

struct VS_OUTPUT
{
   float4 pos : POSITION;
   float4 clipPos : TEXCOORD1;
};


float4x4 gWorldMat;
float4x4 gLightViewMat;
float4x4 gLightProjMat;

float4 gWorldLightPos;

VS_OUTPUT vs_main( VS_INPUT input)
{
   VS_OUTPUT output;
   
   float4x4 lightViewMat = gLightViewMat;  
   
   float3 dirZ = -normalize( gWorldLightPos);
   float3 up = float3( 0, 1, 0);
   float3 dirX = cross( up, dirZ);
   float3 dirY = cross( dirZ, dirX);
  
   lightViewMat = float4x4(
      float4( dirX, -dot( gWorldLightPos.xyz, dirX)),
      float4( dirY, -dot( gWorldLightPos.xyz, dirY)),
      float4( dirZ, -dot( gWorldLightPos.xyz, dirZ)),
      float4( 0, 0, 0, 1));   
   
   lightViewMat = transpose( lightViewMat);   
   
   output.pos = mul( input.pos, gWorldMat);
   output.pos = mul( output.pos, lightViewMat);
   output.pos = mul( output.pos, gLightProjMat);
   
   output.clipPos = output.pos;
      
   return output;
}

/////////////////////////////////////////////////////////////////////
//Pixel Shader

struct PS_INPUT
{
   float4 clipPos :TEXCOORD1;
};

float4 ps_main( PS_INPUT input) : COLOR
{   
   float depth = input.clipPos.z / input.clipPos.w;
   return float4( depth.xxx, 1);
}


float4x4 gWorldMat;
float4x4 gLightViewMat;
float4x4 gLightProjMat;

float4 gWorldLightPos;

정점을 카메라가 아니라 광원을 중심으로 한 공간으로 변환한다.

월드 공간 ===> 광원 뷰 공간 ===> 광원 투영 공간

그래서 전역 변수로 아래 세 개가 필요하다.

float4x4 gWorldMat;
float4x4 gLightViewMat;
float4x4 gLightProjMat;

렌더몽키의 Workspace에도 추가해 준다. 시맨틱은 다음과 같이 정한다.

gWorldMat 시맨틱 : World

gLightViewMat 시맨틱 : 광원의 위치에서 내려다보는 행렬이다. 비슷한 시맨틱이 없기 때문에 옳은 방법이 아니지만 렌더몽키에서는 정점셰이더에서 만들도록 한다.

gLightProjMat 시맨틱 : Projection 시맨틱을 대입한다. 카메라 투영행렬의 인접평면 깊이, 원거리평면 깊이, 종횡비, 시야각을 사용하게 된다고 보면 된다.

광원-뷰행렬을 만들려면 광원의 위치와 바라보는 타겟이 필요하다.
광원의 타겟은 (0, 0, 0), 위치는 gWorldLightPos로 전역 변수로 추가한다. 렌더몽키에서 값은 (500, 500, -500)으로 설정한다.

float4 gWorldLightPos;

광원-뷰 행렬를 구해보자.

광원-뷰 행렬은 게임엔진에서 넘겨준다. 렌더몽키에서 돌리기 위해서 만든코드이니 이해 할 필요는 없다.

뷰 행렬에 대한 설명은 mathematics/tutorial19.html의 뷰 행렬을 참고한다.

 VS_OUTPUT output;
 
 float4x4 lightViewMat = gLightViewMat;  
 
 float3 dirZ = -normalize( gWorldLightPos);
 float3 up = float3( 0, 1, 0);
 float3 dirX = cross( up, dirZ);
 float3 dirY = cross( dirZ, dirX);

 lightViewMat = float4x4(
    float4( dirX, -dot( gWorldLightPos.xyz, dirX)),
    float4( dirY, -dot( gWorldLightPos.xyz, dirY)),
    float4( dirZ, -dot( gWorldLightPos.xyz, dirZ)),
    float4( 0, 0, 0, 1));   
 
 lightViewMat = transpose( lightViewMat);

이렇게 광원-뷰 행렬을 구했으니 정점을 변환해보자.

정점 ===> 월드 공간 ===> 광원 뷰 공간 ===> 광원 투영 공간

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

광원 투영 공간의 위치 값과 clipPos에도 똑같이 한번더 대입해준다.

   output.clipPos = output.pos;

 

2. 픽셀 셰이더

깊이 값을 계산하기 위해 위치 값을 받는다.

struct PS_INPUT
{
   float4 clipPos :TEXCOORD1;
};

깊이 값을 계산하기 위해 input.clipPos.z 값을 w 값으로 나눠 준다.

float4 ps_main( PS_INPUT input) : COLOR
{   
   float depth = input.clipPos.z / input.clipPos.w;
   return float4( depth.xxx, 1);
}

3차원 좌표에서 위치 벡터는 (x, y, z, 1), 방향 벡터는 (x, y, z, 0)이다.
그리고, 동차좌표에 있는 값을 화면상의 좌표 값으로 사용할려면 w값으로 나눠준다.

float 데이터 타입인데 인데 depth.xxx 문법도 된다.

참조 ) 한빛 미디어 셰이더 프로그래밍 입문

다운로드 : ShadowMapping1.rfx