Notice
«   2026/07   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

월세 500 호기심 천국

5주차: 헐크 아저씨도 깜짝 놀란 Unity Render Pipeline이 작동하는 과정 본문

내 맘대로 쓰는 Unity 심층분석 析深層分

5주차: 헐크 아저씨도 깜짝 놀란 Unity Render Pipeline이 작동하는 과정

원펀치 쓰리 갓냥이 2024. 1. 27. 06:59
0주차: 인트로 - 새롭고 신기한 것을 좋아하거나, 모르는 것을 알고 싶어 하는 마음
1주차: Unity 엔진은 왜 개발 언어로 C#을 선택했을까?
2주차: 의외로 세상 잡다한 Transform에 대한 모든 것
3주차: EulerAngle아, Quaternion아, 둘이 싸우지 말고 사이좋게 지내야 한다~ (1/2)
4주차: EulerAngle아, Quaternion아, 둘이 싸우지 말고 사이좋게 지내야 한다~ (2/2)
5주차: 헐크 아저씨도 깜짝 놀란 Unity Render Pipeline이 작동하는 과정
6주차: 매 프레임 나보다 갓생 사는 Unity의 Life Cycle
7주차: 남들보다 더 갓생 사는 Coroutine의 충격적인 정체
8주차: 야물(YAML)딱진 그 녀석의 은밀한 속마음 - Unity Scene 파일 (.unity)
9주차: 내 과자 포장지 누가 버렸어? - meta data 파일 (.meta) 파헤치기
10주차: 철가방 채로 배달온 Unity 빌드 파일. 난 단무지 시킨 적 없는데, 왜 있지?
11주차: 마무리 - 후기

 


 

렌더링 중

 

컴퓨터는 사용자가 원하는 결과물을 내놓기 위해

짧은 시간 안에 수많은 연산상호작용을 한다.

 

그리고 자신이 연산해낸 것들을 사람에게 보여주기 위해

그것을 화면에 띄워내야 하는데,

이 과정 역시도 또 다른 연산들을 거쳐야 한다.

 

모니터는 컴퓨터가 원하는 대로 연필로 쓰고 지우개로 지워 내놓으면 그만인 도화지가 아니고,

가로 세로 정해진 개수픽셀들을 칠해

표현된 그림을 매순간 갈아치워야 하는 애니메이션 플립 북이기 때문이다.

 

이 때문에 컴퓨터는 매 프레임마다 어떤 픽셀어떤 색을 채워 내보내야

이용자가 원하는 화면을 나타낼 수 있는지 계산해야 하고,

개발자는 그런 연산 과정을 효율적으로 잘 프로그래밍 해줘야 한다.  

 

 


 

 

 

하지만 그렇다고 해서 모든 개발자들이

이런 과정들까지 세세하게 구현해야 할 필요는 없다.

이제 막 SpriteRenderer와 RawImage 배치를 만지작거리는 새싹 개발자들에게

해당 오브젝트들이 씬 화면에 보일 수 있도록 그 과정을 구현하게 하는 것은 좀 이상하지 않은가?

 

때문에 이런 과정들을 쉽게 구현할 수 있도록

순차적인 단계로 나누어 코드를 미리 작성하고,

누구나 사용할 수 있도록 모듈화된 라이브러리로 배포한 케이스가 있다.

예시로는 그래픽 라이브러리 중 인지도가 가장 높은 OpenGL, DirectX 등이 있다.

 

그렇다면 여기에서 위 그래픽 라이브러리들이 구현해낸 '순차적인 단계'란 무엇일까?

고화질의 2D, 혹은 3D 이미지들을 모니터에 그려내는 것은 CPU 혼자서 처리할 수 있는 일이 아니다.

CPU복잡한 연산단시간에 뚝딱 처리할 수 있는 뛰어난 성능을 가지고 있지만,

모니터에 방대한 양의 그래픽을 그려내는 것은 단일 속도만으로 커버해내기는 어렵기 때문이다.

 

하나의 일을 차근차근 처리하는 CPU와 단순한 일들을 병렬적으로 한 번에 처리하는 GPU

 

그래픽 연산계산 과정 자체는 단순하지만 그 어마무시하게 많다.

그렇기에 복잡한 연산에 능하더라도 한 번에 많은 연산을 처리하지 못하는 CPU보다는,

