본문 바로가기
Computer Graphics/Math

[Math] 내적, 램버트 반사, 시야각 판별

by 진현개발일기 2023. 12. 6.

■ 램버트 반사

 램버트 반사 모델 (Lambertian reflection)이란 현실 세계와 비슷한 조명 효과를 주기 위해 고안된 방법이다.

계산량이 적어 속도가 빠르지만 그럴듯한 조명 효과를 줄 수가 있어 3차원 실시간 조명 구현에 널리 사용된다.

 

 

두 벡터 N, L의 내적을 사용하면 램버트 반사에 필요한 cos사잇각을 구할 수 있다.

 

■ 램버트 반사 (유니티 실습)

 

이해한 내용을 바탕으로 유니티에 0부터 구현해봤다.

 

(구조)

1. Start에서 텍스처를 복사하여 새로운 스프라이트를 객체에 씌워준다.

2. 스프라이트의 픽셀(256x256) 하나하나를 대상으로 픽셀의 위치(표면)가 향하는 단위 벡터 n과 원점을 기준으로 공전할 mover의 위치에 따른 l값을 내적하여 사잇각 cosθ값을 구한다.

3. 기존 색깔에 사잇각을 곱해줘, 멀리있는 픽셀은 어둡고 가까이 있는 픽셀은 밝게, 그라데이션 형태로 표현해준다.

 

먼저 표면이 되어줄 동그라미를 원점에 배치하였고.해당 원에 CircleColor라는 스크립트를 붙여줬다.

 

CircleColor는 아래와 같다. (초기 스크립트 모습)

초기 스크립트 모습

ColourChangeBylight는 다른 스크립트에서 mover의 월드포지션을 인자로 사용하고있다.

 

실행시 예상과 다르게 원이 나오지 않고 사각형으로 아래와 같이 모습을 보였다.

자료를 찾아봤는데 Sprite 생성시 Sprtie.create 메서드를 사용하면 피벗의 위치가 변경될 수 있다는 것이다.

이때 원본 텍스처의 크기와 피벗 정보를 고려하여 만들어야할 것 같아서 해당 부분을 수정하였다.

 

▼ 피벗 정규화 및 PixelsPerUnit을 256으로 동일시켜줬다.

 

또한 프레임이 느려져서 일부 최적화를 하였다.

각 픽셀의 color값을 갖고있을 array를 선언해주고 초기에 초기화를 해준 뒤

컬러를 변경하는 구문에서 컬러값만 바꿔준 뒤 텍스처 적용은 맨 마지막에 한 번만 호출되도록 수정했다.

 

▼ 1차 완료

 

원하는 모습대로 Circle이 형성되었고 사이즈와 피벗 또한 조정이 되었다. 그런데 여기서 문제는

윗 부분은 잘 적용이 되었는데 아래 부분에서는 모든 픽셀이 검은색으로 되어버리는 것이다.

 

 

 

 y축이 -일 때에만 문제가 생긴다는 것인데 이를 위해 코드를 다시 천천히 읽어봤다. 문제는 아래와 같다.

 

(1)  y축, x축이 0부터 시작하였던 것

(2) 픽셀의 월드 좌표는 단순히 월드 좌표 Vector2 (x, y)가 아니다.

 

 우리가 객체를 드래그해서 월드 공간에 배치할 경우 마치 눈으로 봤을 땐 로컬 공간내에 점들을 월드 공간으로 이동시키는 것처럼 보이지만, 로컬 공간에 물체를 표현하고 그 공간을 월드 공간 위에 덮어서 포개는 것이다. 즉,  월드 좌표를 나타내는 x,y는 점으로 이루어진, 물체를 표현하는 공간인 아핀 공간의 피벗을 나타내는 것이다. 그러므로 픽셀의 x, y위치를 월드 좌표로 알기 위해선 어떻게 해야할까?

 

 지금 글을 작성하면서 생각났는데 예전에 실무에서 screentoworldPosition을 사용했었다.. 코드를 수정할 때는 생각이 안났어가지고 다른 방식으로 했는데 

위와 같이  [-radius ~ radius] 범위의 x, y를 구한 뒤 픽셀 단위의 공간을 표현하고 싶어서 0.002f 근사치로 곱해봤다.

그런 뒤 원래 의도대로 램버트 반사 공식에 n과 l을 대입하여 해당 인덱스의 픽셀 컬러값을 변경해줬다.

 

▼ 성공한 모습

 

■ 시야각 판별

 이왕 내적을 사용하는 것 시야각 까지 표현해봤다.

변수는 아래와 같이 선언해줬다.

HalfFovRadian은 타겟 객체가 시야각 내에 존재하는지 판별하기 위한 기준이 된다.

벡터 c가 아니라 벡터 b이다. 타이핑 실수

위와 같이 벡터 a와 벡터 b가 존재한다고 할때 두 벡터의 내적 값 공식은 아래와 같다.

타이핑 실수, 노름 a * 노름 c가 아니라 노름 a * 노름 b이다.

위 공식대로라면 두 벡터 노름과 cosθ값을 다 곱해줘야하나, 사실 우리는 각도만 알면 되기 때문에 노름 a, 노름 b는 단위벡터를 사용함으로써 1로 만들어주면 된다. 그러므로 우리는 cos(시야각) 값만 생각해주면 된다.

적이 내가 바라보는 시선의 정가운데에 근접할수록 1에 가까워진다. 그러므로 시야의 경계선은 HalfFovRadian으로 정해준 뒤 두 벡터의 내적값이 HalfFovRadian 보다 높을 경우 내 시야에 위치해있다는 의미이다.

 

HalfFovRadian값을 초기화해주고 LineRenderer를 통해 Game씬 내에서 라인 (시야)을 그려놓을 예정이다.

코드는 아래와 같이 작성하였다.

 

그리고 Mover가 움직일때 콜백으로 호출되는 ColourChangeByLight 함수에 아래와 같이 Mover가 시야각 내에 있는지 판별한 뒤에 True라면 빨간색으로 False라면 청록색으로 바뀌도록 설정해놨다.

 

▼ 완성된 모습

 

 

[참고]

 

이득우의 게임 수학 | 이득우 - 교보문고

이득우의 게임 수학 | 39가지 실시간 렌더링 게임 프로그래밍 실습 예제를 하나씩 따라 해보며 독자가 직접 체득하는 흥미로운 게임 수학의 세계! 게임 개발자와 그래픽 아티스트들이 궁금해 했

728x90