Unity 공부 (9)

도토코·2025년 2월 26일

Unity공부

목록 보기
9/22

Raycast & 투영의 개념 & LayerMask


Raycast

광선을 쏘아 충돌하는 오브젝트가 있는지 검사하는 것.

EX)

  • 게임 속 물체를 클릭하는 것은 카메라의 위치로부터 Raycast를 쏘아 닿은 물체를 활성화 시키는 것
  • 3인칭 카메라에서 플레이어를 향해 Raycast를 쏠 때 장해물이 존재한다면 플레이어를 가리지 않도록 카메라를 장해물보다 좀 더 앞으로 이동시킴
    void Update()
    {
        Debug.DrawRay(transform.position + Vector3.up, Vector3.forward * 10, Color.red);

        RaycastHit hit;

        if(Physics.Raycast(transform.position + Vector3.up, Vector3.forward, out hit, 10))
        {
            Debug.Log($"Raycast{hit.collider.gameObject.name}");
        }
    }

플레이어의 앞으로 Raycast를 쏘아 레이저와 만나는 오브젝트의 이름을 가져오는 코드

Physics.Raycast(transform.position + Vector3.up, Vector3.forward, out hit, 10)
  • transform.position + Vector3.up 위치에서 광선 발사

    • Vector3.up을 더해준 이유는 더해주지 않으면 광선이 발에서 발사되므로 (0,1,0)만큼 위로 올려주어 광선을 쏘게 한다.
  • 월드 좌표를 기준으로 정면으로 Ray를 발사한다.

  • Ray에 닿은 오브젝트의 정보가 hit에 담긴다.

    • position, normalized 등 충골한 오브젝트의 정보가 담긴다.
    RaycastHit hit;
  • 광선의 길이를 10으로 설정한다.


void Update()
{
    Vector3 look = transform.TransformDirection(Vector3.forward);
    Debug.DrawRay(transform.position + Vector3.up, look * 10, Color.red);

    RaycastHit[] hits;
    hits = Physics.RaycastAll(transform.position + Vector3.up, look, 10);

    foreach(RaycastHit hit in hits) // 광선이 관통하여 닿은 모든 오브젝트들이 hits배열에 담긴다.
    {
       Debug.Log($"Raycast{hit.collider.gameObject.name}"); 
    }
}

이 상태에서 정면에 하나의 오브젝트가 아닌 여러가지의 오브젝트가 있고 그 충돌한 모든 오브젝트를 알고 싶다면 RaycastAll을 사용해야한다.

Vector3 look = transform.TransformDirection(Vector3.forward);
  • 이전의 코드는 월드 좌표로 무조건 z축의 정면으로만 Ray가 발사되었고 위와 같은 코드를 작성한 후에 기존의 Vector3.forward대신 look을 입력해주면 케릭터가 바라보는 곳을 향해 Ray를 발사하게 된다.
RaycastHit[] hits;
hits = Physics.RaycastAll(transform.position + Vector3.up, look, 10);
  • Raycast와 다르게 Ray에 관통된 모든 오브젝트 배열을 리턴한다.

투영의 개념

화면 좌표계

좌표계의 종류

  • Local

  • World

  • 화면 좌표계 -> 카메라 오브젝트가 촬영해서 보여주는 게임(Game)화면 상의 좌표. 2D좌표이다.

    • Screen(픽셀 좌표를 기준)

      • Screen좌표는 애당초 x,y만 가지고 있기 때문에 3번째 좌표인 z는 애당초 항상 0
      • 게임 왼쪽 하단 끝 좌표(0,0)
      • 게임 오른쪽 상단 끝 좌표 모니터의 해상도 ex)1280 x 900 -> (1280,900)
      • 픽셀의 개수를 화면 좌표로 사용
    • Viewport(픽셀을 비율로)

      • 게임 왼쪽 하단 끝 좌표(0,0)
      • 게임 오른쪽 상단 끝 좌표 (1,1)
      • 화면상 픽셀의 좌표를 0에서 1사이의 값(비율)로 나타낸 화면 좌표
      void Update()
      {
           Debug.Log(Input.mousePosition); // 현재 마우스 좌표를 픽셀 좌표(스크린 좌표)로 뽑아온다. 
           Debug.Log(Camera.main.ScreenToViewportPoint(Input.mousePosition));
      }
  • Input.mousePosition
    • 현재 마우스 위치를 (0,0)에서 해상도의 좌표로 나타낼 수 있는 픽셀 좌표로 나타냄
  • Camera.main.ScreenToViewportPoint
    • (0,0)에서 해상도릐 좌표로 나타낼 수 있는 픽셀 좌표(Screen)를 (0,0) 에서 (1,1)로 나타내는 비율 좌표로 변환하여 리턴

2D좌표 <--> 3D좌표