간단한 연산만 가능하더라도 병렬적으로 한 번에 많은 연산을 처리할 수 있는 GPU(그래픽 카드)가 작업에 더 적합하다.

 

하지만 GPU가 그래픽 관련 모든 연산을 다 도맡아서 할 수는 없다.

GPUCPU가 하기엔 양이 많은 연산들을 가져가 대신 해주는 보조자의 역할일 뿐이므로.

때문에, CPUGPU분업이 중요할 것이다.

 

전체에서 누가 어떤 파트의 연산을 맡을 것인가?

연산 결과를 서로에게 어느 시점에, 어떤 방식으로 전달할 것인가?

어떤 단계의 연산들을 프로그래머가 직접 수정할 수 있도록 허용할 것인가?

 

이러한 일련의 과정을 미리 정해놓고 하나의 회로처럼 단계별로 설계한 것을 Rendering Pipeline이라고 한다. 

그리고 OpenGL, DirectX, WebGL등의 유명 그래픽 라이브러리들은 전부 저마다의 Rendering Pipeline을 가지고 있다.

 

가장 유명한 OpenGL Rendering Pipeline

 

 


 

 

 

그렇다면 Unity는 과연 어떤 렌더 파이프라인을 사용할까?

Unity가 사용하는 파이프라인에는 크게 다음 세 가지가 있다.

 

 

Built-In Render Pipeline

Unity기본적으로 내장되어있는 파이프라인이다.

기본 아이템인 만큼 인터넷상에서 관련 정보를 찾아보기가 쉽고,

기존의 여느 타 기능들과 섞여도 잘 작동하는 보편성을 보장 받으며,

Asset Store에서 받아온 에셋들과도 호환성이 뛰어나다.

그곳에 있는 에셋들도 전부 기본 Pipeline의 사용을 염두에 두고 제작되었을 것이므로.

 

다만 여느 '내장 탑재'의 이름이 붙은 것들이 그러하듯,

코드의 수정불가능하고 사용할 수 있는 기능들이 제한적이다.

정확히 말하자면, 개발자가 코드를 직접 작성해 새로운 기능추가하고 싶어도 추가할 수가 없는 것이다.

 

Universal Render Pipeline (URP) (구 LWRP)

위의 기본 파이프라인에 비해 최적화 방면에서 더 좋은 모습을 보이는 파이프라인이다.

Lighting, Shading 등 다양한 부분에서 trade-off를 진행해,

제한된 성능의 플랫폼에서도 고사양 품질의 그래픽을 뽑아내는 것이

해당 파이프라인의 개발 목적이다.

 

또한 URPScriptable Render Pipline(SRP)에 속한 파이프라인이기 때문에

코드 수정기능의 추가가 비교적 자유로워 높은 확장성을 가지고,

Shading면에서도 더 다채로운 모습을 보인다.

 

다만 SRP에 포함되는 만큼 코드의 수정이 많아질수록

Asset Store에 있는 에셋들과 호환이 되지 않을 가능성이 높아진다.

 

High Definition Render Pipeline (HDRP)

URP가 플랫폼의 성능 편차에 구애받지 않는 최적화를 추구했다면,

HDRP가 추구하는 것은 오로지 최상의 퀄리티이다.

고성능 PC, 콘솔 등 하이 엔드 급플랫폼들을 타겟으로 할 때 주로 사용되며,

Real-Time으로  최고 품질Lighting, Shading 등을 표현해내는데 용이하다.

마찬가지로 SRP에 속해있는 파이프라인으로, 코드 수정기능 추가가 자유롭다.

 

 


 

 

 

Unity 파이프라인의 종류들을 알아봤으니,

이제 실제로 그것들이 어떤 과정을 거쳐 화면에 그림을 그려내는지 알아볼 때가 되었다.

Built-In Render PipelineForward Rendering Path (가장 일반적인 렌더링 경로)

를 택했을 때의 경우를 기준으로 준비했다.

 

 

0. Pipeline Overview

Unity Built-In Render Pipeline Overview

 

1. Input Assembler (Vertex Specification)

그래픽 상에서 나타나는 모든 물체들은 폴리곤이라는

조그마한 삼각형들로 이루어져 있다.

 

곡면을 있는 그대로 계산해 묘사하기보다는

무수히 많은 평면으로 나누어 묘사하는 편이 더 효율적이고,

