『사랑과 심연』 첫 번째 심층 기술 공유 회고!

초현실적인 3D 몰입형 연애 인터랙티브 모바일 게임인 《연과 심공》은 2024년 1월 18일 출시 이후 전 세계 플레이어 수가 7,000만 명을 돌파했으며, 2025년 쾰른 게임 쇼에서 ‘최고의 모바일 게임’상을 수상했다.

오늘 열린 ‘Unite Shanghai 2025’ 행사에서 『사랑과 심우주』의 엔진 엔지니어들이 처음으로 심도 있는 기술 공유 세션을 진행하며, 『사랑과 심우주』 제작의 이면을 공개하고, 영화급 렌더링 파이프라인과 사실적이고 섬세한 물리 효과를 통해 플레이어들에게 실감 나는 세계를 어떻게 구현해냈는지 소개했습니다!

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

△행사장은 좌석이 하나도 남지 않을 정도로 꽉 찼다

올해 유니티 컨퍼런스에서 가장 화제가 된 세션이 되었다

디에즈지에서는 공유와 창작이 우리 업무의 일상이자 핵심입니다. 지난 1년 동안 디에즈지는 약 40회의 사내 공유 세션을 개최했으며, 누적 참가 인원은 5,000명에 달했습니다!그중 기술 관련 공유가 50% 이상을 차지했습니다! 발표자들은 각 제작팀과 기술 부서에서 나왔으며, 내용은 다양한 분야의 실무 경험과 최첨단 탐구를 아우르고, 개방적인 교류 분위기 속에서 전문 지식과 아이디어가 자유롭게 흐르며 충분히 발현될 수 있도록 했습니다!

다음은 《사랑과 심연》 기술 공유 세션의 전체 내용을 정리한 것입니다:

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

『사랑과 심우주』의 렌더링 기반 프레임워크에 대한 공유 내용은 주로 【씬 렌더링 최적화, 조명 솔루션 및 파이프라인 설계, 그림자 최적화】의 세 가지 부분으로 구성됩니다.개발 과정에서 저희는 Unity 2019를 기반으로 엔진 소스 코드를 심도 있게 수정하여, 독자적인 SRP 파이프라인을 개발했습니다. 현재 안드로이드 정식 버전은 GLES 3.1을 지원하며, 향후 Vulkan 버전도 출시하여 지속적으로 성능을 향상시키고, 플레이어들의 고품질 게임에 대한 요구를 충족시켜 나갈 예정입니다.

1. 씬 렌더링 최적화

씬 렌더링 최적화 과정에서, 우리는 【RendererGroupRenderer】라는 씬 렌더링 시스템을 개발했으며, 각 렌더링 배치를 RenderGroup이라고 부릅니다. 이 시스템을 통해 다음과 같은 기능을 구현했습니다:

  • 사용자 정의 정적 씬 설명: GameObject를 제거함으로써, 대량의 GameObject를 업데이트할 때 발생하는 성능 저하를 방지했습니다.
  • CPU→GPU 업로드 빈도 최적화: 주로 InstanceData와 ConstantBuffer의 업로드를 포함합니다. InstanceData 업로드 최적화와 관련하여, 프로젝트 초기에는 실내 소규모 씬을 대상으로 InstanceDataBuffer를 정적으로 생성하고, BVH 분할 및 트리밍 방식을 병행했습니다.프로젝트가 진행됨에 따라 씬의 정밀도 요구 사항이 지속적으로 높아지자, 후반부에는 GPU 측에서 클리핑과 인스턴스 채우기를 수행하도록 전환했습니다. ConstantBuffer 업로드 최적화에 대해서는 뒤의 【단일 DrawCall 성능 최적화】 섹션에서 자세히 설명하겠습니다.
  • CPU 측 Burst+Job System을 활용한 병렬 대략적 트리밍: 정적 오브젝트의 경우, Burst+Job System을 통해 고도의 동시성을 갖춘 트리밍 시스템을 구현하였으며, CPU 측에서는 대략적인 트리밍만 수행하고, 세밀한 트리밍 작업은 GPU에 맡기고 있습니다.

InstanceData 데이터 형식

업계에서 흔히 사용되는 Constant Buffer 형태의 InstanceData에는 64KB 크기 제한, 상수 버퍼 용량이 작다는 점, 동적 인덱싱 시 캐시 브레이크스루가 발생하기 쉬워 성능 저하를 초래한다는 점 등 몇 가지 단점이 있습니다.또 다른 일반적인 방식은 SSBO를 사용하여 InstanceData를 전달하는 것이지만, 이 방법은 일반적으로 캐시 브레이크가 발생하지 않은 상황의 Constant Buffer보다 읽기 성능이 떨어지며, 일부 안드로이드 기기는 GLES 환경에서 버텍스 셰이더 내에서 SSBO를 읽는 것을 지원하지 않아 호환성에도 제한이 있습니다.또한 이 두 방식 모두 동적 인덱싱에 의존한다는 공통된 문제가 있어, 저사양 스마트폰의 성능에는 불리합니다.

이러한 문제점을 해결하기 위해, 우리는 “새 병에 담긴 옛 술”과 같은 접근 방식인 ‘Vertex Stream 기반 인스턴스 데이터(Vertex Stream based Instance Data)’를 제안합니다.

  • PerInstance Step의 Vertex Stream을 Instance Buffer로 사용;
  • Vertex Fetch 캐시를 사용하므로 동적 인덱스가 필요 없고, 캐시 적중률이 높으며, 호환성 문제가 없습니다.
  • ComputeShader를 통해 Instance VertexBuffer로 출력하여 GPU Driven을 구현합니다.

PerInstance Step의 Vertex Stream을 Instance Buffer로 사용하는 것——이는 GPU 인스턴싱이 처음 등장했을 때부터 지원되어 온 인스턴싱 방식으로, 동적 인덱싱으로 인한 성능 문제를 피할 수 있을 뿐만 아니라 SSBO의 호환성 문제도 피할 수 있습니다.또한 ComputeShader를 통해 Instance VertexBuffer로 출력함으로써 GLES에서 호환성이 높은 GPU Driven을 구현할 수도 있습니다.

마지막으로, Unity 엔진이 저수준에서 PerInstance Step 방식의 Vertex Stream을 지원하지 않기 때문에, 엔진에 대한 맞춤형 수정 작업을 수행했습니다. 그 결과, 상위 계층에 노출된 것은 CommandBuffer에 추가된 DrawMeshInstancedTraditional 인터페이스이며, 이 인터페이스는 다른 메시를 인스턴스 데이터로 전달받아야 합니다.또한 인스턴스 메시 내 각 데이터 세그먼트에 해당하는 정점 세미언틱을 구성할 수 있는 인터페이스도 추가했습니다.

GPU 기반

우리는 그룹 수와 인스턴스 수에 따라 IndirectParameter Buffer와 Instance Data Buffer를 미리 할당합니다(여기서 Instance Data Buffer는 단순히 공간을 미리 할당해 둔 것일 뿐이며, 실제 데이터는 GPU 컬링 시에 채워집니다).

동시에, 각 그룹의 인스턴스 오프셋을 미리 계산하여 파라미터의 InstanceStart 항목에 저장하며, 전체 과정에서 인스턴스 버퍼는 단 하나만 바인딩됩니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

