결국 Wave Intrinsics는
하드웨어의 물리적 구조를 소프트웨어 알고리즘의 영역으로 끌어들여,
GPU 성능을 한계까지 끌어올린 SM6의 정점입니다.

 지난 시간, DX11(SM5)의 등장과 함께 Compute Shader와 Tessellation이 어떻게 데이터와 워크로드 모델의 패러다임을 바꿨는지 살펴보았습니다. 이는 렌더링 엔진의 책임이 커졌음을 의미하며, 동시에 GPU 프로그래밍의 주도권이 하드웨어(고정 기능)에서 소프트웨어(개발자의 설계) 로 크게 이동하는 서막이기도 했습니다. DX12는 통제권을 개발자에게 돌려준 Low-level API의 전환이었고, SM6는 Wave Intrinsics로 하드웨어의 웨이브 실행 단위를 셰이더 코드에 노출시켰습니다.

Low-Level API, Thin API

 DX11에서의 리소스 상태 관리(추적 / 검증)는 드라이버의 몫이었습니다. 드라이버가 내부에서 암묵적으로 상태를 관리하고 검증과 최적화를 대신 수행해주었습니다. 이는 필연적으로 CPU 오버헤드를 동반했습니다. DX12는 이 API 레이어를 아주 얇게(Thin) 걷어냈습니다. 이제 리소스 배리어(Resource Barrier) 설정부터 메모리 배치(Heap 기반)까지 과거 드라이버가 수행하던 저수준 제어를 개발자가 명시적으로 다룰 수 있게 된 것입니다.

Command Queue와 병렬화

 단일 컨텍스트(Immediate Context) 기반이었던 DX11은 아무리 코어가 많아도 렌더링 명령은 제한적인 경로로 GPU에 전달되어야만 했습니다. 특히 중간에 상태 변경이나 검증 비용이 누적되면 CPU 병목이 커지기 쉬웠습니다. 반면 DX12는 멀티스레드 환경에서 여러 개의 Command List를 동시에 기록하고 이를 GPU로 제출할 수 있는 구조를 택했습니다. 이는 현대 멀티코어 프로세서의 성능을 최대로 끌어내며 CPU 병목을 완화한 핵심 동력이 되었습니다.

Bindless Resource

 과거 Draw Call 오버헤드의 큰 부분은 리소스 바인딩과 그에 따른 드라이버 검증 및 상태 변경 비용이었습니다. 즉, 드로우를 호출할 때마다 텍스처·버퍼 같은 리소스를 슬롯에 바인딩하고, 드라이버가 그 상태를 확인·정리하는 작업이 반복되면서 CPU 비용이 누적되었습니다. DX12에서는 이를 Descriptor Heap Descriptor Table 이라는 거대한 메모리 풀에 리소스를 몰아넣고 인덱스만 전달하는 Bindless 방식으로 개선했습니다. 덕분에 리소스 전환 비용을 최소화하고 GPU가 연산에만 집중할 수 있는 환경을 구축했습니다.

LDS vs Wave Intrinsics

Wave Intrinsics

 SM6의 등장과 함께 추가된 기능 중 가장 혁신적인 것을 꼽으라면 단연 Wave Intrinsics입니다. 이는 GPU의 서브그룹 실행 단위(Warp 혹은 Wavefront)를 셰이더 코드에서 직접 다룰 수 있게 해줍니다. 같은 Wave 내 Lane들 사이에서 값을 교환하거나 집계(Reduction)할 수 있어서 그룹 공유 메모리(groupshared / LDS)와 배리어에 의존하던 패턴을 같은 Wave 내부에서는 더 싸게 연산할 수 있는 선택지를 제공하게 됐습니다.

  1. groupshared / LDS + 배리어 의존도
     SM5까지의 모델은 스레드 그룹 단위로 데이터를 공유할 때 보통 groupshared(LDS)에 쓰고, GroupMemoryBarrierWithGroupSync()로 동기화한 뒤 읽는 패턴이 일반적이었습니다. 이때 메모리 접근 지연과 배리어 대기 시간이 최적화의 비용으로 작동하기 쉬웠습니다.
  2. Wave 내부에서 레지스터 수준 교환 및 연산을 제공하다.
     SM6의 Wave Intrinsics는 일부 패턴에서는 이 메모리 단계를 대체하거나 줄일 수 있습니다. Wave 내부에 한해 Lane 간 값을 레지스터 수준으로 교환 및 연산할 수 있게 합니다. (단, wave 밖에서는 여전히 LDS/배리어가 필요합니다.)
    • WaveActiveSum() : Wave 내 합 (Reduction)
    • WaveReadLaneAt() : 특정 Lane 값 읽기 (Shuffle)
    • WavePrefixSum() : Prefix 연산
  3. 통제가 만든 최적화의 선택지
     결과적으로 개발자는 하드웨어가 실제로 실행하는 서브그룹 단위를 전제로 알고리즘을 설계할 수 있게 되었고, 특정 워크로드에서는 groupshared(LDS)나 배리어 비용을 줄이는 방식으로 성능을 개선할 여지가 생겼습니다. (Occlusion / Visibility 계열 처리, 타일링 / 스캔 / 리덕션 기반 작업 등)

(참고: wave 크기는 벤더와 아키텍처, 설정에 따라 달라질 수 있습니다.)

맺음말

 우리는 지금까지 DX11에서 DX12로 넘어오며, GPU의 실행 모델과 리소스와 동기화 책임이 어떻게 더 명시화되어 왔는지 살펴보았습니다. 누군가에게 DX12는 드라이버가 해주던 일을 개발자가 더 많이 떠맡게 된 불친절한 변화였을지 모릅니다. 하지만 역설적으로 이 통제권은, UE5의 거대한 두 기둥인 나나이트(Nanite)루멘(Lumen) 같은 현대적 렌더링 기술을 뒷받침하는 중요한 기반이 되었습니다.

이전글

 

GPU의 역사 - 4 : DX11(SM5), Tessellation과 Compute Shader

GPU의 역사 - 3 : DX10/SM4와 Unified Shader 전환GPU의 역사 - 2 : SIMD에서 SIMT로, Branch DivergenceGPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금

chessire.tistory.com

 

DX11(SM5)은 UE4에게 ‘업데이트’가 아니라 구조 전환이었다.
Compute Shader가 파이프라인의 고정관념을 깨고,
Tessellation·PBR은 포토리얼리즘을 “실무”로 만들었다.

 이전 글에서 다룬 DX11(SM5)의 등장은 UE4에게 단순한 라이브러리 업데이트 이상의 의미를 가지고 있습니다. UE3가 DX9 기반의 고정된 파이프라인에서 벗어나려 애썼다면, UE4는 Compute Shader와 Tessellation이라는 핵심 기능을 도입하며 '리얼타임 포토리얼리즘'에 도전했던 시기이기 때문입니다.

렌더링 파이프라인의 파괴적 혁신

 DX11(SM5)의 도입에서 가장 파괴적인 변화는 역시 Compute Shader(CS)의 등장이었습니다. 이전까지의 GPU가 정해진 그래픽스 파이프라인(VS / PS)에 종속되어 있었다면, CS는 GPU에 고정 파이프라인 외 연산 모델을 제공했습니다.

  • Deferred Shading의 고도화 (Tiled/Clustered): UE4는 수천 개 수준의 동적 광원을 다루기 위한 흐름에서 타일 / 클러스터 기반의 라이트 컬링 같은 접근과 함께 CS 활용이 중요한 선택지로 부상했습니다. 화면을 타일 단위로 나누고, 해당 타일에 영향을 주는 광원만 추려내 연산량을 획기적으로 줄이는 Tiled Deferred Shading이 다수 광원 처리를 위한 대표 전략으로 자리 잡았습니다. 이는 단순한 속도 향상을 넘어, 복잡한 실내외 환경에서도 라이팅 설계의 제약을 완화하는 데 기여했습니다.
  • Niagara와 GPU 시뮬레이션: 기존의 Cascade가 CPU의 연산 능력에 갇혀 수천 개의 파티클도 버거워했다면, UE4 후반부에 Niagara가 확장되면서, 특히 GPU 시뮬레이션 경로에서는 입자를 GPU에서 직접 처리하는 접근이 본격적으로 활용되었습니다. Vector Field 추종이나 입자 간 상호작용 등, 이전보다 복잡한 동작을 실시간으로 다루려는 시도가 가능해졌습니다.
  • Post-Processing의 재정의: 후처리 영역에서는 PS/CS를 상황에 따라 혼용할 수 있게 되면서, 블러·DoF·시간축 누적(TAA)처럼 연산량이 큰 효과를 더 적극적으로 시도할 수 있는 기반이 마련되었습니다. 특히 TAA는 이전 프레임의 정보를 현재 프레임에 투영(Reprojection)하는 복잡한 계산을 수행하며, 계단 현상을 제거하는 동시에 포토리얼리즘의 기반이 되는 부드러운 화질을 제공하는 방향으로 작동했습니다.

