📱 AR은 어떻게 내 위치를 아는 걸까?

– AR 트래킹 원리부터 ARCore/ARKit, AR Foundation까지

AR을 처음 접하면 대부분 이렇게 생각한다.

“카메라로 세상을 보고 AI가 분석해서 위치를 아는 건가?”

절반은 맞고, 절반은 틀리다.
실제 AR의 핵심은 AI보다는 수학 + 컴퓨터 비전 + 센서 융합이다.

이 글에서는 다음 내용을 순서대로 정리한다:

  • AR 트래킹의 핵심 원리 (Visual-Inertial SLAM)
  • IMU(자이로/가속도)의 역할
  • 자이로/가속도 기반 방식과의 차이
  • ARCore / ARKit의 공통점과 차이
  • AR Foundation이 이걸 어떻게 통합하는지

1️⃣ AR 트래킹의 핵심 원리: Visual-Inertial SLAM

ARCore와 ARKit은 공통적으로 다음 구조를 사용한다.

카메라 영상 + IMU → 위치 추정
 

이걸 전문적으로는:

👉 Visual-Inertial SLAM
(Simultaneous Localization And Mapping)

이라고 한다.


🔍 1. 카메라 영상에서 “특징점”을 찾는다

AR은 화면을 그냥 보는 게 아니라, 이런 걸 찾는다:

  • 모서리
  • 텍스처가 있는 부분
  • 패턴 변화가 있는 점

👉 이걸 Feature Point (특징점)이라고 한다.


🔁 2. 프레임 간 특징점을 추적한다

핵심 질문:

이 점이 다음 프레임에서는 어디로 이동했는가?
 

수백~수천 개의 점을 동시에 추적한다.


📐 3. 카메라의 움직임을 역으로 계산한다

예를 들어:

  • 모든 점이 오른쪽으로 이동했다면
    👉 카메라가 왼쪽으로 움직인 것

즉,

👉 “점의 이동 = 카메라 이동의 반대”


🧭 4. IMU(자이로/가속도)로 보정한다

여기서 중요한 개념이 하나 나온다.

✔ IMU란 무엇인가?

👉 IMU (Inertial Measurement Unit)
= 관성 측정 장치

구성:

  • Gyroscope (자이로) → 회전 (각속도)
  • Accelerometer (가속도계) → 선형 가속도
  • (일부 기기) Magnetometer → 방향 보정

✔ IMU의 역할

영상만 사용하면:

  • 빠른 움직임에서 추적이 끊김
  • 프레임 사이 공백 발생
  • 회전 계산이 불안정

그래서 IMU를 같이 사용한다.

👉 역할 정리:

  • 자이로 → 회전 안정화 (빠르고 정확함)
  • 가속도 → 이동 방향 보조
  • 영상 → 절대 위치 보정

👉 즉:

영상 = 정확하지만 느림
IMU = 빠르지만 틀어짐
→ 둘을 합쳐서 안정화
 

🗺 5. 공간을 동시에 만들어낸다

SLAM의 핵심:

  • Localization (내 위치 추정)
  • Mapping (공간 생성)

👉 동시에 진행됨


2️⃣ AR에서 말하는 “내 위치”란 무엇인가?

여기서 중요한 오해 하나를 짚고 가야 한다.

👉 AR에서 말하는 “내 위치”는
GPS 좌표가 아니다


✔ 정확한 의미

👉 카메라(= 기기)의 3차원 위치 + 회전 (Pose)

즉:

Position (x, y, z) + Rotation (Quaternion)
 

✔ 기준은 어디인가?

AR은 시작할 때 기준을 만든다.

👉 AR Session 시작 시점 = 월드 원점(0,0,0)

그래서:

  • 실제 현실 좌표 ❌
  • AR 내부 좌표계 ⭕

✔ 쉽게 말하면

👉 “현실 위치”가 아니라

👉 “시작 지점을 기준으로 내가 얼마나 이동했는지”


✔ 그래서 가능한 것

  • 앞으로 가면 → 오브젝트 가까워짐
  • 옆으로 이동 → 시점 변화
  • 돌아서 보면 → 뒤쪽도 보임

👉 이게 바로 6DOF (6 Degrees of Freedom)


3️⃣ 센서 기반 AR이 한계가 있는 이유

많이 시도하는 방식:

가속도 → 속도 → 위치
 

이론적으로 맞지만 현실에서는 문제가 생긴다.


❗ 드리프트(Drift)

  • 센서 노이즈 존재
  • 적분 과정에서 오차 누적

👉 결과:

시간이 지날수록 위치가 계속 틀어짐
 
눈을 감고 똑바로 100걸음을 걷는다고 생각해보세요. 처음 몇 걸음은 정확하겠지만, 조금씩 발각도가 틀어지면서 100걸음 뒤에는 원래 목적지와는 완전히 다른 곳에 서 있게 됩니다. 이게 바로 '드리프트(Drift)' 현상입니다.

❗ 절대 위치를 모른다

센서는:

  • 변화량만 측정
  • 기준 좌표 없음

👉 그래서:

  • 오브젝트를 “공간에 고정” 불가능

❗ 6DOF 구현 불완전

  • 자이로 → 회전
  • 가속도 → 이동

👉 따로 놀기 때문에
자연스러운 공간 이동이 안 됨

📌 핵심 차이

구분 센서 기반 ARCore / ARKit
위치 계산 적분 영상 기반 추적
오차 계속 누적 지속 보정
공간 인식 없음 있음
AR 품질 낮음 높음

6DOF란 무엇인가?

👉 6DOF = 6 Degrees of Freedom (6자유도)

