[Unity] Raycasting

이정석·2023년 6월 26일
0

Unity

목록 보기
5/22

Raycasting

실제 동작하는 Scene은 공간(3D)환경인데 사용자가 모니터로 보는 환경은 스크린(2D)환경이다. 화면상에 있는 어떤 Object를 누를 때, 입력값은 스크린좌표(2D 좌표)인데 실제 선택되는 것은 공간좌표(3D 좌표)를 가지는 Object이다.

2D 좌표는 카메라의 위치, Object의 위치 각도와 같은 여러 요인에 의해서 달라질 수 있기 때문에 2D 좌표로 계산하는 것은 매우 어렵다. 이런 상황에서 사용하는 것이 Raycasting인데 다음과 같은 아이디어가 적용되었다.

  • 스크린좌표를 공간에서의 선벡터(Ray)로 나타낼 수 있다.

C#코드에서의 접근방법

Raycasting은 카메라 시점에서 누른 좌표로 광선을 쏜다고 가정해 어떤 물체에 부딪히게 된다면 해당 물체를 선택하게 된다는 접근을 기반으로한 기술이다.

    Physics.Raycast(위치(Vector3), 방향(Vector3))

Physics.Raycast()는 많은 형태의 오버로딩이 구현되어 있지만 그 중 시작위치방향을 사용하는 코드를 가져왔다. 반환 자료형은 bool로 어떤 Object와 부딪히는지에 대한 정보를 반환한다.

Physics.Raycast()는 부딪히는 Object를 반환하는 인수로 넘길수도 있고, Raycast하는 광선의 길이도 지정할 수 있는 많은 버전이 존재한다.

인수로 넘어가는 방향(Vector3)은 World기준이다. Obejct가 바라보는 방향으로 Raycasting을 하고 싶다면 Local->World의 전환이 필요하다.

만약 특정 GameObject에 Raycast가 되지 않는다면 해당 GameObject에 Collider가 있는지 확인해보자.

1. RaycastHit

앞서 말한것 처럼 Physics.Raycast()에는 많은 오버로딩이 구현되어 있는데 그중 하나는 다음과 같은 형태이다.

    Physics.Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitinfo, float maxDistance)
  • Vector3 origin: 원래 위치
  • Vector3 direction: Ray의 방향
  • out RaycastHit hitinfo: Ray와 충돌한 Object에 대한 정보를 저장하는 구조체로 Object의 collider, point, distance등 여러 정보를 알 수 있다.
  • float maxDistance: Ray의 거리

특정 Object가 바라보는 방향에 Raycasting을 하는 코드

Vector3 look = transform.TransformDirection(Vector3.forward);
RaycastHit hit;
if (Physics.Raycast(transform.position, look, out hit, 10.0f)){
	//내용
}

Physics.Raycast()는 Ray에 부딪히는 첫번째 Object를 가져오는데 만약 모든 Object를 가져오고 싶다면 Physics.RaycastAll()을 사용하면 된다.


Projection

Raycating는 어떤 Object에서 특정 위치의 어떤 방향으로 광선을 쏘았을 때 어떤 Object와 충돌하는지를 알아내는 것이다. 하지만, Raycating만으로는 화면을 눌렀을 때 어떤 Object가 선택되는지를 구현하기에는 위치에 해당하는 요소가 무엇인지 알 수 없다.

1. Screen 좌표계

Rotate에서 Local좌표계와 World좌표계에 대한 얘기를 하였는데 화면좌표에 해당하는 Screen좌표계를 World좌표계로 변환할 수 있다.

  • Local: 특정 물체를 기준으로 한 좌표
  • World: 전제 물체를 아우르는 좌표

Scene에 존재하는 Object들은 카메라의 가시권에 들어오게 되면 사용자의 화면에 출력해야 하는데 결국 3D세상에 있더라도 2D좌표(화면)로 나타내야 한다는 것을 의미한다.
Collision04

  • 스크린좌표를 가져오는 방법: Input.mousePosition을 사용하면 된다. 반환형은 Vector3이다.
    Input.mousePosition은 화면에 있는 마우스의 픽셀좌표를 의미하고 왼쪽아래가 (0,0)이다.