Epic Games, 랜드스케이프 머터리얼의 Tessellation 및 Displacement 에서 발췌

디테일의 혁명과 Nanite를 향한 여정

 DX11의 핵심 기능 중 하나인 Hardware Tessellation은 메시의 기하학적 복잡도를 GPU가 실시간으로 결정하게 만들었습니다. 이는 UE4에서 지형(Landscape)과 캐릭터 디테일을 표현하는 핵심 전략이 되었습니다.

  • Displacement Mapping의 실현: 아티스트가 평면적인 노멀 맵(Normal Map)에 의존하던 시대에서 벗어나, SM5의 Hull Shader와 Domain Shader를 통해 실제 폴리곤을 쪼개고 솟아오르게 하는 Displacement Mapping이 가능해졌습니다. 덕분에 근거리에서의 돌과 흙의 질감은 눈속임이 아닌 실제 기하학적 굴곡을 가진 입체물로 변모했습니다.
  • 최적화의 딜레마와 유산: 하지만 하드웨어 테셀레이션은 공짜가 아니었습니다. 과도한 폴리곤 분할은 성능 저하를 일으켰고, 테셀레이션된 메시 간의 균열(Crack)이나 하드웨어 성능 편차는 개발자들에게 끝없는 최적화 과제를 던져주었습니다. 역설적으로, 이 시기의 고민인 '어떻게 하면 성능 손실 없이 무한한 폴리곤을 그릴 것인가' 는 훗날 하드웨어를 직접 제어하여 수십억 개의 폴리곤을 처리하는 UE5 Nanite의 개념적 초석이 됩니다.

하드웨어 가속과 PBR(Physically Based Rendering)의 정착

 DX11 세대에 이르러 GPU의 부동소수점 연산 성능과 메모리 대역폭이 유의미하게 향상되면서, UE4의 상징인 PBR(물리 기반 렌더링) 파이프라인이 실무적으로 안정화되기 시작했습니다.

  • 에너지 보존 원칙의 수치화: 과거에는 아티스트의 직관(속칭 '감')에 의존해 스펙큘러 값을 조정했습니다. 하지만 UE4 후기 파이프라인은 빛이 표면에 닿아 반사되고 흡수되는 물리적 법칙을 Roughness, Metallic, Specular라는 정제된 데이터로 제어하게 했습니다.
  • 표준화된 제작 환경: 하드웨어 가속 덕분에 복잡한 BRDF(Bidirectional Reflectance Distribution Function) 모델을 실시간으로 계산할 수 있게 되었고, 이는 곧 '어떠한 라이팅 환경에서도 재질이 일관되게 보인다'는 신뢰성을 확보해주었습니다. 결과적으로 아티스트는 기술적 제약보다 창의적인 표현에 더 집중할 수 있는 환경을 맞이하게 되었습니다.

맺음말

 결국 UE4 후기의 역사는 DX11(SM5) 세대의 제약을 엔진 소프트웨어가 어디까지 극대화하여 활용할 수 있는가를 증명해온 과정이었습니다.

 Compute Shader를 통한 연산의 자유는 그래픽 파이프라인의 고정관념을 깨뜨렸고, 테셀레이션과 PBR의 정착은 실시간 렌더링과 오프라인 렌더링(CGI) 사이의 간극을 획기적으로 좁혀놓았습니다. 우리가 지금 당연하게 여기는 ‘포토리얼리즘’은, 사실 이 시기에 성능 제약을 전제로 한 최적화가 강하게 요구되던 흐름 위에서 성립했습니다.

 하지만 기술의 발전은 늘 새로운 갈증을 불러옵니다. DX11 기반의 하드웨어 테셀레이션은 성능과 디테일 사이의 아슬아슬한 줄타기를 반복해야 했고, 수천 개의 광원을 처리하려는 시도는 동적 라이팅의 완전한 구현이라는 숙제를 남겼습니다.

 이러한 DX11 시대의 고민과 유산은 이제 DirectX 12와 Vulkan, 그리고 이를 집대성한 UE5의 나나이트(Nanite)루멘(Lumen)으로 계승됩니다. 가상화된 마이크로 폴리곤 렌더링과 실시간 레이 트레이싱의 시대가 열린 지금, 우리가 지나온 이 DX11의 연대기는 단순한 과거가 아니라 현대 그래픽스 기술의 가장 단단한 뿌리라고 할 수 있을 것입니다.

이전글

 

UE4 전기: Unified Shader 이후

UE3 - Forward Rendering을 넘어 Deferred Rendering으로UE2의 Overlay Shader와 초기 Post Process EffectGPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금

chessire.tistory.com

 

GPU의 역사 - 4 : DX11(SM5), Tessellation과 Compute Shader

GPU의 역사 - 3 : DX10/SM4와 Unified Shader 전환GPU의 역사 - 2 : SIMD에서 SIMT로, Branch DivergenceGPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금

chessire.tistory.com

 

Tessellation은 기하를 '런타임 생성 데이터'로 바꿨고,
Compute Shader는 연산을 '정식 워크로드'로 분리했다.

 지난 시간에는 DX10/SM4로 넘어가며 Unified Shader라는 공용 셰이더 코어 풀에 대해서 다뤘습니다. 이번 시간에는 DX11(SM5)에 새롭게 추가된 Tessellation과 Compute Shader에 대해 알아보겠습니다.

Tessellation의 등장

 그래픽이 발전함에 따라 거리에 따른 최적화인 Level Of Detail(LOD)가 많이 발전하게 되었습니다. 거리에 따라 폴리곤 수를 줄임으로써 깜빡임도 완화시키며 기하 aliasing 문제도 어느정도 해결하고 렌더링을 진행할 폴리곤 수도 줄어드니 1석 2조의 해결책이었죠. GPU Level에서도 기하 디테일을 정적인 폴리곤(Asset Level)로 들고가는 방식의 비용을 줄이고, 런타임에서 연속적으로 LOD와 실루엣을 개선하려는 니즈가 촉발했습니다.

 DX11(SM5) 탄생 직전에 와서는 Programmable Pipeline 자체도 성숙해졌고, 단계 추가를 감당할 수 있는 설계 및 검증 역량도 증가하였습니다. 그리고 GPU 또한 메모리와 대역폭의 압박이 완화되어 초고폴리 저장 대신 필요시 생성이 더 유리해졌죠. 그리하여 고정기능으로 Tessellator + Hull Shader / Domain Shader를 결합하여 표준 파이프라인에 추가할 수 있었습니다.

Compute Shader의 등장

 DX11/SM5에서 또 하나의 큰 축은 Compute Shader(DirectCompute)입니다. 이 시점부터 GPU의 병렬 연산을 그래픽 파이프라인(픽셀 셰이더 등)에 억지로 끼워 넣는 방식이 아니라, 별도의 “계산 워크로드”로 분리해 다룰 수 있는 표준 경로가 강화됩니다.

 Compute Shader가 보급되기 전에는 VS / PS로 GPGPU(General-Purpose computing on Graphics Processing Units)를 진행하여 알고리즘을 돌린 사례가 많았습니다. 데이터를 텍스처나 렌더 타겟으로 포장하고 VS/PS를 커널처럼 사용해 여러 패스로 핑퐁하는 방식이 흔했습니다. 게임에서는 블룸, 톤매핑, 가우시안 블러, SSAO 같은 화면 공간 처리들은 ‘그래픽 파이프라인 기반의 데이터 병렬 계산’ 패턴으로 구현되어 왔고, 산업 영역에서도 영상/신호 처리, 과학 계산, 금융 등에서 GPU 병렬 연산 수요가 점점 커지고 있었습니다.

 이런 니즈가 커지는 상황에서, GPU의 대량 병렬 실행을 안정적으로 수행하는 기반이 성숙하고(스케줄링/실행 모델), 리소스·버퍼 접근 모델(쓰기/구조화 데이터 등)도 강화되면서, 그래픽 셰이더에 억지로 넣던 작업을 Compute 워크로드로 분리할 수 있게 됩니다. 결과적으로 이후의 GPGPU/머신러닝 같은 데이터 병렬 분야가 GPU를 더 직접적으로 활용할 수 있는 토대가 마련됩니다.

