Skip to content
라코와 마코 edited this page Jul 27, 2024 · 60 revisions

Introduce PBR Rendering Engine [사진을 클릭하면 유튜브 동영상으로 이동합니다.]

PBR Rendering Engine

안녕하세요? 이 문서는 제가 취미로 개발한 OpenGL 기반 PBR 렌더링 엔진을 소개하는 문서입니다.

이 프로젝트는 주로 Learn OpenGL, Scratch Pixel 등의 페이지에서 이론을 참고하여 진행되었습니다.

이 엔진은 그래픽 요소의 표현과 렌더링에 중점을 두고 있으며, 씬 관리나 게임 물리와 같은 부가적인 기능은 포함하지 않습니다.

또한, 이 페이지에서는 PBR 이론이나 수학적 배경 설명보다는 엔진에서 사용된 기능들을 소개하는 데 중점을 두었습니다.

엔진 주요 기능

물리 기반 렌더링 : 언리얼 엔진에서 소개된 방식을 이용하여 구현되었습니다. BRDF 함수로 Cook-Torrance 모델을 사용하엿고 GI는 IBL 기법을 사용하였습니다.

Bloom : Bloom Disaster를 피하기 위해 업스케일과 다운스케일을 사용한 Physically Based Bloom을 사용하였습니다.

그림자 : 더 사실적인 그림자 묘사를 위해 PCF(Percentage-Closer Filtering)와 CSM(Cascaded Shadow Maps)을 적용하였습니다.

작은 환경 효과 : Screen Space God Rays, Lens Flare Effect를 추가하였습니다.

그 외 사소한 것들 : 그외에 SSAO, HDR, ACES Tone Mapping등 여러 기술을 혼합하여 엔진을 구성하였습니다. 하지만 이 부분은 작은 기능들이기 때문에 상세히 소개하지는 않겠습니다.


물리 기반 렌더링

물리 기반 렌더링을 구성하는덴 크게 DFG 항이 필요합니다. DFG항은 PBR에서 중요한 역할을 맡고 있고 세부적인 요소는 아래와 같습니다.

1. D(Normal Distribution) : 마이크로패싯의 표먼 거칠기를 나타냅니다. 텍스처의 roughness값을 사용하여 물체의 거칠기를 표현하는 항입니다. 이 프로젝트에선 NDF GGX를 사용하였습니다. 코드

2. F(Fresnel) : 입사 각도에 따라 반사되는 빛의 양을 나타냅니다. 빛의 입사각도가 커질수록 반사율이 증가합니다. 물표면을 바라볼때 수직에 있는 물은 투시가 잘 되지만 수평선에 있는 물에선 하늘이 반사되어 비치는것과 같은 원리입니다. 코드

3. G(Geometry) : 표면 미세 구조에서 빛이 차폐되는 정도를 나타냅니다. 코드

340966073-7cf39f0e-e491-47bc-8833-2ff3bbdc0941

이런 DGF항을 사용하는 Cook Torrance 모델을 BRDF로 Direct Lighting을 구사하였습니다.

GI에는 IBL을 사용하였습니다. 언리얼엔진 논문에서 소개한 방식으로 Irradiance Diffuse IBL Map과 Specular IBL Map을 사전에 CubeMap에 생성하여 렌더링시 샘플링하여 광원으로 사용하도록 구현하였습니다.

Diffuse Map Specular Map
Diffuse Map Specular Map

성과

좌 우
Phong + IBL PBR + IBL

왼쪽은 과거에 Phong 라이팅과 IBL을 섞어서 만든 엔진이고, 오른쪽은 PBR + IBL을 사용한 모델입니다.

오른쪽이 더 화사한 느낌을 주고 있음을 확인할 수 있습니다.

Phong 라이트 모델은 간편하고 계산이 빠르지만, 현실적인 재질 표현에는 명확한 한계가 있습니다.

PBR을 도입하면서 더 사실적인 고품질 그래픽 이미지를 생성할 수 있었고, 라이팅에 대해 심층적으로 이해할 수 있는 계기가 되었습니다.

Bloom

화면에서 빛이 퍼지는 느낌을 더욱 잘 표현하기 위해 Bloom 이펙트를 도입하였습니다.

