브라우저 내 3D의 놀라운 기능을 조명하면서 슬로우 로드가 게이머와 개발자 모두를 놀라게 하는 방법

이 캐주얼 운전 게임에서 끊임없이 생성되는 무한한 풍경을 통해 WebGL의 잠재력을 발견해 보세요.

Slow Roads는 끊임없이 생성된 경치에 중점을 둔 캐주얼 드라이빙 게임으로, 브라우저에서 WebGL 애플리케이션으로 호스팅됩니다. 많은 경우 브라우저의 제한된 맥락에서 이처럼 집약적인 경험을 하는 것은 적절하지 않은 것처럼 보일 수 있습니다. 이 프로젝트를 통해 이러한 경험을 바로잡는 것이 저의 목표 중 하나였습니다. 이 문서에서는 웹 환경에서 종종 간과되는 3D의 잠재력을 강조하기 위해 제가 겪은 성능 장애물을 탐색하는 데 사용한 몇 가지 기법을 자세히 살펴보겠습니다.

Slow Roads를 출시한 후 피드백에서 "브라우저에서는 이러한 기능이 가능한지 몰랐습니다."라는 댓글을 반복적으로 보았습니다. 이러한 감정을 공유한다면 소수만이 아닙니다. 2022년 JS 현황 설문조사에 따르면 개발자 중 약 80% 가 아직 WebGL을 실험하지 않은 것으로 나타났습니다. 제 생각에는 특히 브라우저 기반 게임과 관련하여 많은 잠재력을 놓칠 수 있다니 안타깝습니다. Slow Roads를 통해 WebGL을 더욱 널리 알리고 '고성능 JavaScript 게임 엔진'이라는 문구를 꺼리는 개발자의 수를 줄이고자 합니다.

많은 사람에게 WebGL은 신비롭고 복잡하게 보일 수 있지만, 최근 몇 년 동안 WebGL의 개발 생태계는 성능이 뛰어나고 편리한 도구와 라이브러리로 크게 발전했습니다. 이제 프런트엔드 개발자는 컴퓨터 그래픽에 관한 사전 경험이 없어도 그 어느 때보다 쉽게 3D UX를 작업에 통합할 수 있습니다. 업계를 선도하는 WebGL 라이브러리인 Three.js는 3D 구성요소를 React 프레임워크로 가져오는 react-three- Fiber를 비롯한 여러 확장의 기반이 됩니다. 이제 익숙한 인터페이스와 통합 도구 모음을 제공하는 Babylon.js 또는 PlayCanvas와 같은 포괄적인 웹 기반 게임 편집기도 사용할 수 있습니다.

이러한 라이브러리의 유용성이 놀라울 정도로 높음에도 불구하고 야심 찬 프로젝트는 결국 기술적 한계에 봉착하게 됩니다. 브라우저 기반 게임이라는 아이디어에 대한 회의론자들은 JavaScript가 단일 스레드이며 리소스가 제한되어 있음을 강조할 수 있습니다. 하지만 이러한 한계를 탐색하면 숨겨진 가치를 이끌어낼 수 있습니다. 즉, 다른 어떤 플랫폼에서도 해당 브라우저에서 지원하는 즉각적인 접근성과 대량 호환성을 제공하지 않습니다. 브라우저를 지원하는 모든 시스템의 사용자는 애플리케이션을 설치하거나 서비스에 로그인할 필요 없이 클릭 한 번으로 재생을 시작할 수 있습니다. 개발자는 UI를 빌드하거나 멀티플레이어 모드의 네트워킹을 처리하는 데 사용할 수 있는 강력한 프런트엔드 프레임워크를 매우 편리하게 이용할 수 있습니다. 제 생각에 이러한 가치 때문에 브라우저가 플레이어와 개발자 모두에게 유용한 플랫폼이 되었습니다. Slow Roads에서 알 수 있듯이 기술적 한계는 종종 디자인 문제로 인해 줄어들 수 있습니다.

서행 도로에서도 원활한 주행 가능

'느린 도로'의 핵심 요소에는 고속 모션과 많은 비용이 드는 장면 생성이 포함되므로 원활한 성능에 대한 필요성은 모든 설계 결정을 내리게 되었습니다. 주된 전략은 엔진의 아키텍처 내에서 상황별 지름길을 가져올 수 있는 간소한 게임플레이 디자인으로 시작하는 것이었습니다. 단점은 미니멀리즘을 추구하는 데 유용한 기능 몇 가지를 포기하는 것이지만, 그 결과 다양한 브라우저와 기기에서 원활하게 작동하는 고도로 최적화된 맞춤형 시스템이 구축됩니다.