DX11 Graphics Pipeline(ref. Microsoft)

데이터 모델의 변화

 DX11/SM5 이전에는 Asset(Mesh, Texture, ...)처럼 "미리 만든 데이터"를 가져와 렌더링하는 비중이 컸습니다. 하지만 DX11/SM5 세대에 들어서며 테셀레이션, 시뮬레이션, 후처리 등으로 인해 GPU가 프레임 안에서 새 데이터를 생성 및 갱신하는 비중이 커졌습니다. 즉, 데이터는 더 이상 입력만이 아니라 "중간 산출물"이 되었습니다.

 데이터 구조가 단순했던 텍스처나 상수에서 "구조화 데이터"로 확장됐습니다. 조명 리스트, 파티클 상태, 타일 / 클러스터 정보, 누적 결과 등. "그림"이 아니라 "테이블 혹은 레코드"에 가까워졌습니다. SM5 세대는 이런 데이터를 GPU에서 다루기 위한 구조가 더 자연스러워지고, 결과적으로 GPU 워크로드는 점점 그래픽 + 데이터 처리의 혼합이 되게 됩니다.

워크로드 모델의 변화

 그렇게 프레임은 [기하 처리] => [래스터라이즈] => [픽셀 셰이딩] => [출력]이라는 단일 파이프라인이 아닌 여러 작업(기하 세밀화, 조명 누적, 후처리, 시뮬레이션, 누적/정리 연산 등)으로 쪼개지는 "작업들의 그래프"가 되었습니다. 그 작업들이 서로의 결과를 다시 입력받습니다. 그렇게 프레임은 점점 데이터 의존성을 가진 패스들의 연결 구조가 됩니다. Compute Shader 또한 계산 워크로드로 분리되면서 렌더링 단계의 제약(출력 형태, 파이프라인 규칙)에 덜 묶인 방식으로 설계할 수 있게 됩니다.

 거기에 읽기 전용 리소스 중심에서 "쓰기 / 갱신 가능한 리소스" 비중 또한 커지면서 GPU는 단순 출력 장치가 아니라 중간 결과를 저장하고 재사용하는 계산 공간이 됩니다. 그것은 단순히 그리기(Draw) 단위가 아니라 Dispatch와 Pass 단위로 사고해야 한다는 것입니다. 화면 결과는 더 이상 "몇 번 그렸나"만으로 설명되지 않습니다. 어떤 작업을 그래픽으로 수행했고, 어떤 작업을 컴퓨트로 수행했으며, 그 사이에 어떤 데이터가 오갔는지가 핵심이 됩니다. 엔진 관점에서는 프레임을 Pass의 집합으로 보고, 각 Pass의 입력과 출력 리소스를 명시적으로 관리하게 됩니다.

 병렬성의 핵심은 "연산량"에서 "데이터의 이동, 합치기"로 이동합니다. 단순히 많이 돌리는 것에서 끝나는 것이 아닙니다. 여러 스레드가 만든 결과를 합치거나 동일 자원에 동시에 접근할 때 규칙이 필요한데 SM5 세대 이후로는 "무엇을 계산하냐"만큼 "결과를 어떻게 축적하고 정리하는지"가 워크로드 설계의 중심으로 올라옵니다.

 화면 품질의 무게중심 또한 "픽셀 셰이딩" 단일 단계에서 "프레임 전체 구성"으로 이동합니다. DX10(SM4)에서 이미 픽셀 작업과 화면 공간 패스 비중이 커졌다면, DX11(SM5)에서는 그 흐름이 더 강화되어 품질은 특정 셰이더 한 방이 아니라 여러 패스의 조합과 데이터 재사용에서 만들어집니다. 조명, 후처리, 시뮬레이션이 서로 데이터를 주고받는 구조가 자연스러워졌습니다.

렌더링 엔진의 책임

 결국 렌더링 엔진의 책임이 단순히 "셰이더 작성"에서 그치지 않고 "파이프라인과 리소스 오케스트레이션"으로 확장되게 됩니다. 이젠 셰이더 한 개를 잘 짜는 것만으로는 부족해집니다. 어떤 리소스가 언제 생성되고, 어떤 단계에서 읽히고, 어떤 단계에서 갱신되는지, 그리고 그 순서가 맞는지(의존성)가 엔진 구조의 핵심이 됩니다. 즉, 엔진은 점점 워크로드 스케줄러 + 리소스 매니저의 성격을 띄게 됩니다.

끝으로

 테셀레이션은 기하를 런타임 데이터 생성 대상으로 만들었고, 컴퓨트는 비그래픽 연산을 정식 워크로드로 분리했습니다. 이 둘이 합쳐지면서, 프레임은 데이터 흐름을 가진 작업 그래프로 재정의되고, 이후의 렌더링 / 시뮬레이션 / 머신러닝 같은 데이터 병렬 워크로드가 GPU로 더 자연스럽게 이동할 수 있는 기반이 형성될 수 있었습니다.

다음글

 

UE4 후기 : DX11(SM5)과 모던 렌더링의 안정화

GPU의 역사 - 4 : DX11(SM5), Tessellation과 Compute ShaderGPU의 역사 - 3 : DX10/SM4와 Unified Shader 전환GPU의 역사 - 2 : SIMD에서 SIMT로, Branch DivergenceGPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반

chessire.tistory.com

 

GPU의 역사 - 5 : DX12(SM6), Wave Intrinsics로 드러난 하드웨어 아키텍처

GPU의 역사 - 4 : DX11(SM5), Tessellation과 Compute ShaderGPU의 역사 - 3 : DX10/SM4와 Unified Shader 전환GPU의 역사 - 2 : SIMD에서 SIMT로, Branch DivergenceGPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반

chessire.tistory.com

이전글

 

GPU의 역사 - 3 : DX10/SM4와 Unified Shader 전환

GPU의 역사 - 2 : SIMD에서 SIMT로, Branch DivergenceGPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금처럼 복잡한 셰이더 기반 렌더링이 불가

chessire.tistory.com

워크로드 불균형을 흡수한 공용 코어 풀,
엔진 데이터 모델이 된 PBR 머터리얼,
언리얼엔진의 전제가 바뀌었다.

 지난 글에서 DX10 / SM4 시점의 Unified Shader(공용 셰이더 코어 풀) 전환을 다뤘습니다. 이 변화는 "파이프라인 단계가 사라졌다."가 아니라 Programmable 단계(VS, GS, PS)가 같은 실행 자원을 공유하게 됐다는 의미였습니다.

 

 엔진 관점에서 중요한 건, 이 하드웨어 변화가 단순히 GPU 내부 구조에 그치지 않고 렌더러 설계의 전제를 바꿨다는 점입니다. UE4의 초기 렌더링 설계는 이 전제를 강하게 반영한 사례라고 볼 수 있습니다.

UE3 시대의 한계

 UE3는 DX9(SM3) 중심 세대에서 성장했고, 그 시절 GPU는 VS / PS가 상대적으로 분리된 구조를 갖는 경우가 많았습니다. 그래서 한 프레임에서 픽셀 작업이 과도해지거나(혹은 반대로 정점 / 기하 작업이 과도해지면) 특정 단계가 병목이 되고 다른 단계 자원은 놀게 되는 로드 불균형이 구조적으로 발생하기 쉬웠습니다.

 

 또한 당시에는 "픽셀 단계에서 하고 싶은 일"이 지금만큼 과감하게 커지기 어려웠습니다. 화면 해상도, 셰이딩 복잡도, 포스트 프로세스 규모 자체가 지금보다 작았고, GPU가 이를 감당하기 위한 동적 스케줄링 / 대규모 병렬 실행 기반도 점진적으로 성숙해가는 중이었습니다.

UE4, 화면 품질은 픽셀 작업이 만든다.