2. 투영

화면은 카메라의 위치와 카메라가 날아가는 방향에 따라 다르다. 여기서 보이는 화면은 카메라를 윗꼭지점으로 하는 사각뿔을 생각하면 되는데 여기서 가장 중요한 것은 일정한 비율이 존재한다는 것이다.

카메라가 Scene을 찍는 영역은 아래 그림과 같이 나타낼 수 있다. 영역안에 있고 카메라 시점에 보이는 화면이 결국 사용자가 보는 화면이 된다.
Collision05

카메라에 보이는 화면은 위 그림과 같은 구조면 결국 이 되는데 실제 보이는 화면은 으로 모이기 전에 형성되는 사각형이며 아래 그림의 파란색 사각형이 화면에 출력되는 화면이다.

Collision06

카메라의 속성 중에 NearFar라는 속성이 있다.

  • Near: 위 그림에서 카메라와 파란색 사각형까지의 거리
  • Far: 위 그림에서 카메라와 빨간색 사각형까지의 거리, 즉 촬영 영역에 해당

3. World좌표로의 변환

Screen 좌표계에서 화면상의 2D좌표를, 투영에서는 실제 화면과 카메라 위치와의 깊이차이(Near)를 알아 냈으니 다음과 같은 과정을 거쳐 클릭한 방향 벡터를 얻을 수 있다.

  1. 화면을 클릭했을 때 Screen 좌표를 Input.mousePosition을 통해 얻는다.
  2. Input.mousePosition의 값은 z=0.0인 Vector3이다.
  3. z에 해당하는 깊이는 카메라의 Near값이다.
  4. Near값을 z값으로 설정하면 카메라에서 클릭한 방향벡터를 얻을 수 있다.

위 단계를 한번에 처리할 수 있는 방법이 Unity에 구현되어 있는데 Camera.main.ScreenToWorldPoint()함수이다. 입력값은 Vector3로 MousePosition.x, MousePosition.y, Near를 입력하면 된다.

    Vector3 mousPos = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, Camera.main.nearClipPlane));
    Vector3 dir = mousPos - Camera.main.transform.position;
    dir = dir.normalized;

Camera.main은 메인카메라를 의미하기 때문에 다른 카메라를 기준으로 할 때는 Camera.XX로 사용한다.

  • Camera.main.ScreenToWorldPoint()은 클릭한 위치를 World좌표계로 하였을 때의 위치를 Vector3로 반환하는 함수이다.
  • 위 세가지 단계를 한번에 하고 싶으면 Camera.main.ScreenPointToRay(Input.mousePosition)을 사용하면 된다.

4. Layer Mask

충돌을 구현할 때 물체가 정육면체나 구와같이 모형이 범위를 특정하기 쉬운 물체라면 좋겠지만 울퉁불퉁한 벽과 같은 경우 Collider를 구현하기 힘들다.

연산 부하를 줄이기 위해 충돌 전용 Mesh Collider를 만들기도 한다. 충돌 전용 Mesh Collider일반 Mesh Collider보다 더 적은 수의 도형으로 이루어져 있기 때문에 연산 부하가 적다.

같은 Object에 복수의 Collider가 있을 때 어떻게 특정 Collider만 Raycast를 할 수 있을까

Layer를 사용하면 연산하고 싶은 애들만 골라서 Raycast가 가능하다.

  • Physics.Raycast()의 여러 버전중에 Layermask를 입력받는 버전이 존재한다.

Layermask는 bitflag로 실제로 보여줄 Layer의 번호비트를 켜서 여러가지 Layer중 보여주고싶은 Layer만 보여줄 수 있다.

  • 예시코드

아래 예시 코드는 Monster 또는 Wall LayerMask를 가진 Object와 Ray가 충돌했을 때를 나타내는 코드이다.

LayerMask mask = LayerMask.GetMask("Monster") | LayerMask.GetMask("Wall");
if (Physics.Raycast(ray, out hit, 100.0f, mask){
	//내용
}
profile
게임 개발자가 되고 싶은 한 소?년

0개의 댓글