다음은 서행 도로를 원활하게 유지하기 위한 주요 구성요소입니다.

게임플레이를 중심으로 환경 엔진 형성

게임의 핵심 구성요소인 환경 생성 엔진은 불가피하게 비용이 많이 들기 때문에 메모리와 컴퓨팅 예산에서 가장 많은 부분을 차지합니다. 여기서 사용되는 요령은 성능 급증으로 프레임 속도를 중단하지 않도록 일정 기간 동안 과중한 계산을 예약하고 분산하는 것입니다.

환경은 카메라에 얼마나 가깝게 표시되는지에 따라 크기와 해상도가 다른 도형 타일로 구성됩니다('세부 수준' 또는 LoD로 분류됨). 자유 로밍 카메라가 있는 일반적인 게임에서 플레이어의 주변 환경을 자세히 표시하기 위해 여러 LoD를 지속적으로 로드하고 언로드해야 합니다. 이는 특히 환경 자체가 동적으로 생성되는 경우 비용이 많이 들고 낭비가 될 수 있는 작업입니다. 다행히 사용자가 도로에 머물러야 한다는 상황별 기대로 인해 '느리게 도로'에서는 이 규칙이 완전히 완전히 바뀔 수 있습니다. 대신 경로 바로 옆에 있는 좁은 복도에 세부적인 도형을 예약할 수 있습니다.

미리 도로를 만들면 사전 예방적 일정을 예약하고 환경 생성을 캐싱할 수 있음을 보여주는 다이어그램입니다.
느린 도로의 환경 도형을 와이어프레임으로 렌더링하여 도로 옆에 있는 고해상도 도형의 경로를 나타냅니다. 가까이서 볼 수 없는 환경의 먼 부분은 훨씬 낮은 해상도로 렌더링됩니다.

도로의 중간선은 플레이어가 도착하기 훨씬 전에 생성되므로 환경 세부정보가 필요한 시점과 위치를 정확하게 예측할 수 있습니다. 결과적으로 비용이 많이 드는 작업을 사전에 예약하여 각 시점에 필요한 최소한의 결과만 생성하고, 표시되지 않는 세부정보에 노력을 낭비하지 않고도 간결한 시스템을 구축할 수 있습니다. 이 기법은 도로가 분기형이 아닌 단일 경로이기 때문에만 사용할 수 있습니다. 이는 아키텍처의 지름길을 수용하는 게임플레이 절충의 좋은 예입니다.

미리 도로를 만들면 사전 예방적 일정을 예약하고 환경 생성을 캐싱할 수 있음을 보여주는 다이어그램입니다.
도로를 따라 특정 거리를 바라보면 환경 청크를 미리 비우고 필요하기 직전에 점진적으로 생성할 수 있습니다. 또한 가까운 미래에 다시 방문할 모든 청크를 식별하고 캐시하여 불필요한 재생성을 방지할 수 있습니다.

물리학 법칙 까다로움

환경 엔진의 컴퓨팅 수요에 대한 두 번째는 물리 시뮬레이션입니다. Slow Roads는 모든 지름길을 이용할 수 있는 최소한의 맞춤 물리 엔진을 사용합니다.

여기서 중요한 점은 애초에 너무 많은 객체를 시뮬레이션하는 것을 피하는 것입니다. 동적 충돌 및 파괴 가능한 객체와 같은 것을 무시하여 최소한의 젠 컨텍스트로 향합니다. 차량이 도로에 유지된다는 가정은 오프로드 객체와의 충돌을 합리적으로 무시할 수 있음을 의미합니다. 또한 도로를 희소 미드라인으로 인코딩하면 도로 중심까지의 거리 확인을 기반으로 노면과 가드레일에서의 신속한 충돌 감지를 위한 정교한 기법을 사용할 수 있습니다. 오프로드 운전은 더 비싸지만, 이는 게임플레이의 맥락에 적합한 공정한 절충의 또 다른 예입니다.

메모리 사용량 관리

브라우저의 제약을 받는 또 다른 리소스이므로 JavaScript가 가비지로 수집되더라도 메모리를 주의해서 관리하는 것이 중요합니다. 간과하기 쉽지만 게임 루프 내에서 소량의 새 메모리를 선언해도 60Hz에서 실행할 때 심각한 문제가 발생할 수 있습니다. 대규모 가비지 컬렉션은 멀티태스킹을 할 가능성이 높은 컨텍스트에서 사용자의 리소스를 소모하는 것 외에도 완료하는 데 여러 프레임이 걸리기 때문에 눈에 띄는 버벅거림이 발생할 수 있습니다. 이를 방지하기 위해 루프 메모리는 초기화 시 클래스 변수에 미리 할당하고 각 프레임에서 재활용할 수 있습니다.