그러한 평면을 이루는 최소 조건이 세 개의 점, 즉 삼각형이기 때문.

 

때문에 이 폴리곤을 만들기 위해서는 각 폴리곤의 꼭짓점(= 정점)들이 3개씩 필요하다.

Input Assembler 단계에서 CPUGPU에게 정점들의 정보를 전달한다.

삼각형을 만들고 싶다면 삼각형의 꼭짓점 세 개를,

사각형을 만들고 싶다면 삼각형 두 개의 꼭짓점 여섯 개를 전달한다.

 

2. Vertex Shader

CPU에게서 전달받은 정점들의 좌표 정보를 있는 그대로 사용할 수는 없다.

 

지구라는 행성에 의자라는 모델링을 하나 추가한다고 할 때,

의자의 위치를 (209동 1305호, 안방, 컴퓨터 책상 앞)으로 계산할 수는 없지 않은가?

위도, 경도, 고도를 다 따져 범지구적인 스케일의 정확하고 범용성 있는 수치로 나타내야 한다.

 

이 과정을 Modeling CoordinatesWorld Coordinates로 변환해주는

Model Transform (= Local Transform)이라고 한다.

 

위 그림과 같이 GPU공간 좌표계를 여러 단계 변환해주는 동시에,

개발자가 원한다면 Gouraud Shading이라 불리는

다소 원시적인 기법의 Lighting도 적용해줄 수 있는 단계이다.

Gouraud Shading: 정점 단위로 빛의 반사가 계산되는 만큼 반사광 모양이 어색하고 뾰족하게 표현된다. 옛날 3D 게임에서 자주 찾아볼 수 있다.

 

3. Tesselator

가까이 있는 물체는 멀리 있는 물체에 비해 폴리곤더 밀도있게 짜여질 필요가 있다.

폴리곤이 부족하다는 것은 곧 해상도가 떨어진다는 것이고,

가까이서 볼수록 티가 나는 것은 당연하기 때문이다.

얘는 폴리곤이 부족한게 아니라 초점이 안맞은 거긴 하지만

 

Tesselator라는 이름에서 알 수 있듯이

빈 공간에 타일을 채우듯 기존 모델의 정점쪼개어

폴리곤 사이 사이 빈 공간에 새로운 폴리곤들을 추가해

근거리에서는 더 높은 해상도를 얻을 수 있게끔 한다.

 

4. Geometry Shader

 

Tesselator에서 전달받은 새로운 정점들에 더해

기존 모델의 정점추가/삭제/수정할 수 있는 단계이다.

수정 단계가 끝나면 해당 정점들이 이루는 폴리곤추가로 생성한다.

 

5. Rasterizer

Rasterizer의 역할은 다양하다.

Unhinging Transformation을 통해 원근감을 부여하고,

기존 사각뿔 형태의 시야각을 직육면체 형태로 확장해 가까운 물체를 크게 키워 원근감을 부여한다.

 

정점정점 사이, 폴리곤 내부의 빈 공간들을 보간을 통해 픽셀로 채운다.

정육면체의 각 꼭짓점(정점)들을 서로 다른 색깔로 설정해두면, 보간으로 인해 폴리곤 내부에는 무지개빛 그라데이션의 픽셀들이 생성된다.

 

또한 카메라 시야에 들어오지 않는 픽셀들은 가차 없이 잘라낸다.

 

6. Depth Test

Rasterizer 이후 픽셀들을 바로 렌더링하게 되면

폴리곤들이 처리되는 순서실제로 카메라에 보이게 되는 순서무관하기 때문에

카메라 기준으로 더 뒤에 있는 폴리곤

앞에 있는 폴리곤뚫고 나오거나, 역으로 가리는 상황이 발생할 수 있다.

폴리곤 처리 순서 차이로 큐브의 아랫면이 사라진 것처럼 보인다.

 

때문에 Depth Test에서 각 픽셀들의 깊이값(카메라로부터의 거리, Z값)을 계산/결정하고

Z-Buffer에 저장하는 과정을 거친다.

알고리즘 특성상 Z-Buffer에 저장된 결과값은 각 폴리곤의 처리 순서와 무관하게 항상 같다.

 

7. Stencil Test

 

Depth Test가 픽셀에 의해 가려지는 다른 픽셀, 물체의 뒷면 등이