즉, 물체가 3차원 공간에서 움직일 수 있는 모든 자유도를 의미한다.


✔ 1. 위치 이동 (3 DOF)

  • X축 이동 → 좌/우
  • Y축 이동 → 위/아래
  • Z축 이동 → 앞/뒤

✔ 2. 회전 (3 DOF)

  • Pitch → 위/아래 고개 움직임
  • Yaw → 좌/우 회전
  • Roll → 기기 기울이기

👉 합치면:

3 (이동) + 3 (회전) = 6 DOF
 

✔ AR에서 6DOF가 의미하는 것

👉 단순히 화면이 움직이는 게 아니라:

  • 앞으로 걸어가면 → 가까워짐
  • 옆으로 이동하면 → 시점이 바뀜
  • 물체를 돌아가면 → 뒤도 보임

👉 즉,

“공간 안에서 실제로 움직이는 것처럼 보이는 상태”


❗ 3DOF vs 6DOF 차이

🔹 3DOF (센서 기반)

  • 회전만 가능
  • 위치 이동 없음

👉 결과:

고개는 돌릴 수 있지만, 앞으로 갈 수는 없음
 

🔹 6DOF (ARCore / ARKit)

  • 회전 + 위치 이동 모두 가능

👉 결과:

공간을 실제로 돌아다니며 관찰 가능

 


🔥 핵심 포인트

👉 “AR의 본질은 6DOF 구현이다”


4️⃣ ARCore vs ARKit – 원리는 같고, 구현은 다르다

✔ 공통점 (핵심)

둘 다 동일한 구조:

Visual-Inertial SLAM
 

✔ 차이점 (실무에서 중요)

1. 하드웨어 통제

  • ARKit
    • 애플 기기만 대상
    • 하드웨어 완전 통제
  • ARCore
    • 다양한 안드로이드 기기
    • 기기별 성능 편차 존재

👉 결과:

  • ARKit → 더 안정적
  • ARCore → 환경/기기 영향 큼

2. Depth / LiDAR

  • ARKit:
    • LiDAR 지원 (일부 기기)
    • 깊이 정확도 높음
  • ARCore:
    • Depth API 존재
    • 기기별 편차 큼

3. 트래킹 품질

  • ARKit:
    • 지터 적음
    • 빠른 복구
  • ARCore:
    • 저사양 기기에서 흔들림 증가

5️⃣ ARCore도 완벽하지 않다 (Drift & Jitter)

ARCore / ARKit도 다음 문제가 있다:

  • Drift (미세 위치 오차)
  • Jitter (떨림)
  • Tracking Loss (특징점 부족 시)

🔥 중요한 차이

센서 기반

오차가 계속 누적 (Unbounded)
 

ARCore / ARKit

오차가 생겨도 다시 보정됨 (Bounded)
 

👉 이 차이가 “진짜 AR”과 “가짜 AR”을 나눈다.


6️⃣ AR Foundation – Unity에서의 통합 레이어

Unity에서는 직접 ARCore / ARKit을 다루지 않는다.

👉 대신:

AR Foundation
 

을 사용한다.


✔ 구조

Unity AR Foundation
 ├─ Android → ARCore
 └─ iOS → ARKit
 

✔ 주요 컴포넌트

  • ARSession → 전체 AR 상태 관리
  • ARCamera → 카메라 트래킹 (즉, “내 위치”)
  • ARPlaneManager → 평면 인식
  • ARRaycastManager → 터치 위치 추적

✔ 장점

  • 하나의 코드로 양 플랫폼 대응
  • 내부 구현 신경 안 써도 됨

✔ 단점

  • 플랫폼별 세부 기능 접근 제한
  • 디버깅 난이도 존재

7️⃣ 왜 단색 벽에서 AR이 안 될까?

이건 SLAM 구조 때문에 발생한다.

특징점 없음 → 추적 불가 → 위치 계산 실패
 

👉 그래서:

  • 흰 벽 ❌
  • 어두운 환경 ❌
  • 텍스처 많은 공간 ⭕

8️⃣ 결론

AR 기술의 본질은 다음 한 줄로 정리된다:

👉 “카메라로 세상을 이해하는 게 아니라, 점의 움직임으로 내 위치를 계산하는 기술”


🔥 핵심 요약

  • AR은 AI가 아니라 컴퓨터 비전 + 수학 기반 기술
  • IMU는 자이로 + 가속도 기반의 관성 센서 묶음
  • AR에서 “내 위치”는 카메라의 상대 좌표 (Pose)
  • 자이로/가속도만으로는 진짜 AR 구현 불가능
  • ARCore / ARKit은 Visual-Inertial SLAM 기반
  • AR Foundation은 이를 Unity에서 통합하는 레이어

📌 한 줄 정리

“AR은 현실을 인식하는 기술이 아니라, 현실을 기준으로 ‘카메라의 위치를 계산하는 기술’이다”

1. 유니티 라이프사이클 개요

유니티는 프레임 기반 엔진입니다.
즉, 게임은 “한 장면을 계속 새로 그리는 반복 루프”로 돌아갑니다.

이때 각 스크립트는 아래 순서로 실행됩니다:

Awake → OnEnable → Start → Update → LateUpdate → OnDisable → OnDestroy
 

여기에 물리 전용 루프(FixedUpdate)가 따로 존재합니다.


2. 초기화 단계

Awake()

  • 가장 먼저 실행
  • 오브젝트가 씬에 로드될 때 호출
  • 비활성 상태여도 실행됨

특징

  • 다른 오브젝트보다 먼저 실행될 수 있음 (순서 보장 X)
  • “자기 자신 초기화”에 적합
 
void Awake()
{
    Debug.Log("Awake");
}
 