Slow Roads 코드베이스를 최적화하는 중 메모리 프로필의 전후 비교를 통해 상당한 절감 효과와 가비지 컬렉션 비율 감소를 확인할 수 있습니다.
전체 메모리 사용률은 거의 변경되지 않지만 루프 메모리를 사전 할당하고 재활용하면 비용이 많이 드는 가비지 컬렉션의 영향을 크게 줄일 수 있습니다.

또한 도형 및 관련 데이터 버퍼와 같은 복잡한 데이터 구조를 경제적으로 관리하는 것도 매우 중요합니다. Slow Roads와 같이 무한하게 생성되는 게임에서는 대부분의 도형이 일종의 러닝머신에 존재합니다. 오래된 조각이 거리로 떨어지면 이 데이터 구조를 저장하고 다시 재활용하여 앞으로 나오도록 할 수 있습니다. 이러한 설계 패턴은 객체 풀링으로 알려져 있습니다.

이러한 관행은 약간의 코드 단순성을 희생하면서 린(lean) 실행의 우선순위를 지정하는 데 도움이 됩니다. 고성능 컨텍스트에서는 편의 기능이 개발자의 이익을 위해 클라이언트에서 빌려오는 방식에 유의해야 합니다. 예를 들어 Object.keys() 또는 Array.map()와 같은 메서드는 매우 편리하지만 각각 반환 값의 새 배열을 만든다는 점을 간과하기 쉽습니다. 이러한 블랙박스의 내부 동작을 이해하면 코드를 강화하고 부적절한 성능 저하를 피하는 데 도움이 될 수 있습니다.

절차상 생성된 애셋으로 로드 시간 단축

런타임 성능은 게임 개발자에게 주요 관심사가 되어야 하지만, 초기 웹페이지 로드 시간에 관한 일반적인 원칙은 여전히 유효합니다. 사용자는 고의로 무거운 콘텐츠에 액세스할 때 더 관대할 수 있지만, 사용자 유지가 아니라면 로드 시간이 길어지면 환경에 부정적인 영향을 줄 수 있습니다. 게임에는 텍스처, 사운드, 3D 모델 형태의 대형 애셋이 필요한 경우가 많으며, 적어도 세부정보를 지킬 수 있을 때마다 신중하게 압축해야 합니다.

또는 클라이언트에서 자산을 절차상으로 생성하면 애초에 긴 이전이 이루어지지 않도록 방지할 수 있습니다. 이는 연결 속도가 느린 사용자에게 큰 이점이 되며 개발자는 초기 로드 단계뿐만 아니라 다양한 품질 설정에 맞게 세부정보 수준을 조정할 때도 게임 구성 방식을 더 직접적으로 제어할 수 있습니다.

느린 도로에서 절차상 생성된 도형의 품질을 사용자의 성능 니즈에 맞게 동적으로 조정하는 방법을 비교

느린 도로의 도형은 대부분 절차상으로 생성되고 단순하며, 맞춤 셰이더가 여러 텍스처를 결합하여 디테일을 제공합니다. 단점은 이러한 텍스처가 무거운 자산일 수 있다는 점입니다. 하지만 여기서는 확률적 텍스처와 같은 방법을 사용하면 작은 소스 텍스처에서 더 큰 세부정보를 얻을 수 있으므로 비용을 절감할 수 있는 기회가 더 있습니다. 극단적인 수준에서 texgen.js와 같은 도구를 사용하여 클라이언트에서 완전히 텍스처를 생성할 수도 있습니다. Web Audio API를 사용하면 오디오 노드를 통한 사운드 생성이 가능하므로 오디오도 마찬가지입니다.

절차적 자산의 이점 덕분에 초기 환경을 생성하는 데 평균 3.2초밖에 걸리지 않습니다. 작은 초기 다운로드 크기를 최대한 활용하기 위해 간단한 스플래시 화면이 신규 방문자를 맞이하고 버튼을 확실히 누를 때까지 비용이 많이 드는 장면 초기화를 연기합니다. 이는 또한 이탈한 세션의 편리한 버퍼 역할을 하여 동적으로 로드된 애셋의 낭비되는 전송을 최소화합니다.

로드 시간 히스토그램에서 처음 3초에 강한 최고점을 기록하여 사용자의 60% 이상을 차지하고 이후 급격히 감소하는 것을 확인할 수 있습니다. 히스토그램에 따르면 97% 이상의 사용자가 10초 미만의 로드 시간을 경험합니다.

지연 최적화에 대한 민첩한 접근 방식

