3D 모델 하나가 GPU에 들어가서 모니터의 픽셀이 되기까지 무슨 일이 일어나는지 설명할 수 있다.
3D 모델의 정점
↓
정점 변환
↓
화면에 보이는 부분만 남김
↓
삼각형을 픽셀 후보들로 변환
↓
각 픽셀의 색 계산
↓
깊이/스텐실 등의 검사
↓
이미지 버퍼에 기록
↓
화면 출력
게임에서 메시 하나를 그린다고 했을때 대략 이런 과정을 거침
이것을 크게 세부분으로 나눈다
그래픽 처리기 -> 정점 변환 -> 레스터화와 단편 연산
GPU는 무엇을 받는가??
GPU입장에서는 기본적으로 정점(Vertex)과 정점들이 어떻게 연결되어 있는가가 중요
V0
/\
/ \
/ \
V1------V2
삼각형 하나라면 정점 세개가 있고 이 세개를 연결해 삼각형을 만든다. 3D 모델은 이런 삼각형이 아주 많이 모여있는 형태라고 생각하면 된다.
수천~수만개가 보이면 메시가 되고, 이때 3차원 벡터가 등장한다.
CPU는 GPU에게
이 메시 그려
이 텍스처 사용해
이런 설정으로 렌더링해
같은 렌더링 작업을 전달하면 GPU가 그 작업을 진행한다.
이는 비동기적으로 작동한다 (CPU와 GPU를 억지로 계속 동기화하면 성능이 떨어진다)
CPU
│
│ "이거 렌더링해"
▼
GPU ───── 렌더링 중 ─────→
│
│
CPU ───── 다른 게임 로직 수행 ─────→
GPU에도 사용할 메모리가 필요하다.
VRAM에는 여러 그래픽 데이터가 들어가는데 대표적으로
텍스처
이미지 버퍼
깊이 버퍼
스텐실 버퍼
등이있다.
GPU가 화면을 그린다고 해서 모니터에 직접 픽셀 하나씩 바로 그리는 방식은 아니다.
전면 이미지 버퍼(Front Buffer)와 후면 이미지 버퍼(Back Buffer)가 있다.
Front Buffer는
현재 사용자에게 보여주고 있는 화면
Back Buffer는
GPU가 다음 화면을 그리고 있는 곳
이라고 생각하면 됨
Back Buffer가 완성되면 두 버퍼를 교체하는데 이를 Buffer Swap이라고 한다.
GPU가 단순히 픽셀 색만 그리면
먼 물체를 나중에 그렸다는 이유로
앞의 문제를 덮어버리는 문제
가 생길 수 있음 그래서 픽셀마다 깊이값을 저장한다.
Color Buffer -> (120, 50, 20) : 픽셀 색상
Depth Buffer -> 0.37 : 픽셀 깊이
개념적으로 현재 저장된 깊이 = 0.7, 새로운 픽셀 깊이 = 0.3 이렇게 새로운 픽셀이 더 앞에 있다면 그 픽셀이 살아남는다.
그래서 뒤의 물체가 앞 물체를 뚫고 보이지 않도록 만들 수 있다.
이미지의 각 픽셀에 대한 깊이 값을 담고, 가려진 표면을 제거하는데 사용하는 버퍼
각 픽셀에 대해서 여기를 그릴지 말지 등을 제어하기 위한 정수 마스크
이미지 버퍼 각 픽셀에 대한 정수 마스크, 픽셀단위의 렌더링 활성화/비활성화에 사용
캐릭터 머리의 어떤 정점 좌표가 (0, 0, 100)이라고 했을때 이 좌표가 무엇을 기준으로 한건가 이게 중요하다.
그래서 3D모델에는 정점 좌표가 있다.
물체 공간
↓
세계 곤강
↓
카메라 공간
↓
동차절단공간
↓
정규화된 장치 좌표
↓
윈도우 공간
Object Space
캐릭터 모델을 만들었다고 했을때 모델 중앙이 (0, 0, 0)이라 했을때
머리
(0,0,2)
│
│
몸통
(0,0,1)
│
│
(0,0,0)
이 좌표는 게임 세계 전체 좌표가 아님 이 캐릭터 모델 자체를 기준으로 하는 좌표이다.
물체 공간은 특정 모형에 국한되어 그 모형에 쓰이는 좌표계
World Space
게임 월드 전체 기준
화면을 만드려면 중요한 문제가 있다. 카메라가 어디있는가??
세계의 모든 물체를 카메라를 기준으로 다시 표현한다.
이를 Camera Space라고 한다.
카메라 공간을 x, y 축이 화면과 정렬되고 z축이 시선 방향과 평행한 좌표계라고 한다.
카메라 입장에서 이 물체는 어디에 있는가?
3D에서는 (x, y, z)인데
모니터는 (x, y)이다.
더 중요한거는 원근감 그래서 투영변환(Projection Transformation)을 한다.
투영 변환은 카메라에서 멀수록 물체가 작게 보이게 해 장면에 원근감을 추가하는 역할
카메라 뒤에 있는 물체나 화면 밖에 완전히 벗어난 삼각형까지 그릴필요는 없다.
Camera
📷
/ \
/ \
/ \
/ Visible \
/ \
그래서 보이는 영역 밖을 잘라냄
동차절단공간의 절단이 이 의미와 연결됨
화면 해상도가 컴퓨터마다 다르기때문에 처음부터 특정 픽셀 좌표로 작업하는 것보다 일정한 범위의 좌표로 표현하는 단계가 있음
정규화된 장치 좌표의 x, y,z를 마지막에 실제 화면 크기로 바꿈
정규화된 좌표를 마지막 실제 화면 좌표로 변환 FHD라고 했을때 화면 중앙이면 대충 960, 540 같은 위치가 됨
여기까지 오면 정점이 화면의 어디에 위치할 것인가가 결정됨
이를 위해 행렬을 배움
Object -> World -> Camera
정점 세개의 안쪽을 다채우는것을 레스터화 라고 함
기본 도형의 영역을 픽셀들로 채우는 과정
삼각형 -> 픽셀 후보들
레스터화가 되면 바로 화면에 찍는게 아님
각 픽셀 위치마다 여러 정보가 생김 이를 묶어서 단편(Fragment)라고 부름
Fragment
화면 위치
깊이값
색
텍스처 좌표
등등...
대신 Fragment는 최종 픽셀이 아니다.
아직 최종적으로 화면에 기록될지 결정되지 않음
왜냐하면
다른 물체 뒤에 가려졌을 수도
스텐실 조건에 실패할 수도
화면 제한 영역 밖일수도있다.
레스터화 전에 필요없는 면을 제거할 수도 있다.
상자를 앞에서 보면 뒤쪽면은 어차피 보이지 않는다. 그래서 카메라 반대쪽을 향하는 면을 제거해서 불필요한 렌더링을 줄일 수 있다.
이를 면 선별(Face Culling)이라고 함
안보이는 방향의 삼각형을 미리 버릴 수 있다.
각 Fragment의 색을 계산
텍스처 색 + 빛의 세기 + 정점에서 전달된 정보
이런 정보를 이용해 Fragment의 최종 색상을 계산한다.
단편 셰이딩(Fragment Shading) 또는 픽셀 셰이딩이라고 표현한다.
계산된 Fragment는 여러 판정을 거친다.
Fragment
↓
픽셀 소유권 판정
↓
가위 판정
↓
알파 판정
↓
스텐실 판정
↓
깊이 판정
↓
혼합
↓
Image Buffer
여기서 Depth Test란 같은 화면 위치에 두 Fragment가 왔다고 했을때 Depth가 더 낮은 즉 카메라에 더 가까운 Fragment만 화면에 남는다.
3D 공간에서 어떤 물체가 앞에 있고 뒤에 있는지를 최종 픽셀에서 해결함
모든 테스트를 통과했다면 색을 이미지 버퍼에 반영
그냥 덮어 쓸수도 반투명 물체라면 기존 색과 섞을 수도 있다.
이를 Blending이라고 함
① Object Space의 정점
↓
② World Space로 변환
↓
③ Camera Space로 변환
↓
④ Projection
↓
⑤ Clip
↓
⑥ 화면 좌표 결정
↓
⑦ Triangle Rasterization
↓
⑧ Fragment 생성
↓
⑨ Fragment 색 계산
↓
⑩ Depth / Stencil 등의 판정
↓
⑪ Image Buffer 기록
↓
⑫ Back Buffer 완성
↓
⑬ Buffer Swap
↓
화면에 보임
줄이자면
Vertex
↓
Object
↓
World
↓
Camera
↓
Projection
벡터와 행렬은 이를 이해하기 위한 도구
삼각형의 집합
Front Buffer는 현재 화면에 보이는 화면 Back Buffer는 다음 프레임 화면을 그리고 BackBuffer가 다 그려지면 Buffer Swap을 통해서 화면에 출력한다.
Depth Buffer에 들어있는 깊이 값으로 Depth Test를 해서 값이 더 작은 즉 카메라에 더 가까운 A만 남도록한다.
기준이 어떤 값이냐에 따라 다르기 때문 Object Space의 경우에는 물체 기준으로 좌표를 매김, World Space는 세계 좌표를 기준으로 하기때문
Object Space
World Space
Camera Space
Projection / Clip
Window Space
레스터화는 삼각형의 정점만 있을때 그 내부 픽셀을 채우는것을 말한다.
개선
화면에 투영된 삼각형이 어떤 픽셀 위치들을 덮는지를 계산해서 Fragment들을 생성하는 과정
Fragment를 검증하는 단계가 더 있기 때문이다 대표적으로 오늘배운 Depth Test가 있다.
Projection이 의심? 혹은 Clipping이 의심된다.
오답
벽 뒤에 캐릭터가 벽 위에 그대로 그려지고 있다면 의심할것은 Depth Test/Depth Buffer다. 왜냐면 이미 캐릭터가 화면에 투영되는것 자체는 성공한 상태이기 때문, Projection은 3D위치를 화면에 어떻게 투영할까? 이고 Clipping은 카메라가 보는 영역 밖의 물체를 어떻게 잘라낼까?
Object Space, World Space, Camera Space, Window Space
3D캐릭터는 여러 정점과 그 정점들로 이루어진 삼각형 메시로 구성된다. 먼저 캐릭터의 정점들은 캐릭터 자신을 기준으로 하는 Object Space에 존재한다. 이 정점들에 캐릭터의 위치, 회전, 크기 등의 변환을 적용해서 World Space좌표로 바꾼다. 그 후 카메라를 기준으로 Camera Space로 변환한다.
이후 Projection을 통해 3D좌표를 화면에 표시할 수 있는 형태로 변환하고, 카메라가 볼 수 있는 영역 밖의 부분은 Clipping한다. 변환된 삼각형은 Rasterization을 통해 화면에서 차지하는 Fragment들로 만들어진다. 각 Fragment에 대해 색과 텍스처등의 값을 계산하고 Depth Test나 Stencil Test같은 판정을 수행한다. 살아남은 Fragment의 색을 이미지 버퍼에 기록한다. GPU가 Back Buffer에 한 프레임을 모두 그리면 Buffer Swap을 통해 완성된 이미지가 화면에 출력된다.
벡터가 무엇인지, 벡터끼리 더하고 빼는것이 무슨 의미인지, 벡터의 길이를 어떻게 구하는지, 정규화가 무엇이고 왜 하는지를 설명할 수 있다.
벡터를 기본적으로 여러개의 실수를 하나로 묶은것을 정의한다.
FVector PlayerPosition(100, 200 ,50);
숫자 세개로 3D공간의 무언가를 표현할 수 있다.
반대로 방향으로 해석할 수도 있다.
FVector Forward(1, 0, 0);
벡터는 크게 두가지 정보가 있다고 생각하면 편하다 어느 방향인가 + 얼마나 긴가
두 벡터를 더할때는 같은 성분끼리 그냥 더함
게임에서는 현재 위치에 이동량을 추가한다 라고 이해하면 된다.
얘도 성분끼리 빼는건 같다.
플레이어 P = (2, 3, 0) 이라고하고
적이 E = (7, 5, 0) 이라고 했을때
E - P = (5, 2, 0)은 플레이어에서 적까지 얼마나, 어느 방향으로 이동해야 하는가를 뜻한다.
게임에서도 Target - Current는 결론적으로 Current에서 Target으로 향하는 벡터를 만드는구나라고 생각하면 된다.
벡터와 함께 등장하는 스칼라는 그냥 숫자 하나라고 생각
스칼라는 벡터에 곱해서 사용을 하는데 벡터의 방향이나 크기를 조절할때 사용한다.
Velocity = Direction * Speed;
만약에 Direction(1, 0, 0), Speed = 500이라고 했을때 Velocity = (500, 0, 0)이 된다.
Direction은 어느쪽으로 갈지, Speed는 얼마나 빠르게 갈지를 담당
|V| =
벡터의 길이는 이렇게 계산함 2D개념의 피타고라스를 3D로 확장
당연히 위치 두개 사이의 거리도 이거를 착안해서 나옴
Player=(1,1,0)
Enemy=(4,5,0)
Enemy - Player = (3, 4, 0)
= 5
따라서 플레이어와 적 사이의 거리는 5
Distance = |EnemyPosition - PlayerPosition|
벡터의 길이가 정확히 1이면 단위벡터라고 한다
방향만 필요할때 편하게 사용
V=(3,4,0)
|V| = 5
정규화 = V / |V| -> (0.6, 0.8, 0) 이런식으로 된다.
1이아닌 벡터를 단위벡터로 만드는것을 정규화라고 한다.
크기만 사라지고 방향만 남음
예시로 플레이어 위치와 적 위치가 있는데
FVector ToEnemy = EnemyPosition - PlayerPosition;
이 상태의 ToEnemy에는 두 정보가 섞여있다..
적이 어느 방향인가? + 적이 얼마나 멀리 있는가?
그런데 AI에게 적 방향으로 총알을 발사해 라고 한다면 거리정보는 필요가 없다.
FVector Direction = ToEnemy.GetSafeNormal();
velocity = Direction * BulletSpeed;
방향만남기고 스피드를 곱하는 식으로
영벡터는 정규화할 수 없다 0으로 나눠야 하기때문 그래서 일반적으로 정규화하기전에 길이가 0인지 확인해야함
Normalize는 벡터의 길이를 1로 만드는 연산이고
Normal Vector는 어떤 표면에 수직인 벡터를 말함
5
(0.6, 0.8, 0)
P+Q = (3, 3, 1)
P-Q = (1, -5, 5)
E-P = (6, 8, 0), 거리 10
(6, 0, 0)
(1, 0, 0)
(-2, 0, 0)
3배, 0.5배, -1배, -1의 경우 방향이 바뀌었음, 나머지는 길이가 늘거나 줄어듬
A = (10, 0, 0)
B = (1, 0, 0)
둘은 값이 다른데도 "같은 방향을 가리킨다"고 말할 수 있는 이유는 무엇인가? 그리고 A를 정규화하면 무엇이 되는가?
정규화하면 (1, 0, 0)이 되기때문에 둘의 방향은 같다 둘다 x에만 값이 있기도하고
FVector ToTarget = TargetPosition - PlayerPosition;
FVector Direction = ToTarget.GetSafeNormal();
FVector Velocity = Direction \* 500.0f;
특히 ToTarget과 Direction이 가지고 있는 정보의 차이를 설명해봐.
ToTarget이라는 플레이어 위치에서 타겟 위치를 향하는 벡터가 있다. 이는 방향과 얼마나 떨어져있는지가 벡터의 크기 형태로 함께들어있고, Direction은 이를 정규화해서 방향만 남기고 길이는 1로 만듬
0으로 나눠야하기때문에 문제가 생긴다.
ToTarget = EnemyPosition - PlayerPosition
↓
적까지의 방향 + 거리
Normalize(ToTarget)
↓
적 방향만 남김, 길이 = 1
Direction × 1000
↓
적 방향으로 속력 1000을 가진 Velocity 생성
- 내적을 계산할 수 있다
- 내적의 결과가 왜 벡터가 아니라 숫자인지 안다.
- 내적의 부호로 두 방향의 관계를 판단할 수 있다.
- 게임에서 "적이 내 앞에 있는가?"를 내적으로 판단할 수 있다.
P=(Px,Py,Pz)
Q=(Qx,Qy,Qz)
두 벡터의 내적은
P⋅Q=PxQx+PyQy+PzQz
이다.
P=(1,2,3)
Q=(4,5,6)
P⋅Q = 1×4 + 2×5 + 3×6 = 4 + 10 + 18 = 32
여기서 중요한것은 두 벡터를 내적하면 스칼라가 반환이 된다. 즉 숫자 하나가 반환된다.
내적의 핵심 공식은
P⋅Q = ∥P∥∥Q∥cosθ
여기서 (\theta)는 두 벡터 사이의 각도
즉 내적은 두 벡터 사이의 각도와 연결이 되어있다.
정규화를 하면
∥P∥ = ∥Q∥ = 1
그러면 결국 P⋅Q = 1 x 1 x cosθ = cosθ
두 벡터가 정확히 같은 방향이면 cos 0 = 1
따라서 정규화된 두 벡터라면 내적이 1이다.
내적이 1에 가까울수록 같은 방향을 보고 있다.
cos90 = 0이므로 두벡터의 내적이 0이면 두 벡터는 서로 수직이다.
cos180 = −1 이므로 -1이면 완전히 반대방향
A = (1,0,0)
B = (1,0,0)
이면
A⋅B = 1
A = (100,0,0)
B = (100,0,0)
이면
A⋅B = 100×100 = 10000
벡터의 길이도 결과에 들어가버린다.
그래서 방향 관계만 보고 싶으면 정규화를 해서 확인해야 한다.
| 내적 | 의미 |
|---|---|
1 | 완전히 같은 방향 |
0보다 큼 | 대체로 같은 쪽 |
0 | 90도 |
0보다 작음 | 반대쪽 |
-1 | 완전히 반대 방향 |
두 벡터를 정규화하면 -1 ~ 1사이로 나옴
플레이어가 오른쪽을 바라본다고 가정하고 적이 플레이어 오른쪽 앞에 있다고 했을때
Player Forward = (1, 0)
ToEnemy = EnemyPosition - PlayerPosition;
저번에 배운대로 적의 위치 벡터 - 플레이어 위치 벡터를해서 ToEnemy 벡터를 구함
Direction = Normalize(ToEnemy)
이를 정규화해서 방향만 남김
Dot(PlayerForward, Direction)
내적을해서 결과를 구함 예를들어 0.8이 나온다 하면
적이 플레이어가 바라보는쪽에 있다.
-0.7이라고 하면 적이 플레이어 뒤쪽에 있다는 뜻
그래서 실제 게임에서는
if (Dot > 0.8f)
{
// 꽤 정면에 있음
}
이런식으로 0.8정도 값을 해서 일정 각도 안에 들어오면 시야에 들어왔다고 판단할 수도 있다.
x끼리 곱하고 y끼리 곱하고 z끼리 곱해서 더한다. 는 단순 계산법
두 벡터가 얼마나 같은 방향을 향하고 있는지를 숫자 하나로 알아내는 연산
을 내적이라고 생각하는게 좋다.
내적으로 두 벡터 사이의 각도를 구할 수 있다.
벡터 투영이 무엇인지 설명할 수 있다.
어떤 이동 벡터에서 특정 방향 성분만 뽑아낼 수 있다.
A⋅B=∥A∥∥B∥cosθ
내적 식은 이랬다. 벡터 길이 x 길이 x 코사인 세타
이는 다시말하면
cosθ = A⋅B / ∥A∥∥B∥
가 될 수 있다.
여기서 세타만 남기면
θ=cos−1(∥A∥∥B∥A⋅B)
가 될수있다. (-1승)
A=(1,0)
B=(0,1)
라고 했을때 내적은 0
즉, 코사인 세타는 0 / 1x1 = 0
θ=cos−1(0)=90∘
정규화된 벡터 기준으로
| 각도 | Dot |
|---|---|
| 0° | 1 |
| 60° | 0.5 |
| 90° | 0 |
| 120° | -0.5 |
| 180° | -1 |
Projection, 투영이다.
벡터를 다른 벡터위에 투영
어떤 벡터에서 원하는 방향으로 향하는 성분만 뽑아내는 것
내적은 두벡터가 얼마나 같은 방향을 향하고 있는가를 알려준다. 그러니 P안에 Q방향이 얼마나 들어있는가? 를 계산할때 내적이 사용되는건 자연스럽다.
벡터 P가 벡터 Q방향으로 얼마나 들어있는지를 벡터 형태로 뽑아낸것?
번역이 조금 이상한데 예시를 보면
P = (3, 4)
Q = (1, 0)
이라고 했을때 x축방향 Q에 P를 투영하면 P에서 Q방향 성분은 당연히 (3, 0)이 된다.
Q가 (0.8, 0.6)같은 대각이면 어떨까?
어떤 성분이 그 방향에 평행한지 눈으로 바로 구하기가 어렵다.
그래서 내적을 사용
벡터 P와 Q 사이 각도를 θ라고 하면
P가 Q방향으로 가진 길이는
∥P∥cosθ이다.
그런데 내적 공식은 P⋅Q=∥P∥∥Q∥cosθ
양변을 |Q|로 나누면
P⋅Q / |Q| = ∥P∥cosθ가 된다.
Q방향으로 투영된 P의 길이를 내적으로 구할 수 있다.
P
/|
/ |
/ |
/ |
/ θ |
------●-----●------------→ Q
<---->
Q 방향 성분
여기서 P를 Q방향으로 내려서 직각삼각형을 만듬
빗변 = P의 길이 = |P|
세타의 이웃변 = P가 Q방향으로 가지고 있는 길이
나머지변 = Q의 수직 방향의 길이
코사인세타는 빗변분의 이웃변, 이웃변/빗변
즉, 코사인 세타 = P의 Q방향 성분 길이 / |P|
Q가 (6, 8)이라고 했을때
Q의 길이는 10, 현재 길이가 10이니까 전체를 10으로 나누면
Q / 10 = (0.6, 0.8)
이 벡터의 길이는 = 1
그래서 Q / |Q|는 Q와 같은 방향을 바라보면서 길이만 1로만든 단위벡터
외적을 하면 무엇이 나오는가
외적을 어떻게 계산하는가
왜 결과가 두 벡터에 수직인가?
왜 A x B와 B x A의 방향이 반대인가?
내적은 숫자 하나가 반환된다.
A · B → 숫자 하나
A · B = 0이면 두 벡터가 90도라는걸 알 수 있었다.
반면 외적은
A x B -> 새로운 벡터
즉,
내적은 두방향을 비교한다 -> 스칼라
외적은 두 방향으로부터 새로운 방향을 만든다 -> 벡터
외적의 핵심은 둘다에 수직인 방향을 구한다 이다.
B
↑
│
│
A ──────→ C
A에서 C로 향하는 벡터와, A에서 B로 향하는 벡터가 있다.
두 벡터 모두 바닥에 붙어있다.
그렇다면 바닥에서 수직으로 튀어나오는 방향이 하나 필요하다.
이 삼각형의 앞면이 어디인가?
빛을 어느 방향으로 반사해야 하는가?
이 표면의 Normal은 무엇인가?
이때 외적을 사용한다.
두벡터가 만드는 평면에 수직인 벡터를 만들어주는 것
A×B=(AyBz−AzBy,AzBx−AxBz,AxBy−AyBx)
외적또한 방향뿐아니라 크기에도 의미가 있다.
외적의 크기 -> ∣A×B∣=∣A∣∣B∣sinθ
B
/|
/ |
/ | height
/ θ |
/____|
A
A의 길이 |A|, B에서 A에 수직인 높이는 |B|sinθ
외적의 방향은 두 벡터에 수직인 방향이고
외적의 크기는 두 벡터가 만드는 평행사변형의 넓이이다.
X, Y축 모두에 수직인 방향은 생각해보면 +Z, -Z축 두개가 있다.
이때 오른손 법칙이 등장한다.
오른손으로 A에서 B쪽으로 손가락을 감았을때 엄지가 향하는쪽이 외적방향이다.
A x B = -(B x A)가 성립한다.
A = (1, 0, 0)
B = (3, 0, 0)
외적 크기는
|AxB| = |A||B|sin0∘
근데 sin0은 0이기때문에
두벡터가 평행하면 영벡터가 된다. A x B = (0, 0, 0)
────────────→
───────────────→
두벡터가 이렇게 평행하게 있을때 두 벡터가 만드는 평행사변형은 높이가 0이다 그래서 넓이, 크기 모두 0
FVector A(2.f, 0.f, 0.f);
FVector B(0.f, 3.f, 0.f);
FVector Cross = FVector::CrossProduct(A, B);
FVector Normal = Cross.GetSafeNormal();
같은 형태로 볼 수 있다.
결과가 벡터다.
결과는 A, B 둘다에 수직
크기는 A벡터의 크기 곱하기 B벡터의 크기 곱하기 sin세타
외적은 순서가 중요하다
평행하면 0이다
C
/ \
/ \
A-----B
B-A는 A에서 B로가는 방향과 길이 벡터
C-A는 A에서 C로가는 방향과 길이 벡터를 말한다.
삼각형에서 Normal을 만드는 방법
정점 순서를 바꾸면 왜 Normal이 뒤집히는지
외적으로 삼각형 넓이를 구하는 방법
플레이어 기준으로 대상이 왼쪽/오른쪽인지 판정하는 방법
3D모델은 결국 수많은 삼각형으로 이루어져 있다.
P2
*
/ \
/ \
/ \
P0 *-------* P1
삼각형이 공간에서 어느 방향을 보고 있는지 알고 싶다.
예를들어 빛이 들어왔을때
Light
↓
↓
---------
Triangle
↑
Normal
빛과 표면의 방향 관계를 계산하려면 표면에 수직인 방향이 필요하다.
그게 Normal이다
삼각형의 세점이 p0, p1, p2라고 했을때
점 자체끼리 바로 외적하는게 아님, 먼저 삼각형의 두 변을 만든다.
E1 = P1 - P0, E2 = P2 - P0
여기서 P1 - P0은 P0에서 P1로 가는 벡터
P2
*
↗
/ E2
/
P0 *────────→ * P1
E1
따라서 E1과 E2는 삼각형 표면위에 있다.
그러면 E1 x E2를 하면 둘 모두에 수직인 벡터가 나온다.
삼각형이 놓인 평면의 법선을 N = (P1 - P0) x (P2 - P0) 형태로 구함
P0 = (0, 0, 0)
P1 = (4, 0, 0)
P2 = (0, 3, 0)
E1 = P1 - P0 = (4, 0, 0)
E2 = P2 - P0 = (0, 3, 0)
외적 E1 x E2 = (4, 0, 0) x (0, 3, 0) = (0, 0, 12)
즉, 표면에서 +Z 방향으로 튀어나온다.
길이가 12라서 조명등에 사용할 방향만 필요하면 정규화해서 N = (0, 0, 12) / 12 = (0, 0, 1)
게임 코드에서는 보통 조명, 반사, 충돌 방향등에 사용할때 단위 Normal을 원한다.
(P1 - P0) x (P2 - P0) 는 +Z가 나왔었다.
그런데 두번째 세번째 정점을 바꾸면 (P2 - P0) x (P1 - P0) = (0, 3, 0) x (4, 0, 0) 이면 (0, 0, -12)가 된다.
왜냐면 외적은 A x B = -(B x A) 이기때문에
삼각형 세점이
P0 ------ P1 ------ P2
이렇게 한직선위에있다고 가정해보자
E1 x E2 = 0이고 Area = 0이기 때문에 이 삼각형은 실제 면을 만들지 못한다.
이를 Degenerate Triangle 즉, 퇴화 삼각형이라고 한다.
게임/그래픽 코드에서 외적 크기가 거의 0인지 검사하면
이 세점이 제대로 된 삼각형을 만드는가?
를 확인하는데 사용할 수 있다.
외적으로 왼쪽/오른쪽을 판정할 수 있다.
플레이어가 +X방향을 보고있다고 가정
Forward = +X
Right = +Y
Up = +Z
라고 했을때 플레이어 Forward F = (1, 0, 0)
적이 플레이어의 오른쪽에 있다면 T = (0, 1, 0)
외적 F x T = (1, 0, 0) x (0, 1, 0) = (0, 0, 1) -> 즉 +Z
반대로 적이 왼쪽이면 T = (0, -1, 0), F x T = (1, 0, 0) x (0, -1, 0) = (0, 0, -1) -> -Z
따라서 이 상황에서는 Cross의 Z > 0 오른쪽, Z < 0 왼쪽 -> 이렇게 판단할 수 있다.
정리
삼각형의 Normal
E1=P1−P0
E2=P2−P0
N=E1×E2
방향만 필요하면 Normalize
정점 순서
E1 x E2 = -(E2 x E1)
정점 순서를 뒤집으면 Normal도 뒤집힌다.
삼각형 넓이는
Area = 1/2 * |E1 x E2|
퇴화 삼각형
|E1 x E2| = 0
0에 가까울수록, 삼각형의 면적이 거의 0이다
좌우 판정
Side=(Forward×ToTarget)⋅Up
- (3, 0, 0), (0, 2, 0), (0, 0, 6), (0, 0, 1)
- -Z를 바라본다. 외적은 A x B = -(B x A) 이기 때문에
- 10, 외적해서 (0, 0, 20) 의 길이 20 을 반으로 (삼각형이니까)
- E1, E2벡터를 구함 P0에서 P1, P2로 향하는 벡터, 그것을 외적을 한다. 둘다에 수직인 벡터를 구함 이의 크기를 구하면 이것은 삼각형이 아니고 평행사변형이다. 이를 반으로 나누면 삼각형의 넓이
- 같은 방향에 있기때문에 사실상 변을 구할수가없다 외적해도 0이 나오므로 크기가 0
- 음수 -Z 즉 왼쪽이다.
- 같은 결과가 이니다 방향이다름 위에서 했던 외적의 성질때문