사용 예

  • 컴포넌트 참조 캐싱
  • 싱글톤 초기화

OnEnable()

  • 오브젝트가 활성화될 때마다 호출
 
void OnEnable()
{
    Debug.Log("OnEnable");
}
 

사용 예

  • 이벤트 구독
  • 상태 초기화

Start()

  • 첫 프레임 직전에 한 번만 실행
  • 반드시 Awake 이후

특징

  • 모든 오브젝트의 Awake가 끝난 뒤 실행됨
 
void Start()
{
    Debug.Log("Start");
}
 

사용 예

  • 다른 오브젝트와의 관계 설정
  • 초기 게임 로직 시작

3. 프레임 기반 업데이트

핵심: Update()

  • 매 프레임마다 실행됨
  • 가장 많이 사용하는 함수
 
void Update()
{
    transform.Translate(Vector3.forward * Time.deltaTime);
}
 

특징

  • FPS에 따라 호출 횟수 달라짐
  • 입력 처리에 최적

대표 사용

  • 키 입력 처리 (Input)
  • 일반적인 움직임
  • UI 상태 변경

중요한 개념: Time.deltaTime

프레임마다 시간 간격이 다르기 때문에 반드시 보정해야 함

 
speed * Time.deltaTime
 

안 쓰면

  • FPS 높은 기기 = 빠르게 움직임
  • FPS 낮은 기기 = 느리게 움직임

4. 물리 업데이트

FixedUpdate()

  • 고정된 시간 간격으로 실행
  • 기본: 0.02초 (50Hz)
 
void FixedUpdate()
{
    rigidbody.AddForce(Vector3.up);
}
 

특징

  • 프레임과 무관
  • 물리 연산 전용

Update vs FixedUpdate

 

구분 Update FixedUpdate
호출 기준 프레임 시간
실행 빈도 가변 일정
용도 입력, 일반 로직 물리

핵심

  • Rigidbody 관련은 무조건 FixedUpdate

FixedUpdate 실행 횟수

Update는 한 프레임에 무조건 1번 실행되지만, FixedUpdate는 0번 또는 여러 번 실행될 수 있다.

  • Update → 프레임마다 1번
  • FixedUpdate → 고정 시간 스텝 기반 (예: 0.02초)

그래서:

  • FPS가 높으면 → FixedUpdate가 건너뛰어질 수 있음 (0번)
  • FPS가 낮으면 → FixedUpdate가 한 프레임에 여러 번 실행됨

핵심 원인
"물리 시간"과 "렌더 프레임 시간"이 따로 놀기 때문

 

 

물리 연산과 Time.deltaTime: FixedUpdate 안에서 물리 연산을 할 때는 Time.deltaTime 대신 Time.fixedDeltaTime을 사용하는 것이 원칙입니다. (다만, 유니티 내부에서 FixedUpdate 시점에는 deltaTime이 자동으로 fixedDeltaTime 값을 반환하므로 결과는 같습니다.)


5. 후처리 단계

LateUpdate()

  • Update가 끝난 뒤 실행
 
void LateUpdate()
{
    camera.LookAt(player);
}
 

사용 예

  • 카메라 추적
  • 최종 위치 보정

이유

  • 모든 오브젝트 이동이 끝난 뒤 실행되기 때문

6. 종료 / 비활성화

OnDisable()

  • 오브젝트 비활성화 시 호출
 
void OnDisable()
{
    Debug.Log("OnDisable");
}
 

OnDestroy()

  • 오브젝트 파괴 시 호출
 
void OnDestroy()
{
    Debug.Log("OnDestroy");
}
 

사용 예

  • 이벤트 해제
  • 메모리 정리

7. 전체 실행 흐름 정리

[초기화]
Awake
OnEnable
Start

[반복]
Update
FixedUpdate (별도 루프)
LateUpdate

[종료]
OnDisable
OnDestroy
 

8. 실무에서 중요한 포인트

1. Update 남용 금지

Update는 모든 오브젝트에서 매 프레임 실행됨
성능 문제의 핵심 원인

나쁜 예

 
void Update()
{
    FindObjectOfType<Player>();
}
 

해결

  • 캐싱
  • 이벤트 기반 구조 사용

2. 역할 분리

  • 입력 → Update
  • 물리 → FixedUpdate
  • 카메라 → LateUpdate

이거 안 지키면

  • 동기화 문제 발생

3. 실행 순서 의존 금지

Awake / Start 순서 믿으면 위험함

 

해결

  • 명시적 초기화
  • Script Execution Order 설정

4. Update 최적화 전략

  • 조건문으로 실행 제한
  • 코루틴 활용
  • 이벤트 기반 처리
 
if (!isActive) return;
 

9. 한 줄 핵심 요약

Update는 “매 프레임 실행되는 게임의 심장”
FixedUpdate는 “물리 엔진의 타이머”
LateUpdate는 “최종 보정 단계”


10. 실무 패턴 예시

 
void Update()
{
    HandleInput();
}

void FixedUpdate()
{
    HandleMovement();
}

void LateUpdate()
{
    UpdateCamera();
}
 

이렇게 나누면 유지보수 + 버그 감소 + 성능 개선

 

1. 불필요한 연산 제거

  • 물리 → FixedUpdate
  • 입력 → Update
  • 카메라 → LateUpdate

각 작업을 맞는 타이밍에만 실행해서 중복 계산과 보정 비용을 줄임

 

2. Update 부담 감소

  • 모든 로직을 Update에 몰지 않음
    → 프레임당 실행 코드 감소
    → 프레임 드랍 방지