저는 Slow Roads의 코드베이스를 항상 실험용으로 생각하여 매우 민첩하게 개발해 왔습니다. 복잡하고 빠르게 진화하는 시스템 아키텍처를 사용할 때는 중요한 병목 현상이 발생할 수 있는 위치를 예측하기 어려울 수 있습니다. 원하는 기능을 완전하게 구현하기보다는 신속하게 구현한 후 실제로 중요한 시스템을 최적화하는 데 역점을 두어야 합니다. Chrome DevTools의 성능 프로파일러는 이 단계에서 매우 유용하며, 이전 버전의 게임에서 발생하는 몇 가지 주요 문제를 진단하는 데 도움이 되었습니다. 개발자의 시간은 소중하므로 중요하지 않거나 중복될 수 있는 문제를 검토하는 데 시간을 허비하지 마세요.

사용자 환경 모니터링

이러한 트릭을 모두 구현할 때는 게임이 실제 상황에서 예상대로 작동하는지 확인하는 것이 중요합니다. 다양한 하드웨어 기능을 수용하는 것은 모든 게임 개발의 중요한 측면이지만, 웹 게임은 동시에 최고급 데스크톱과 10년 전의 휴대기기로 구성된 훨씬 더 광범위한 스펙트럼을 타겟팅할 수 있습니다. 가장 간단한 접근 방법은 GPU를 많이 사용하는 작업과 CPU를 많이 사용하는 작업 모두에서 프로파일러에 나타난 바와 같이 코드베이스에서 가장 가능성이 높은 병목 현상을 조정할 수 있는 설정을 제공하는 것입니다.

하지만 자체 머신에서 프로파일링은 많은 것을 처리할 수 없으므로 어떤 식으로든 사용자와의 피드백 루프를 닫는 것이 중요합니다. 느린 도로의 경우 화면 해상도와 같은 상황별 요인과 함께 성능을 보고하는 간단한 분석을 실행합니다. 이러한 분석은 사용자가 게임 내 양식을 통해 제출한 의견과 함께 socket.io를 사용하여 기본 노드 백엔드로 전송됩니다. 초기에는 이러한 분석을 통해 UX의 간단한 변경으로 완화할 수 있는 여러 중요한 문제를 발견했습니다. 예를 들어 지속적으로 낮은 FPS가 감지될 때 설정 메뉴를 강조 표시하거나 성능이 특별히 나쁘면 사용자가 하드웨어 가속을 사용 설정해야 할 수 있습니다.

전방의 느린 도로

이러한 조치를 모두 취한 후에도 낮은 설정(주로 GPU가 없는 경량 기기 사용)에서 플레이해야 하는 플레이어층의 상당 부분이 남아 있습니다. 사용 가능한 품질 설정 범위가 상당히 균일하게 분포되어 있지만 플레이어의 52% 만이 55FPS 이상을 달성합니다.

세부정보 설정에 대한 조회 거리 설정으로 정의되는 행렬로, 서로 다른 페어링에서 달성한 초당 평균 프레임을 표시합니다. 45와 60 사이에 상당히 고르게 분포되어 있으며 60이 우수한 실적을 위한 목표입니다. 낮은 설정의 사용자는 높은 설정의 사용자보다 낮은 FPS를 경험하는 경향이 있는데, 이는 클라이언트 하드웨어 기능의 차이를 강조합니다.
이 데이터는 하드웨어 가속을 사용 중지한 상태로 브라우저를 실행하는 사용자에 의해 다소 왜곡되어 있어 종종 인위적으로 성능이 저하됩니다.

다행히 성능을 절감할 수 있는 기회가 아직 많이 있습니다. GPU 수요를 줄이기 위해 추가 렌더링 기법을 추가하는 것 외에도, 빠른 시일 내에 환경 생성을 병렬화하는 웹 작업자를 실험하고자 하며, 궁극적으로는 WASM 또는 WebGPU를 코드베이스에 통합해야 할 필요성을 느끼게 될 것입니다. 확보할 수 있는 여유 공간은 더 풍부하고 다양한 환경을 가능하게 하며, 이는 프로젝트의 나머지 부분에서 지속적인 목표가 될 것입니다.

취미 프로젝트가 진행됨에 따라 Slow Roads는 놀라울 정도로 정교하고 성능이 뛰어나며 인기 있는 브라우저 게임이 얼마나 놀라운지 보여주는 성취감을 선사합니다. WebGL에 관심을 갖게 됐다면 기술적으로 Slow Roads가 모든 기능을 충분히 보여주는 예입니다. 독자들은 Three.js 쇼케이스를 확인해 보시기 바랍니다. 특히 웹 게임 개발에 관심이 있는 사용자는 webgamedev.com의 커뮤니티에 오신 것을 환영합니다.