03
18

이번에는 법선매핑, 노말 맵을 다이렉트x에 적용한다.

노말 맵은 픽셀에 사용할 법선 정보를 담고 있다.

평평한 면의 법선
울퉁불퉁한 노말맵을 적용한 면

노말 매핑을 사용하면 버텍스의 노말(법선) 정보가 아닌
텍스쳐의 노말(법선)정보를 사용하므로, 텍스쳐의 공간을 사용해야 한다.

법선맵에 관한 내용은 이전에도 포스팅했다
https://dlemrcnd.tistory.com/m/37

셰이더 프로그래밍 - AMD RenderMonkey 사용해서 쉐이더 공부를 해보자5 (법선 매핑)

<본 포스팅은 강좌가 아닙니다.> 조명을 계산할때 각 정점의 법선(노말) 정보를 가져와서 조명효과를 결정했다. 박스면의 법선을 생각해보자면 한 면은 아래와 같은 모습일 것이다. 만약 벽돌처

dlemrcnd.tistory.com


이때는, TBN 행렬을 렌더 몽키의 자체적인 레이아웃에서
만들어 볼 수 있었는데, 직접 다이렉트에서 구현해보려니
이 접선 공간, 탄젠트 스페이스에 대해서 알아야 할 필요성을 느꼈다.


접선 공간(Tangent Space)

접선 공간은 텍스쳐 위의 좌표계라고도 한다.
법선벡터(Normal), 접선벡터(Tangent), 바이노멀(Binormal)
벡터 축으로 하는 공간이다.
이러한 벡터들로 한 픽셀이 어떤 기울기를 갖고 있는지 알 수 있다.

법선(Normal) : 면에서 수직인 벡터
접선(Tangent) : 정점 표면의 정보
종법선(Bi-Normal) : 접선과 법선의 외적 된 결과, 노말의 수직인 벡터

이러한 공간을 사용하는 이유는 각 정점마다 좌표계가 모두 다르기 때문이다.
이러한 기준으로 노말 정보를 담은 게 탄젠트 공간 노말 맵이라 한다.

라이팅 연산을 위해서 벡터와 벡터끼리 공간을 맞춰줘야 한다.
빛 방향이 월드 공간에 있다면 노말 방향도 월드 공간에 있어야 한다.
위에서 말했듯이 우리의 텍스쳐인 법 선 맵 (노말 맵)은 탄젠트 공간에 있음

변환을 하기 위해서는 TBN(tangent, binormal, normal) 행렬을 만들어야 한다.

기준 행렬( Row major matrix ) - d3d
| Tx Ty Tz |
| Bx By Bz |
| Nx Ny Nz |

열기 준 행렬 ( Column major matrix ) - open gl
| Tx Bx Nx |
| Ty By Ny |
| Tz Bz Nz |


세 점으로 법선, 종법선, 접선을 구하는 함수를 구현했다.

CreateTangentSpace

void KBoxObj::CreateTangentSpace(KVector3* v1, KVector3* v2, KVector3* v3, KVector2* uv1, KVector2* uv2, KVector2* uv3, KVector3* normal, KVector3* tangent, KVector3* binormal)
{
	KVector3 vEdge1 = *v2 - *v1;
	KVector3 vEdge2 = *v3 - *v1;

	float vEdge1_U = uv2->x - uv1->x;
	float vEdge1_V = uv2->y - uv1->y;
	float vEdge2_U = uv3->x- uv1->x;
	float vEdge2_V = uv3->y - uv1->y;


	float fDenominator = vEdge1_U * vEdge2_V - vEdge2_U * vEdge1_V;

	if (fDenominator < 0.0001f && fDenominator>-0.0001f)
	{
		*tangent =KVector3(1.0f, 0.0f, 0.0f);
		*binormal= KVector3(0.0f, 1.0f, 0.0f);
		*normal= KVector3(0.0f, 0.0f, 1.0f);
	}
	else
	{
		//계산
		float fScale = 1.0f / fDenominator;

		KVector3 T;
		KVector3 B;
		KVector3 N;
		T = KVector3((vEdge2_V * vEdge1.x - vEdge1_V * vEdge2.x) * fScale,
			(vEdge2_V * vEdge1.y - vEdge1_V * vEdge2.y) * fScale,
			(vEdge2_V * vEdge1.z - vEdge1_V * vEdge2.z) * fScale);
		B = KVector3((-vEdge2_U * vEdge1.x + vEdge1_U * vEdge2.x) * fScale,
			(-vEdge2_U * vEdge1.y + vEdge1_U * vEdge2.y) * fScale,
			(-vEdge2_U * vEdge1.z + vEdge1_U * vEdge2.z) * fScale);
            
		D3DXVec3Cross( &N, &T, &B );
	
		float fScale2 = 1.0f / ((T.x * B.y * N.z - T.z * B.y * N.x) +
								(B.x * N.y * T.z - B.z * N.y * T.x) + 
								(N.x * T.y * B.z - N.z * T.y * B.x));
		KVector3 vTemp;
		(*tangent).x = D3DXVec3Cross(&vTemp, &B, &N)->x * fScale2; 
		(*tangent).y = -(D3DXVec3Cross(&vTemp, &N, &T)->x * fScale2);
		(*tangent).z = D3DXVec3Cross(&vTemp, &T, &B)->x * fScale2;
		D3DXVec3Normalize(&(*tangent), &(*tangent));

		(*normal).x = -(D3DXVec3Cross(&vTemp, &B, &N)->y * fScale2); 
		(*normal).y = D3DXVec3Cross(&vTemp, &N, &T)->y * fScale2;
		(*normal).z = -(D3DXVec3Cross(&vTemp, &T, &B)->y * fScale2);
		D3DXVec3Normalize(&(*normal), &(*normal));
        
		(*binormal).x = D3DXVec3Cross(&vTemp, &B, &N)->z * fScale2;
		(*binormal).y = -(D3DXVec3Cross(&vTemp, &N, &T)->z * fScale2);
		(*binormal).z = D3DXVec3Cross(&vTemp, &T, &B)->z * fScale2;
		D3DXVec3Normalize(&(*binormal), &(*binormal));
	}

}