3. 엔진 흐름과 정렬

  • Input → Update → Physics → LateUpdate 흐름에 맞춤
    → 재계산 감소
    → CPU 사용 효율 증가

4. 불필요한 보정/떨림 제거

  • 카메라를 LateUpdate에서 처리
    → 중간 계산 제거
    → 추가 연산 감소

5. 유지보수 개선 = 성능 유지

  • 구조가 분리되어 불필요한 코드 증가 방지
    → 장기적으로 성능 유지

1. DOTS는 무엇인가

DOTS = Data-Oriented Technology Stack

 

✔️ 단어 그대로 풀면

  • Data-Oriented → 데이터 중심 설계
  • Technology Stack → 기술 묶음 (세트)

❗ “데이터 중심으로 성능을 극대화하기 위한 Unity 기술 묶음”

❗ DOTS는 단순한 기능이 아니라
👉 Unity를 사용하는 방식 자체를 바꾸는 아키텍처


많이 헷갈리는 부분부터 정리하면:

❗ “DOTS = 그냥 코드 스타일?”
👉 ❌ 반만 맞다


정확히는:

✔️ 새로운 코드 구조 (데이터 중심)
✔️ Unity 전용 시스템 (ECS, Jobs, Burst)
✔️ 성능 최적화 기술

👉 이 세 가지가 합쳐진 것


2. DOTS는 왜 등장했을까

기존 Unity를 쓰다 보면 이런 문제들이 생긴다:

  • 오브젝트 많아지면 느려짐
  • Update 많으면 성능 저하
  • GC 때문에 프레임 끊김

🎯 근본 원인

❗ 기존 구조(GameObject + MonoBehaviour)가 성능에 비효율적


이유 1: 객체 중심 구조

class Enemy : MonoBehaviour
{
    float speed;
    int hp;
}
 

👉 데이터가 객체 안에 묶여 있음
👉 메모리에 흩어짐


이유 2: CPU 캐시 비효율

[Enemy1][UI][Enemy2][Player][Enemy3]
 

👉 필요한 데이터가 연속적이지 않음 → 느림


이유 3: C# ↔ C++ 호출 비용

transform.position
 

👉 내부적으로 C++ 호출 (Interop 비용 발생)


이유 4: GC 문제

  • new / string / LINQ
    👉 GC 발생 → 프레임 끊김

3. DOTS 핵심 개념

❗ “객체 중심 → 데이터 중심”


4. DOTS 구성 요소

1️⃣ ECS (Entity Component System)

👉 구조 자체를 바꿈

2️⃣ Job System

👉 멀티스레드 처리

3️⃣ Burst Compiler

👉 C#을 네이티브 코드로 컴파일


5. 기존 방식 vs DOTS


❌ 기존 (MonoBehaviour)

public class Move : MonoBehaviour
{
    public float speed;

    void Update()
    {
        transform.position += Vector3.forward * speed * Time.deltaTime;
    }
}
 

✅ DOTS (ECS)

데이터

public struct MoveSpeed : IComponentData
{
    public float Value;
}
 

시스템

public partial struct MoveSystem : ISystem
{
    public void OnUpdate(ref SystemState state)
    {
        foreach (var (transform, speed) in 
            SystemAPI.Query<RefRW<LocalTransform>, RefRO<MoveSpeed>>())
        {
            transform.ValueRW.Position += 
                new float3(0, 0, speed.ValueRO.Value) * SystemAPI.Time.DeltaTime;
        }
    }
}
 

6. 왜 DOTS가 빠른가


✔️ 1. 메모리 구조 (ECS)

기존:

데이터가 흩어짐
 

DOTS:

같은 데이터끼리 연속 저장
 

👉 CPU 캐시 효율 ↑

 


기존 방식을 AOS(Array of Structures), DOTS 방식을 SOA(Structure of Arrays) 라고 부름


✔️ 2. Job System

  • 멀티코어 자동 활용
  • 병렬 처리

✔️ 3. Burst Compiler

❗ C# → 네이티브 코드로 직접 컴파일

👉 결과

  • GC 없음
  • Interop 없음
  • C++급 성능

7. Interop 비용 (중요 포인트)

기존 Unity:

transform.position
 

👉 내부:

C# → C++ 호출
 