DX10(SM4) 이후에는 Programmable 단계가 공용 코어 풀을 공유하면서, 프레임마다 달라지는 부하를 더 유연하게 흡수할 수 있는 기반이 생겼습니다. 엔진 입장에서 이건 간단히 말해

  1. 픽셀 작업을 더 공격적으로 늘려도 하드웨어가 그 부담을 흡수할 여지가 커졌다.
  2. 결과적으로 "화면 품질"을 픽셀 단계 / 화면 공간 패스에서 더 많이 만들 수 있게 됐다.

입니다.

 

 UE4의 초기 렌더링 방향은 이 흐름과 맞물립니다.UE4는 초기부터 Deferred Rendering을 기본 축으로 가져가고, 머티리얼 / 조명 / 후처리를 "여러 패스의 조합"으로 구성하는 쪽으로 무게가 실립니다.

UE4 전기의 핵심

 UE4의 초기 렌더러를 이해할 때 중요한 포인트는 "한 번에 끝내는 파이프라인"이 아니라, 프레임을 여러 단계의 화면 공간 처리로 쪼개는 방식입니다. 대표적인 흐름은 다음처럼 요약할 수 있습니다.

  • GBuffer 생성 (기하 정보 / 머티리얼 속성 저장)
  • 조명 계산 (여러 광원을 누적)
  • 후처리 (톤매핑, 블룸, 컬러 그레이딩 등)

 이 구조는 "픽셀 단계가 프레임 비용의 큰 비중을 차지한다."는 현실과 자연스럽게 연결됩니다. 즉, UE4 전기의 렌더링 설계는 픽셀 작업 비중 증가를 전제로 안정적으로 동작하도록 구성되어 있습니다.

머티리얼 시스템

 UE4는 머티리얼을 단순한 셰이더 코드가 아니라, 엔진 내부에서 관리되는 일관된 모델(그래프 / 파라미터 / 퍼뮤테이션)로 다루는 비중이 커집니다. 이 변화의 의미는 다음입니다.

  • 머티리얼이 복잡해질수록 "코드 몇 개"로 관리하기 어렵다.
  • 렌더링이 여러 패스로 쪼개질수록, 동일한 머티리얼 정보가 여러 단계에서 재사용된다.
  • 결국 엔진은 머티리얼을 데이터 모델로 표준화하고, 필요에 따라 셰이더 코드를 생성 / 변형하는 쪽으로 구조가 이동한다.

 UE4 전기에서 "렌더러의 복잡도"가 증가하는 만큼, 이를 감당하기 위한 엔진 내부 구조(머티리얼 / 셰이더 관리)가 같이 강화됩니다.

Physically Based Rendering (PBR)

Epic Games, 물리 기반 머티리얼에서 발췌

 

 PBR은 머티리얼을 "예쁜 값 몇 개"가 아니라 물리 기반 파라미터 집합으로 강제합니다.

  • Base Color
  • Metallic
  • Roughness
  • Specular / IOR 성격의 값
  • Normal
  • AO 등

 즉, 조명 / 환경이 달라도 일관된 반응을 내기 위해, 엔진은 머티리얼 입력을 표준화하고 그 의미를 고정해야 합니다. 이게 머티리얼 시스템을 "엔진의 핵심 데이터 모델"로 끌어올린 큰 이유 중 하나입니다.

 

 하지만 머티리얼 시스템에는 PBR 말고도 “엔진이 감당해야 하는 운영 요소”가 같이 들어갑니다.

  • 머티리얼 그래프를 셰이더 코드로 변환(코드 생성/컴파일)
  • 다양한 렌더 패스에서의 동일 머티리얼 재사용(GBuffer, Depth, Shadow, Velocity 등)
  • Feature/플랫폼별 퍼뮤테이션 관리(옵션 조합 폭발)
  • 파라미터/인스턴싱(런타임 조정)

그래서 관계는 보통 이렇게 보는 게 안전합니다.

  • PBR은 머티리얼 시스템의 의미 / 표준 입력을 강제한 핵심 원인
  • 머티리얼 시스템은 거기에 더해 엔진 운영(생성/컴파일/퍼뮤테이션/패스 재사용)까지 포함하는 더 큰 구조

맺음말

 정리하면 UE4 전기의 렌더링 전제는 파이프라인 단계가 바뀌어서가 아닌 Unified Shader 이후의 GPU에서 Programmable 단계가 공용 코어 풀을 공유하게 되면서 프레임 워크로드를 더 유연하게 흡수할 수 있게 된 점에서 시작합니다. 그 위에서 UE4는 Deferred + 다중 패스 + 머티리얼 / 셰이더 관리 구조를 엔진레벨로 끌어올렸고, PBR은 그 머티리얼 시스템의 표준 입력과 그 의미를 고정하는핵심 축으로 작동했습니다. 다음 글에서는 DX11 / SM5 그리고 UE4 후기로 넘어가며 이 흐름이 어떤 식으로 확장 및 결합하는지 이어서 보겠습니다.

다음글

 

UE4 후기 : DX11(SM5)과 모던 렌더링의 안정화

GPU의 역사 - 4 : DX11(SM5), Tessellation과 Compute ShaderGPU의 역사 - 3 : DX10/SM4와 Unified Shader 전환GPU의 역사 - 2 : SIMD에서 SIMT로, Branch DivergenceGPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반

chessire.tistory.com

이전글

 

GPU의 역사 - 4 : DX11(SM5), Tessellation과 Compute Shader

GPU의 역사 - 3 : DX10/SM4와 Unified Shader 전환GPU의 역사 - 2 : SIMD에서 SIMT로, Branch DivergenceGPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금

chessire.tistory.com

 

UE3 - Forward Rendering을 넘어 Deferred Rendering으로

UE2의 Overlay Shader와 초기 Post Process EffectGPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금처럼 복잡한 셰이더 기반 렌더링이 불가능했

chessire.tistory.com

Unified Shader는 단계 통합이 아니다.
VS / GS / PS가 같은 공용 코어 풀을 공유하는 구조 전환이다.

 지난 시간, SIMT모델과 그것의 가장 좋은 사례인 UE3에 대해서 알아보았습니다. 이번 시간에는 DX9(SM3) 이후 DX10(SM4)이 등장하면서 Unified Shader가 왜, 어떻게 전환됐는지 알아보도록 하겠습니다.

Unified Shader의 등장(DX10, SM4)

 VS / PS 같은 단계별로 유닛을 분리하여 Programmable Shader를 실행해 연산을 처리하던 DX9에도 문제점은 있었습니다. 바로 각 단계에서 워크로드가 한쪽 단계로 쏠리면 다른 단계의 유닛이 유휴(idle) 상태가 되는 로드 불균형이 발생하는 점이었습니다. 기존 DX9시절 GPU는 기본적으로 Vertex 쪽과 Pixel 쪽 연산 코어는 분리 설계가 더 저렴하고 빠르고 구현이 단순했기 때문에 분리되어 있었습니다. FFP의 유산으로 단계별 파이프를 만들기가 더 쉬웠죠.

 

 이 부분을 더 자세히 들여다보면 SM2 / SM3 시절엔 실제로 VS / PS가 지원하는 기능이 미묘하게 달랐습니다. Pixel 쪽은 화면상의 2x2 quad 기반 파생값(ddx/ddy), 보간, 텍스처 접근 패턴 등 "래스터 기반 규칙"이 강했습니다. 하지만 Vertex 쪽은 그런 제약이 상대적으로 적고, 대신 기하 데이터 처리에 최적화 돼 있었습니다. 즉, "둘 다 셰이더니까 코어를 공유하면 되지 않나?"가 오늘 관점에서는 쉬워보여도 당시에는 실행 조건이 달라서 코어를 공용화하려면 추가 하드웨어, 규칙 정리가 필요했습니다.