세 점의 벡터를 얻어와서 가장자리의 벡터를 구한다.
텍스쳐 위의 좌표이기 때문에 UV값도 가져온다.
점과 점 사이의 간격이 너무 좁은 예외 사항 처리를 하고,
탄젠트와 바이 노말은 UV의 좌표에서 수직 관계이어서 이를 이용한다.
탄젠트와 바이 노말을 구했으면 외적 결과로 노말 값을 얻는다. 이런 함수로
어떤 면이든 3점을 얻어서 TBN 행렬을 만들 수 있도록 해준다.

만든 TBN을 버텍스 버퍼에 담아 보내주고
PS에서 TBN행렬을 제작한다. 버퍼로 행렬만 넘겨줘도 되지만,
빠른 구현을 위해 픽셸셰이더에서 계산했다.

PSShader

cbuffer CBuf
{
	matrix g_matWorld : packoffset(c0);
	matrix g_matView : packoffset(c4);
	matrix g_matProj : packoffset(c8);
	matrix g_matNormal : packoffset(c12);
	float4 g_lightPos : packoffset(c16); //라이트 방향
	float4 g_lightColor : packoffset(c17); //라이트 색상
	float4 g_camPos : packoffset(c18); //카메라 방향
	float4 g_value : packoffset(c19); //기타 시간 값등
};
struct VS_OUTPUT
{
	float4 p : SV_POSITION;
	float2 t : TEXCOORD0;
	float3 mLightDir : TEXCOORD1; //방향
	float3 mViewDir : TEXCOORD2; //방향
	float3 mT        : TEXCOORD3;
	float3 mB        : TEXCOORD4;
	float3 mN        : TEXCOORD5;
};

Texture2D		g_txDiffuse : register(t0);
Texture2D		g_txSpecular : register(t1);
Texture2D		g_txNormal : register(t2);
SamplerState	g_Sample : register(s0);
float4 PS(VS_OUTPUT Input) : SV_TARGET
{
	//텍스쳐에서 노말, 법선 좌표 구해옴
   float3 tangentNormal = g_txNormal.Sample(g_Sample, Input.t).xyz;
   tangentNormal = normalize(tangentNormal * 2 - 1);

   float3x3 TBN = float3x3(normalize(Input.mT), normalize(Input.mB),normalize(Input.mN));
   TBN = transpose(TBN);
   float3 worldNormal = mul(TBN, tangentNormal);
   //디퓨즈 텍스쳐
   float4 albedo = g_txDiffuse.Sample(g_Sample, Input.t);
   float3 lightDir = normalize(Input.mLightDir);
   float3 diffuse = saturate(dot(worldNormal, -lightDir));
   diffuse = g_lightColor.rgb*albedo.rgb * diffuse;

   float3 specular = 0;

   if (diffuse.x > 0.0f)
   {
	  float3 reflection = reflect(lightDir, worldNormal);
	  float3 viewDir = normalize(Input.mViewDir);

	  specular = saturate(dot(reflection, -viewDir));
	  specular = pow(specular,20.0f);

	  //스페큘러 텍스쳐
	  float4 specularInten = g_txSpecular.Sample(g_Sample, Input.t);
	  specular *= specularInten.rgb* g_lightColor;
   }

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

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

결과


COMMENT