* Interop : 서로 다른 언어(C#, C++)가 서로 통신하는 것


❗ 문제

  • 호출마다 CPU 비용 발생
  • 반복되면 성능 저하

🎯 DOTS는?

❗ 이 경계를 줄이거나 제거하는 방향


8. DOTS는 “설치”가 아니라 “방식”

많이 하는 오해:

❗ “DOTS = 패키지 설치하면 끝?”

👉 ❌ 아님


정확히는:

✔️ 전용 패키지 필요 (Entities 등)
✔️ 하지만 핵심은 “코드 구조 자체 변화”


9. DOTS의 단점

❗ 1. 난이도 - 기존 Unity랑 완전히 다름

❗ 2. 생산성 - 코드 직관성 떨어짐

❗ 3. 생태계 - 기존 Asset과 호환 제한


10. 실제로 많이 쓰나?

❗ “흔한 구조냐?” → 아직은 아니다


📊 현실

  • MonoBehaviour: 대부분 프로젝트
  • DOTS: 일부 고성능 프로젝트

✅ 쓰이는 경우

  • 수천~수만 오브젝트
  • 시뮬레이션
  • RTS

❌ 안 쓰이는 경우

  • 모바일
  • AR / XR
  • 일반 게임

11. 중요한 관점

❗ DOTS는 기본이 아니라 “필요할 때 쓰는 도구”


12. 그래도 왜 알아야 하나

DOTS를 안 써도 얻는 것:

  • 데이터 중심 사고
  • 캐시 친화 구조
  • GC 줄이기
  • 반복 처리 최적화

👉 이건 일반 Unity에서도 바로 적용 가능


🔥 최종 핵심 요약

✔️ DOTS는 단순 기능이 아니라 구조 변화
✔️ 객체 중심 → 데이터 중심
✔️ C#을 C++처럼 실행하게 만드는 기술
✔️ 아직은 특수 상황에서 사용하는 구조


💡 한 줄 결론

❗ DOTS는 “기본 구조”가 아니라
👉 “성능이 필요할 때 꺼내는 특수 무기”다

1. 결론

❗ Unity는 “성능 + 개발 편의성”을 동시에 잡기 위해
👉 C++ (엔진) + C# (스크립트) 구조를 선택했다


2. 왜 C++를 쓸 수밖에 없었을까 (엔진 관점)

게임 엔진은 일반 앱이랑 요구사항이 완전히 다르다.

🎯 요구 조건

  • 1초에 60프레임 이상 유지
  • 실시간 렌더링 (GPU 직접 제어)
  • 물리 연산 (Physics)
  • 메모리 직접 제어 필요

❗ C#만으로는 어려운 이유

C#은 좋은 언어지만:

  • GC 때문에 “언제 멈출지 모름”
  • 메모리 직접 제어 불가
  • GPU 접근 제한적

👉 즉, 예측 가능한 성능이 중요할 때 불리함


✅ 그래서 C++ 사용

C++은:

  • 메모리 직접 관리 가능
  • GC 없음 (멈춤 없음)
  • GPU / 하드웨어 제어 가능
  • 매우 빠름

👉 그래서

🎯 “엔진 핵심 (렌더링, 물리 등)”은 C++로 구현


3. 그럼 왜 C#을 쓸까? (개발자 관점)

만약 Unity가 전부 C++이었다면?

👉 개발 난이도 급상승


❗ C++만 쓸 때 문제

  • 메모리 누수 위험 높음
  • 포인터, 해제 실수 → 크래시
  • 생산성 낮음
  • 유지보수 어려움

✅ C#을 쓰는 이유

C#은:

  • GC로 메모리 자동 관리
  • 문법 간결
  • 빠른 개발
  • 안정성 높음

👉 그래서

🎯 “게임 로직(스크립트)”은 C#으로 작성


4. 결국 Unity 구조는 이렇게 된다

[ C# (스크립트, Managed) ]
          ↓ 호출
[ C++ (엔진, Native) ]
          ↓
[ GPU / 하드웨어 ]
 

🎯 핵심 역할 분리

영역역할
C# 게임 로직
C++ 렌더링, 물리, 리소스 관리
GPU 실제 그래픽 처리

5. 이 구조가 만들어낸 현상


5-1. 왜 객체가 “두 개”로 존재할까

Unity 객체는 사실 이렇게 구성된다:

[C# Wrapper 객체]
      ↓ 참조
[C++ 실제 객체]
 

❔ 왜 이렇게 나눴을까?

이유는 간단하다:

❗ C#은 엔진을 “직접 건드릴 수 없기 때문”

그래서

  • C#에서는 “껍데기(Wrapper)”만 만들고
  • 실제 처리는 C++에서 수행

📌 예시

 
transform.position = new Vector3(0, 0, 0);
 

실제로는:

  1. C# 코드 실행
  2. C++ 엔진 함수 호출
  3. 실제 위치 변경

5-2. 그래서 생긴 문제: “Destroy vs null”

❌ 직관적인 생각

obj = null;
 

👉 “삭제됐다!”


✅ 실제

  • C# 참조만 끊김
  • C++ 객체는 그대로

✔️ 진짜 삭제

Destroy(obj);
 

👉 C++ 객체 제거


🎯 핵심

❗ C#은 “참조”, C++은 “실제 데이터”


5-3. GC가 Unity를 완전히 관리 못하는 이유

GC는 이렇게 동작한다:

“C#에서 안 쓰면 지운다”


근데 Unity 객체는:

  • 실제 데이터 → C++
  • GC 접근 불가

👉 결과

  • GC는 Wrapper만 삭제
  • 실제 메모리는 남음

🎯 그래서 반드시 필요한 것

Destroy()
 

6. 성능 문제는 왜 여기서 터질까

이 구조 때문에 생기는 대표적인 문제 3가지


🚨 1) GC 스파이크

new List<int>()
 
  • C# 메모리 증가
  • GC 발생
  • 프레임 끊김

🚨 2) Native 메모리 누수

Instantiate(prefab);
 
  • C++ 객체 생성
  • Destroy 안 하면 계속 쌓임

🚨 3) 이중 부담

Instantiate는 사실:

C# 객체 생성 + C++ 객체 생성
 

👉 그래서 매우 비쌈


7. Unity가 이 구조를 유지하는 이유

이 구조 단점 많아 보이지만 계속 쓰는 이유는 명확하다.


🎯 이유 1: 성능

  • 렌더링, 물리 → C++이 압도적으로 빠름

🎯 이유 2: 확장성

  • 다양한 플랫폼 지원 (모바일, 콘솔, PC)

🎯 이유 3: 생산성

  • C#으로 빠르게 개발 가능

👉 결론

❗ “엔진은 빠르게, 개발은 쉽게”
→ 그래서 이중 구조 유지


8. 실무에서 바로 적용되는 사고 방식 

✔️ 질문 바꾸기

기존:

  • “GC 왜 돌지?”

이제:

  • “이건 C# 문제인가?”
  • “아니면 C++ 객체 문제인가?”

✔️ 판단 기준

상황원인
프레임 순간 끊김 GC
메모리 계속 증가 Native
Instantiate 많음 둘 다