(참고로 SM3의 VS에서도 텍스처 룩업 자체는 가능했지만, 픽셀 단계의 2x2 quad 기반 파생값(ddx/ddy) 같은 래스터 규칙은 본질적으로 픽셀 단계에 묶여 있었습니다.)

 

 게다가 부하가 한쪽으로 쏠릴 때 발생하는 손해가 그때는 덜 치명적이기도 했습니다. DX9 시절의 게임은 평균 해상도, 셰이딩 복잡도, 포스트프로세스가 지금보다 낮았고, 파이프라인도 지금보다 단순했습니다. VS / PS 고정 분리 비효율이 확실한 손해로 체감되는 구간이 지금보다 덜했습니다. 그러다보니 "일을 동적으로 나눠주는 로직(스케줄러, 디스패처, 레지스터 파일, 컨텍스트 스위칭 등)" 을 넣는 비용은 당시 공정, 전력, 검증 대비 이득이 불확실했죠.

 

 하지만 DX10 / SM4에서는 셰이더 길이와 픽셀 작업 비중이 커지면서 로드 불균형이 더 자주 체감되기 시작했습니다. 동시에 기술의 발전으로 비용도 낮아지면서 스테이지별 제약을 정리(거의 동일한 명령어, 레지스터, 리소스 접근)하고 통합된 셰이더 모델을 전제로 설계를 재정의하면서 Unified Shader 구조가 자리잡게 됩니다.

 

DX10(SM4) Pipeline Stages

 

 Direct3D 10 그래픽 파이프라인의 공식 단계 순서를 그대로 배치하고 그 위에 Unified Shader(= Common Shader Cores)이 무엇을 뜻하는지 함께 표시한 그림입니다. 핵심은 두 층으로 나뉩니다.

  • 위쪽 : DX10의 파이프라인 단계(stage)
  • 아래쪽 : VS/GS/PS가 실행되는 공용 연산 자원(core pool)

즉, 단계(stage)가 통합된 것이 아니라 Programmable 단계가 같은 연산 자원을 공유하게 된 것이 Unified shader의 포인트 입니다.

단계별 의미

1) Input Assembler (IA)

  • 버텍스 버퍼/인덱스 버퍼에서 데이터를 읽어와서
  • “정점 스트림”을 조립해 다음 단계로 넘긴다.

2) Vertex Shader (VS) (Programmable)

  • 정점 단위 연산을 수행한다.
    (좌표 변환, 스키닝, 정점 속성 계산 등)

3) Geometry Shader (GS) (Programmable)

  • 프리미티브(삼각형/라인/포인트) 단위로 연산한다.
  • 필요하면 프리미티브를 증감/변형할 수 있다.

4) Stream Output (SO) [Optional]

  • GS의 결과(또는 파이프라인 중간 결과)를 버퍼로 기록하는 옵션 단계다.
  • 그림의 **점선 화살표(Feedback)**는 SO에 저장한 데이터를 다시 IA로 재투입할 수 있음을 나타낸다.
    (즉 “GPU 안에서 결과를 저장→다시 입력으로 사용”하는 루프가 가능)

5) Rasterizer (RS)

  • 정점/프리미티브를 **픽셀 후보(프래그먼트)**로 변환한다.
  • 화면 공간으로 투영하고, 클리핑/컬링 같은 고정 기능 처리도 포함된다.

6) Pixel Shader (PS) (Programmable)

  • 픽셀(프래그먼트) 단위 연산을 수행한다.
  • 텍스처 샘플링, 조명 계산, 머티리얼 평가 같은 대부분의 “화면 품질” 연산이 여기서 발생한다.

7) Output Merger (OM)

  • PS의 출력 색/깊이 결과를 최종 렌더 타깃에 합성한다.
  • 블렌딩, 깊이/스텐실 테스트 같은 “최종 합성 규칙”이 적용된다.

Common Shader Cores가 의미하는 것

 DX10 / SM4에서 VS, GS, PS는 같은 종류의 Programmable Core에서 실행될 수 있습니다. 따라서 어떤 프레임에서 픽셀 작업이 많으면 PS쪽에 코어가 더 배정되고, 정점 / 지오메트리 작업이 많으면 VS / GS 쪽에 코어가 더 배정되는 식입니다. 따라서 로드 불균형을 줄이는 방향으로 하드웨어가 설계된 것입니다.

중요한 점

  • 파이프라인 "단계"는 여전히 존재합니다.(IA > VS > GS > SO (Optional) > RS > PS > OM 순서 유지)
  • Unified Shader는 단계 통합이 아니라 연산 자원 통합(공용 코어 풀)입니다.

맺음말

 정리하면 DX10/SM4의 Unified Shader는 파이프라인 단계가 사라진 것이 아니라, VS / GS / PS가 같은 실행 자원(공용 코어 풀)을 공유하도록 바뀐 구조 변화입니다. 그 결과 프레임마다 달라지는 워크로드에 맞춰 연산 자원을 더 유연하게 배분할 수 있게 되었고, DX9 시절의 로드 불균형 문제도 완화되는 방향으로 설계가 이동했습니다. 다음 글에서는 이 통합 구조 위에서 SM5 시대로 넘어가며 무엇이 확장됐는지 이어서 보겠습니다.

 

다음글

 

UE4 전기: Unified Shader 이후

UE3 - Forward Rendering을 넘어 Deferred Rendering으로UE2의 Overlay Shader와 초기 Post Process EffectGPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금

chessire.tistory.com

 

 

GPU의 역사 - 4 : DX11(SM5), Tessellation과 Compute Shader

GPU의 역사 - 3 : DX10/SM4와 Unified Shader 전환GPU의 역사 - 2 : SIMD에서 SIMT로, Branch DivergenceGPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금

chessire.tistory.com

이전글

 

GPU의 역사 - 2 : SIMD에서 SIMT로, Branch Divergence

GPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금처럼 복잡한 셰이더 기반 렌더링이 불가능했습니다. VRAM 용량도 32~64MB 수준이라 SSAA

chessire.tistory.com

 

 

UE3 - Forward Rendering을 넘어 Deferred Rendering으로

UE2의 Overlay Shader와 초기 Post Process EffectGPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금처럼 복잡한 셰이더 기반 렌더링이 불가능했

chessire.tistory.com

UE3는 하드웨어를 이기려 하지 않았다.
Hybrid는 당시의 타협이었고, Full Deferred는 결국 도착한 최적해였다.
GPU 친화적인 구조로 수렴할 수밖에 없었다.

앞선 글에서 GPU가 SIMD / SIMT 구조로 동작하며,
분기(Branch Divergence)에 매우 취약하다는 이야기를 했습니다.
이 특성은 단순히 셰이더 코드 작성에만 영향을 주는 것이 아니라,
엔진 전체의 렌더링 파이프라인 설계에도 직접적인 제약으로 작용합니다.

이러한 GPU 아키텍처의 제약이 실제 엔진 설계에
어떻게 반영되었는지 보여주는 대표적인 사례가
Unreal Engine 3입니다.
이번 글에서는 UE3의 렌더링 구조 변화를 살펴보겠습니다.

UE3 초기

Forward Rendering

2006년 처음 출시된 Unreal Engine 3는
완전한 Forward Rendering 기반의 엔진이었습니다.

Lightmap과 Static Lighting 중심의 구조였고,
다양한 Post Effect까지 폭넓게 지원했습니다.

다만 라이팅이 많아질수록
Pixel 수에 비례해 연산 비용이 증가하는
Forward Rendering 특유의 문제도 함께 가지고 있었죠.

그럼에도 불구하고,
당시의 낮은 해상도와
제한적인 메모리·대역폭 환경에서는
Forward Rendering이 현실적인 선택이었고,
실제로 많은 주목을 받게 됩니다.

 

하지만 해상도가 올라가고,
동적 라이팅에 대한 요구가 커지면서
Forward Rendering의 한계는 점점 더 뚜렷해지기 시작합니다.

UE3 중기

Deferred Rendering

이러한 한계 속에서 Unreal Engine 3는 중기에 접어들며
Multiple Render Targets(MRT)을 활용한
G-Buffer 기반의 Deferred Lighting을 도입하게 됩니다.

메시 위에 또 다른 텍스처를 덧씌워 표현하던 Decal 기능 역시
Deferred Decal이라는 형태로 진화하게 됩니다.


이를 Deferred Lighting과 결합함으로써
라이팅 연산 비용을 줄이는 동시에,
Z-Fighting 문제를 완화하는 성과를 거두게 됩니다.

다만 G-Buffer가 요구하는 메모리 비용이 상당했기 때문에,
UE3는 Full Deferred Rendering이 아닌
Hybrid Rendering 구조를 채택하는 선택을 하게 됩니다.

 

이 Hybrid Rendering 구조는
당시 하드웨어 환경에서는 합리적인 선택이었지만,
동적 라이팅의 규모가 더 커지면서
결국 또 다른 한계를 드러내게 됩니다.