화면에 나타나지 않도록 Z-Buffer에 값들을 저장해두는 역할을 했다면,

 

Stencil Test는 픽셀의 깊이(거리)와 무관하게

어떤 부분을 보이게 하고, 어떤 부분을 안보이게 할지를 개발자가 직접 설정하는 단계이다.

마찬가지로 Stencil Buffer픽셀의 렌더링 유무 정보를 넣는다.

 

선택적으로 렌더링 되지 않을 곳을 정해주는 기능이기 때문에,

그림자, 거울, 외곽선 표현 등 다양한 곳에서 사용된다.

 

8. Fragment Shader (= Pixel Shader)

 

렌더링의 꽃이라고 할 수 있는 Fragment Shader이다.

폴리곤의 픽셀들에 색을 칠하고,

Height Map, Bump Map, Normal Map 등 다양한 기법Texture를 적용한 뒤,

정점들폴리곤의 법선 벡터를 이용해 각종 Lighting 효과를 입힌다.

 

사람의 눈으로 보았을 때 화려하다고 느껴지는 부분의 최소 70% 이상이 구현되는 곳이며,

컴퓨터 그래픽 기술의 발전이 가장 크게 체감되는 단계이다.

 

9. Output Merger

 

Fragment Shader 이후 화면에 나타나기 전에 거치는,

사실상 렌더링에서의 마지막 단계이다.

 

Depth Test/Stencil Test에서 얻어낸 Z-Buffer/Stencil Buffer를 이용해

어떤 픽셀을 나타내고 어떤 픽셀을 숨길지 계산하며,

색과 색이 섞일 때의 Blending 기법을 적용하고, (예: Alpha Blending)

픽셀과 픽셀의 경계를 흐릿하게 해주는 Anti-Aliasing를 통해 계단 현상을 방지하는 등

모니터에 보이기 전 최종 마무리 단계를 거친다.

 

10. Post Processing

 

Post Processing은 1~9의 렌더링 단계를 거치고 난 뒤

필요에 따라 한 번 더 렌더링 과정을 더하는 작업이다.

블러 효과, 렌즈 효과 (빛 번짐), 화면 왜곡, 흑백/색조 변환 등

앞의 과정들에 더하여 비주얼적으로 한 층 더 발전한 결과물들을 얻어낼 수 있다.

 

 


 

 

 

이렇게 하여 

Unity Render Pipeline의 일반적인 작동 과정을 살펴보았다.
라고 써놓기는 했지만, 사실 OpenGL이니 DirectX이니 대부분 것들의 Pipeline이

이와 비슷한 과정을 취하고 있을 것이다.

필자도 찾아보면서 그렇게 느끼기도 했고.

 

우리가 게임, 영화, 혹은 애니메이션에서 별 생각 없이 봤던 장면들이

실제로는 이렇게 까다로운 과정을 거쳐 화면에 나타난 거였다니...

원래도 알고는 있었지만 파면 팔수록 세상 일 돌아가는거 진짜 복잡하구나 하는 생각이 들었다.

 

 

 

그럼 이번 주도 끝!!!

 


 

0주차: 인트로 - 새롭고 신기한 것을 좋아하거나, 모르는 것을 알고 싶어 하는 마음
1주차: Unity 엔진은 왜 개발 언어로 C#을 선택했을까?
2주차: 의외로 세상 잡다한 Transform에 대한 모든 것
3주차: EulerAngle아, Quaternion아, 둘이 싸우지 말고 사이좋게 지내야 한다~ (1/2)
4주차: EulerAngle아, Quaternion아, 둘이 싸우지 말고 사이좋게 지내야 한다~ (2/2)
5주차: 헐크 아저씨도 깜짝 놀란 Unity Render Pipeline이 작동하는 과정
6주차: 매 프레임 나보다 갓생 사는 Unity의 Life Cycle
7주차: 남들보다 더 갓생 사는 Coroutine의 충격적인 정체
8주차: 야물(YAML)딱진 그 녀석의 은밀한 속마음 - Unity Scene 파일 (.unity)
9주차: 내 과자 포장지 누가 버렸어? - meta data 파일 (.meta) 파헤치기
10주차: 철가방 채로 배달온 Unity 빌드 파일. 난 단무지 시킨 적 없는데, 왜 있지?
11주차: 마무리 - 후기