스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
Metal 레이 트레이싱 가이드
Metal 레이 트레이싱으로 게임과 앱의 시각적 퀄리티를 높이는 방법을 알아보세요. Metal 레이 트레이싱 API의 기초를 살펴봅니다. 더 크고 복잡한 장면들을 만들고, 메모리 사용량과 빌드 시간을 줄이고, 머리카락이나 털과 같은 시각적 콘텐츠를 효율적으로 렌더링할 수 있는 최신 개선 사항과 기술을 살펴보세요.
챕터
- 0:09 - Intro
- 1:50 - Build your scene
- 9:48 - Scale with instancing
- 17:34 - Build with parallelization
- 18:34 - Refitting
- 19:47 - Compaction
- 21:14 - Intersect rays
- 26:16 - Debug and profile
- 30:42 - Wrap-Up
리소스
- Accelerating ray tracing using Metal
- Metal for Accelerating Ray Tracing
- Rendering reflections in real time using ray tracing
관련 비디오
Tech Talks
WWDC22
WWDC21
WWDC20
-
다운로드
♪ ♪
안녕하세요, 제 이름은 Pawel Szczerbuk입니다 GPU 소프트웨어 엔지니어죠 Metal은 여러분의 레이 트레이싱 애플리케이션을 복잡하고 세세한 장면에 확장시킬 수 있습니다 프로덕션 렌더링에서는 이미지 충실도의 기반이 되며 게임에서의 레이 트레이싱은 높은 프레임률에 중점을 두고 영상의 품질도 개선하죠 '모아나' 섬 장면은 Metal의 레이 트레이싱으로 렌더링했죠 오늘은 Metal 레이 트레이싱 활용 방법을 얘기하겠습니다 여러분이 사용할 수 있는 새로운 기능을 강조하여 게임과 프로덕션 렌더링의 레이 트레이싱 가속 방법을 다루죠
레이 트레이싱 애플리케이션은 장면 안에서 굴절하는 각각의 빛을 시뮬레이션합니다 Metal 레이 트레이싱으로 렌더링할 때 첫 번째 단계는 장면 기하를 정의하는 거죠 이후 Metal에서 가속 구조를 빌드하는데 여러분의 기하를 포함하며 GPU 가속을 이용한 인터섹션의 효율적인 쿼리 작업을 수행합니다 GPU 함수에서 여러분의 장면과 교차하는 빛을 생성하세요 셰이더에 Intersector 오브젝트를 만들어서 빛과 가속 구조를 해당 오브젝트에 제공하세요 그러면 인터섹션 결괏값이 반환되며 픽셀에 셰이딩 작업을 해야 할지 더 처리해야 할지 결정할 수 있는 정보를 포함합니다 각 부분이 함께 작동하여 장면을 빌드하게 해 주며 인스턴싱을 활용하여 시각적 복잡성을 추가하고 레이 인터섹션을 수행하게 합니다 여러분이 레이 트레이싱 애플리케이션으로 작업할 수 있는 놀라운 툴도 제공하죠 모든 것은 장면 빌드에서 시작합니다 Metal 레이 트레이싱 API는 여러 형태의 기하를 지원하죠 모든 기하는 가속 구조에 저장돼 있습니다
가속 구조가 기하를 재귀적으로 파티션하여 레이 트레이싱 절차를 빠르게 합니다 이렇게 하면 빛과 교차하지 않는 모든 기하를 빠르게 제거할 수 있죠 가속 구조는 3단계로 준비할 수 있습니다 먼저 실제 기하를 제공할 곳에 가속 구조 디스크립터를 생성하세요 디스크립터가 만들어졌으면 가속 구조를 할당한 뒤 빌드하면 됩니다 1개 이상의 기하 디스크립터가 가속 구조 디스크립터에 들어 있죠 Metal에서는 세 종류의 기하 디스크립터를 지원합니다 삼각형은 우리에게 익숙한 프리미티브로 컴퓨터 그래픽에서 거의 모든 것을 모델링할 때 사용하죠 바운딩 박스 프리미티브는 커스텀 인터섹션 함수로 전체가 정의되며 빛이 바운딩 박스에 닿으면 Metal이 호출합니다 올해 곡선이 추가됐죠 머리카락과 털을 렌더링할 때 좋습니다 삼각형을 이용하여 가속 구조를 만들려면 단일 기하 요소를 위한 삼각형 기하 디스크립터를 만드세요 버텍스 버퍼, 인덱스 버퍼 삼각형 개수를 제공해야 하죠 바운딩 박스 기하도 비슷하게 작동하지만 버텍스를 제공하는 대신 여러분의 기하를 에워싸는 바운딩 박스를 제공해야 합니다 추가로 인터섹션 함수를 제공하면 바운딩 박스 프리미티브에 빛이 닿을 때 Metal이 인보크하죠
인터섹션 함수를 구성하는 자세한 내용이 궁금하면 2020년 'Metal로 레이 트레이싱 발견하기' 세션을 참고하세요
머리카락, 털, 초목과 같은 기하는 수천, 수백만 개의 프리미티브가 있을 수 있죠 이런 것들은 보통 가늘고 부드러운 곡선으로 모델링합니다 삼각형으로 이런 곡선의 근사치를 계산하는 대신 Metal의 새로운 곡선 프리미티브를 사용할 수 있죠 카메라가 줌인해도 곡선이 부드럽게 유지됩니다 삼각형과 비교했을 때 곡선의 메모리 사용이 더 압축돼 있어서 가속 구조 빌드가 빨라지죠
완전한 곡선은 곡선 세그먼트를 연결하여 만듭니다 곡선의 각 세그먼트는 자체 프리미티브가 있고 Metal이 세그먼트마다 고유 프리미티브 ID를 부여하죠 각 세그먼트를 정의하는 건 일련의 제어점으로 곡선의 모양을 제어합니다 제어점들은 기저 함수들을 사용하여 보간되죠 기저 함수에 따라 각 곡선 세그먼트는 2, 3, 4개의 제어점을 갖습니다 Metal은 4개의 곡선 기저 함수를 제공하죠 베지어 캣멀-롬 B-스플라인 그리고 선형입니다 기저 함수마다 장점이 있으므로 유스 케이스에 제일 맞는 걸 고르세요 Metal은 제어점 인덱스 버퍼도 필요합니다 이 버퍼에 있는 각 곡선의 세그먼트는 인덱스가 1개로 세그먼트의 첫 번째 제어점을 대표하죠 예를 들어 제어점이 4개라고 가정합시다 첫 제어점의 인덱스를 이용해 곡선 세그먼트를 정의하려면 인덱스 버퍼에 0을 추가하세요 이 예는 캣멀-롬 기저 함수를 사용하고 있어서 실제 곡선 세그먼트는 제어점 1, 2 사이에만 정의돼 있죠 또 다른 곡선 세그먼트를 연결하려면 제어점을 하나 더 추가하면 됩니다 추가 곡선 세그먼트는 제어점 1에서 4까지 사용하므로 인덱스 버퍼에 1을 추가하세요 인덱스 버퍼 때문에 두 곡선 세그먼트가 제어점 3을 공유하는데 그런 이유로 곡선이 메모리를 아끼는 겁니다 곡선을 완성할 때까지 이 과정을 반복하세요 새로운 곡선을 시작하려면 제어점을 추가하면 되고 이전 제어점과 겹치지 않아야 하며 상응하는 인덱스를 인덱스 버퍼에 추가하세요 지금까지 설명한 곡선은 추상적인 수학적 오브젝트입니다 곡선들을 렌더링하려면 3차원의 모양이어야 하죠 각 제어점은 반지름이 있는데 곡선의 길이를 따라 보간되어 있죠 기본적으로 곡선은 3D 원통형 크로스 섹션으로 렌더링됩니다 근접하여 보는 곡선에 좋죠 먼 곳에서만 보이는 곡선을 위해 Metal이 납작한 곡선도 지원합니다 완전한 3D 기하가 필요 없을 때 성능을 개선할 수 있죠
삼각형, 바운딩 박스와 유사하게 곡선 기하 디스크립터로 곡선 기하를 대표합니다 제어점을 포함한 버퍼와 상응하는 반지름 제어점 인덱스를 첨부하세요 제어점의 개수와 실제 곡선 세그먼트의 수를 제어점 버퍼 안에 설정하세요 이는 인덱스 버퍼에 있는 인덱스의 개수와 같을 겁니다 어떤 곡선을 사용하는지 명시하세요 여기선 곡선 세그먼트가 제어점이 4개인 베지어 곡선을 사용하죠 이렇게만 하면 곡선 기하 디스크립터를 설정할 수 있습니다
이제 기하 디스크립터를 생성했으니 가속 구조 디스크립터를 설정할 수 있죠 프리미티브 가속 구조 디스크립터를 삼각형, 바운딩 박스, 곡선 같은 프리미티브 기하에 사용하세요 가속 구조 디스크립터에 기하 디스크립터를 추가하세요 다수의 기하 디스크립터를 단일 가속 구조에 추가하여 기하를 합칠 수 있죠 가속 구조 디스크립터가 준비되면 가속 구조를 위한 메모리를 할당할 수 있습니다 Metal이 메모리를 할당하는 곳을 여러분이 전적으로 결정할 수 있죠
이것은 두 단계의 작업입니다 먼저 빌드에 필요한 오브젝트의 크기를 계산하세요 Metal 기기는 가속 구조에 필요한 할당 크기를 계산하는 방법을 제공합니다 Metal 기기에서 직접 가속 구조를 위한 스토리지를 할당하는 것이 가능하지만 힙에서 할당할 경우 나중에 리소스 관리 오버헤드를 줄일 수 있죠 힙에 추가적인 크기나 얼라인먼트 요구 사항이 있다면 Metal 기기에서 다른 메서드로 쿼리 작업을 할 수 있습니다 이러한 크기를 통해 메모리를 할당하여 가속 구조를 저장할 수 있죠 MTLAccelerationStucture 오브젝트가 이 저장을 대표합니다 오브젝트 중 하나를 할당하려면 makeAccelerationStructure 메서드를 힙이나 Metal 기기에 호출하여 크기를 전달하세요 또한 Metal이 사용할 스크래치 메모리를 가속 구조를 빌드하면서 할당할 겁니다 이 메모리는 GPU를 통해서만 접근해야 하는데 Metal 기기에서 개인 저장 모드 버퍼를 할당하면 되죠
이제 실제로 가속 구조를 빌드할 준비가 됐습니다 빌드 작업을 스케줄링하면 Metal이 여러분을 위해 GPU에 가속 구조를 만들죠 AccelerationStructure CommandEncoder를 이용하세요
이 인코더에 포함된 여러 개의 메서드를 통해 가속 구조를 구성하고 수정할 수 있죠 이때 build 메서드를 호출하고 목표 가속 구조와 디스크립터, 스크래치 버퍼를 설정하세요 Metal가 여러분의 기하를 위해 프리미티브 가속 구조를 빌드하고 이후의 GPU 명령에서 사용할 수 있게 됩니다 이렇게 프리미티브 가속 구조를 이용하여 여러분 장면의 기하를 대표할 수 있죠 더 큰 장면으로 확장하는 걸 돕기 위해 Metal에서 인스턴스 가속 구조도 지원합니다 '모아나'의 섬 장면처럼 복잡하고 세세한 환경을 단일 프리미티브 가속 구조에 저장하려면 엄청난 양의 메모리가 필요하죠 하지만 복잡한 장면에도 반복되는 구조가 있죠 수천 그루의 나무 수백만 개의 잎, 다른 오브젝트를 모두 활용하여 효율적으로 장면을 렌더링할 수 있습니다 장면의 독특한 오브젝트인 산, 산호와 나무들은 프리미티브 가속 구조로 대표할 수 있습니다 이는 전체 장면을 대표하는 인스턴스 가속 구조로 통합할 수 있죠 프리미티브 가속 구조에도 기하가 포함되지만 인스턴스 가속 구조는 다른 가속 구조의 참조를 포함하며 다른 위치, 크기, 방향으로 변환되어 전체 장면을 구성합니다 각 인스턴스에는 변환 매트릭스가 있어 장면에서 참조하는 가속 구조를 배치하죠 인스턴스 가속 구조를 구성하는 것은 프리미티브 가속 구조를 구성하는 것과 비슷합니다 먼저 디스크립터를 만드는 것으로 시작하죠 이번에는 기하 대신 인스턴스가 참조하는 가속 구조처럼 각 인스턴스의 정보를 포함하는 버퍼와 장면에 이를 배치하는 변환 매트릭스를 제공합니다 그리고 GPU에서 가속 구조를 빌드할 때 프리미티브 가속 구조와 같은 방식으로 빌드하세요
MTLInstanceAcceleration Structure를 구성하고 포함되는 인스턴스 수를 설정하여 디스크립터를 만드세요 그리고 프리미티브 가속 구조의 배열을 제공해야 하는데 이는 인스턴스로 참조할 수 있으며 어떤 타입의 인스턴스 디스크립터가 인스턴스 버퍼에 포함될지 지정해야 합니다 Metal은 선택할 수 있는 여러 개의 인스턴스 디스크립터를 유스 케이스에 따라 제공하죠 가속 구조에서 2단계로 인스턴스를 설정할 수 있습니다
먼저 버퍼를 할당하여 인스턴스별 데이터를 저장하세요 버퍼의 크기는 인스턴스의 수와 각 인스턴스 디스크립터의 크기에 따라 달라지지만 다른 Metal 버퍼처럼 할당됩니다 버퍼를 할당했으면 인스턴스 가속 구조 디스크립터에 배정하세요
다음은 인스턴스 버퍼를 가속 구조에 대한 모든 정보로 채워야 합니다 인스턴스별로 디스크립터를 만들고 인스턴스가 참조하는 가속 구조를 지정하세요 인스턴스 가속 구조 디스크립터에 설정했던 배열 내 인덱스로 가속 구조를 식별할 수 있습니다 각 인스턴스에는 변환 매트릭스와 비저빌리티 마스크가 있으며 다른 프로퍼티는 사용 중인 인스턴스 디스크립터의 종류에 따라 다릅니다
마지막 단계는 실제 가속 구조를 빌드하는 건데 프리미티브 가속 구조와 절차가 같죠 빌드 전의 모든 단계는 CPU에서 실행할 수 있습니다 만약 인스턴스의 수가 많으면 인스턴스 버퍼를 채우는 절차에 많은 계산이 요구될 수 있죠 보통의 Metal 버퍼에 인스턴스 디스크립터를 저장하므로 GPU에서 디스크립터를 채워 단계를 빠르게 할 수 있습니다 이는 GPU 가속을 위한 좋은 기회이며 GPU에 작업을 맡기기 전에 가속 구조에 인스턴스가 몇 개인지만 알고 있으면 되죠 하지만 인스턴스 컬링 같은 작업을 하고 싶으면 CPU에서 인스턴스를 컬링해야 최종 인스턴스 개수를 디스크립터에 설정할 수 있습니다 올해부터 이 절차를 GPU에서 할 수 있는데 간접 인스턴스 가속 구조 디스크립터를 이용하는 거죠 간접 디스크립터로 인스턴스를 컬링하고 인스턴스 버퍼를 채우고 최종 인스턴스 개수를 전부 GPU에서 설정할 수 있습니다 GPU 주도의 가속 구조 빌드를 수행하려면 간접 인스턴스 가속 구조 디스크립터를 만드세요 최대 인스턴스 개수를 디스크립터에 설정하고 버퍼에서 최종 인스턴스 개수를 GPU를 통해 작성하죠 이후 인스턴스 디스크립터 버퍼를 설정하기만 하면 GPU에서 인스턴스 설정을 시작할 준비가 됐습니다
인스턴스 버퍼에서는 다른 디스크립터를 사용하죠 간접 인스턴스 디스크립터는 직접 인스턴스 디스크립터와 비슷하지만 디스크립터에 할당하는 것만으로 인스턴스 되는 가속 구조를 식별할 수 있습니다 이렇게 인스턴스 가속 구조를 빌드하는 거죠 지금까지는 인스턴싱의 2단계 모델을 얘기했습니다 이 모델에서는 '모아나' 섬 장면의 숲이 수천 그루의 나무 인스턴스로 구성되어 있죠 하지만 자세히 살펴보면 나무는 같은 잎을 복사한 줄기입니다 새로운 다단계 인스턴스 기능을 이용하여 이러한 구조를 활용할 수 있죠 다단계 인스턴싱을 통해 인스턴스 가속 구조에 프리미티브 가속 구조만 포함할 수 있는 것이 아니라 다른 인스턴스 가속 구조도 포함할 수 있습니다 예를 들어, 이 장면의 야자수는 줄기와 잎 인스턴스를 표현한 인스턴스 가속 구조로 표현할 수 있죠 한편 전체 장면은 야자수의 인스턴스를 포함할 수 있습니다 다단계 인스턴싱의 성능을 모아나 섬 장면이 잘 보여 주죠 2단계의 인스턴싱을 사용할 때 장면에 한 종류의 나무를 추가한다는 건 수백 개, 또는 수천 개의 나무 부분 사본을 추가하는 겁니다 하지만 다단계 인스턴싱으로 나무의 부분을 반복하여 정의한 복잡한 나무의 인스턴스를 추가할 수 있죠 이는 모아나 섬 장면 전체에서 수백만 개의 인스턴스를 아낍니다 하지만 다단계 인스턴싱은 프로덕션 렌더러에만 쓰이지 않죠 게임과 같은 실시간 프로그램에도 귀중한 기능입니다 게임도 2단계 가속 구조 패턴을 사용하여 게임 오브젝트의 인스턴스로 세상을 구축하죠 하지만 게임은 프로덕션 렌더러와 다릅니다 프로덕션 렌더러는 계층 구조로 오브젝트를 재사용하지만 게임은 게임 오브젝트에 긴 목록의 인스턴스를 사용하죠 게임은 동적 콘텐츠를 위해 인스턴스 가속 구조를 프레임마다 다시 빌드하므로 인스턴스 수가 많으면 재빌드에 GPU 시간이 많이 들죠
하지만 게임에서는 많은 콘텐츠가 정적이므로 프레임마다 업데이트할 필요가 없습니다 세상은 정적 가속 구조와 동적 가속 구조로 나누어서 변하는 콘텐츠에 대해서만 가속 구조를 업데이트할 수 있죠 그러면 동적 콘텐츠만 다시 빌드하는데 정적 콘텐츠에 비해 훨씬 적죠 정적 콘텐츠와 동적 콘텐츠를 분리할 때 계층 구조의 단계와 빛 이동에 대한 추가 비용 사이의 균형을 맞추는 게 중요합니다 가속 구조 빌드와 레이 트레이싱이 있는 프레임에서 3단계의 인스턴싱을 사용하여 빌드 시간을 줄일 수 있으며 트레이싱 시간에 사소한 영향을 줘 전체 프레임 시간이 감소하죠 다단계 인스턴싱은 메모리 사용을 줄이고 재빌드 속도를 높이는 좋은 툴입니다 Metal 레이 트레이싱 앱을 최적화하는 다른 방법도 있죠 그중 하나는 빌드 병렬화입니다
전형적인 애플리케이션은 여러 장면이나 장면의 부분을 대표하는 다수의 가속 구조를 빌드하거나 업데이트해야 합니다 이 빌드를 병렬로 실행하면 착수 시간을 확 줄일 수 있죠
가능하다면 언제나 빌드를 배치할 때 같은 명령 인코더에 다수의 빌드를 인코딩하여 병렬로 실행되게 하세요 최대한 많은 빌드를 병렬화하면서 작업 중인 세트가 메모리에 맞는지 확인하세요 또한, 가속 구조 빌드가 완료된 후에는 스크래치 버퍼가 더는 필요 없다는 걸 기억하세요 이는 가속 구조 빌드의 배치에서 사용한 스크래치 버퍼를 또 사용할 수 있다는 의미입니다 가속 구조의 재빌드 시간을 줄이는 제일 좋은 방법은 재빌드 자체를 피하는 거죠 여기서 가속 구조 재피팅이 등장합니다 Metal이 가속 구조를 빌드할 때 주변의 프리미티브를 계층 구조의 상자들로 그룹화하죠 프리미티브가 움직이면 상자들이 장면을 정확하게 대표하지 않으므로 가속 구조를 업데이트해야 합니다 하지만 기하가 조금만 바뀌어도 계층 구조가 여전히 합리적일 수 있죠 새로운 가속 구조를 처음부터 빌드하는 대신 Metal이 기존 가속 구조를 다시 피팅하여 여러분의 기하 내 프리미티브의 새로운 위치를 반영할 수 있습니다 가속 구조를 처음부터 다시 빌드하는 것보다 비용이 저렴하죠 재피팅은 빌드 작업처럼 스크래치 버퍼가 필요합니다 재피팅 스크래치 버퍼의 크기는 앞에서 사용했던 구조체에 있어 가속 구조를 할당합니다 재피팅 작업은 GPU에서 실행되며 가속 구조 명령 인코더로 인코딩되어 있죠 리피팅은 원래 자리 또는 다른 가속 구조에서 운영됩니다
마지막으로 압축을 통해 메모리의 가속 구조 크기를 줄일 수 있죠 가속 구조를 처음 빌드할 때 Metal은 얼마나 많은 메모리가 필요한지 모르므로 보수적으로 추정해야 합니다 가속 구조를 빌드하면 Metal이 이를 대표하기 위한 최소 크기를 계산할 수 있죠 압축을 통해 새로운 가속 구조를 최소 크기로 할당한 뒤 GPU를 사용하여 현재 가속 구조를 새 구조에 복사합니다 프리미티브 가속 구조에는 더욱 중요하죠 압축을 사용하려면 명령을 인코딩하여 GPU의 압축된 가속 구조의 크기를 계산하세요 명령을 실행할 때 Metal이 압축 크기를 제공하는 버퍼에 쓸 겁니다 압축 크기를 읽었으면 새로운 가속 구조를 해당 크기로 할당할 수 있고 기존에서 새로운 가속 구조로 복사 및 압축 작업을 인코딩하세요 명령 버퍼가 완료되면 기존의 가속 구조를 내보낼 수 있습니다 Metal 레이 트레이싱 앱을 최적화하는 방법을 알고 싶으면 2022년의 'Metal 레이 트레이싱 성능 극대화' 세션을 확인하세요 이 섹션에서는 인스턴싱을 설정하는 방법과 새로운 다단계 인스턴싱 기능을 활용하는 방법 대규모 인스턴싱을 처리하는 법을 다뤘습니다 이제는 장면과 빛을 교차할 시간이죠 Metal에서는 일부 명령을 수행하는 GPU 함수에서 레이 인터섹션을 수행합니다 Apple Silicon은 계산과 렌더 명령이 레이 인터섹션을 수행하고 AMD와 인텔에서는 계산 명령에서 레이 인터섹션을 수행하죠 레이 인터섹션을 준비하려면 가속 구조를 명령 인코더에 바인딩하세요 이제 GPU 함수에 있는 가속 구조를 이용하여 레이 인터섹션을 할 수 있습니다 가속 구조 매개변수로 함수를 선언하고 Intersector 오브젝트를 만드세요 Intersector에 속성을 설정하여 최고 성능의 레이 인터섹션을 설정할 수 있습니다 장면에 빛을 교차하려면 ray를 생성하고 intersect 메서드를 intersector 오브젝트에 호출하여 ray와 acceleration_structure를 매개변수로 통과시키세요 이를 통해 인터섹션에 관한 모든 정보가 반환되는데 레이 인터섹션이 이루어진 프리미티브의 종류와 인터섹션의 거리 프리미티브의 ID 등이 있죠
삼각형 교차점에 관한 정보를 얻고 싶다면 intersector에 triangle_data 태그와 intersection_result 타입을 추가하세요 이를 통해 인터섹션 결과 안에 있는 삼각형 무게 중심 좌표를 알아낼 수 있죠 프리미티브 가속 구조를 통한 레이 인터섹션을 다뤘습니다 인스턴스 가속 구조의 레이 인터섹션도 비슷합니다 인스턴스 가속 구조를 바인딩할 때 프리미티브 가속 구조와 비슷한 방식으로 하고 useResource나 useHeap을 꼭 호출하여 GPU 안에 있는 인스턴스 가속 구조 내 가속 구조가 참조되도록 하세요 GPU 함수에서 몇 가지만 수정하면 인스턴스 가속 구조의 레이 인터섹션이 가능합니다 acceleration_structure 타입에 instancing 태그를 추가하고 instancing과 max_levels 태그를 intersector와 intersection_result에 추가하세요 max_level 태그는 인스턴스 구조의 인스턴싱 단계를 명시합니다 예를 들어 '모아나' 섬 장면을 대표하는 가속 구조는 3단계 가속 구조죠 첫 단계는 인스턴스 가속 구조로 전체 장면을 포함합니다 두 번째 단계에 포함된 건 산호초, 나무, 지형 인스턴스죠 세 번째 단계는 나무의 부분인 잎, 꽃, 줄기의 인스턴스가 있습니다 빛이 이 장면과 교차할 때 프리미티브뿐만 아니라 프리미티브를 포함하는 인스턴스도 교차하죠 만약 빛이 이 나무의 잎과 교차한다면 이 나무의 인스턴스와도 교차하고 나무에 있는 잎의 인스턴스와도 교차합니다 Metal은 교차 인스턴스 별로 ID를 기록하여 이러한 상황을 추적하죠 여기서는 처음 교차한 인스턴스가 6번 ID의 나무입니다 두 번째로 교차한 인스턴스는 1번 ID의 잎이죠 빛이 하나의 인스턴스와 교차할 수도 있습니다 예를 들어 빛이 지형과 교차하면 Metal이 지형 인스턴스의 ID만 기록하죠 교차한 인스턴스의 개수와 교차한 인스턴스의 ID는 인터섹션 결과에서 볼 수 있습니다 프리미티브 가속 구조 및 인스턴스 가속 구조와 빛을 교차하는 방법이죠 곡선 프리미티브를 사용할 때 유념할 사항이 있습니다 Metal은 기본적으로 레이 인터섹션을 수행할 때 곡선 프리미티브를 사용하지 않는다고 가정하죠 Metal에 곡선을 사용한다고 알리려면 intersector 오브젝트에서 geometry_type을 설정하면 됩니다 geometry_type을 설정했으면 곡선과 교차할 수 있죠 이전과 같이 인터섹션에 관한 정보를 intersection_result에서 찾으세요 curve_data 태그를 사용하면 intersection_result에 curve_parameter를 포함합니다 곡선의 기반 함수에 이 값을 넣으면 곡선이 빛과 교차한 지점을 계산할 수 있죠 Metal Shading Language에 이런 함수들이 적용돼 있습니다 Metal Shading Language 스펙에서 더 많은 내용을 배울 수 있죠 많은 애플리케이션의 곡선 기하는 한 종류의 곡선으로 대표합니다 예를 들어 장면의 모든 곡선이 원형의 교차 부분이 있는 입방체 베지어 곡선으로 표현될 수 있죠 이때 어떤 곡선을 사용하는지 Metal에 전달할 수 있는데 intersector 오브젝트에 곡선의 프로퍼티를 설정하세요 곡선 프리미티브를 사용할 때 최고의 성능을 낼 수 있죠 장면과 빛을 교차하는 방법을 다뤘습니다 Xcode로 레이 트레이싱 작업을 디버깅하고 프로파일링할 수 있죠
어렵거나 디버깅 문제를 처리할 때 사용할 수 있는 툴은 셰이더 유효성 검사입니다 셰이더의 런타임 확인을 수행하고 크래시나 오염으로 이어질 수 있는 문제를 잡아내죠 셰이더 유효성 검사는 전체 Metal API를 아우르며 최신의 레이 트레이싱 기능을 포함합니다 셰이더 유효성 검사로 셰이더 컴파일링 시간도 확 줄였죠 레이 트레이싱 애플리케이션에서 쉽게 볼 수 있는 길고 복잡한 셰이더 작업 시 아주 유용합니다 여러분을 도울 수 있는 다른 툴은 최첨단 가속 구조 뷰어죠 인터섹션 테스트에 사용하는 장면을 검사하게 해 줍니다 가속 구조 뷰어를 열면 왼쪽의 개요를 통해 가속 구조의 개별 빌드 요소를 기하 프리미티브 단위로 탐색할 수 있죠 여기서 삼각형 기하를 구성하는 개별 삼각형을 목록화합니다 오른쪽에는 뷰포트가 있는데 다양한 강조 모드로 가속 구조를 검사할 수 있죠 예로 '축 정렬 바운딩 박스 이동' 강조 모드는 높은 단계로 이동한 빛을 시각적으로 볼 수 있는데 비용이 많이 드는 인터섹션 테스트입니다 장면 위로 포인터를 움직이면 지정한 방향으로 빛이 교차한 횟수를 인스펙터가 업데이트하죠 또 다른 예는 가속 구조 강조 모드입니다 가속 구조를 다양한 색깔로 시각화하죠 가속 구조 뷰어는 다단계 인스턴싱 기능과 곡선 기하를 지원합니다 뷰포트에서 카메라를 움직이면 일부 나무의 인스턴스 가속 구조와 수풀의 곡선을 찾을 수 있습니다 가속 구조를 식별하려면 뷰포트를 클릭하여 개요에서 볼 수 있죠 이제 야자수 나뭇잎의 가속 구조를 더 자세히 보겠습니다 이 가속 구조에서는 야자수 잎이 곡선으로 이뤄졌죠 뷰포트를 프리미티브 강조 모드로 바꿔서 곡선 세그먼트를 시각화할 수 있습니다 곡선 세그먼트를 제대로 보기 위해 조금 확대하죠 이전 장면의 가속 구조를 선택한 것과 비슷하게 각 세그먼트를 클릭해서 선택할 수 있습니다 레이 트레이싱 작업을 검사할 때 사용할 수 있는 다른 툴은 셰이더 디버거죠 셰이더 코드에서 발생한 문제를 도와줄 수 있습니다 여기는 산출 디스패치로 셰이더의 인터섹션 테스트를 수행하죠 셰이더의 디버깅을 시작하려면 셰이더 디버깅 버튼을 선택하여 팝업 창에서 스레드를 선택하고 디버그 버튼을 클릭합니다
데이터 수집을 마치면 셰이더 실행의 모든 시점에 관한 각 변수의 값을 확인할 수 있죠 프리미티브 ID의 값을 더 자세히 봅시다 디버깅 맥락을 제공하기 위해 셰이더 디버거가 이웃 스레드의 데이터도 제공하죠 여기서 포인터를 값 뷰 위에 올리면 같은 스레드 그룹의 프리미티브 ID를 볼 수 있습니다
어느 앱이든 성능이 중요한 특징이죠 프로파일링 타임라인이 레이 트레이싱 작업 성능의 개요로 다양한 성능 수치를 함께 검사하고 연관 지을 수 있습니다 또한 디버그 내비게이터를 바꿔 작업의 모든 파이프라인 상태를 볼 수 있죠 셰이더 프로파일링 데이터를 통해 내비게이터가 제일 고비용의 파이프라인 상태를 위에 둡니다 파이프라인 상태를 더 확장하면 셰이더 코드가 나타나죠 셰이더를 연 다음에 행별 셰이더 프로파일링 정보에서 개별 셰이더가 실행 시간을 얼마나 썼는지 알 수 있습니다 포인터를 사이드바의 파이 차트로 옮기면 더 자세한 내용이 담긴 팝업 창을 보여 주고 해당 코드 행의 비용을 보여 줍니다 이러한 도구들이 Metal의 레이 트레이싱 기능을 지원하고 Metal 애플리케이션에서 작업할 때 디버깅과 프로파일링에 큰 도움이 되죠
Metal 레이 트레이싱은 훨씬 많은 기능을 지원합니다 애니메이션 작업에 대한 프리미티브와 인스턴스 모션과 커스텀 레이 인터섹션을 위한 커스텀 인터섹션 함수에는 알파 테스트와 같은 개선 사항도 적용됐으며 쿼리 기반 API의 포팅을 지원하는 인터섹션 쿼리가 있죠 Metal 레이 트레이싱 API와 언어 및 도구는 게임, 프로덕션 렌더러와 같은 실시간 렌더링 앱을 지원합니다 최신 Metal 레이 트레이싱 API를 이용하여 장면을 빌드할 때 곡선 기하를 포함한 프리미티브 가속 구조를 사용하세요 인스턴싱과 새로운 다단계 인스턴싱 기능은 대규모의 복잡한 장면으로 확장할 수 있게 해 주죠 여러분의 GPU 함수가 직접 레이 트레이싱 API를 호출합니다 마지막으로 Xcode가 여러분 앱의 디버깅, 프로파일링을 돕죠 레이 트레이싱과 관련된 이전 세션을 확인하면 관련 주제를 더 자세히 살펴볼 수 있으며 샘플 코드와 문서도 있습니다 시청해 주셔서 감사합니다 ♪ ♪
-
-
3:06 - Create triangle geometry descriptor
// Create geometry descriptor: let geometryDescriptor = MTLAccelerationStructureTriangleGeometryDescriptor() geometryDescriptor.vertexBuffer = vertexBuffer geometryDescriptor.indexBuffer = indexBuffer geometryDescriptor.triangleCount = triangleCount
-
3:20 - Create bounding box geometry descriptor
// Create geometry descriptor: let geometryDescriptor = MTLAccelerationStructureBoundingBoxGeometryDescriptor() geometryDescriptor.boundingBoxBuffer = boundingBoxBuffer geometryDescriptor.boundingBoxCount = boundingBoxCount
-
6:42 - Create curve geometry descriptor
let geometryDescriptor = MTLAccelerationStructureCurveGeometryDescriptor() geometryDescriptor.controlPointBuffer = controlPointBuffer geometryDescriptor.radiusBuffer = radiusBuffer geometryDescriptor.indexBuffer = indexBuffer geometryDescriptor.controlPointCount = controlPointCount geometryDescriptor.segmentCount = segmentCount geometryDescriptor.curveType = .round geometryDescriptor.curveBasis = .bezier geometryDescriptor.segmentControlPointCount = 4
-
7:29 - Create primitive acceleration structure descriptor
// Create acceleration structure descriptor let accelerationStructureDescriptor = MTLPrimitiveAccelerationStructureDescriptor() // Add geometry descriptor to acceleration structure descriptor accelerationStructureDescriptor.geometryDescriptors = [ geometryDescriptor ]
-
8:08 - Query for acceleration size and alignment requirements
// Query for acceleration structure sizes let sizes: MTLAccelerationStructureSizes sizes = device.accelerationStructureSizes(descriptor: accelerationStructureDescriptor) // Query for size and alignment requirement in a heap let heapSize: MTLSizeAndAlign heapSize = device.heapAccelerationStructureSizeAndAlign(size: sizes.accelerationStructureSize)
-
8:39 - Allocate acceleration structure and scratch buffer
// Allocate acceleration structure from heap var accelerationStructure: MTLAccelerationStructure! accelerationStructure = heap.makeAccelerationStructure(size: heapSize.size) // Allocate scratch buffer let scratchBuffer = device.makeBuffer(length: sizes.buildScratchBufferSize, options: .storageModePrivate)!
-
8:40 - Encode the acceleration structure build
let commandEncoder = commandBuffer.makeAccelerationStructureCommandEncoder()! commandEncoder.build(accelerationStructure: accelerationStructure, descriptor: accelerationStructureDescriptor, scratchBuffer: scratchBuffer, scratchBufferOffset: 0) commandEncoder.endEncoding()
-
11:30 - Create instance acceleration structure descriptor
var instanceASDesc = MTLInstanceAccelerationStructureDescriptor() instanceASDesc.instanceCount = ... instanceASDesc.instancedAccelerationStructures = [ mountainAS, treeAS, ... ] instanceASDesc.instanceDescriptorType = .userID
-
12:07 - Allocate the instance descriptor buffer
let size = MemoryLayout<MTLAccelerationStructureUserIDInstanceDescriptor>.stride let instanceDescriptorBufferSize = size * instanceASDesc.instanceCount let instanceDescriptorBuffer = device.makeBuffer(length: instanceDescriptorBufferSize, options: .storageModeShared)! instanceASDesc.instanceDescriptorBuffer = instanceDescriptorBuffer
-
12:33 - Populate instance descriptors
var instanceDesc = MTLAccelerationStructureUserIDInstanceDescriptor() instanceDesc.accelerationStructureIndex = 0 // index into instancedAccelerationStructures instanceDesc.transformationMatrix = ... instanceDesc.mask = 0xFFFFFFFF
-
14:06 - Configure indirect instance acceleration structure descriptor
var instanceASDesc = MTLIndirectInstanceAccelerationStructureDescriptor() instanceASDesc.instanceDescriptorType = .indirect instanceASDesc.maxInstanceCount = ... instanceASDesc.instanceCountBuffer = ... instanceASDesc.instanceDescriptorBuffer = ...
-
14:29 - Populate indirect instance descriptor
device MTLIndirectAccelerationStructureInstanceDescriptor *instance_buffer = ...; // ... acceleration_structure<> as = ...; instance_buffer[i].accelerationStructureID = as; instance_buffer[i].transformationMatrix[0] = ...; instance_buffer[i].transformationMatrix[1] = ...; instance_buffer[i].transformationMatrix[2] = ...; instance_buffer[i].transformationMatrix[3] = ...; instance_buffer[i].mask = 0xFFFFFFFF;
-
19:22 - Update geometry using refitting
// Allocate scratch buffer let scratchBuffer = device.makeBuffer(length: sizes.refitScratchBufferSize, options: .storageModePrivate)! // Create command buffer/encoder ... // Refit acceleration structure commandEncoder.refit(sourceAccelerationStructure: accelerationStructure, descriptor: asDescriptor, destinationAccelerationStructure: accelerationStructure, scratchBuffer: scratchBuffer, scratchBufferOffset: 0)
-
20:24 - Use compaction to reclaim memory
// Use compaction to reclaim memory // Create command buffer/encoder ... sizeCommandEncoder.writeCompactedSize(accelerationStructure: accelerationStructure, buffer: sizeBuffer, offset: 0, sizeDataType: .ulong) // endEncoding(), commit command buffer and wait until completed ... // Allocate new acceleration structure using UInt64 from sizeBuffer ... compactCommandEncoder.copyAndCompact(sourceAccelerationStructure: accelerationStructure, destinationAccelerationStructure: compactedAccelerationStructure)
-
21:36 - Set acceleration structure on the command encoder
encoder.setAccelerationStructure(primitiveAccelerationStructure, bufferIndex:0)
-
21:48 - Intersect rays with primitive acceleration structure
// Intersect rays with a primitive acceleration structure [[kernel]] void trace_rays(acceleration_structure<> as, /* ... */) { intersector<> i; ray r(origin, direction); intersection_result<> result = i.intersect(r, as); if (result.type == intersection_type::triangle) { float distance = result.distance; // shade triangle... } }
-
22:24 - Use triangle_data tag to get triangle barycentric coordinates
// Intersect rays with a primitive acceleration structure [[kernel]] void trace_rays(acceleration_structure<> as, /* ... */) { intersector<triangle_data> i; ray r(origin, direction); intersection_result<triangle_data> result = i.intersect(r, as); if (result.type == intersection_type::triangle) { float distance = result.distance; float2 coords = result.triangle_barycentric_coord; // shade triangle... } }
-
22:51 - Set instance acceleration structure on the command encoder
encoder.setAccelerationStructure(instanceAccelerationStructure, bufferIndex:0) encoder.useHeap(accelerationStructureHeap);
-
23:07 - Intersect rays with instance acceleration structure
// Intersect rays with an instance acceleration structure [[kernel]] void trace_rays(acceleration_structure<instancing> as, /* ... */) { intersector<instancing, max_levels<3>> i; ray r(origin, direction); intersection_result<instancing, max_levels<3>> result = i.intersect(r, as); if (result.type == intersection_type::triangle) { float distance = result.distance; // shade triangle... } }
-
24:43 - Find intersected instance information in the intersection result
// Intersect rays with an instance acceleration structure [[kernel]] void trace_rays(acceleration_structure<instancing> as, /* ... */) { intersector<instancing, max_levels<3>> i; ray r(origin, direction); intersection_result<instancing, max_levels<3>> result = i.intersect(r, as); if (result.type == intersection_type::triangle) { float distance = result.distance; for (uint i = 0; i < result.instance_count; ++i) { uint id = result.instance_id[i]; // ... } // shade triangle... } }
-
25:02 - Intersect rays with curve primitives
// Intersect rays with curve primitives [[kernel]] void trace_rays(acceleration_structure<> as, /* ... */) { intersector<> i; i.assume_geometry_type(geometry_type::curve | geometry_type::triangle); ray r(origin, direction); intersection_result<> result = i.intersect(r, as); if (result.type == intersection_type::curve) { float distance = result.distance; // shade curve... } }
-
25:26 - Find curve parameter in the intersection result
// Intersect rays with curve primitives [[kernel]] void trace_rays(acceleration_structure<> as, /* ... */) { intersector<curve_data> i; i.assume_geometry_type(geometry_type::curve | geometry_type::triangle); ray r(origin, direction); intersection_result<curve_data> result = i.intersect(r, as); if (result.type == intersection_type::curve) { float distance = result.distance; float param = result.curve_parameter; // shade curve... } }
-
26:04 - Set geometry type on the intersector for better performance
// Intersect rays with curve primitives [[kernel]] void trace_rays(acceleration_structure<> as, /* ... */) { intersector<curve_data> i; i.assume_geometry_type(geometry_type::curve | geometry_type::triangle); i.assume_curve_type(curve_type::round); i.assume_curve_basis(curve_basis::bezier); i.assume_curve_control_point_count(3); ray r(origin, direction); intersection_result<curve_data> result = i.intersect(r, as); if (result.type == intersection_type::curve) { float distance = result.distance; float param = result.curve_parameter; // shade curve... } }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.