UE3 후기

SSAO

그래픽카드의 메모리는 지속적으로 증가했습니다.
결국 Unreal Engine 3는 후기 단계에 들어서
Full Deferred Lighting 구조로 귀결합니다.

모든 Geometry Pass에서 동일한 G-Buffer를 생성하고,
Lighting Pass에서는 화면 공간 기준으로
동일한 라이트 연산을 수행하는 방식으로 전환됩니다.

이 과정에서 픽셀 간 분기 가능성이 크게 줄어들며,
Branch Divergence를 최소화한,
GPU 구조에 가장 잘 맞는 형태로 수렴하게 됩니다.

이러한 흐름 속에서 등장한 대표적인 기법이
Screen Space Ambient Occlusion(SSAO)입니다.
화면 공간 기준으로 빛의 차폐를 계산함으로써,
그림자 표현의 퀄리티는 한 단계 도약하게 됩니다.

맺는말

오늘은 Forward Rendering에서 Deferred Rendering으로 이어지는 흐름 속에서,
Unreal Engine 3의 렌더링 구조 변화를 살펴보았습니다.

메모리 사용량과 컴퓨팅 연산을 맞바꾸며
그래픽스 퀄리티를 끌어올린 과정은,
엔진 설계에서의 Optimizing이 무엇인지 보여주는
좋은 사례라고 생각합니다.

다음 글에서는 SIMT 구조를 전제로,
본격적으로 문제를 해결하기 시작한
Unreal Engine 4의 렌더링 구조에 대해 다뤄보겠습니다.

다음글

 

UE4 전기: Unified Shader 이후

UE3 - Forward Rendering을 넘어 Deferred Rendering으로UE2의 Overlay Shader와 초기 Post Process EffectGPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금

chessire.tistory.com

이전글

 

 

UE2의 Overlay Shader와 초기 Post Process Effect

GPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금처럼 복잡한 셰이더 기반 렌더링이 불가능했습니다. VRAM 용량도 32~64MB 수준이라 SSAA

chessire.tistory.com

 

GPU의 역사 - 2 : SIMD에서 SIMT로, Branch Divergence

GPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금처럼 복잡한 셰이더 기반 렌더링이 불가능했습니다. VRAM 용량도 32~64MB 수준이라 SSAA

chessire.tistory.com

Determinism은 기능이 아니라
파이프라인의 합의 속에서 드러난다.
Lockstep을 통해 오차 발산을 구조로 막는다.

개요

 언리얼엔진의 물리연산은 Determinism(결정론적 동작)을 보장하고 있지 않지만 Physics Divergence(물리 연산의 오차 발산)을 막기 위해 어느정도 고려하고 있습니다. 특히 네트워크 동기화에서는 매 Frame마다 물리 연산이 조금이라도 달라지면 짧게는 수 Frame, 길게는 수 초 후의 결과가 완전히 달라지기 때문에 여러 구조적 개선을 지속적으로 진행해왔습니다. 이 글은 그러한 언리얼 엔진의 Physics를 Determinism 관점으로 오차 최소화를 어떻게 달성하였는지에 대해 정리한 글입니다.

언리얼엔진의 물리, 각 스텝 정리

[1] PrePhysics

Actor Tick (PrePhysics Group)

  • 입력 처리 / 이동 요청 확정
  • 물리에 들어가기 전 게임 상태 정리(게임 로직 기반 상태 업데이트)
  • Determinism 관점
    • Lockstep에서 "동일한 입력"이 보장되어야 하므로 이 단계가 핵심입니다.
    • 모두 같은 프레임에서 같은 입력을 받고 동일한 초기 상태를 맞춰야 Physics Divergence를 방지할 수 있습니다.

▼  (Barrier: 모두 완료 시 다음 단계)

 

[2] StartPhysics

  • Chaos Physics 엔진에 Step 시작 명령
  • Substep 준비
  • Collision BroadPhase 준비
  • Determinism 관점
    • Substep 개수, Step 간격, BroadPhase(충돌 탐색 알고리즘 순서)가 플랫폼과 스레드에 영향을 받지 않게 유지될수록 Determinism이 향상된다.
    • UE5에서 이곳의 안정성을 강화하고 있습니다.

 

[3] DuringPhysics

  • Substep 단위로 Physics Integration
  • 강체를 이동시킨 후, 충돌 감지, 반응 계산을 병렬로 수행
  • Solver 수행
  • Chaos Simulation 실행
  • Determinism 관점
    • 가장 Non-deterministic한 단계로 이 단계에서는 Divergence가 아래의 세가지 이유로 발생합니다.
      • Floating point차이
      • 멀티스레드 스케줄링 차이
      • Solver 순서 차이
    • 그렇기에 Lockstep 구현 시에는 Fixed Step, Fixed Seed, 동일 Substep을 강제로 통일합니다.

▼  (Barrier: 모든 물리 연산 완료 시 다음 단계)

 

[4] EndPhysics (PostPhysics)

  • Physics 결과를 게임 월드로 반영
  • Transform Sync
  • CharacterMovement / VehicleMovement 이동 보정(RootMotion/Prediction 보정)
  • PostPhysics Tick 실행
  • Determinism 관점
    • Physics가 만든 결과가 실제 게임 로직으로 흘러 들어가는 구간으로 Lockstep에서의 값들이 모든 클라이언트에 동일해야 이후 Prediction/Interpolation이 안정적으로 작동합니다.

 

[5] PostUpdate

  • During Physics의 모든 병렬 스레드를 대기하여 정리
  • Rendering 준비
  • Determinism 관점
    • 물리 결과가 렌더링 이전에 완전히 확정되는 곳입니다. 즉 Lockstep에서는 Frame Boundary가 되는 지점입니다.

Solver란?

 물리 연산 과정에서 발생한 힘, 속도, 충돌 등을 정리하여 게임에 맞게 보정하는 단계입니다. Chaos에서는 충돌 후의 위치, 속도, 반응을 반복 계산(Iterative Solve)하여 안정적인 결과로 수렴시키며, 최종적으로는 게임 월드에 반영하기 적합한 형태로 물리 상태를 정제하는 역할을 합니다.

Substep이란?

 하나의 물리 Frame을 여러개의 더 작은 시간 단위로 나누어 여러 번 시뮬레이션을 수행하는 구조입니다. 그로 인해 PhysicsThread는 고정된 시간 간격(Fixed Step)으로 더 촘촘하게 물리를 계산할 수 있습니다. 기본적으로 GameThread의 Tick 속도와 관계없이 진행됩니다. 하지만 PrePhysics와 PostPhysics가 GameThread에 종속되어 있기 때문에 시작과 끝으로 동기화 된다고 보시면 됩니다.

 Substep은 다음 목적에서 사용됩니다.

  • 빠르게 움직이는 객체의 충돌 누락 방지
  • 폭발, 충돌 등 입출력이 빠르게 변하는 상황에서 안정적인 결과 유지
  • Lockstep에서 모든 클라이언트가 동일한 수의 Substep을 수행하도록 강제하여 Determinism을 향상시킴

실무 사례

 실제로 자율주행 시뮬레이터를 개발할 때, 여러 대의 차량 에이전트가 동일한 시나리오에서 항상 같은 결과를 내는 것은 매우 중요했습니다. 특정 센서 데이터의 타이밍이 미세하게 어긋나거나, 물리 연산의 비결정성으로 인해 차량의 궤적이 1cm만 달라져도 자율주행 알고리즘의 판단은 완전히 뒤바뀔 수 있기 때문입니다.

 이를 해결하기 위해 위에서 언급한 Fixed Timestep 설정과 Lockstep 기반의 동기화 구조를 설계했고, 덕분에 하드웨어 성능이 다른 환경에서도 동일한 테스트 케이스를 재현할 수 있는 기반을 마련할 수 있었습니다.

결론

 이처럼 물리 파이프라인의 각 단계를 통일된 방식으로 처리하여 Lockstep 구조에서 발생하는 Physics Divergence를 크게 줄일 수 있습니다. 또한 UE5가 지속적으로 강화하고 있는 Substep·Solver·Tick 구조는 Determinism을 향상시키는 방향으로 설계되어 있어 언리얼엔진에 있어 네트워크 게임에서의 물리 불일치 문제를 완화하는 핵심 축이 되었습니다.