Camera에 대한 이해

  • near

    • 카메라가 렌더링을 시작할 위치까지의 거리
      • z축에서의 거리 (렌더링을 하기 시작할 위치는 카메라 오브젝트의 앞이므로)
    • 카메라의 위치에서 near값을 더한 위치에서부터 렌더링 시작
      • 첫번째 사진의 노란 부분
  • far

    • 카메라가 렌더링을 끝내는 위치까지의 거리
      • z축에서의 거리 (렌더링 한계 위치는 카메라 오브젝트의 앞이므로)
    • 카메라 위치에서 far값을 더한 위치까지의 게임 월드를 게임 화면에 렌더링
      • 두번째 사진의 노란 부분. far범위 밖의 것들은 게임 화면에 비추지 않는다.

마우스 클릭한 곳에 ray발사

    if(Input.GetMouseButton(0))  // 게임 화면에 마우스 좌클릭
    {
        Vector3 mousePos = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, Camera.main.nearClipPlane));
        Vector3 dir = mousePos - Camera.main.transform.position;
        dir = dir.normalized;

        Debug.DrawRay(Camera.main.transform.position, dir * 100.0f, Color.red, 1.0f); // 1 초동안 Camera.main.transform.position 위치로부터 dir * 100.0f 만큼을 더한 위치까지의 광선을 그림

        RaycastHit hit;
        if (Physics.Raycast(Camera.main.transform.position, dir, out hit, 100.0f))
        {
            Debug.Log(hit.collider.gameObject.name);
        }
    }

마우스 클릭한 좌표를 월드 좌표로 변환

Vector3 mousePos = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, Camera.main.nearClipPlane));
  • Input.mousePosition → 마우스의 화면 좌표 (X, Y) (스크린 좌표)
  • ScreenToWorldPoint() → 스크린 좌표를 월드 좌표로 변환
    • 인자 : Vector3 screenPosition
      • screenPosition.x → 마우스 클릭한 화면 X 좌표
      • screenPosition.y → 마우스 클릭한 화면 Y 좌표
      • screenPosition.z → 카메라로부터 얼마나 떨어진 거리로 해석됨 (z축 직접 지정X)
    • 결과: 마우스 클릭한 화면 좌표를 월드 좌표로 변환한 값(Vector3) 반환

중요 (Z축 처리)

  • 마우스 클릭한 좌표 (Input.mousePosition)

    • (X, Y)는 화면 좌표 (스크린 좌표)로 주어짐
    • 하지만 화면 좌표에는 Z 값이 없음
  • screenPosition.z = Camera.main.nearClipPlane 를 넣는 이유

    • ScreenToWorldPoint()는 screenPosition.z를 “카메라와의 거리”로 해석
    • 즉, screenPosition.z = nearClipPlane이면 → “카메라 앞 가장 가까운 거리”를 기준으로 변환
    • 만약 screenPosition.z = 10을 넣으면? → 카메라에서 10 거리 앞을 기준으로 변환
  • 결과적으로 변환된 mousePos의 Z값은?

    • mousePos는 “카메라 위치에서 nearClipPlane 거리만큼 앞쪽의 월드 좌표” 가 됨!
    mousePos.z = Camera.main.transform.position.z + Camera.main.nearClipPlane

<다른 분 블로그에서 퍼온 설명>

예를 들어 게임 화면 상에서 화면상 픽셀 좌표 기준으로 (160, 180)에 해당하는 픽셀에 마우스 클릭을 했다고 가정해보자. 이 좌표는 Vector3 로 보면 (160, 180, 0) 상태일 것이다. 화면상 좌표는 z 값 없이 2D 니까.. 클릭했던 이 (160, 180) 픽셀 좌표를 게임 월드 기준으로 변환하고자 ScreenToWorldPoint(Vector3(160, 180, 3)) 로 실행시켰다고 가정해보자. z값으로 넘긴 3 을 카메라와의 ‘거리’를 3 이라고 고려하여 화면상 좌표인 (160, 180)를 월드 기준 좌표로 변환했더니 (3.7, 4.8, -0.6)이 되었다. 현재 카메라 위치의 z 값 + 3 👉 - 0.6.
그리고 인수로 넘겨준 z 값, 즉 고려할 카메라와의 거리를 렌더링 시작 위치까지의 거리인 Camera.main.nearClipPlane로 넘겨준 이유는! 카메라 현재 위치로부터, 위에서 변환했던 클릭한 마우스 위치의 World 좌표로 향하는 방향을 구해서 그 방향으로 Raycast를 쏠 것이기 때문에 어차피 방향이 중요하지 z 값은 크게 중요하지 않아서 렌더링 시작 거리인 Camera.main.nearClipPlane로 선택하신 듯 하다

마우스 방향으로 레이 쏘기 위한 벡터 계산