또한, GPU 클리핑 시 각 오브젝트의 속성을 가져오기 위해 오브젝트별 정보 버퍼(GroupID, LOD 거리 범위, Bounds, Transform 등의 정보를 포함)를 생성해야 합니다.

  • CPU 트리밍: GPU 트리밍에 앞서, 그룹 전체가 가시 영역 내에 있는지 판단하기 위해 CPU를 이용한 1차 대략적인 트리밍을 수행합니다.루트 바운딩 박스에서 시작하여, 오브젝트 바운딩 박스의 부피 합계와 병합된 바운딩 박스의 부피 비율을 비교하고, 이 비율이 임계값보다 낮으면 재귀적으로 바운딩 박스를 분할합니다(주요 목적은 두 오브젝트 간의 거리가 너무 멀어 초대형 총 바운딩 박스가 생성되는 상황을 방지하기 위함입니다).동시에 PVS를 활용하여 그룹의 가시성을 추가로 판단합니다. DX12의 IndirectExecute와 유사한 기능이 없기 때문에, 우리의 GPU 트리밍은 인스턴스 수만 줄일 수 있을 뿐 그룹 전체의 드로우콜을 제거할 수는 없습니다. 따라서 CPU 트리밍을 통해 완전히 보이지 않는 그룹을 가능한 한 정확하게 제거해야 합니다.
  • GPU 클리핑: GPU 클리핑은 한 번의 디스패치를 통해 모든 그룹에 대해 오브젝트별로 3단계 클리핑(시놉스 클리핑, LOD 클리핑, Hiz 오클루전 제거 포함)을 수행하며, 클리핑을 통해 파라미터의 인스턴스 수를 1 증가시키고 인스턴스 데이터를 출력합니다.
  • 그림자 제거: 우리는 ‘드래곤 도그마’에서 공유한 방법을 참고하여, 화면의 깊이를 그림자 공간으로 재투영하여 ‘Shadow Receiver Mask’로 사용했습니다. 만약 ‘Shadow Caster’가 투사하는 볼륨이 이 마스크와 교차하지 않는다면, 이를 제거하여 불필요한 그림자 렌더링을 방지할 수 있습니다.

또한 “왜 Cluster/Meshlet을 구현하지 않았는가”라는 부분과 관련하여, 첫째, 모바일 환경에서 상당한 기본 오버헤드가 발생하며, 둘째, GLES 환경에서 Cluster를 구현할 경우 호환성 문제가 발생합니다. 이러한 점들을 종합적으로 고려한 결과, 단일 DrawCall의 성능을 우선적으로 최적화하는 것이 더 큰 비용 부담 없이 직접적인 성능 향상을 가져다줄 것이라고 판단했습니다.

단일 DrawCall 성능 최적화

지금까지의 관찰 결과, 렌더링에 소요되는 CPU 시간을 최적화할 때 많은 경우 드로우 콜(DrawCall)의 수에만 지나치게 집중하고, 각 드로우 콜 자체에 소요되는 시간은 간과하는 경향이 있음을 발견했습니다. 저희는 드로우 콜의 수를 줄이는 것이 단지 하나의 최적화 방법에 불과하며, 궁극적으로 CPU 소요 시간만이 유일한 평가 지표라고 생각합니다.

현대 모바일 기기와 그래픽 표준은 사실 이미 오래전부터 대량의 드로우콜을 처리할 수 있는 능력을 갖추고 있습니다. 이 부분에 대해서는 HypeHype 엔진 팀이 SIGGRAPH 2023에서 발표한 바 있습니다. 그들은 아이폰 6s에서 1만 개의 서로 다른 메시와 텍스처에 대한 드로우콜을 테스트한 결과, 소요 시간이 단 11.27ms에 불과했습니다.다른 동급 안드로이드 기기들도 기본적으로 60프레임 이상을 유지할 수 있습니다. 또한 2014년 Metal이 처음 등장했을 때도 GLES보다 10배 더 많은 드로우콜을 처리할 수 있다는 슬로건이 제시된 바 있습니다.

11년이 지난 오늘날에도 우리가 여전히 DrawCall이 너무 많다는 점에 골머리를 앓는 이유는 주로 여러 측면의 오버헤드 때문인데, 여기에는 PSO 전환이 너무 잦은 것, 버퍼 제출 및 복사, 엔진 렌더링 로직, 그리고 과도한 RHI 인터페이스 호출 등이 포함되며, 이 모든 것이 CPU 부하를 증가시킵니다.따라서 성능 최적화는 단순히 DrawCall 수에만 집중해서는 안 되며, 이러한 요소들을 종합적으로 고려해야 한다고 생각합니다.

  • PSO 전환 최적화: 주로 각 프로젝트별로 셰이더 변형의 수와 셰이더의 복잡도 간의 균형에 따라 결정됩니다.RenderGroup 렌더링 큐는 셰이더, 머티리얼, 메시의 우선순위에 따라 정렬되며, 동시에 그림자에 대해서는 특별한 처리를 적용합니다. 알파 테스트가 없는 머티리얼은 모두 동일한 셰이더를 사용하여 섀도우 깊이를 렌더링함으로써, 그림자 렌더링 시 PSO 전환 빈도를 줄입니다.
  • 버퍼 제출 최적화: GLES 환경에서 버퍼 매핑/언매핑은 상당한 오버헤드를 유발합니다. 현대 RHI가 지원하는 퍼시스턴트 매핑은 업로드 소요 시간을 크게 줄일 수 있지만, 메인 스레드에서 렌더링 스레드를 거쳐 버퍼 메모리로의 반복적인 데이터 복사 및 memcmp 연산을 여전히 피할 수는 없습니다.따라서 우리는 다음과 같은 세 가지 맞춤형 전략을 채택하여 버퍼 업로드를 현저히 줄였습니다:
    • PerRendererBuffer는 각 Renderer별 매개변수(예: 오브젝트가 받는 환경광 SH)를 Renderer 객체가 관리하는 Uniform Buffer에 저장하며, 렌더링 시 직접 바인딩합니다;
    • PerShaderBuffer는 텍스처별로 변경할 필요가 없는 유니폼 버퍼에 대해 셰이더 전환 시에만 한 번 제출되며, PerRendererBuffer에 비해 PerShaderBuffer는 더 유연하여 서로 다른 셰이더 변형을 지원할 수 있습니다.
    • PerMaterialBuffer의 경우, SRP Batcher 코드를 활용하여 각 머티리얼별 버퍼를 미리 생성한 후 직접 바인딩했습니다.
  • 렌더링 로직 최적화: 상용 게임 엔진은 유연성과 안정성을 보장하기 위해 렌더링 시 복잡한 논리적 판단을 수행합니다. 예를 들어 Unity 엔진 내부에서는 Draw를 호출할 때마다 먼저 ApplyMaterial 함수를 호출하는데, 이 함수는 렌더링 전에 모든 렌더링 상태와 매개변수를 업데이트하며, DrawCall의 수가 많을 경우 상당한 시간이 소요됩니다. 따라서 다음과 같은 최적화를 수행했습니다:
    • ApplyMaterial 인터페이스를 별도로 분리하여, 텍스처나 매개변수를 전환해야 할 때에만 상위 계층에서 능동적으로 호출하도록 했습니다;
    • PerMaterialBuffer만 변경할 때는 간소화된 전용 인터페이스를 사용하십시오.

최적화 결과, 동일한 DrawCall에서 CPU 처리 시간이 1/3로 단축되었습니다.

  • RHI 호출 최적화: RHI 호출 최적화의 주요 목표는 Draw Primitive를 제외한 다른 그래픽 API 호출을 줄이는 것이며, 구체적인 최적화 내용은 다음과 같습니다:
    • 동일한 스트라이드를 가진 Vertex 및 Index 버퍼를 병합하여, Draw Call마다 VB/IB를 바인딩하는 과정을 피함으로써 처리 시간을 15% 단축;
    • Resource에 변경 사항이 없을 경우 DescriptorSet 설정을 건너뛰면 소요 시간이 30% 더 단축됩니다. SetDescriptors 자체에 소요되는 시간이 길고, Descriptor를 전환하면 다음 draw 작업의 소요 시간이 증가하는데, 이에 대해서는 Arm의 Best Practice Guide에 소개된 바 있습니다.