CPU는 SIMD를 ‘더 강한 벡터’로 키웠고,
GPU는 SIMD를 ‘스레드 추상화’로 바꿨다.

 지난 시간 FFP에서 고전 SIMD의 등장까지 알아보았습니다. 이제 SIMD에서 SIMT로 넘어가던 DirectX9 ~ DirectX10 시절을 알아보도록 하겠습니다.

개요

 하나의 명령어로 여러 데이터를 동시에 처리하던 고전 SIMD는 구조적인 한계가 명확했습니다.

  • 데이터 길이가 workload에 맞지 않으면 cost 낭비가 발생
  • 스레드 개념이 없음
  • 분기 시 lane이 갈라지면 실행이 직렬화되어 효율(활성 lane 비율)이 크게 하락
  • gather/scatter 미지원 => 다양한 메모리 주소 접근이 불가능

이 구조는 단순한 벡터 연산에는 강력하지만,
픽셀·조명·텍스처·조건문이 많은 실제 그래픽스 workload에는 치명적이었다.

 

 CPU는 SIMD 폭을 확장하고 ISA를 강화하는 방식으로 발전 방향을 잡았습니다.

  • 벡터 폭 증가
  • gather/scatter 지원 (일반 load/store 관점)
  • mask 연산 강화
  • 분기 최소화 등

즉, CPU는 SIMD를 더 강한 벡터 처리 장치로 발전시키는 방향을 택했다.

SIMT의 등장

 하지만 GPU는 완전히 다른길을 선택했습니다. 그것은 바로 SIMD를 Thread로 추상화하여 재해석하는 방법입니다. GPU는 수천 ~ 수만 개의 Pixel Fragment를 병렬 처리해야 했기 때문에 PC(ProgramCounter)를 Thread마다 하나씩 둘 수 없었습니다. PC를 Thread마다 하나씩 두면 면적과 전력소모가 폭발적으로 증가하기 때문이었습니다.

 그래서 GPU는 SIMD를 다음과 같이 재해석했습니다.

"SIMD lane을 Thread처럼 보이게 만들고,
프로그래머에게는 각 lane이 독립적인 스레드처럼 보이게 하자."

 

 이 방식이 바로 SIMT 모델입니다.

about SIMT

Warp란 무엇인가?

 CPU는 Thread마다 PC가 하나씩 있습니다. 그렇기 때문에 완전한 MIMD 실행 모델로 각자 다른 위치에서 다른 코드를 실행할 수 있습니다. 하지만 GPU의 Warp의 구조는 정반대입니다.

 

 예를 들어 Warp = 32 thread가 있다면,

(벤더/세대에 따라 warp/wavefront 크기가 다를 수 있음)

32개의 스레드는 각자 독립적으로 보이지만,
Warp 전체가 PC 1개,
그렇기 때문에 32개의 스레드는 한번에 동일한 명령어만 실행 가능.

각 Thread는 고유한 Register 값을 갖지만 명령어 흐름은 공유.

 이렇게 동일한 명령어를 동시에 실행하는 한 단계가 Lockstep입니다.

Branch Divergence

 이 조건을 보면 Lockstep의 의미가 바로 드러납니다.

 아래와 같은 조건이 나왔다고 합시다.

if (x < 16)
  A 실행
else if(x < 32)
  B 실행

 

  • thread 0 ~ 15 =>  A 실행
  • thread 16 ~ 31 => B 실행

 이렇게 CPU 에서는 스레드 별로 명령어를 다르게 실행할 수 있기 때문에 각 스레드에서 A와 B를 나눠서 실행하게 됩니다. 하지만 GPU는 Warp는 하나의 PC만 있으므로 두 경로를 동시에 실행할 수 없습니다.

 

 그렇기에 SIMT는 다음과 같은 방식으로 처리합니다.

 Thread 32개를 컨트롤 하는 Warp의 PC를

  1. A 경로로 이동
    • true thread만 활성 (mask = on)
    • false thread는 비활성 (mask = off)
    • Warp 실행(A만 실행 됨)
  2. B 경로로 이동
    • false thread만 활성 (mask = on)
    • true thread는 비활성 (mask = off)
    • Warp 실행 (B만 실행 됨)

 이렇게 한 warp 안에서 분기가 갈리면, warp는 A와 B를 직렬화해 순차적으로 실행합니다. 이것이 lockstep 실행의 제약인 Branch Divergence입니다. 고전 SIMD에서도 분기 직렬화는 실행 효율을 떨어뜨렸습니다. 다만 SIMT에서는 warp 단위로 작업이 쪼개져 있고, 실행 유닛이 다른 warp를 교대로 실행하며 파이프라인을 유지하기 때문에, “전체가 멈춘다”기보다는 활성 lane 감소로 효율이 하락하는 형태로 나타납니다.

맺음말

 SIMT의 등장은 고전 SIMD의 한계를 '극복'했다기보다는, 그래픽스라는 특수한 workload를 처리하기 위한 현실적인 타협이었습니다.

  • SIMD lane을 Thread처럼 추상화하여 프로그래머에게는 MIMD처럼 보이게 만들고
  • 내부에서는 여전히 lockstep 기반 SIMD의 효율을 유지하며
  • Massive Parallel Pixel Workload를 감당할 수 있도록 한 구조

 즉, SIMT는 "GPU스럽게 동작하는 MIMD의 환상"을 만들어낸 모델이라고 할 수 있습니다.

 Branch divergence는 여전히 존재하며 성능을 떨어뜨립니다. 하지만 FFP 시대에는 상상할 수 없었던 복잡한 조명, 그림자, 포스트 프로세싱, 물리 기반 셰이딩을 GPU가 처리할 수 있게 된 것도 결국 이 SIMT 모델 덕분입니다.

 다음 글에서는 이 SIMT 모델이 어떻게 구체적으로 구현되었는지, 그리고 DirectX10~11, UE3~UE4 시대를 지나면서 하드웨어와 셰이더 모델이 어떤 방향으로 진화했는지를 이어서 다뤄보겠습니다.

다음글

 

GPU의 역사 - 3 : DX10/SM4와 Unified Shader 전환

GPU의 역사 - 2 : SIMD에서 SIMT로, Branch DivergenceGPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금처럼 복잡한 셰이더 기반 렌더링이 불가

chessire.tistory.com

이전글

 

GPU의 역사 - 1 : FFP에서 SIMD까지

그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금처럼 복잡한 셰이더 기반 렌더링이 불가능했습니다. VRAM 용량도 32~64MB 수준이라 SSAA나 고급 조명 기법을 적용하기

chessire.tistory.com

 

UE2의 Overlay Shader와 초기 Post Process Effect

GPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금처럼 복잡한 셰이더 기반 렌더링이 불가능했습니다. VRAM 용량도 32~64MB 수준이라 SSAA

chessire.tistory.com

UE2는 FFP 위에 셰이더를 ‘겹쳐’ 올린 시대였다.
Combine 기반 Overlay, 머티리얼 시스템의 씨앗

UE2의 등장

 UE2는 2001년에 빌드 633 형태로 처음 공개되었습니다. 2001년 이전의 GPU는 대부분 FFP(Fixed Function Pipeline) 기반이었지만, DirectX 8 세대에 들어서면서 픽셀 셰이더와 버텍스 셰이더를 활용할 수 있게 되었습니다. UE2는 이러한 변화에 맞추어 GPU 가속 렌더링과 멀티플랫폼을 고려한 구조로 설계되었고, 이때부터 고정 함수 결과에 셰이더 출력을 조합(Combine) 하는 형태의 Material 처리 방식이 자리 잡기 시작했습니다. 이 과정에서 지금의 Material System으로 이어지는 초기 형태의 조합 기반 효과(= Add, Mul, Lerp 등을 활용한 간단한 Overlay 스타일 처리)가 사용되었고, 이후 언리얼 엔진 3에서 노드 기반 Material Editor로 확장되며 우리가 알고 있는 Overlay 계열 블렌딩이 정식 기능으로 자리 잡게 됩니다.

Rendering Pipeline

Fixed-Function Pipeline

 앞서 설명드린 것 처럼 DirectX8 이전 버전은 FFP 기반이었고, DirectX9 초기까지도 FFP를 기반한 Rendering Pipline형태였습니다. (위 이미지 참고) Diffuse Lighting, Lightmap 같은 연산은 여전히 하드웨어 단계에서 단계적으로 수행하였죠. 여기까지는 Programmable Shader가 개입할 수 없었습니다.