Vector3 dir = mousePos - Camera.main.transform.position; // 마우스 위치 - 카메라 위치
dir = dir.normalized; // 정규화 (길이를 1로 만듦)
  • mousePos - Camera.main.transform.position
    • 카메라에서 마우스 클릭한 곳을 향하는 방향 벡터
  • .normalized
    • 벡터의 길이가 1인 단위 벡터로 만들어 방향만 유지
    • Why? -> 레이캐스트는 방향만 필요하고, 길이는 Raycast()의 maxDistance로 설정할 거라서!

Debug.DrawRay() - 디버깅용 광선(빨간 선) 그리기

Debug.DrawRay(Camera.main.transform.position, dir * 100.0f, Color.red, 1.0f);
  • Debug.DrawRay(Vector3 start, Vector3 direction, Color color, float duration = 0.0f)
    • start → 광선을 그릴 시작 위치 (카메라 위치)
    • direction → 광선이 향할 방향과 길이 (dir * 100.0f)
      • dir은 방향 벡터,
      • 100.0f → 광선의 길이를 100으로 설정
    • color → 광선의 색상 (Color.red)
    • duration → 광선이 보이는 시간 (1초)

Physics.Raycast() - 실제 레이 쏘기 & 충돌 감지

RaycastHit hit;
if (Physics.Raycast(Camera.main.transform.position, dir, out hit, 100.0f))
{
    Debug.Log(hit.collider.gameObject.name);
}
  • Physics.Raycast(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance)
    • origin → 레이를 쏘는 시작 위치 (카메라 위치)
    • direction → 레이가 향하는 방향 (방향 벡터 dir)
      • 메인 카메라의 현재 위치로부터 마우스 클릭한 화면상의 좌표를 월드 좌표로 변환한 곳을 향하는 방향
    • out hitInfo → 충돌 정보를 저장할 변수 (hit) -> True리턴
    • maxDistance → 최대 탐색 거리 (100.0f)

위의 기능을 다른 코드로 풀이

    if(Input.GetMouseButtonDown(0))
    {
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);

        Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f);

        RaycastHit hit;
        if(Physics.Raycast(ray, out hit, 100.0f))
        {
            Debug.Log($"Raycast Camera @ {hit.collider.gameObject.name}");
        }
    }
  • ScreenPointToRay() - 마우스 클릭한 위치에서 레이 생성
    • 인자: Vector3 screenPosition
    • screenPosition.x → 마우스 클릭한 화면 X 좌표
    • screenPosition.y → 마우스 클릭한 화면 Y 좌표
    • screenPosition.z → 사용되지 않음 (자동 처리됨)
  • 결과:
    • Ray 타입을 반환 → 카메라에서 마우스 클릭한 방향으로 나가는 가상의 광선 (Ray)

💡 즉, “마우스 클릭한 위치에서 카메라를 기준으로 한 광선(Ray)을 자동으로 생성!”
💡 ray.origin → 카메라 위치
💡 ray.direction → 마우스를 향하는 방향 벡터

🔍 ScreenPointToRay()는 내부적으로 어떻게 동작할까?

  • ScreenToWorldPoint()를 이용해서 마우스 클릭한 위치의 3D 월드 좌표를 계산
  • 카메라 위치에서 그 월드 좌표를 향하는 방향 벡터(direction)를 자동으로 생성
  • 결과적으로 Ray 객체를 만들어서 ray.origin(시작점)과 ray.direction(방향)을 쉽게 가져올 수 있음!

기존과의 차이

  • ScreenToWorldPoint()
    • Z값(깊이)을 직접 정해야 함 (nearClipPlane 같은 값 필요)
    • 마우스 클릭 위치를 3D 월드 좌표로 변환하지만, 방향은 직접 계산해야 함
    • 즉, 월드 좌표를 얻고, 방향 벡터를 직접 구해야 하는 번거로움이 있음
  • ScreenPointToRay()
    • Z값 신경 안 써도 됨 (카메라에서 마우스를 향하는 방향을 자동 계산)
    • 마우스 클릭한 화면 좌표에서 나가는 Ray(광선)를 자동 생성
    • 즉, 방향 벡터까지 자동 계산해서 더 간결함

참조
https://ansohxxn.github.io/unity%20lesson%202/ch4-2/#1%EF%B8%8F%E2%83%A3-%EB%A7%88%EC%9A%B0%EC%8A%A4%EB%A5%BC-%ED%81%B4%EB%A6%AD%ED%95%9C-%EA%B3%B3%EC%9D%98-%ED%99%94%EB%A9%B4%EC%83%81%EC%9D%98-%EC%A2%8C%ED%91%9C%EB%A5%BC-%EC%9B%94%EB%93%9C-%EC%A2%8C%ED%91%9C%EB%A1%9C-%EB%B3%80%ED%99%98

profile
코(딩)(꿈)나무

0개의 댓글