처음에는 널리 알려진 Bloom 알고리즘을 적용하였으나, 어두운 부분도 Bloom이 퍼져 그림자의 경계선이 흐려지는 Bloom Disaster를 경험하였습니다. 이를 해결하고자 Physically Based Bloom을 적용하였습니다.

Bloom Disaster

(Bloom Disaster 예시)

이 기술은 Call of Duty 팀이 제안하였고, Learn OpenGL에서 소개한 방식을 참고하여 구현하였습니다.

기존 Bloom은 2개의 버퍼로 밝은 영역의 색을 축출해 뭉개는 방식이라면 Physcailly Based Bloom은 UpSample, Down Sample 알고리즘을 통해 밝은영역을 축출한다는 점이 다릅니다.

bloom-diagram

좀 더 직관적인 설명을 위하여 Render Doc에서 캡처한 이미지도 추가하겠습니다.

1 2 3 4 5

이렇게 생성된 Bloom 이펙트는 최종화면과 결합되어 Bloom 효과를 주게 됩니다. |

1 2

(좌 : Bloom 없음, 우 : Bloom 있음)

그림자 구현 및 개선

그림자는 제가 가장 열정적으로 구현한 요소 중 하나입니다. 그림자는 물체의 위치와 깊이를 파악하게 해주는 화면에서 중요한 요소이면서 동시에 구현하기 까다로운 부분이 다소 있습니다. 이 과정에서 저는 PCF와 CSM을 적용하여 여러 개선을 진행하였습니다.

그림자는 Shadow Map에 z값을 기록합니다. 이후, 이 Shadow Map은 렌더링 시 물체가 그림자에 있는지 아닌지를 분간하는 데 사용됩니다.

만약 광원이 Orthogonal Projection을 사용하는 Directional Light라면 그림자 해상도 문제에 직면하게 됩니다.

1

Orthogonal Projection 특성상 카메라에 보이는 영역 이외의 영역도 뎁스를 Shadow Map에 기록하게 되고 이는 필연적으로 Shadow Map 해상도 이슈에 직면하게 됩니다.

충분히 해상도가 확보되지 않으면 아래와 같이 그림자가 직각직각으로 나타나는 현상이 보이게 됩니다.

2

이를 해결하고자 CSM을 적용하였습니다.

CSM은 Cascade Shadow Mapping의 약자로 여러개의 뎁스맵을 사용하여 그림자를 정밀하게 표현하는 기술입니다.

View Frustum에서 거리별 섹션을 나누어 별도의 뎁스맵을 만듭니다. 가까운 거리는 더 높은 해상도를 가지고 먼 거리의 그림자는 낮은 해상도를 가지게 됩니다.

1

저의 렌더링 엔진에선 그림자가 아래와 같이 기록됩니다.

1 2 3

한번에 여러개의 쉐도우맵을 만들어낼시 프레임드랍이 발생하는걸 피하기 위해 인스턴싱을 활용하여 그려내고 이를 Texture2DArray에 담아 쉐이더에서 사용할 수 있도록 구조를 개선하는 작업도 병행하였습니다.

스크린샷 2024-07-28 005927

CSM의 도입으로 좀 더 선명한 그림자를 얻을 수 있게 되었습니다.

하지만 CSM을 도입한다 하더라도 근본적인 그림자 해상도 이슈에선 벗어날 수 없습니다. 예를 들어서 커튼 사이로 빛이 세어나오는 장면을 보겠습니다.

before2

이 빛이 세어나오는데 사용된 쉐도우 맵은 다음과 같습니다.

스크린샷 2024-07-28 010143

여전히 작은 해상도로 인하여 직각직각의 그림자를 얻게 되었는데 이 부분은 해상도를 늘리기 보단 샘플링을 바꾸는 방식으로 해결하였습니다. 코드

PCF에서 주변 그림자를 샘플링할때 4개의 depth값을 보간하여 채우는 방식을 사용하였습니다.

before3

bilinear sampling을 도입하자 그래도 직각보단 더 나은 그림자를 얻을 수 있게 되었습니다.

작은 환경 효과

Clone this wiki locally