9. 핵심 요약

✔️ Unity는 의도적으로 C# + C++ 구조다
✔️ C#은 편의성, C++은 성능 담당
✔️ 객체는 항상 “두 개”로 존재한다
✔️ GC는 절반만 관리한다
✔️ Destroy는 필수다


10. 한 줄 정리

❗ “Unity는 자동 메모리 관리가 아니라, 반자동이다”

adb 실행하는 법 자꾸 까먹어서 기록해둠

 

 

1. ADB란 무엇인가?

ADB (Android Debug Bridge)
PC와 안드로이드 디바이스 사이를 연결해주는 디버깅 통신 도구다.

좀 더 정확히 말하면:

ADB는 PC에서 실행되는 클라이언트와, 안드로이드 디바이스에서 실행되는 데몬(adbd) 사이의 통신을 통해 앱 설치, 로그 조회, 파일 전송, 쉘 명령 실행 등을 가능하게 하는 브리지 역할을 한다.


2. 왜 ADB를 사용해야 하는가?

안드로이드 앱은 디바이스 내부에서 실행된다.
문제는:

  • 앱에서 발생하는 로그는 디바이스 내부 시스템(log buffer)에 저장됨
  • PC에서는 기본적으로 이 로그에 접근할 수 없음

👉 그래서 필요한 게 ADB

ADB를 사용하면:

  • 디바이스 내부 로그(logcat)를 PC로 스트리밍
  • 실시간 디버깅 가능
  • 크래시 원인 추적 가능

3. 전체 동작 구조

ADB는 다음 구조로 동작한다:

[ PC (adb client) ]
          ↓
[ adb server (PC 백그라운드) ]
          ↓ USB / TCP 연결
[ adbd (디바이스 내부 데몬) ]
          ↓
[ Android System Log Buffer ]
 

핵심 포인트:

  • adb client: 우리가 CLI에서 입력하는 adb 명령
  • adb server: PC에서 연결 관리 (자동 실행됨)
  • adbd: 디바이스 내부에서 명령을 받아 실행

* 데몬(daemon)은 사용자 눈에는 보이지 않지만, 시스템에서 항상 백그라운드로 실행되며 요청이 들어오면 작업을 처리하는 프로그램이다


4. 디바이스 연결 방식

ADB는 두 가지 방식으로 디바이스와 연결된다:

1) USB 연결 (기본)

  • 가장 일반적인 방식
  • 초기 설정 및 디버깅에 주로 사용

2) TCP/IP (Wi-Fi 연결)

  • 네트워크 기반 연결
  • Android 11 이상부터는 adb pair를 통한 공식 Wi-Fi 페어링 지원
adb pair <ip>:<port>
adb connect <ip>:<port>
 

물리적인 USB 연결 없이도 동일한 디버깅 환경을 구성할 수 있다.


5. 왜 ‘USB 디버깅’을 켜야 하는가?

기본적으로 안드로이드는 보안상 이유로 외부에서 접근을 막는다.

👉 USB 디버깅을 켜면:

  • adbd가 외부(PC)의 adb 요청을 수락
  • 인증된 PC만 디바이스에 접근 가능

즉,

USB 디버깅은 “디바이스 내부 디버깅 인터페이스를 외부에 열어주는 설정”이다.


6. 왜 PC에서 디바이스 인증을 해야 하는가?

처음 연결 시 뜨는 메시지:

“이 컴퓨터를 신뢰하시겠습니까?”

이건 보안 절차다.

  • PC의 공개키를 디바이스에 등록
  • 이후 해당 PC만 adb 접근 허용

👉 이유:

  • 악성 PC가 무단으로 디바이스 제어하는 것 방지

7. adb devices 명령의 의미

 
adb devices
 

이 명령은:

  • 현재 연결된 디바이스 목록 조회
  • adb 서버 ↔ 디바이스 연결 상태 확인

출력 상태 의미:

  • device → 정상 연결
  • unauthorized → 사용자 승인 필요
  • offline → 연결 문제

8. logcat이란 무엇인가?

안드로이드에는 시스템 로그 저장소(log buffer)가 있다.

여기에는:

  • 앱 로그 (Log.d, Log.e 등)
  • 시스템 로그
  • 크래시 정보

👉 이 로그를 읽는 도구가 logcat

 
adb logcat
 

즉,

logcat은 디바이스 내부 로그 시스템을 외부에서 조회하는 표준 인터페이스다.


9. 왜 Unity 로그는 따로 필터링하는가?

Unity는 로그를 출력할 때 "Unity" 태그(tag)를 사용한다.

그래서:

 
adb logcat -s Unity
 

를 사용하면:

  • Unity 관련 로그만 필터링
  • 불필요한 시스템 로그 제거

👉 이유:

  • 안드로이드 로그는 매우 많아서 그대로 보면 분석이 어려움

10. 전체 과정이 필요한 이유 정리

USB 디버깅 활성화 디바이스 내부 디버깅 인터페이스 개방
PC 인증 보안 (허용된 PC만 접근 가능)
adb devices 연결 상태 확인
adb logcat 디바이스 내부 로그 조회
Unity 필터링 필요한 로그만 추출

10. 결론

ADB는 단순한 도구가 아니라:

“PC에서 디바이스 내부 상태를 관찰하고 제어하기 위한 공식 디버깅 통신 계층”

이며, 로그 확인 과정은 단순히 번거로운 절차가 아니라:

  • 보안
  • 시스템 구조
  • 디버깅 효율성

을 모두 고려한 설계다.


✍️ 요약

