본문 바로가기
서적 정리/DirectX11을 이용한 3D 게임 프로그래밍 입문

58.법선 벡터(Normal Vector)

by 민돌이2 2022. 3. 1.

면 법선(face normal)은 다각형이 향하고 있는 방향을 나타내는 단위 벡터고, 표면 법선(surface normal)은 표면의 한 점의 접평면에 수직인 단위벡터이다(39장 참고). 좀 더 쉽게 표현하자면 면 법선은 다각형의 모든 점에 수직인 단위벡터이고 표면 법선은 표면의 한 점이 바라보는 방향 벡터이다.

 

조명을 계산하기 위해서는 삼각형 메시 표면의 모든 점에서 표면 법선이 필요하다. 표면 법선이 있어야 광선이 메시 표면의 점으로 입사한 각도를 구할 수 있기 때문이다. 래스터화 과정에서 세 정점 법선의 보간을 통해서 표면 점의 법선이 결정된다.

 

 

1.법선 벡터의 계산

삼각형 △p0p1p2의 면 법선을 구하려면 삼각형의 두 변에 놓인 벡터를 구한후, 외적을 하면 구할 수 있다.

이 방식을 코드로 나타내면 아래와 같다.

void ComputeNormal(const D3DXVECTOR3& p0, const D3DXVECTOR3& p1, const D3DXVECTOR3&p2, D3DXVECTOR3& out)
{
	D3DXVECTOR3 u = p1 - p0;
	D3DXVECTOR3 v = p2 - p0;

	D3DXVec3Cross(&out, &u, &v); //외적
	D3DXVec3Normalize(&out, &out); //정규화
}

 

미분이 가능한 표면의 경우 미적분 기법들을 이용해서 표면의 점의 법선을 구할 수 있다. 일반적으로 다각형 메시는 미분이 가능하지 않다. 다각형 메시에 대해서는 정점 법선 평균을 이용한 기법이 흔히 쓰인다. 임의의 정점 v의 정점 법선 n을 구할 때, 정점 v를 공유하는 메시의 모든 다각형의 면 법선의 평균으로 근사한다.

이 방식을 코드로 나타내면 아래와 같다.

for (UINT i = 0; i < mNumTriangles; ++i)
{
	//i번째 삼각형의 인덱스
	UINT i0 = mIndices[i * 3 + 0];
	UINT i1 = mIndices[i * 3 + 1];
	UINT i2 = mIndices[i * 3 + 2];

	//i번째 삼각형의 정점
	Vertex v0 = mVertices[i0];
	Vertex v1 = mVertices[i1];
	Vertex v2 = mVertices[i2];

	//면 법선 계산
	Vector3 e0 = v1.pos - v0.pos;
	Vector3 e1 = v2.pos - v0.pos;
	Vector3 faceNormal = Cross(e0, e1); //외적

	//면 법선을 각 정점의 법선에 누적
	mVertices[i0].normal += faceNormal;
	mVertices[i1].normal += faceNormal;
	mVertices[i2].normal += faceNormal;
}

for (UINT i = 0; i < mNumvertices; ++i)
	mVertices[i].normal = Normalize(&mVertices[i].normal);

 

 

 

2.법선 벡터의 변환

외적이란 벡터와 수직인 벡터라는 것이고, 수직이란 것은 두 벡터의 내적은 0이라는 뜻이다. 즉, 접선 벡터 u = v1 - v0은 법선 벡터 n과 수직이고, u · n = 0이여야 한다는 뜻이다.

하지만 비균등 비례 변환 A를 적용한다면, uA = v1A- V0A와 변환된 법선 벡터 nA는 더 이상 수직이 아니다.

점과 벡터를 변환하는 변환 행렬 A가 적용되었을 때, 변환 후 접선 벡터와 법선 벡터가 여전히 직교가 되는 법선 벡터 변환 행렬 B를 구해야 한다. 즉, uA · nB = 0을 만족하는 B = (A-1)T인 A의 역행렬의 전치행렬을 구해야 한다.

아래의 코드는 역전치행렬을 계산하는 보조 함수이다.

static XMMATRIX InverseTranspose(CXMMATRIX M)
{
	XMMATRIX A = M;
	A.r[3] = XMVectorSet(0.0f, 0.0f, 0.0f, 1.0f); //4행 설정
	XMVECTOR det = XMMatrixDeterminant(A); //행렬식

	return XMMatrixTranspose(XMMatrixInverse(&det, A)); //역행렬의 전치
}

 

728x90

댓글