12
21

<본 포스팅은 강좌가 아닙니다.>

 

이전 다렉 프로젝트할때 뎁스맵 쉐도우를 적용했었다.

그러면서 3가지의 그림자 기법인

평면쉐도우, 프로젝션 쉐도우, 뎁스맵 쉐도우 대해 알고있었다.

 

1. 평면쉐도우

는 똑같은 캐릭터를 바닥면에 평면으로 렌더링 하는 것이고,

2. 프로젝션 쉐도우

는 카메라(광원)으로 부터 투영된 텍스쳐를 만들어 (렌더타깃 텍스처 = 그림자 맵)

바닥 텍스쳐에 덧붙여 그림자를 만들어내는 것이다. 

3. 뎁스맵 쉐도우

는 프로젝션 쉐도우에서 더 해서 깊이 바이어스를 사용해

self-shadow를 구현할 수 있는 그림자 기법이다.

 

또한, 이전 다렉 프로젝트에서 그림자 여드름, 투영앨리어싱(계단현상), 피터펜현상 등

쉐도우 구현에 고려해야할 문제들을 봤었다.

 

셰이더 프로그램 입문 책에는 바로 이 뎁스맵 쉐도우를 설명하신다.

렌더타깃을 이용해 그림자 깊이 값들을 저장해 픽셀 셰이더에서 그림자 계산에

쓴다. 그 텍스처를 그림자 맵이라하고, 그래서 그림자 매핑이라고 한다.

 

"그림자는 어떤 물체가 빛을 막아서 생기는 현상이다.

빛을 막는 물체가 여럿이면 처음에 빛을 막는 물체만 의미가 있음

처음 막는 물체의 깊이를 기억함

기억한 깊이와 현재 픽셀의 깊이를 비교해서 처음으로 빛을 막는지 체크

처음이 아니라면 그림자를 씌운다. "

 

 1. 그림자 생성단계

- 깊이를 저장할 렌더타깃 정하기

- 카메라를 광원의 위치에 두고 물체 그리기

- 픽셀셰이더에서 빛으로부터 현재 픽셀까지의 깊이를 반환

 

 2. 그림자 적용단계

 - 렌더링 결과를 화면백버퍼에 저장

 - 카메라를 눈의 위치 두고 물체를 그림

 - 빛으로부터 현재 픽셀까지의 깊이를 그림자맵에 담겨있는 결과와 비교

 - 현재 깊이가 그림자맵의 깊이보다 크면 그림자 씌운다.


렌더몽키를 켜보자.

D3D에서는 여러개의 셰이더를 사용하지만 부득이하게도,

랜더몽키에서 그림자 매핑을 만들려면 패스를 여러개 써야한다.

 

우선 렌더타깃 텍스처 위에 뎁스를 저장하는 패스부터 구현한다.

 

VS 버텍스셰이더

vs 구조체와 변수

 

카메라(플레이어)가 아닌 빛의 위치의 카메라다. 

그래서 view 행렬과 proj행렬이 기존과 다르다.

 

c++이였으면 행렬을 만들어서 보내주면 되는데

렌더 몽키에서는 불가능하기 때문에

여기 패스의 vs에서 광원 뷰 행렬을 제작해줘야한다.

 

vs 광원뷰행렬 직접 만든다. c++이 아닌 렌더몽키여서 하는것

 

직교를 구하는 외적을 이용해서 xyz의 방향을 구한다.

그걸로 행렬을 만들어서 전치해, 변환해주는 행렬로 만든다.

 

clipposition으로 보내는데 굳이 position을 안쓰는것은

픽셀셰이더에서 position시멘틱 변수를 곧바로 엑세스 할수 없기 때문이다.

그래서 다른 시멘틱인 texcoord로 보낸다. 그래도 position은 보내야한다.

레스터라이저가 픽셀들을 찾지 못하기 때문이다.

 

PS 픽셀셰이더

뎁스를 리턴

 

z는 나아가는 방향이다. w로 나누는 이유는

3차원 좌표에서 위치벡터 : (x,y,z,1), 방향벡터 : (x,y,z,0) 이된다.

위치벡터에 원근투영행렬을 곱하면 w 성분은 1이 아니게 된다.

 

이런 좌표를 동차좌표라고 하는데 이 좌표를  w로 나누면 w값이 1로 된다.

올바른 값을 구하기 위해 w로 나눈다고 생각하면 됨.

z의 범위는 0~1이 된다.

 

텍스처, 뎁스맵이다. clearcolor를 흰색으로 해야한다.

 

여기로 텍스쳐 저장
렌더타깃 설정

 

텍스쳐 shadowMap를 만들고 그걸 createShadow 패스의 렌더타겟으로 설정한다.

이게 깊이 값이 저장될 텍스쳐이다.


ApplyShadowTorus이름으로 새로운 패스를 만든다.

 

VS 버텍스셰이더

VS 변수 구조체
vs_main

아까처럼 광원 뷰행렬 만들어주고

실제 카메라 공간 변환

mClipPosition은 깊이를 구하는 공간으로 변환함. 

 

PS 픽셀셰이더

 

깊이 맵 저장한거 읽는 sampler2D 변수로 읽어온다.

tex2D로 읽어와야하는데 UV좌표를 우린 모른다. 

투영좌표계로 그려진 텍스쳐는 XY -> UV가 필요하다.

좌표를 구했으면 tex2D로 읽어온다.

tex2D(ShadowSampler, uv).r 이 R값만 가져오는것은

저 텍스처의 포맷이 R32F이다. R만 있음

 

XY 좌표 -> UV 좌표 UV 좌표 -> XY 좌표
u = x / 2 + 0.5 x = u * 2 - 1
v = -y / 2 + 0.5 y = -v * 2 -1

 