안드로이드 앱 로그는 디바이스 내부에 저장되기 때문에, ADB를 통해 PC와 연결하여 logcat으로 읽어오는 구조이며, 이 과정은 보안과 시스템 구조상 필수적인 절차다.

Sprite 이미지 크기 개념이 헷갈려서 기록해둠,,

Scale 1은 크기가 1이라는 뜻이 아님

 

 

유니티 2D에서 자주 헷갈리는 개념 4가지:

  • 스프라이트 크기
  • Pixels Per Unit (PPU)
  • Orthographic 카메라
  • Max Size

이 4개는 역할이 다르지만 서로 영향을 주기 때문에 정확히 구분해야 한다.


1. 스프라이트 실제 크기 공식

유니티에서 스프라이트의 월드 크기는 아래 공식으로 결정된다.

실제 크기 (Unit) = 이미지 픽셀 크기 ÷ Pixels Per Unit (PPU)


📌 예시

  • 이미지: 64px
  • PPU: 64
64 ÷ 64 = 1 unit

👉 결과: 1 unit 크기

  • 이미지: 64px
  • PPU: 32
64 ÷ 32 = 2 unit

👉 결과: 2 unit 크기


❗ Scale = 1의 의미

transform.localScale = (1,1,1);

👉 “원본 크기 그대로 사용”
👉 크기가 1 unit이라는 의미는 아님


 

2. Pixels Per Unit (PPU)

1 유닛(Unit)에 몇 픽셀(px)을 대응시킬지 정하는 값


📊 PPU에 따른 크기 변화

이미지PPU결과 크기

64px 64 1 unit
64px 32 2 unit
64px 16 4 unit

💡 핵심 해석

  • PPU ↑ → 같은 픽셀이 더 작은 공간에 들어감 → 작아짐
  • PPU ↓ → 같은 픽셀이 더 넓은 공간에 사용됨 → 커짐

👉 이는 공식 (픽셀 ÷ PPU)로 정확히 설명된다


 

3. Orthographic 카메라

유니티 2D 카메라는 기본적으로 Orthographic이다.


🔑 핵심

orthographicSize = 화면 세로 “절반 길이” (Unit 기준)


📌 예시

cam.orthographicSize = 5;

👉 전체 화면 높이 = 10 unit


📏 가로 크기

가로 = 세로 × aspect 비율

예: 16:9

width ≈ 5 × 1.78 ≈ 8.9

👉 전체 가로 ≈ 17.8 unit


 

4. 스프라이트와 카메라 관계

  • 스프라이트: 1 unit
  • 카메라 높이: 10 unit
10 ÷ 1 = 10개

👉 화면에 세로로 약 10개 표시


 

5. Max Size (중요: 역할이 다름)

Max Size는 스프라이트의 “월드 크기”가 아니라
텍스처의 “최대 해상도”를 제한하는 설정이다


📌 무엇을 하는가

이미지를 GPU에 올릴 때:

  • 원본이 Max Size보다 크면
    👉 강제로 축소됨

예시

  • 원본: 64px
  • Max Size: 32

👉 내부적으로:

64 → 32로 축소됨

❗ 중요한 점

Max Size는 크기(unit)를 직접 바꾸지 않는다

👉 크기는 여전히:

(현재 텍스처 픽셀 크기) ÷ PPU

로 결정된다


📌 실제 영향

Max Size를 줄이면:

  • 해상도 ↓ (픽셀 수 감소)
  • 이미지 선명도 ↓
  • 필터링 영향 ↑

👉 결과:

  • 번져 보이거나
  • 크기가 달라진 것처럼 느껴질 수 있음

⚠️ 오해 정리

  • Max Size로 크기 조절 ❌
  • PPU로 크기 조절 ⭕

 

🧠 전체 관계 정리

요소역할

이미지 픽셀 원본 데이터
PPU 월드 크기 변환
카메라 화면에 보이는 범위
Max Size 텍스처 해상도 제한

🔥 최종 요약

  • 스프라이트 크기 = 픽셀 ÷ PPU
  • PPU = 1유닛당 픽셀 수
  • 카메라 = 보이는 월드 크기
  • Max Size = 텍스처 품질/해상도 설정

💬 한 줄 정리

크기는 PPU가 결정하고,
Max Size는 화질만 결정한다

 

Surface Shader는

복잡한 연산이 많이 생략되어 있어 초보자가 사용하기 좋음

다양한 환경(모바일,pc, 라이트맵 유무, 조명이 픽셀 라이팅인 경우, 버텍스 라이팅인 경우 등등)에

맞게 각각의 쉐이더가 제작되어야 하는데

Surface Shader는 자동으로 제작해주어서 실무에서 간편하게 사용가능 (물론 최적화 필요) 

 

 

쉐이더 Properties - 익스펙터에서 입력할 수있는 항목을 만들 수 있는 부분

 

자주 쓰는 인터페이스

Range

슬라이더바 만들기

_Brightness ("change Brightness!", Range(0, 1)) = 0.5

 

* _Brightness : 기능의 변수명

* "change Brightness!" : 인스펙터에 나타나는 글자

* Range(0, 1) : 슬라이더 값을 0.0~ 1.0 사이로 설정

* 0.5 : 쉐이더 처음 만들어졌을 때 초기값

 

Float

입력 값이 슬라이더 방식이 아닌 직접 값을 입력해야 함

_TestFloat ("Test Float!", Float) = 0.5

 

Color

R, G, B, A 4자리 숫자 입력 가능 (float4)

_TestColor ("Test Color!", Color) = (1,1,1,1)

 

Vector

float4를 직접 숫자로 입력 가능\

_TestVector ("Test Vector!", Vector) = (1,1,1,1)

 

tex2D