저희는 저사양 안드로이드 기기에서 5,000개의 DrawCall에 소요되는 시간을 테스트했습니다. 엔진의 기본 렌더링 기능을 사용했을 때, 렌더링 스레드에 소요된 시간은 34.79ms였습니다.버퍼 제출 및 렌더링 로직을 최적화한 결과, 소요 시간이 22.97ms로 단축되었습니다. RHI 호출 횟수를 추가로 최적화한 후에는 소요 시간이 11.8ms로 대폭 감소했습니다. 결국 DrawCall 개수를 그대로 유지한 상태에서 CPU 소요 시간을 원래의 1/3 미만으로 줄일 수 있었습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

기타 최적화 시도: 벤치마크 시나리오 테스트 결과

또한 다음과 같은 몇 가지 새로운 RHI 기능을 시험해 보았습니다:

  • Multi-Draw Indirect(MDI): 지원되는 장치에서 뚜렷한 성능 향상을 가져오며, GPU 오클루전 제거 과정에서 빈 DrawCall이 제출될 수 있는 문제를 어느 정도 개선합니다(CPU 측 제출 횟수 감소);
  • Bindless: 그러나 Bindless의 성능은 기대에 미치지 못했으며, 최신 안드로이드 기기에서도 원인을 알 수 없는 역최적화 현상이 나타났습니다. MDI와 Bindless를 결합하면 거의 하나의 DrawCall로 모든 오브젝트를 렌더링할 수 있지만, CPU 소요 시간은 배치하지 않았을 때보다 더 길어집니다. 이는 DrawCall 수에만 지나치게 집중했을 때 발생할 수 있는 부정적인 사례이기도 합니다.물론, 향후 모바일 칩이 Bindless를 더 잘 지원하기를 기대합니다. 현재 단계에서는 Unity Texture Streaming을 기반으로 피드백이 없는 SVT 시스템을 대체 방안으로 확장해 보았으며, 이 방안 역시 아직 검증 단계에 있습니다.