마지막으로 if문을 이용해 현재 뎁스 값이 깊이보다 크면 처음이 아니라는 뜻으로

그림자를 씌우는 모습이다.

0.0002f는 부동소수점 에러 때매 보정해주는 값이다. 보정치

 

보정값 보정치가 없을때
보정값 보정치가이 있을때

 

또한 그림자맵이 카메라에 포함되는 영역이 절반도

되지 않으면 그림자의 품질이 저하된다.

그래서 바닥면을 깔아주었다.

 

캐스케이드 그림자맵이나 , 퍼센티지 클로저 필터링

다른 고급기법이 있는데, 나중에 한번 구현해보도록 해보겠다.

 

특히 퍼센티지 클로저 필터링은 그림자의 외곽선을

자연스럽게 부드럽게 해줘서 한번 해보고 싶다.

더보기

createshadow

//vs
struct VS_INPUT
{
   float4 mPosition : POSITION;
};

struct VS_OUTPUT
{
   float4 mPosition : POSITION;
   float4 mClipPosition : TEXCOORD1; 
};
float4x4 gWorldMatrix;
float4x4 gLightViewMatrix;
float4x4 gLightProjMatrix;
float4   gWorldLightPos;

VS_OUTPUT vs_main(VS_INPUT Input)
{
   VS_OUTPUT Output;
   
   //게임엔진에서 해야하는 것
   float4x4 lightViewMatrix = gLightViewMatrix;
   float3 dirZ = -normalize(gWorldLightPos.xyz);
   float3 up = float3(0,1,0);
   float3 dirX = cross(up, dirZ);
   float3 dirY = cross(dirZ, dirX);
   
   lightViewMatrix = float4x4(
   float4(dirX, -dot(gWorldLightPos.xyz, dirX)),
   float4(dirY, -dot(gWorldLightPos.xyz, dirY)),
   float4(dirZ, -dot(gWorldLightPos.xyz, dirZ)),
   float4(0,0,0,1));
   lightViewMatrix = transpose(lightViewMatrix);
   //
   
   Output.mPosition = mul(Input.mPosition, gWorldMatrix);
   Output.mPosition = mul(Output.mPosition, lightViewMatrix);
   Output.mPosition = mul(Output.mPosition, gLightProjMatrix);
   Output.mClipPosition = Output.mPosition;
   
   return Output;
}
//ps
struct PS_INPUT
{
   float4 mClipPosition : TEXCOORD1;
};
// 깊이 값을 반환하는 함수
float4 ps_main(PS_INPUT Input) : COLOR
{   
   float depth = Input.mClipPosition.z/ Input.mClipPosition.w;
   return float4(depth.xxx,1);
}

 

applyshadowtorus

//vs
struct VS_INPUT 
{
   float4 mPosition : POSITION;
   float3 mNormal   : NORMAL;
};

struct VS_OUTPUT 
{
   float4  mPosition      : POSITION;
   float4  mClipPosition  : TEXCOORD1;
   float   mDiffuse       : TEXCOORD2;
};

float4x4 gWorldMatrix;
float4x4 gLightViewMatrix;
float4x4 gLightProjMatrix;

float4 gWorldLightPos;

float4x4 gViewProjMatrix;

VS_OUTPUT vs_main( VS_INPUT Input )
{
   VS_OUTPUT Output;
 //게임엔진에서 해야하는 것
   float4x4 lightViewMatrix = gLightViewMatrix;
   float3 dirZ = -normalize(gWorldLightPos.xyz);
   float3 up = float3(0,1,0);
   float3 dirX = cross(up, dirZ);
   float3 dirY = cross(dirZ, dirX);
   
   lightViewMatrix = float4x4(
   float4(dirX, -dot(gWorldLightPos.xyz, dirX)),
   float4(dirY, -dot(gWorldLightPos.xyz, dirY)),
   float4(dirZ, -dot(gWorldLightPos.xyz, dirZ)),
   float4(0,0,0,1));
   lightViewMatrix = transpose(lightViewMatrix);
   //
   //실제 장면
   float4 worldPosition = mul(Input.mPosition, gWorldMatrix);
   Output.mPosition = mul(worldPosition, gViewProjMatrix);
   
   Output.mClipPosition = mul(worldPosition, lightViewMatrix);
   Output.mClipPosition = mul(Output.mClipPosition, gLightProjMatrix);
   
   float3 lightDir = normalize(worldPosition.xyz - gWorldLightPos.xyz);
   float3 worldNormal = normalize(mul(Input.mNormal, (float3x3)gWorldMatrix));
   
   Output.mDiffuse = dot(-lightDir, worldNormal);
   return Output;
}

//ps
struct PS_INPUT
{
   float4  mClipPosition : TEXCOORD1;
   float   mDiffuse : TEXCOORD2;
};
sampler2D ShadowSampler;
float4     gObjectColor;
float4 ps_main(PS_INPUT Input) : COLOR
{
  float3 rgb = saturate(Input.mDiffuse)  * gObjectColor;
  //depth 연산 광원으로부터 현재 픽셸의 깊이
  float currentDepth = Input.mClipPosition.z / Input.mClipPosition.w;
  
  float2 uv = Input.mClipPosition.xy / Input.mClipPosition.w;
  uv.y = -uv.y;
  uv= uv*0.5+0.5;
  
  float shadowDepth = tex2D(ShadowSampler, uv).r;
  
  if(currentDepth>shadowDepth+ 0.00002f)
  {
     rgb*=0.5f;
  }
   return(float4(rgb,1.0f));
}
COMMENT