2D 텍스쳐를 받는 인터페이스를 만들어줌

텍스쳐는 UV 좌표와 함께 계산되어야 float4로 출력 가능함

아직 UV와 계산되지 않은 텍스쳐는 색상(float4)로 나타낼 수 없는데 이때까지는 sampler라고 부름

 

_TestTexture ("Test Texture!", 2D) = "white" {}

* {} 는 옵션

 

 

 

 

 

 

'Unity > Shader' 카테고리의 다른 글

ShaderLab을 이용한 제작 방식  (0) 2025.09.28
쉐이더 기초 지식 요약  (0) 2025.09.28

유니티 쉐이더는 ShaderLab 이라고 불리는 자체 스크립트 언어를 이용함

 

ShaderLab을 이용한 제작 방식은 크게 세 가지로 나뉨

1. ShaderLab으로만 작성

  - 매우 가볍고 하드웨어 호환성 좋음

  - 기능이 부족하여 고급 효과 구현 어려움

 

2. Surface Shader로 작성

  - ShaderLab 스크립트와 함께 CG 쉐이더 코드 사용

  - 기본적인 조명 코드와 버텍스 쉐이더의 복잡한 부분은 스크립트로 자동처리

  - 픽셀 쉐이더 부분만 간편하게 작성할 수도 있음

  - 최적화에 다소 무리있음

  - 일정 수준 이상의 고급 기법 구현 어려움

 

3. Vertex & Fragment Shader로 작성

  - ShaderLab 스크립트와 함께 CG 쉐이더 코드 사용

  - 자동으로 처리해주는 부분이 별로 없음

  - CG 쉐이더 방식으로 버텍스의 좌표 변환부터 제대로 처리해야 작동함

  - 수동으로 제어할 수 있어서 최적화, 고급 기법 표현시 좋음

 

 


참고 : 테크니컬 아티스트를 위한 유니티 쉐이더 스타트업

 

'Unity > Shader' 카테고리의 다른 글

Surface Shader 작성하기  (0) 2025.09.28
쉐이더 기초 지식 요약  (0) 2025.09.28

쉐이더(Shader)

3D 컴퓨터 그래픽에서 최종적으로 화면에 출력하는 픽셀의 색을 정해주는 함수

 

렌더링 파이프라인 (렌더링 순서)

1. 오브젝트 데이터 받아오기

  - 그래픽 카드가 버텍스(정점)로 이루어진 물체 데이터 값을 받아옴 

  - 버텍스는 버텍스의 Index number, position, normal, color 등의 정보를 갖고 있음

 

2. 버텍스 쉐이더 (Vertex Shader)

  - 버텍스의 위치 값 변환이 이루어짐

  1. 로컬 좌표
  2. 월드 좌표계 변환 (월드 변환 행렬 곱하기)
  3. 카메라 중심점으로부터 좌표 계산. Orthographic Projection(카메라 행렬 곱하기)
  4. 원근감 부여 (Projection 행렬 곱하기) Perspective 프로젝션일 경우에만 일어남

 

3. 래스터라이져 (Rasterizer)

  - 좌표를 조정한 버텍스들을 픽셀 단위로 변환하는 과정

 

4.픽셀쉐이더(Pixel Shader) / 프레그먼트 쉐이더(Fragment Shader)

  - 픽셀에 조명, 텍스쳐, 그림자, 특수효과 등을 연산하여 최종 픽셀 색상을 결정함


모니터 색은 가산혼합으로 만들어짐

* 가산혼합 - RGB로 이루어진 색을 더할수록 밝아짐

* 컬러 모니터는 RGB로 구성된 3개의 서브픽셀로 이루어져 있음 (설계에 따라 아닌 경우도 있음)

 

R(100%) G(100%) B(100%) => 흰색

 

R(0%) G(0%) B(0%) => 검은색

 

R(50%) G(50%) B(50%) => 회색

 

R(0%) G(0%) B(100%) => 파란색

 

 

컬러를 Float 단위로 나타내기

100%를 1, 50%를 0.5, 0%를 0으로 나타냄

R(100%) G(50%) B(0%) => Float3(1.0, 0.5, 0.0)

 

 

컬러 연산

덧셈

float3(0.5, 0.5, 0.5) + float3(0.5, 0.5, 0.5) = float3(1,1,1)

 

뺄셈

float3(1, 1, 0) + float3(1, 0.5, 0) = float3(0 ,0.5, 0)

 

곱셈

float3(0.5, 0.5, 0.5) * float3(0.5, 0.5, 0.5) = float3(0.25, 0.25, 0.25)

 

나눗셈은 상대적으로 계산이 느리고 곱셈 연산으로 대치 가능해서 잘 안씀

 

 

값이 0 이하 1이상일 경우 =>  숫자 값은 유지되어도, 0 미만, 1 초과의 값은 모니터에서 표현할 수 없음

 

float3(1, 1, 0) + float3(1, 0, 0) = float3(2, 1, 0) => 숫자만 2로 존재할 뿐, 실제로 보이는 건 (1, 1, 0) 

float3(1, 0, 0) - float3(0, 0, 1) = float3(1, 0, -1) => 숫자만 -1로 존재할 뿐, 실제로 보이는 건 (1, 0, 0) 

 

 

컬러 반전(Invert)

1에서 어떤 색을 빼면 그 색이 반점됨

빨간색을 반전하면 하늘색이 됨

1 - float3(1, 0, 0) = float3(0, 1, 1)

 

 

'Unity > Shader' 카테고리의 다른 글

Surface Shader 작성하기  (0) 2025.09.28
ShaderLab을 이용한 제작 방식  (0) 2025.09.28

+ Recent posts