Execute Shader

 완전한 형태의 Programmable은 아니었지만 Pixel Shader Stage가 생기면서 UE2에도 부분적으로 Shader가 들어오기 시작했습니다. 조명 보정을 위한 NormalMap, Overlay에 쓰일 추가 색 계산까지, 드디어 Pixel Shader Stage가 하드웨어에 고정된  형식이 아닌 프로그래머가 작성한 코드를 통해 하드웨어에서 실행할 수 있게 된 것입니다.

Combine Process

 그렇게 Pixel Shader Stage에서 실행된 코드가 Combine Process를 타고 FFP의 결과물에 Overlay되게 됩니다. UE2 시절의 Overlay는 Add/Multiply 기반의 단순 조합이었음에도 언리얼엔진의 초기 머터리얼 시스템의 개념적 기반이 될 수 있었습니다. 즉, 텍스처 샘플링 결과와 셰이더에서 계산된 값을 다양한 방식으로 섞어 최종 색을 만드는 개념이 이때 자리를 잡기 시작한 것입니다. 이 개념은 UE3에서 노드 기반 머터리얼 시스템으로 확장되며 지금과 같은 형태까지 발전하게 됩니다.

Pixel Output

 그렇게 최종적으로 픽셀이 화면에 출력되게 됩니다. Pixel Output은 GPU 파이프라인의 End Point이지만, 그 이전 단계에서 FFP 연산, 쉐이더 계산, 조합 과정 등 다양한 처리가 누적되며 최종적인 화면이 구성됩니다. 즉, 우리가 보는 한 장의 화면은 여러 단계의 계산이 겹쳐 만들어진 결과물인 셈입니다.

Epic Games, Post Process 설명에서 발췌

Post Process Effect

 UE2 후반에 와서 카툰 렌더링(셀 셰이딩 + 외곽선 추출)을 비롯한 다양한 Post Process Effect가 가능해졌습니다. UE2당시의 Post Process는 단순히 화면 렌더링을 한번 더 샘플링 하는 단순 스크린 패스 수준이었지만 UE3에서 실질적인 체계가 확립되고 UE4에서 영화 렌더러 수준까지 진화할 정도의 무서운 약진이라 할 수 있습니다.

 

맺는 말

기술을 배울 때 그 변화의 흐름을 함께 이해하면 구조가 더 명확하게 잡히고, 어떤 설계 철학으로 발전해 왔는지도 선명하게 드러납니다. 언리얼 엔진은 이제 게임뿐 아니라 영화, 건설, 시뮬레이션 등 다양한 분야에 활용되는 범용 실시간 엔진이 되었고, 그중 UE2는 렌더링 시스템의 기반이 형성되던 중요한 시기였습니다.

 오늘은 UE2의 Overlay와 Post Process Effect를 간단히 정리해보았습니다. 다음 글에서는 UE3에서 본격적으로 확립된 머티리얼 시스템과 렌더링 변화에 대해 이어 설명하겠습니다.

다음글

 

UE3 - Forward Rendering을 넘어 Deferred Rendering으로

UE2의 Overlay Shader와 초기 Post Process EffectGPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금처럼 복잡한 셰이더 기반 렌더링이 불가능했

chessire.tistory.com

이전글

 

GPU의 역사 - 1 : FFP에서 SIMD까지

그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금처럼 복잡한 셰이더 기반 렌더링이 불가능했습니다. VRAM 용량도 32~64MB 수준이라 SSAA나 고급 조명 기법을 적용하기

chessire.tistory.com

Programmable Pipeline의 토대,
스칼라 중심 GPU → SIMD Lane 재구성

그래픽카드의 한계

 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금처럼 복잡한 셰이더 기반 렌더링이 불가능했습니다. VRAM 용량도 32~64MB 수준이라 SSAA나 고급 조명 기법을 적용하기 어려웠죠. 이 글에서는 당시 GPU의 한계를 살펴보고, 어떻게 SIMD 기반의 Programmable Pipeline 으로 발전했는지 정리합니다.

 

MSAA (Multi-Sampling Anti-Aliasing)

MSAA vs EQAA 샘플 패턴 비교 이미지

Pixel Boundary

한 픽셀을 크게 확대한 사각형 틀. (픽셀은 점이 아니라 “면적”을 가진 사각형입니.)

Color Sample Location

픽셀 내부의 특정 지점(Point)에서 실제 색상(Color)을 샘플링하는 위치. 여러 Color Sample을 블렌딩하여 최종 픽셀 색을 만든다.

Coverage Sample Location

폴리곤이 픽셀을 얼마나 덮고 있는지(Coverage) 판단하는 지점. 각 샘플이 폴리곤 내부면 1, 외부면 0에 해당하며, 이를 평균내어 블렌딩 가중치(강도)로 사용한다. Color Sample보다 계산 비용이 훨씬 낮다.

 

 초기 GPU의 VRAM 용량이 32 ~ 64MB 하던 시절에는 픽셀 단위로 복잡한 처리를 수행할 수 없었습니다. 그래서 이미지 전체를 더 높은 해상도로 여러 번 렌더링하는 SSAA(Super-Sampling Anti-Aliasing)을 다운 샘플링을 하는 방식이 유일한 선택지였습니다. 해당 방식은 해상도 배율 x 샘플 수 만큼의 비용증가가 있었고, 이는 픽셀 셰이딩, 텍스쳐 샘플링, ROP(Blend / Depth / Color Write) 등 '픽셀 이후 단계'의 비용을 전부 O(S) 수준으로 폭증시켰습니다. 즉, 샘플 수만큼만 비용이 증가하는 것이 아니라, 렌더타겟 자체도 커지기 때문에 파이프라인의 대부분이 정비례로 비싸지는 구조였습니다.

 하지만 VRAM 용량이 어느정도 여유가 생기자, 픽셀 내부의 Color Sample과 Coverage Sample을 저장할 수 있는 구조가 가능해졌고, MSAA는 Pixel Shader는 한 번만 수행하고, Coverage Test와 Color Resolve만 샘플 수에 따라 반복하는 방식으로 SSAA 대비 큰 폭의 최적화를 달성했습니다.

 

Fixed Function Pipeline > Programmable Pipeline 의 과도기

FFP(Fixed Function Pipeline)

  • Transform & Lighting
  • Texture Stage State 기반 2~3단 텍스처 처리
  • 멀티패스 조명
  • DOT3 Bump Mapping(FFP LOD 바탕)
  • Lightmap 기반의 Static Lighting
  • UI, HUD, 간단한 머터리얼 > FFP로 렌더링

하지만 UE2 후반 버전은...

Programmable Pipeline 일부 적용

  • Shader-driven Material Effects
  • Normal Mapping
  • Specular Mask
  • Detail Normal Layer
  • Color Modulation
  • Hardware Skinning
  • NVIDIA/ATI 전용 Shader Path

이것이 가능해진 이유는 GPU의 주요한 변화 덕분.

Scalar ALU vs SIMD Lane

기존

 Scalar ALU에 단순한 microcode 실행기를 사용하여 float4연산은 4개의 스칼라 연산을 순차적으로 연산하도록 실행, 단지 GPU에 Scalar ALU를 많이 담아서 단순 병렬처리를 수행했을 뿐.

SIMD(Single Instruction Multiple Data stream)의 등장

하나의 명령어(Single Instruction)를 Scalar ALU의 묶음인 SIMD Lane에 전달하여 연산에 사용될 Multi-Data를 레지스터/버퍼(L1/L2, GPR)에 담아 연산하도록 실행

 

다음글

 

UE2의 Overlay Shader와 초기 Post Process Effect

GPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금처럼 복잡한 셰이더 기반 렌더링이 불가능했습니다. VRAM 용량도 32~64MB 수준이라 SSAA

chessire.tistory.com

 

GPU의 역사 - 2 : SIMD에서 SIMT로, Branch Divergence

GPU의 역사 - 1 : FFP에서 SIMD까지그래픽카드의 한계 2000년대 초반의 GPU는 FFP(Fixed Function Pipeline)중심이었고, 지금처럼 복잡한 셰이더 기반 렌더링이 불가능했습니다. VRAM 용량도 32~64MB 수준이라 SSAA

chessire.tistory.com