벤치마크 시나리오 테스트 결과에 따르면, RenderGroupRenderer는 인스턴싱이 적용되지 않은 기존 렌더링 방식에 비해 드로우 콜이 1/3로 감소했으며,렌더링 스레드 소요 시간은 4분의 3이나 대폭 감소했으며, 메인 스레드 소요 시간도 3분의 2로 줄었습니다(C# 처리 시간은 증가했으나, 엔진 네이티브 트리밍 및 GameObject 업데이트 소요 시간이 감소하여 전체적으로 여전히 상당한 최적화 효과를 가져왔습니다).

2. 조명 계획

조명 계획

  • 전방 렌더링 파이프라인:

우리는 프로젝트에서 포워드 렌더링 파이프라인을 채택하기로 결정했는데, 여기에는 다음과 같은 여러 가지 고려 사항이 반영되었습니다. 첫째, 포워드 파이프라인은 복잡하고 변화무쌍한 아트 요구 사항을 처리하는 데 장점이 있으며, 일부 머티리얼 속성을 추가할 때 GBuffer가 부풀어 오를지 여부에 대해 걱정할 필요가 없습니다.

둘째, 기존의 지연 파이프라인은 모바일 플랫폼의 대역폭에 그다지 적합하지 않습니다. 반면 OnePassDeferred는 유연성 측면에서 몇 가지 한계가 있는데, 예를 들어 RenderPass 도중 RT의 크기를 변경할 수 없으며, 현재 위치 이외의 픽셀 콘텐츠를 가져올 수도 없습니다.

GLES 환경에서는 FrameBufferFetch의 호환성 문제도 존재합니다. 칩마다 지원하는 fetch RT의 수가 다르며, 일부는 RT 1개만 지원하므로 PLS를 통해 구현해야 하지만, 테스트 결과 PLS의 성능은 기대에 미치지 못했습니다.

또한, 엔진에 기본으로 포함된 오브젝트별 4개의 광원은 크기가 큰 오브젝트의 경우 충분하지 않아, 우리는 Forward+를 시도해 보았습니다. 하지만 Forward+는 구형 기기에서 처리 시간이 너무 오래 걸리며, 타일별 최대 광원 수를 제한할 경우 샷이 변경될 때 타일 내 광원 수를 제어할 수 없어, 상한을 초과하면 렌더링 오류가 발생합니다.

이러한 문제들을 해결하기 위해, 우리는 수평 세계 공간 타일 분할 방식을 채택했습니다. 기본값은 2미터 간격의 타일로, 카메라 전방에 분포되어 있으며, 타일당 최대 4개의 광원과 128×128 인덱스 맵을 사용합니다. 이러한 분할 방식은 타일 간 광원 중첩 상태를 안정적으로 유지하여, 제작 과정에서 한도 초과 문제를 적시에 발견할 수 있게 해줍니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!
  • Vulkan 버전 파이프라인 개선

우리는 향후 Vulkan 버전의 파이프라인에 Subpass 기반의 Light Pre-Pass를 추가했습니다.

Pre-Z Pass에서는 간단한 GBuffer RT를 출력하여 저장합니다.우리의 로컬 라이트(local light)는 프레넬 효과가 없는 단순화된 PBR 모델을 사용하기 때문에, GBuffer에 스페큘러(specular)나 알베도(Albedo)를 출력할 필요가 없으며, 노멀(normal), 러프니스(roughness) 및 일부 특수한 머티리얼 ID나 속성 정보만을 RGBA8 형식의 GBuffer 하나에 패킹합니다.그런 다음 Deferred Shading과 유사한 광원 볼륨 렌더링 프로세스를 한 번 실행하여, 기하학적 조명 결과를 타일 메모리에 저장합니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

그 후 셰이딩 패스에서 오브젝트를 다시 렌더링하고 이러한 조명 정보를 가져온 다음, 렌더링 과정에서 얻은 알베도 등의 재질 속성과 결합하여 최종 조명 결과를 얻게 됩니다.

TAA에 필요한 MotionVector를 RGBA8로 인코딩하며, R + G == 0은 유효한 속도가 없음을 나타내므로, 속도를 출력하지 않는 일부 텍스처의 경우 BA 채널에 다른 정보를 저장할 수 있습니다.

예를 들어, 단순하고 수량이 많은 식생의 경우, MotionVector의 BA 채널에 해당 식생의 UV 정보를 저장해 둡니다. 이렇게 하면 셰이딩 패스 단계에서 g버퍼의 기하 정보와 MotionVector의 UV 정보를 후처리 과정을 통해 추출하기만 하면, 식생의 텍스처 표현을 재현할 수 있습니다.

Vulkan 버전의 파이프라인 흐름은 대략 다음과 같습니다. 먼저 PreZ Pass에서 Depth, GBuffer 및 MotionVector를 출력한 다음, 그림자의 오클루전 제거를 계산하고,이어서 그림자의 깊이 렌더링을 수행하고, 그 후 AO 및 스크린 스페이스 SSS와 같은 계산을 거친 뒤 NativeRenderPass로 진입합니다. SubPass에서는 ShadowMask와 Light Pre-Pass를 계산하고, 일반적인 셰이딩 패스를 실행합니다. 마지막으로 RenderPass를 종료한 후, 다른 후처리 패스를 실행합니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

Vulkan 버전 파이프라인 개선에도 일정한 한계가 존재합니다. 예를 들어, Light Pre-Pass는 기본 라이팅 모델만 대체할 수 있으며, 더 많은 Gbuffer 채널이 필요한 라이팅 모델의 경우 여전히 Forward+ 방식을 채택해야 합니다.

다만, 광원별로 선택 가능한 매개변수를 제공하여 특정 광원에 대해 Standard Lit Model을 강제 적용하고 모든 머티리얼을 일괄 처리할 수 있습니다. 이렇게 하면 Lighting Model의 정확도를 다소 희생하는 대신, 동일한 타일 내의 픽셀이 4개 이상의 조명의 영향을 받도록 할 수 있습니다.

  • 좋은 아침입니다

Diffuse GI 부분에서는 비교적 전통적인 라이트맵(Lightmap) + 라이트 프로브(Light Probe) 방식을 채택했으며, 라이트맵은 간접광 정보만 저장합니다.라이트 프로브의 경우, 일반적인 객체별 단일 샘플링 포인트 방식 외에도 다중 샘플링 포인트 방식을 제공하여 각 객체에 여러 개의 샘플링 포인트를 설정하고, 선분, 삼각형 또는 사면체의 중심 좌표를 기반으로 보간할 수 있습니다.

다음 두 개의 비교 이미지에서, 왼쪽 이미지는 단일 샘플링 지점을 적용한 결과이며, 상자 하단에는 균일한 환경 조명이 비치고 있습니다. 오른쪽 이미지는 두 개의 샘플링 지점을 적용한 결과로, 좌우 양쪽이 서로 다른 간접 조명을 받고 있음을 확인할 수 있습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

Specular GI의 경우, 우리는 주로 AABB 보정이 적용된 리플렉션 프로브를 기반으로 합니다. 또한 특정 바닥이나 수면의 경우, 평면 반사 프록시를 사용하기도 합니다. 이는 대략 반사를 렌더링하는 데 특화된 HLOD로 볼 수 있습니다.

또한 우리는 《갓 오브 워》의 방식을 참고하여 리플렉션 프로브의 큐브맵을 정규화했습니다. 구체적으로 말하면, 큐브맵의 픽셀을 기반으로 환경 조명의 SH 계수를 생성하고, 큐브맵 내 픽셀 색상을 해당 방향의 환경 조명으로 나눈 값을 구하여 정규화된 큐브맵을 얻습니다.실제 렌더링 시에는 각 픽셀이 반사 방향에서 받는 실제 환경광을 큐브맵 픽셀과 곱하여 반사 색상을 복원합니다.

이 방식의 장점은, 많은 오브젝트가 동일한 리플렉션 프로브를 샘플링하더라도 서로 다른 영역의 반사에 따라 명암 차이가 달라질 수 있다는 점입니다.

3. 그림자 최적화

기능 설계

우리의 쉐도우 시스템의 기본 설계는 다음과 같습니다:

  • 3단계 CSM+ 캐릭터 클로즈업 쉐도우/다중 캐릭터 POSM: 3단계 캐스케이드(Cascade)의 CSM과 1단계 캐릭터 전용 클로즈업 쉐도우로, 특정 다중 캐릭터 장면에서는 POSM(Per-Object Shadow Map)이 사용됩니다.
  • 원뿔형 조명 2개를 투사할 수 있으며;
  • ScreenSpaceShadowMask: 위의 그림자 처리 결과를 모두 RGBA8 형식의 ScreenSpaceShadowMask에 출력합니다.
  • R: 방향성 그림자, G: 국소 그림자 1, B: 국소 그림자 2, A: AO: R 채널에는 주광의 그림자가 저장되고, G와 B 채널에는 원뿔형 조명의 그림자가 저장되며, A 채널에는 AO 정보가 저장됩니다.

거리 제거

먼저 간단한 거리 제거 처리를 수행하여, 그림자 거리에 따라 ScreenSpaceShadow 후처리에서 삼각형 정점의 깊이 값을 수정한 후, ZTest Greater를 사용하여 그림자 거리 밖의 그림자 계산을 제거했습니다.

그림자를 계산할 때 깊이(depth)를 샘플링해야 하므로, Test와 Sample에 각각 사용할 두 개의 깊이 버퍼가 필요합니다. 따라서 NativeRenderPass에서 Memoryless Depth Buffer의 사본을 Test용으로 복사하여, 불필요한 읽기/쓰기 대역폭을 최대한 줄이도록 하겠습니다.

반그림자 영역 감지

반음영 영역 감지 기능을 추가했습니다. 먼저 1/4 해상도에서 PCF를 한 번 계산한 다음, 전체 해상도의 Shadow Pass에서 1/4 마스크를 샘플링하여, 그림자 값이 중간 영역에 속하는 픽셀에 대해서만 전체 해상도 PCF를 수행함으로써, 효과를 유지하면서 계산량을 줄였습니다.

이렇게 했을 때 특정 디테일 픽셀의 검출 정확도가 떨어지는 문제를 방지하기 위해, 1/4 버퍼의 Position에 대한 편미분과 전체 해상도 Gather의 4개 깊이 값을 각각 기반으로 두 세트의 법선을 계산합니다. 법선 각도가 임계값보다 크면 저해상도 픽셀을 신뢰할 수 없는 것으로 판단하고, 강제로 전체 해상도 PCF를 실행합니다.

다음은 씬의 디버그 뷰입니다. 빨간색 영역은 반음영 구역으로 판정되었으며, 오직 이 픽셀들에 대해서만 전체 해상도의 PCF가 실행됩니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

픽셀별 바이어스

우리는 Receiver Plane Depth Bias 알고리즘을 활용하여 픽셀 단위의 Shadow Bias를 구현했습니다. 그 원리도 비교적 간단합니다. 먼저 화면 공간의 그림자 좌표에 대한 편미분에 2차원 연쇄 법칙을 적용하여 그림자 공간의 편미분을 구합니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

편미분과 PCF 샘플링 오프셋을 활용하여 바이어스 값을 구할 수 있습니다. 중심점의 경우, 1픽셀 오프셋이 적용된 바이어스 결과를 초기 바이어스로 설정했습니다.

아래 그림은 고정 바이어스와 픽셀별 바이어스의 비교 결과입니다:

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

왼쪽 그림에서는 고정된 바이어스 값을 사용했는데, 박스 하단에 빛이 새어 나오는 영역이 보이고, 조명 방향과 거의 수직인 표면에서는 부분적인 자체 그림자 왜곡이 발생함을 확인할 수 있습니다. 픽셀별 바이어스를 적용한 후(오른쪽 그림)에는 편미분이 큰 영역에만 바이어스를 추가하므로, 디테일한 투영을 유지하면서 자체 그림자 왜곡 문제를 해결할 수 있습니다.

다만, 화면의 심도가 불연속적일 경우 픽셀별 바이어스가 잘못된 결과를 산출하여 일부 빛 새어 나옴 현상이 발생할 수 있습니다. 이 문제를 해결하려면 아트 담당자가 바이어스의 최대 및 최소 범위를 수동으로 지정해야 합니다.

캐시된 섀도 맵 스크롤링

DrawCall이 많은 시나리오를 위해, 우리는 Scrolling Cached Shadow Map도 시도해 보았으며, 구체적으로는 다음과 같습니다:

  • CSM 깊이를 캐싱하여, 앞뒤 두 프레임 모두 음영 원뿔에 완전히 포함된 오브젝트의 경우, 이전 프레임의 CSM을 현재 프레임의 투영 위치로 롤백하여 직접 음영 깊이를 구함으로써, 오브젝트를 직접 렌더링하는 것을 피합니다.
  • Scrolling은 최하위 캐스케이드에만 적용되며, 캐스케이드 범위가 비교적 좁을 경우 많은 오브젝트가 시야뿔과 교차하게 되어 최적화 효과가 제한될 수 있습니다;
  • 여러 프레임 간격으로 캐시를 갱신하여 대역폭 부하를 완화합니다.

향후에는 Local ShadowMap Atlas와 캐싱 메커니즘도 지원할 예정입니다. 두 개 이상의 국소 광원 투영을 지원할 뿐만 아니라, 광원이 화면에서 차지하는 비율에 따라 ShadowDepth의 정밀도를 동적으로 조정할 예정이며, 원거리의 국소 광원에 대해서는 정적 캐싱 지원도 도입할 계획입니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

1. 캐릭터 조명 방안

캐릭터 라이팅 작업 과정에서 여러분도 어느 정도는 다음과 같은 문제들을 겪어보셨을 것입니다:

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

이러한 문제들을 세분화해 보면, 다음 3가지 요구 사항으로 요약할 수 있습니다:

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

이러한 요구 사항을 바탕으로, 구체적인 캐릭터 조명 방안을 설계했습니다.

조명은 【직접광】과 【간접광】으로 구성되며, 일반적으로는 하나의 평행광만 존재합니다. 이를 우리는 보통 ‘주광’이라고 부릅니다. 주광은 장면을 정상적으로 비추지만, 캐릭터를 비출 때는 주광의 방향을 유지한 채, 후처리 박스와 유사한 방식으로 주광의 색상과 밝기를 덮어씁니다. 구체적인 구현 방법은 다음과 같습니다:

  • 셰이더에 캐릭터의 주광 색상을 하나 더 전달하면, 캐릭터의 셰이더가 주광을 가져올 때 얻는 색상은 캐릭터의 주광 색상이 됩니다;
  • 캐릭터에 윤곽광으로 사용할 수 있도록 그림자를 드리우지 않는 평행광을 하나 더 제공했으며;
  • 동시에 캐릭터를 위해 두 개의 추가 조명을 할당해 두었습니다. 추가 조명은 임의의 점광원과 스포트라이트 조합으로 설정할 수 있으며, 범위 내의 캐릭터와 장면 오브젝트를 정상적으로 비출 수 있습니다(2미터 크기의 칸당 최대 4개의 추가 조명을 배치할 수 있으므로, 2개의 조명을 캐릭터에 할당했습니다).

간접광의 경우, Unity의 LightProbe 시스템을 사용하여 프로브를 생성하고, 간접광을 프로브에 저장하는 부분을 직접 구현했습니다. 또한 씬의 프로브와 캐릭터의 프로브를 두 세트로 나누어 각각 저장하고 사용하도록 했습니다;

주변광 하이라이트의 경우 동일한 반사 프로브를 사용하지만, 일부 특수한 재질의 경우 재질 설정에서 큐브맵을 입력하여 환경 반사 프로브를 덮어쓸 수 있는 옵션을 제공합니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

캐릭터에 영향을 미치는 조명 정보를 ScriptableObject에 저장하고, 조명 담당자가 조정을 마친 후 템플릿으로 저장합니다.아래 오른쪽 그림은 캐릭터 조명 설정에 저장된 정보로, 앞서 언급한 두 개의 평행광과 두 개의 추가 광원, 프로브에 저장된 SH, 그리고 후처리 박스에서 추가로 조정할 수 있는 정보와 사용자 정의 반사 프로브 사용 여부 등이 포함되어 있습니다.

마지막으로 매니저를 통해 스택과 유사한 방식으로 관리합니다. 여기서 스택 관리 방식을 선택한 것은 구체적인 사용 방식과 밀접한 관련이 있습니다. 일반적으로 새로운 조명 설정을 불러오는 것 외에 가장 자주 사용되는 기능은 바로 이전 조명 설정 효과를 복원하는 것이기 때문에, 스택 관리 방식을 채택했습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

이 단계에서 이 솔루션은 캐릭터와 장면을 분리하고, 실시간으로 전환할 수 있으며, 사용자 정의 유지 템플릿을 지원하는 등의 기능을 갖추게 되었습니다. 마지막으로 조명 전환 방식을 스토리 에디터상의 이벤트 행동으로 정의하여, 조명 애니메이션을 연속적으로 연결할 수 있도록 지원했습니다.

연결 가능한 조명 효과는 다음과 같습니다:

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

아래 그림은 프로젝트 전용 시나리오 편집 도구로, 기본적으로 모든 조명 및 그림자 관련 매개변수와 일부 후처리, 물리 효과는 이 시나리오 편집기에서 제어할 수 있습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

2. 그림자 클로즈업

빛과 그림자는 항상 뗄 수 없는 관계입니다. 앞서 언급했듯이, 저희의 그림자 방식은 3단계 CSM에 클로즈업 그림자를 더한 것으로, 구현 원리는 캐릭터의 지정 가능한 뼈대를 구의 중심으로 삼아 지정된 반지름의 구체를 구성하고, 이 구체를 이용해 해당 픽셀의 섀도우 맵을 구축 및 생성하는 것입니다.스크린 스페이스 쉐이딩 시 정밀도 비교를 수행하여, 이 쉐도우 맵과 캐스케이드 쉐도우 중 정밀도가 더 높은 것을 해당 픽셀의 쉐도우 맵으로 사용합니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

아래 GIF를 보면 알 수 있듯이, 캐릭터는 원래 전체가 주광 그림자 안에 있었지만, 클로즈업 그림자를 켜면 주광에 정상적으로 비춰지게 되는데, 이는 클로즈업 그림자가 근거리 클리핑 평면을 수정했기 때문입니다.즉, 우리의 클로즈업 쉐도우는 별도로 매개변수를 조정할 수 있는 독립적인 쉐도우 이미지이며, 구체적인 매개변수에는 원거리 및 근거리 클리핑 평면, 최대 거리, 어떤 조명을 사용할지, 그리고 종종 가장 골치 아픈 문제인 바이어스(bias)가 포함됩니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

3. 피부 디테일

피부 표현에 있어 몇 가지 세부적인 부분에 초점을 맞추었는데, 구체적으로는 얼굴이 붉어지는 효과와 땀이 나는 효과를 예로 들 수 있습니다.

  • 얼굴이 붉어지는 효과

일반적으로 얼굴이 붉어지는 과정은 서서히 변화하며, 부위에 따라 붉어지는 정도가 다른 과정입니다. 예를 들어, 대부분의 사람은 얼굴이 붉어질 때 먼저 귀부터 붉어지기 시작하고, 그 다음 뺨으로 번지며, 때로는 얼굴 전체가 붉어지는 경우도 있습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

이 과정을 재현하기 위해, 우리는 화면을 더욱 생생하고 사실적으로 만들기 위해 다음과 같은 방법을 채택했습니다:

  • 수작업 마스크: 마스크 텍스처를 기반으로 홍조 영역, 색상 그라데이션 및 강도를 제어합니다.
  • 다중 채널 독립: 얼굴, 귀, 코 등 각 부위의 홍조 효과를 개별적으로 조절할 수 있습니다.
  • 사전 저장된 변화 과정: 얼굴이 붉어지는 과도기 과정이 채널별로 해당 곡선에 기록되어 자연스러운 감정 표현을 구현합니다.
  • 땀 흘리는 효과

저희 게임에는 운동 동반 기능이 제공되며, 주인공이 운동 훈련을 할 때 곁에서 함께해 주기 때문에 이에 상응하는 땀 흘리는 효과도 구현해야 합니다. 구체적인 구현은 주로 다음 세 가지 측면을 통해 이루어집니다:

  • 머티리얼과 파티클의 결합: 머티리얼 셰이더는 피부 표면의 광택과 촉촉함을 시뮬레이션하며, 땀방울 효과는 피부에 달라붙은 머티리얼 구현과 VFX 구현 중 선택할 수 있습니다.
  • 마스크를 통해 땀이 나는 부위 제어: 마스크 이미지를 사용하여 땀이 나는 부위를 지정함으로써, 땀 효과의 사실성과 예술성을 높입니다.
  • 데이터 자동 전달: 마스터 파라미터의 변화에 따라 머티리얼 및 파티클 파라미터가 자동으로 조정됩니다.

아래 그림은 몇 가지 구체적인 계산 방법과 최종 결과의 예시입니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

△ 땀방울 생성 위치를 계산하고 땀방울 위치의 거칠기를 수정한다

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

△ UV 격자 ID를 통해 난수를 생성

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

△땀방울이 떨어지는 궤적을 모사함

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

△운동 동반 시스템의 땀 흘리는 효과

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

물리 효과에 대한 공유는 주로 네 가지 측면, 즉 천 시뮬레이션 구현, 실시간 퍼포먼스 제어, Unity DOTS 기반 개발, 충돌 감지 모듈을 중심으로 이루어집니다.

1. 직물 시뮬레이션 구현

프로젝트에서 발생하는 몇 가지 구체적인 문제를 해결하기 위해, 저희는 자체적으로 원단 시뮬레이션 시스템을 개발했습니다.

골격 기반 직물 시뮬레이션 시스템: StrayCloth

StrayCloth는 XPBD와 서브 스텝을 결합한 시뮬레이션 방식을 채택하고 있습니다. PBD에 비해 XPBD의 장점은 반복 횟수와 시간 단계에 대한 의존성을 없앴다는 점이며, 서브 스텝과 결합함으로써 해법의 수렴 효과를 현저히 향상시킬 수 있습니다.

다소 특이한 점은, 우리가 뼈대를 시뮬레이션 입자로 사용한다는 것입니다. 즉, 각 입자는 위치 정보 외에도 회전 정보를 가지고 있습니다.

구체적인 서브스텝 구현에서, 우리는 다양한 성능 부하 시나리오에 맞춰 1/200에서 1/300 사이의 동적 서브스텝 시간을 적용합니다. 또한 시나리오 내의 움직이는 객체에 대해 실행 보간을 수행함으로써, 충돌 효과가 더욱 안정적으로 나타납니다.사실 운동 보간은 성능 오버헤드가 그리 크지 않지만, 정적 입자, 충돌체, 풍장 등 유형이 다양하기 때문에 실제로 적용하기에는 상당히 번거롭습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

왜 프록시 메시 대신 스켈레톤을 사용하는가? 주로 다음 세 가지 이유 때문이다:

  • 『연애와 심공』은 스토리, 전투, 의상 변경 등에서 복잡한 표현이 요구되는데, 스켈레톤 방식을 사용하면 애니메이션 전환과 계산 처리를 원활하게 수행할 수 있습니다;
  • 제어성 요구 사항과 모바일 기기의 성능 제한을 고려할 때, 스켈레톤 방식은 아트 팀이 자유롭게 조정할 수 있는 여지가 더 큽니다;
  • 스켈레톤과 제약을 활용하면 메쉬와 유사한 구조를 구축하여 비슷한 효과를 얻을 수 있습니다.

골격 제약 방안

기존의 스켈레톤 클로딩 솔루션에서 스켈레톤 제약 조건 구현은 주로 로컬(Local) 및 글로벌(Global) 형상 제약 조건을 기반으로 이루어지는데, 이는 간단하고 빠르긴 하지만 뚜렷한 단점이 있습니다. 바로 클로딩 시뮬레이션에 사용할 때 결과가 만화풍으로 치우쳐, 《사랑과 심우주》가 추구하는 3D 사실주의 스타일에 부합하지 않는다는 점입니다.게다가 글로벌과 로컬이라는 두 가지 굽힘 강도 매개변수가 있어 매개변수 조정이 직관적이지 않아, 아트 팀의 조정이나 다양한 장면에서의 효과 일관성을 맞추는 데 불리합니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

따라서, 골격 제약 방식으로는 Cosserat Rod 기반의 골격 제약을 선택했습니다. 이 방식의 장점은 다음과 같습니다:

  • 효과 면에서 더욱 자연스러우며, 『연애와 심우주』의 전반적인 사실주의 미술 표현 스타일에 잘 어울립니다.
  • 매개변수 조정이 더욱 직관적이며, 세 축의 강도가 분리되어 있어, 치마를 시뮬레이션하는 경우와 같이 특정 상황에서 이방성 굽힘 강도를 활용하여 크리노린의 효과를 근사적으로 재현할 수 있습니다.
  • 머리카락 시뮬레이션에서 바로 재사용할 수 있으므로, 머리카락과 옷에도 동일한 제약 조건 세트를 적용할 수 있습니다.

구체적인 효과는 최신 일일 패스의 성과를 참고하시면 됩니다:

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

원단과 캐릭터의 연결

소재와 캐릭터의 연결은 주로 다음 두 가지 방식으로 이루어집니다:

  • 계층: 정적 스켈레톤은 캐릭터의 스켈레톤 애니메이션에 직접적인 영향을 받아, 계층 관계에 따라 이동합니다.
  • 이 방법은 비교적 간단하며, 연결 부위가 비교적 경직된 경우 좋은 성능을 보입니다. 하지만 팔꿈치, 어깨, 허리 등 여러 뼈가 만나는 복잡한 부위나 어느 정도 신축성이 있는 곳에서는 원단과 캐릭터가 분리되는 현상이 쉽게 발생합니다.
  • 흡착: 정적 파티클은 캐릭터 모델의 앵커 트라이앵글에 의해 제어됩니다. 메쉬를 병렬로 베이크하고, 무게중심 좌표를 통해 매 프레임마다 계산하여 업데이트합니다.

삼각형의 퇴화 현상이 발생하는 특수한 경우, 삼각형 꼭짓점의 스키닝 본 변환을 사용하여 가중치 균형 조정을 통해 정적 입자의 변환을 업데이트합니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

충돌 시나리오

충돌 처리 방식에서는 다이내믹 BVH를 사용하여 씬 충돌의 브로드 페이즈(broad phase)를 관리하며, 각 캐릭터는 서브 트리로 처리되어 그 내부의 충돌체를 서브 트리 노드로 포함합니다.

또한, 역할 ID, 공유 가시성, 컴포넌트 유형이라는 세 가지 규칙을 통해 서로 다른 역할과 컴포넌트에 대한 충돌 규칙의 공유 관리 기능을 구현합니다.

narrow phase에서는 직접 contact를 생성하지 않고, 대신 충돌체 쌍을 캐싱해 두었다가 substep에서 구체적으로 해결합니다. 우리가 채택한 substep 방식의 장점 덕분에, 대부분의 경우 DCD를 직접 사용하면 고속 이동 시 발생하는 관통 문제를 피할 수 있으므로, ccd나 predictive contact와 같은 추가 연산을 도입할 필요가 없습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!
  • 메쉬 콜라이더 구현

plane, capsule, box와 같은 파라메트릭 지오메트리 콜라이더의 경우, 입자나 에지와의 충돌을 비교적 간단하게 처리할 수 있습니다. 반면 어깨와 목, 가슴과 등 같은 복잡한 부위에서는 파라메트릭 지오메트리가 캐릭터 모델의 형태를 정확하게 표현하기 어려워, 시각적으로 투과 현상이 발생하기 쉬우므로, 이러한 부위에는 메쉬 콜라이더를 광범위하게 사용합니다.

그러나 메쉬 콜라이더는 불규칙한 오목한 형체로서, 때로는 닫힌 형체가 아닐 수도 있으며,정밀한 충돌 효과를 구현하는 것은 매개변수화된 기하체에 비해 상대적으로 어렵습니다. 특히 모바일 기기에서는 더욱 그렇기 때문에, 우리는 해싱(hashing)을 삼각형의 대략적인 검색 방식으로 채택하고, 캐시된 인접 삼각형 결과를 결합하여 반복 계산 시작 전에 한 번의 입자-삼각형 충돌 쌍을 생성합니다.후속 반복 과정에서 입자가 삼각형의 범위 내에 있는지 판단하고, 범위를 벗어날 경우 모델의 삼각형 인접 관계를 통해 단계 폭을 제한하며 삼각형을 검색하여 가장 가까운 삼각형을 찾아내고, 그 결과를 캐싱하여 다음 번에 사용합니다.

아래의 GIF 이미지는 프로젝트에서 나타난 몇 가지 구체적인 예시이며, 성능이 비교적 안정적인 것을 확인할 수 있습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

페이스 콜라이더

얼굴 콜라이더는 특수한 메쉬 콜라이더로 볼 수 있습니다. 기본 메쉬 콜라이더에 비해 형태가 비교적 고정되어 있고 매끄러우며, 모델 중심을 기준으로 삼각형이 겹치는 부분이 거의 없기 때문에, 16×16 큐브맵을 사용하여 각 방향의 삼각형을 미리 계산합니다.이렇게 하면 충돌 계산 시 인접한 삼각형을 빠르게 찾아낼 수 있습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

층간 충돌

게임 내에서 직물 시뮬레이션의 자체 충돌 처리는 가장 다루기 어려운 부분입니다. 성능상의 이유로, 저희가 제시한 해결 방안은 다음과 같습니다:

  • 검색 가속 구조로 스페이셜 해싱 사용
  • 미술 측에서 미리 레이어를 구분하여, 레이어 간의 입자와 삼각형 충돌만 고려한다
  • 레이어 간에 걸리는 현상을 방지하기 위해, 입자와 삼각형의 단일 법선 방향에 대한 충돌만 계산합니다.

그래픽스 엔진이 사전에 원단을 여러 층으로 나누고, 이 층들 간의 충돌만 고려합니다. 해시 테이블을 조회 가속 구조로 사용하며, 층 사이에 끼는 현상을 방지하기 위해 단일 법선 방향의 충돌만 고려합니다. 이미 관통된 경우에는 건너뛰고, 후속 단계에서 수정하도록 합니다.

실제 적용 시, 우리는 이전 서브스텝의 입자 위치를 사용하여 현재 입자 위치와 충돌 여부를 확인하는데, 이를 통해 데이터를 간단하게 분리하여 의존성을 피할 수 있습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

층간 투과 분리

레이어 충돌로 인해 이미 관통된 부분에 대해서는, ‘untangling cloth’ 기법을 참고하여 가벼운 해결 방안을 적용했습니다. 천을 레이어별로 나누고, 천의 고정점을 기점으로 하여 각 레이어의 변과 삼각형의 교점을 계산했습니다. 우리 자산의 구조는 반드시 균일한 메쉬이기 때문에,따라서 메쉬 교차점을 통해 다른 입자가 속한 삼각형을 비교적 간단하게 추론할 수 있으며, 마지막으로 관통된 입자-삼각형 쌍에 스프링 구속을 적용하여 관통 문제를 해결합니다. 실제 적용 시 서브스텝(substep)의 영향으로 관통 확률이 상대적으로 낮기 때문에, 프레임별 및 블록별 실행 방식을 채택하여 성능 부담을 줄였습니다.

2. 실시간 공연 제어

『연과 심공』의 스토리 전개에서 나타나는 대부분의 물리 효과는 컷신을 통해 구현된 다양한 물리 효과의 제어와 조정을 바탕으로 합니다. 우리 팀의 툴 개발자들은 매우 강력한 컷신 툴 세트를 개발하고 유지 관리해 왔으며, 이를 기반으로 우리는 물리 효과를 구체적으로 조절할 수 있는 다양한 기능 트랙을 개발했습니다.

이쪽은 저희 게임의 ‘Cutscene Physics Track’ 예시입니다. 아트 팀원들이 화면 표현에 대해 매우 세심하게 신경 쓰기 때문에, 전체 물리 트랙의 구성이 상당히 복잡하다는 것을 알 수 있습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

△컷신 물리 트랙 예시

SmoothBlendPose 트랙

연기 과정에서 매우 흔히 발생하는 문제 중 하나는 동작이 순간적으로 전환될 때 발생하는 물리적 떨림으로, 스토리 연기를 할 때나 의상을 갈아입을 때 모두 자주 나타납니다.

저희는 비교적 범용적인 방법을 개발했습니다. 초기 물리적 자세를 기록해 두고, 전환 시 초기 자세와 현재 자세 간에 자세 보간 계산을 수행함으로써, 떨림을 크게 완화할 수 있습니다.물론 이 방법은 약간의 시간적 오버헤드를 초래하며, 일반적으로 수십 밀리초 정도 소요되지만 대부분의 경우 허용 가능한 수준입니다. 또한 보간 횟수나 보간 단계 크기 등의 매개변수를 제공하여 아트 팀이 실제 필요에 따라 조정할 수 있도록 했습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

포즈 추적

물론, SmoothBlendPose에는 한계가 있어, 특히 복잡한 연기 장면의 컷 전환 시 완전히 매끄러운 결과를 보장할 수는 없습니다.또한 비교적 직접적인 해결책도 제공합니다. 즉, 특정 시간 프레임의 물리 상태를 오프라인에서 직접 저장해 두고, 재생 시 저장된 물리 상태를 직물에 직접 적용하는 방식입니다. 이렇게 하면 컷 전환으로 인한 문제를 완벽하게 피할 수 있습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

파라미터 트랙 편집

단일한 물리적 자산만으로는 스토리 속 다양한 장면에서 요구되는 표현을 충족시키기 어렵습니다. 예를 들어, 때로는 천의 질감을 더 부드럽게 또는 더 단단하게, 감쇠를 더 크게 또는 더 작게 조절하고 싶을 때가 있습니다.저희는 매개변수 편집 트랙을 제공하며, 이 트랙을 통해 매개변수를 실시간으로 편집하고 수정할 수 있습니다. 대부분의 매개변수는 오버라이드할 수 있어, 짧은 시간대의 프레임에 대해 매우 편리하게 수정할 수 있습니다. 이러한 매개변수 수정은 특수 효과 제작에도 활용될 수 있는데, 예를 들어 GIF 애니메이션에서 제약 조건 매개변수 편집을 통해 구현되는 파열 효과 등이 있습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

애니메이션 트랙

완전한 물리 효과만으로는 실제로 화면의 모든 측면을 표현하기에 충분하지 않으며, 많은 경우 표현을 위해 애니메이션과 물리 효과를 결합하여 상호작용을 구현해야 합니다. 우리는 애니메이션 트랙을 통해 애니메이션과 물리 효과의 연결 및 융합을 실현하고, 서로 다른 프레임 범위에서 표현을 정밀하게 제어합니다.실제 제작 과정에서 DCC 내의 동작과 엔진에 적용된 최종 표현 간에는 상당한 차이가 발생합니다. 엔진의 실시간 리그 시스템을 수정한 후에도 애니메이션이 다른 부분과 겹치는 현상이 발생할 수 있으므로, 애니메이션 융합을 기반으로 물리 충돌 효과를 중첩시켜 이러한 겹침 현상을 방지할 수 있습니다.

이 GIF에는 목걸이의 물리 효과와 애니메이션 간의 상호작용이 보여지며, 물리 상태에서 애니메이션 상태로의 전환과 서로 다른 애니메이션 간의 전환이 포함되어 있습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

충돌 트랙 및 바람 트랙

Collider Track과 Wind Track을 사용하면 컷신 내에서 콜라이더와 풍장을 동적으로 생성하거나 제거할 수 있습니다. 화면의 요구 사항에 따라 콜라이더와 풍장의 상태를 유연하게 변경할 수 있습니다. 캐릭터, 부품 유형, 그리고 패브릭 레이어 그룹화를 통해 영향을 미칠 대상의 범위를 세밀하게 제어할 수 있습니다.

또한, 충돌체와 풍장 궤도의 대부분의 매개변수에는 애니메이션 프레임 제어를 적용할 수 있는데, 여기에는 충돌체의 형태와 크기, 풍장의 방향, 범위, 강도, 난류 등이 포함되어 있어, 아트 팀이 물리 효과를 효과적으로 관리하고 변화를 정밀하게 제어할 수 있습니다.

이 GIF에는 궤도 캡슐과 풍력 발전 단지의 몇 가지 예시가 나와 있습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

3. Unity DOTS 기반 개발

취업 + 버스트 + 수학

DOTS는 매우 강력한 도구로, C# 레이어에서 고성능 멀티스레드 개발을 구현할 수 있습니다. 저희 물리 시스템은 DOTS를 사용하여 C# 레이어에서 완전히 구축되었으며, 기능 개선 및 디버깅이 매우 편리합니다. 현재 최대 2,000개 이상의 스켈레톤 파티클 시뮬레이션을 지원할 수 있습니다.

물론, 저희도 프로젝트 내에서 목표에 맞춰 몇 가지 최적화를 실시하여 성능을 한층 더 향상시켰습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

작업 캐시

시뮬레이션에서 작업의 수와 의존 관계가 정해져 있고, 작업 데이터가 자주 변하지 않기 때문에, 한 프레임 내에서 일반적으로 동일한 수와 의존 관계를 가진 작업 그룹이 여러 번 반복 실행됩니다. Unity Jobs는 작업을 시작할 때마다 매번 작업을 새로 생성해야 하는데, 미리 작업을 시작하여 이를 완화할 수는 있지만 여전히 메인 스레드를 차단하게 됩니다. 또한 작업 실행이 완료된 후에는 clear를 수행해야 합니다.이러한 관찰을 바탕으로, 우리는 ‘Cache Job’ 방안을 개발했습니다. 작업 데이터를 미리 생성해 두고 매 실행 시 이를 재사용함으로써, 매번 작업을 새로 생성할 때 발생하는 성능 오버헤드를 방지합니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

구현은 비교적 간단합니다. 전용 구조이기 때문에 몇 가지 고정된 사용 시나리오만 고려하면 되기 때문입니다. 캐시 작업을 저장하기 위해 Atomic Queue를 추가로 추가했으며, 구체적인 작업은 fetch and add array를 사용하여 저장합니다. 오른쪽은 워커가 캐시 작업을 실행하는 흐름을 보여주는 다이어그램입니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

네온 인트린식스

Burst는 다양한 플랫폼에 맞춰 고성능 SIMD 코드를 생성하며, Burst Inspector에서 이를 매우 편리하게 확인할 수 있습니다. Burst Inspector를 통한 검토와 실제 기기 테스트를 거쳐, 특정 상황에서는 Arm Neon 내장 함수를 직접 작성하여 성능을 더욱 향상시킬 수도 있습니다.

여기서는 벡터에 0보다 큰 원소가 있는지 판단하는 구현 예시를 제시합니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!
『사랑과 심연』 첫 번째 심층 기술 공유 회고!

Dot(float4)

내적 연산의 경우, 여기 3가지 방법을 나열해 두었는데, 테스트 사례에서 NEON 내장 함수를 사용하면 수학 함수(mathematics)에 비해 약 30%의 성능 향상을 얻을 수 있습니다.대상 기기가 armv8.2를 지원하는 경우, 새로 추가된 약식 덧셈 명령어를 사용하여 성능을 더욱 향상시킬 수 있습니다. 일반적으로 현재 시중에 나와 있는 대부분의 인기 기기는 armv8.2를 지원합니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

Transpose(float4x4)

전치 연산의 경우, mathematics가 생성한 어셈블리 코드의 성능이 매우 낮은 것으로 나타납니다. NEON 내장 함수를 직접 작성하면 성능을 크게 향상시킬 수 있습니다.

단순히 전치 연산이 필요한 경우라면 교차 읽기를 직접 사용할 수 있습니다. 여기서 이렇게 구현한 이유는, 제가 일반적으로 실제로 사용할 때 4개의 float4를 전치하여 점 곱을 벡터 곱으로 변환하기 때문입니다.

여기서는 이 두 항목에서 비교적 자주 사용되는 예시만 제시합니다. mathematics 코드는 일반적으로 인라인 처리되므로, 구체적인 최적화를 수행할 때는 코드의 문맥에 따라 세부적인 최적화가 필요하며, 버스트 인스펙터와 실제 기기 테스트를 병행하여 구체적인 성능 테스트를 진행할 수 있습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

4. 충돌 감지 모듈

왜 Unity의 검증된 물리 모듈을 버리고 새로 개발해야 하는가?

Unity 자체에는 PhysX 기반의 완성도 높은 물리 모듈 세트가 포함되어 있지만, Unity의 완성도 높은 물리 모듈을 벗어나 새로 개발하기로 한 주된 이유는 다음과 같습니다:

  • 『연애와 심공』에는 상당히 다양한 종류의 플레이 방식이 있으며, 각 플레이 방식 간의 레이어 설정이 비교적 독립적이므로, 각각 별도의 레이어 설정을 유지할 수 있기를 매우 희망합니다.
  • 전투와 같은 일부 모듈은 특수한 트리거 활성화 및 종료 메커니즘이 필요하므로, 이를 저수준에서 지원할 수 있기를 바라며, 실행 흐름에 대해서도 보다 유연한 제어가 가능하기를 희망합니다.
  • 마지막으로 성능 개선 방안에 대해서도 몇 가지 아이디어가 있는데, 바로 충돌 검사가 필요한 경우에만 DOTS를 활용하면 성능을 향상시킬 수 있을지 여부입니다.
『사랑과 심연』 첫 번째 심층 기술 공유 회고!

『사랑과 심연』에서 구현된 내용은 다음과 같습니다:

  • 모든 네이티브 충돌 조회 기능을 기본적으로 구현했습니다.
  • 맞춤형 업데이트 및 트리거 로직
  • 스레드 안전성이 보장된 쿼리 인터페이스로, 상위 계층에서 부담 없이 호출할 수 있습니다.
  • DOTS의 경량화된 구조 구현과 결합하여, 성능 테스트에서 최대 약 15%의 성능 향상을 달성할 수 있습니다.

조회 절차 예시

실제 기기에서는 스레드 수가 4개로 고정되어 있으므로, 메모리 할당기는 스레드 수에 따라 미리 할당해 둘 수 있으며, 할당 시 현재 스레드 인덱스를 기반으로 직접 가져올 수 있습니다.

SAH 기반 동적 BVH를 브로드페이즈 가속 구조로 사용하여, 삽입, 삭제 및 범위 초과 이동 시 현재 작업 노드에 인접한 몇 단계의 노드에 대해 회전 균형을 조정한다.

충돌 감지 기능의 목표가 비교적 포괄적이고 정확도에 대한 요구 사항이 그리 높지 않기 때문에, 성능을 향상시키기 위해 정확도를 어느 정도 희생하고 충돌 감지 알고리즘을 간소화했습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

트리거 프로세스 예시

전투 모듈의 요구 사항을 충족하기 위해 특수한 트리거 로직을 설계했습니다. 트리거의 Enter와 Exit는 반드시 쌍을 이루어 나타나야 합니다. 아래 흐름도에서 볼 수 있듯이, a가 b를 트리거하는 함수 내에서 b를 제거하면 b와 오버랩이 있는 모든 콜라이더가 트리거됩니다.이 부분은 유니티의 기본 동작과 다릅니다. 기본 유니티에서는 트리거 로직 내에서 b를 제거해도 다른 콜라이더의 트리거는 발동되지 않습니다. 마지막으로, History 카운터를 통해 콜라이더의 버전을 표시함으로써, 재사용 로직으로 인해 발생할 수 있는 잠재적인 문제들을 해결했습니다.

『사랑과 심연』 첫 번째 심층 기술 공유 회고!

저희와 함께해 주셔서 감사합니다,

함께 기술을 통해 더 많은 아름다움을 만들어 갑시다!

原创文章,作者:游茶妹儿,禁止转载:https://youxichaguan.com/ko/archives/213133

Like (0)
游茶妹儿
비컨센서스에 베팅, 사용자 수 1,000만 명 돌파… ‘AI+게임’은 이렇게도 즐길 수 있다
Previous 15 hours ago
업계 전문가 원탁 토론: 왜 AI 게임의 발전이 기대에 미치지 못하는가?
Next 15 hours ago

相关推荐