서론
저희는 Hellblade의 "vertical slice" 빌드를 리뷰하기 이전부터 360 비디오 캡쳐 작업을 해 왔습니다. 그리고 360도로 완전히 입체 캡쳐를 했던 첫 번째 씬은 플레이 가능한 첫 번째 빌드 버전 게임 속 인트로 스틸컷이었습니다.
원래 저희는 내부적으로 육면체 캡쳐에 기반한 모노스코픽(monoscopic) 360 비디오 캡쳐를 해서 구에 투사하고 최종 이미지를 위한 추가 이미지 왜곡 작업을 하였습니다.
이 방법은 잘 작동하였습니다. 하지만 모노스코픽 캡쳐 특성 상, 모든 것들이 "크게" 보이고 비디오 속의 주제를 친근하게 묘사할 수 없었기 때문에 여러분이 완전히 주변에 둘러싸였음에도 씬 속에 들어가 있는 느낌 보다는 멀리서 관찰하는 느낌을 받게 됩니다.
(모노스코픽 랜더링 캡쳐의 경우 크기 감각을 잃고 크게 보이는 이유는 뇌가 자기 자신을 인식하는 무의식의 어떤 부분에서 유래한 것으로 보입니다. 제가 말씀드릴 수 있는 것은 머리의 움직임에 따른 시차, 입체적 집합이 없다는 것은 물체가 아주 멀리 있다는 것을 의미한다는 점입니다. 그렇게 되면 이런 정보들을 가지고 여러분의 시야에서 물체가 아주 큰 부분을 차지하고 물체가 아주 크며 멀리 있다는 느낌을 받게 됩니다.)
스테레오스코픽(Stereoscopic) 및 모노스코픽 프레임을 캡쳐해서 직접 실험해 보세요. 둘을 바꿔서 보시면 깊이감이 없어지는 것 뿐만 아니라 크기에 대한 느낌도 없어지고 물체들이 엄청 커 보일 것입니다.
여기서 저희는 어떻게 양안의 적절한 시차를 만들어서 입체 이미지를 만들지에 대한 조사를 시작했습니다. 그리고 그 과정에서 저희는 Kite and Lightning devs 에서 제공한 입체 360 캡쳐 플러그인을 발견했습니다.
이 때는 그냥 GitHub에 업로드 하였고, 언리얼 엔진 4 배포에는 포함되지 않았습니다. 하지만 요즘은 언리얼 엔진 4에 힘입어 더 "특별해 졌습니다." 확인해 보시기를 추천드립니다.
아래에서 Ninja Theory에서 360 입체 영화 캡쳐를 어떻게 했는지 사용한 설정과 워크플로우에 대해 다루어 보겠습니다.
Original non-360 trailer:
360 stereoscopic version:
이 포스트에서는 여러분이 언리얼 엔진 최신 버전을 사용하고 계신 것으로 간주합니다. (작성 기준 4.11.2) 저희는 이 플러그인을 4.9 버전부터 사용해 왔기 때문에 대부분이 직접 적용할 수 있을 것입니다. (몇가지 코드 fix 추가가 필요할 것입니다. 글의 하단 trouble shooting 섹션에서 자세히 다루도록 하겠습니다.)
"Stereo Panoramic Movie Capture" 플러그인 활성화 및 테스트 캡쳐
중요한 것부터 먼저 하도록 하겠습니다. 적절한 플러그인을 활성화 했는지 확인해야 합니다.
에디터를 열고, 편집 - Plugins로 가서 좌측 탭의 "Movie Capture"를 선택하고 오른쪽에 뜬 "Stereo Panoramic Movie Capture."의 "Enabled"에 체크를 합니다.
오른쪽 하단에 Restart Now 버튼을 눌러서 재시작을 해 줍니다. 플러그인 등의 변경을 하고 나면 반드시 재시작을 해야 합니다.
노트: 여러분 엔진 로컬 브랜치의 변경 사항에 따라 플러그인 dll이 오래되어서 사용할 수 없을 것이기 때문에 다시 '빌드'를 해야 할 수도 있습니다.
에디터가 다시 시작하고 에디터의 Plugins 설정에서 Movie Capture로 가서 Enabled 를 다시 더블 클릭해서 체크가 되었는지 확인합니다. (역주, GitHub 버전에서 엔진 재시작시 Missing or imcompatible modules in StereoPanorama plugin ... - 메시지 또는 Plugin 'ExampleDeviceProfileSelector' failed to load because... 로 시작되는 메시지가 나오는 경우에는 엔진 솔루션 파일을 비주얼 스튜디오에서 열고 DevelopmentEditor 로 빌드한 뒤 다시 열어보시기 바랍니다.)
이 플러그인은 콘솔 커맨드로 켜고 끌 수 있는 몇가지 설정이 있습니다. 하지만 이 내용에 대해 다루기 전에 기본 설정에서 예상한대로 잘 동작하는지 빠르게 한 번 캡쳐를 해 보겠습니다.
콘솔 커맨드 창을 켜고(키보드 숫자1 왼쪽의 ` 버튼) SP.OutputDir 에 출력물을 저장할 적당한 주소를 입력합니다. 예제는 아래와 같습니다. (역주: forward slash / 를 사용해야 에러가 나지 않습니다.)
SP.OutputDir F:/StereoCaptureFrames
노트: 여러분은 반드시 에디터나 게임을 켰을 때 이 작업을 해 주어야 합니다. 경로가 저장되지 않기 때문이죠.
그리고 나서 한 프레임 캡쳐를 해 봅니다. 예제는 아래와 같습니다.
SP.PanoramicScreenshot
여기서 여러분은 두 눈에 해당하는 두 개의 이미지가 SP.OutputDir에서 지정한 폴더에 저장되는 동안 아마도 약간의 (1~2분 정도) 화면이 멈춰있는 현상을(hitch) 경험할 것입니다. (지정된 위치에 날짜-시간 의 폴더를 만들고 그 속에 저장됩니다.)
결과물을 잠깐 열고 예상했던 대로 잘 저장되었는지를 확인해 봅니다. 구부러짐 등의 문제는 지금 단계에서는 신경쓰지 않도록 합니다. 이 부분은 나중에 다룰 것입니다. (스크린 스페이스에서 랜더링 되는 light shaft 등은 동작하지 않습니다. 이 부분은 나중에 고쳐나갈 계획입니다.)
코드는 좌안과 우안의 차이를 합쳐내기 위해 자동으로 한 장의 이미지로 바꿔줍니다.
내부적인 워크플로우에서 저희는 '프레임'당 한 장의 완전한 top/bottom 이미지를 덤프하기를 원했습니다. 이렇게 하면 처리 과정 일부로써 수동으로 좌안/우안을 합치지 않고도 쉽게 "모든 프레임을 동영상으로" 만들어 낼 수 있기 때문이었습니다.
이런 방법을 사용하면서 코드 편집을 해도 괜찮으시다면, 아래 간단한(그리고 특정하게 최적화 되지 않은) 코드로 결과물 이미지를 저장하기 전에 좌안과 우안을 합칠 수 있습니다.
1. SceneCapturer.cpp 파일을 엽니다.
2. 합칠지 여부를 결정할 변수를 만들어 줍니다. 내부적으로 저희는 항상 합쳐진 이미지를 출력하려 하기 때문에 이 변수를 const global bool 로 만들었습니다.
- const bool CombineAtlasesOnOutput = true;
3. USceneCapturer::SaveAtlas 의 하단에서 현재 계산할 한쪽 출력분을 조건에 따라 끕니다. (CombineAtlasesOnOutput 체크가 유일하게 새로운 비트입니다.)
IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper( EImageFormat::PNG );
if (!CombineAtlasesOnOutput) //*NEW* - 이미지를 합치려는 경우 실행하지않는 부분.
{
ImageWrapper->SetRaw(SphericalAtlas.GetData(), SphericalAtlas.GetAllocatedSize(), SphericalAtlasWidth, SphericalAtlasHeight, ERGBFormat::BGRA, 8);
const TArray
FFileHelper::SaveArrayToFile(PNGData, *AtlasName);
}
"GenerateDebugImages->GetInt() != 0" 분기에서 바로 에러가 날 것인데, PNGData를 반환하기 때문입니다. 하지만 PNGDataUnrpojected로 반드시 반환해야 하는 것이므로 이 부분도 고쳐 줍니다.
4. 그리고 나서 새로운 코드를 조금 추가하여 USceneCapturer::Tick에서 양안 랜더 결과물을 합치고 단일 이미지를 출력합니다. 이 줄을 검색합니다.
"TArray
아래 코드를 추가합니다. (정확한 위치를 찾으실 수 있도록 주변 코드를 같이 보여드립니다.)
TArray
TArray
//*NEW* - 시작
if (CombineAtlasesOnOutput)
{
TArray
CombinedAtlas.Append(SphericalLeftEyeAtlas);
CombinedAtlas.Append(SphericalRightEyeAtlas);
IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::JPEG);
ImageWrapper->SetRaw(CombinedAtlas.GetData(), CombinedAtlas.GetAllocatedSize(), SphericalAtlasWidth, SphericalAtlasHeight * 2, ERGBFormat::BGRA, 8);
const TArray
// 이름 생성
FString FrameString = FString::Printf(TEXT("Frame_%05d.jpg"), CurrentFrameCount);
FString AtlasName = OutputDir / Timestamp / FrameString;
FFileHelper::SaveArrayToFile(PNGData, *AtlasName);
ImageWrapper.Reset();
}
//*NEW* - 끝
// 처리 시간을 출력합니다.
FDateTime EndTime = FDateTime::UtcNow();
FTimespan Duration = EndTime - StartTime;
UE_LOG( LogStereoPanorama, Log, TEXT( "Duration: %g seconds for frame %d" ), Duration.GetTotalSeconds(), CurrentFrameCount );
StartTime = EndTime;
여기까지가 설정의 끝입니다! (에픽이나 K&L이 이 부분을 플러그인에 추가해줄지도 모르겠네요)
노트: 위에서 출력을 JPEG로 설정하였는데, 이렇게 한 이유는 4096x4096 크기의 PNG 파일을 초당 60개씩 저장하면 하드 공간이 남아나질 않기 때문입니다.
위와 같이 SP.OutputDir을 설정하고 SP.PanoramicScreenshot 을 호출하면 양안 각 이미지를 위/아래에 배치시킨 결과물을 얻을 것입니다.
360 사진 뷰어 장치를 갖고 계신 경우, 이 사진을 그냥 입력하시면 바로 보실 수 있습니다. 멋진 시작이죠!
캡쳐의 역할에 대한 아주 간단한 설명
아래는 캡쳐를 하게 되면 일어나는 일에 관해 아주 높은 수준으로 설명하는 것입니다. 주로 아래의 설정값들이 어떤 역할을 하는지를 설명합니다. 원하시면 다른 정보들을 검색해서 찾아보실 수 있겠지만 일단 플러그인을 사용하기 위해서는 그다지 알 필요가 없을 것입니다.
이해를 위한 핵심은, 여러분이 입체 정보를 올바를 방법으로 양안 이미지 캡쳐할 때 올바른 입체감을 갖게 되는 것은 캡쳐 중점 사이의 수직이등분선 방향 뿐입니다. (예를 들어, 양안 사이) 최소한 캡쳐 목적으로 말이죠. 보이지 않는 곳에서의 캡쳐 프로세스는 표준 게임뷰 전체를 랜더링 합니다. (비록 다른 FOV가 제공되었다고 해도) 그리고 그중 대부분을 버립니다.
현실에서 영역의 너비는 HorizontalAngularIncrement과 CaptureHorizontalFOV에 달려 있지만 '고품질' 결과물을 위해 캡쳐 설정은 아주 작은 영역만을 사용합니다.
이런 사실 때문에 플러그인이 여러분의 360 뷰를 랜더하면 실제로는 서로 다른 여러 캡쳐를 뜨고, 매 번 카메라를 약간 돌려서 나중에 쓸 부분만 조금 추출합니다. 여기에 대해 생각해 볼 한 가지는 개별 샘플을 더 추출할 수록 더 정확한 입체 정보가 주어진 지점에서 얻어진다는 것입니다.
이를 조절하는 두 개의 변수가 있습니다.
- SP.HorizontalAngularIncrement 는 캡쳐마다 수평 방향으로 얼마나 많은 회전을 할 것인지를 조절합니다.
- SP.VerticalAngularIncrement 는 매 번 완전한 360도 수평 회전을 할 때마다 얼마나 위/아래로 회전할 것인지를 조절합니다.
이 둘 중, HorizontalAngularIncrement는 그 중 확연하게 작은 값으로 조정되어야 할 변수입니다. 만약 10도 정도의 값으로 조정한다면 (1바퀴 회전시마다 36번의 캡쳐를 합니다.) 그렇게 되면 depth 를 끔으로 설정해 놓았다는 것을 알게 될 것이고, '밴드' 무늬가 생기며 빠르게 캡쳐가 될 것입니다. 하지만 컬러 밴드가 생기는 것 보다는 뎁스 정보를 인식하는 것이 낫죠.
최고의 품질을 위한 플러그인 설정
SP.OutputDir 외에 더 좋은 품질을 얻기 위해 조정 가능한 다른 여러 CVar값들이 있습니다. 이런 변수들은 얼마나 많은 수평/수직 뷰를 프레임마다 캡쳐할 것인가(더 정확하게 입체적으로 보이는 결과물을 만들어 냅니다.) 출력 해상도, 개인의 뷰 크기(출력 해상도와는 분리되어 있습니다.) 그리고 기타 등등에 관련된 것입니다.
저희가 사용하는 설정을 보여 드리겠습니다. 설정 전문은 StereoPanoramaManager.cpp의 상단에서 보실 수 있습니다.
여기에서 우리가 사용하는 설정들:
SP.OutputDir F:/StereoCaptureFrames
SP.HorizontalAngularIncrement 2
SP.VerticalAngularIncrement 30
SP.CaptureHorizontalFOV 30
SP.ConcurrentCaptures 6
SP.CaptureSlicePixelWidth 720
SP.ShouldOverrideInitialYaw 0
그리고 4096x4096 크기로 캡쳐를 함 (안구당 4096*2048)
SP.StepCaptureWidth 4096
Or for 6144x6144 captures
SP.StepCaptureWidth 6144
보시듯, 저희는 HorizontalAngularIncrement에 아주 작은 값을 사용하여 안구당 한 바퀴마다 180개의 랜더링을 합니다. 값을 더 낮게 설정하면 더 좋은 품질을 얻을 수 있을 것이지만, 프레임 랜더링에 시간이 너무 많이 걸릴 것입니다. 저희가 발견한 "최적값"은 2였습니다. 더 낮게 설정한다고 해서 품질이 크게 증가하지 않는 점이었습니다.
VerticalAngularIncrement에는 아주 큰 값을 적용하였습니다. 수직 방향으로는 입체성이 크게 차이가 나지 않는다는 것을 알게 되었습니다, 적어도 씬에서 저희가 캡쳐 하려는 (캐릭터 모델을 포함하는 것으로, 전부 평평한 벽은 아니니까요!) 씬을 위해서, 여기서는 30정도가 최적값이었습니다.
이 두 파라미터 값에 의해 한 프레임당 얼마만큼의 랜더링이 있을지를 정리해 보는 게 좋겠군요.
하늘을 바라보고 씬을 캡쳐합니다, HorizontalAngularIncrement 값 만큼 회전하고 다시 캡쳐를 합니다, HorizontalAngularIncrement 만큼 또 회전하고... 이런 식으로 한바퀴를 다 돌 때 까지 계속 합니다. 그리고 나서 VerticalAngularIncrement 만큼 아래로 회전하고 위 과정을 반복합니다. 그렇게 완전히 아래를 바라볼 때 까지 계속 합니다.
그렇게 하면 한 장의 360 캡쳐 프레임을 위해서 시행되는 총 랜더링의 수는
(360 / HorizontalAngularIncrement) * (180 / VerticalAngularIncrement) * 2 (안구당 1회이므로)
위와 같은 설정에서는 2520 프레임의 랜더링으로 한 장의 입체 360 이미지를 만들어 냅니다! 넓게 보면, 2520 프레임을 초당 60프레임씩으로 계산하면 42초가 걸립니다.
SP.ConcurrentCaptures 또한 고려할 중요한 변수입니다. 왜냐하면 주로 큰 값으로 설정되었을 때 (기본 값이 30입니다!) 비디오 램이 모자르는 문제에 처할 것입니다.
이 CVar값이 실제로 하는 역할은 얼마나 많은 캡쳐 컴포넌트가 만들어지고 동시에 랜더링 되는지를 정하는 것입니다, 그리고 각각은 연결된 랜더 타겟 메모리가 필요하기 때문에 GPU 메모리 리소스를 많이 차지하게 됩니다. 그리고 제가 "동시"라고 말씀드렸지만 현실에서 GPU는 한 번에 한 가지만 다룰 수 있습니다. 그래서 어떤 순간에는 CPU에서의 랜더된 프레임 처리가 GPU 프레임을 한참 앞서갈 수 있고 이는 보통 30 한참 앞에서 일어납니다. 저희가 찾아낸 최적값은 이 언저리였습니다. 기본적으로 이 값을 아무리 올려도 (30까지 조사) 프레임마다 2초 정도만을 줄일 수 있을 뿐인데 VRAM에서 차지하는 용량 (Out of Memory 에러 위험)이 주는 위험성을 감안할 때 며칠이 걸리는 캡쳐에서는 좋은 설정이 아니기 때문입니다.
마지막으로 SP.CaptureSlicePixelWidth은 여기에서 언급할 아주 중요한 변수입니다. 오랫동안 저희는 이 값을 기본값으로 그냥 두었는데, 2048 정도의 값이었습니다. 이 값은 여러분이 랜더링 하는 '각 뷰'의 크기(프레임당 랜더링 하는 2520개의 뷰들)이기 때문에 이 값을 줄이는 것은 랜더링 타임 전반적으로 큰 영향을 줍니다. 이 것은 실제로 최종 이미지와 분리되어 있고 여러분은 얼마나 많은 '세로 캡쳐'를 하는지에 그리고 최종 이미지를 위해 얼마나 많은 멀티샘플링을 할 것인지에 따라 조절할 수 있습니다. (180/VerticalAngularIncrement)
실제로, 여러분의 버퍼가 2048이고 6개의 세로 캡쳐를 한다고 하면 실제로 세로 크기가 12,288 픽셀짜리인 이미지를 랜더링 합니다. 최종 이미지가 안구당 4096x2048 크기라면 여러분은 시작값으로 그정도로 많은 픽셀을 필요로 하지 않는다는 것을 알 수 있습니다. 여기서는 이미 6xFSAA 필터링만으로 충분합니다. 세로 해상도만 생각하기로 하는데, 왜냐하면 위의 이미지를 생각해 보신다면 이미지의 전부가 아니라 일부만 활용하기 때문입니다. 중간의 일부만을 사용하는 것이죠. 그러므로 가로 크기는 크게 중요하지 않습니다. (가운데서 가져온 이미지를 담기에 충분한 크기이기만 하면 됩니다.)
극명하게 값을 720으로 맞추면 높이가 대략 ~4320정도의 랜더링을 한 후 2048로 다운샘플링을 합니다. 여전히 2xFSAA(저희가 찾아낸 충분히 괜찮은 값입니다.)와 동일한 값입니다. 이렇게 하면 2520 뷰에 대한 랜더링 시간이 설정 전 3분에서(선형적으로 감소하는 것은 아니지만 괜찮게 이득을 봤습니다.) 40초 정도로 줄어듭니다.
블루프린트에서 모든 설정하기
좋습니다, 위에서 말씀드렸듯 이 설정들이 엔진/에디터 실행시에 하나도 저장되지 않기 때문에 시작할 때 마다 설정하는 것이 고통스러울 수 있습니다.
나중에 저희는 내부적으로 저희가 사용할 콘솔 커맨드 하나를 만들어서 캡쳐 전에 호출하였습니다.
콘솔커맨드를 추가하는 대신에 여러분은 블루프린트를 사용할 수 있습니다. 아래 테이블 레이어가 엉망이지만 읽을 수 있게(잘 보였음 좋겠네요) 만들어 보았습니다.
이렇게 설정하고 "ce NTCaptureStereo"를 실행하면 캡쳐하기 전에 필요한 설정을 전부 해 줍니다. 작업하기 편할 뿐만 아니라 에러도 적습니다. 그리고 2일 정도 걸리는 캡쳐를 시작하게 되더라도 실수로 파라미터 설정을 잊어버리지 않게 됩니다!!!
영상 캡쳐하기
그렇죠, 여기까지 중요한 설정과 편리하게 블루프린트를 이용하고 콘솔 커맨드 한 개로 동작시키는 멋진 방법을 알아보았습니다.
동영상을 캡쳐할 때 가장 처음으로, 항상 기억해야 할 것은, 엔진 실행 시에 고정된 타임 스탭(타임 스케일)을 사용해야 한다는 것입니다.
한 프레임 캡쳐는 40초까지도 시간이 걸립니다. 그러므로 80초짜리 시네마틱 씬을 캡쳐하면 단 두 프레임만이 캡쳐될 것입니다. 이렇게 되면 안 되기 때문에 엔진이 고정된 타임 스텝을 사용하도록 설정해야 합니다.
이 방법은 간단하고 쉽습니다. (언리얼 엔진 덕분이죠) 아래 커맨드 라인을 입력하기만 하면 됩니다.
-usefixedtimestep -fps=
예를 들어, 프레임레이트를 60으로 고정하면 각 프레임 때 마다 16msec가 걸립니다. 이렇게 되면 그만큼의 시간이 지날 때 마다 60프레임을 캡쳐하게 되죠. 30hz짜리 영상을 만들려면 프레임레이트를 30으로 맞추면 됩니다. 하지만 보통 60프레임으로 캡쳐를 하기 때문에 60hz 또는 30hz중에서 선택하는 옵션이 있습니다.
덧붙여, 여기에 -notexturestreaming을 이용해서 텍스쳐 스트리밍을 끄는 것이 좋은데, 하루종일 캡쳐를 했는데 바닥 텍스쳐가 흐릿하면 기분이 안 좋겠죠 ;)
완전한 예제로, 내부적으로 저희는 캡쳐를 하려고 게임/에디터를 켤 때 아래 커맨드라인을 이용했습니다.
-usefixedtimestep -fps=60 -notexturestreaming
이 내용을 가지고 어떻게 실제 무비를 캡쳐하나요?
제가 위에서 SP.PanoramicScreenshot above를 언급하였습니다. 하지만 위 스크린샷을 유심히 보셨다면 여러 프레임을 무비로 캡쳐해 낼 수 있다는 것을 알 수 있을 것입니다. 특히
SP.PanoramicMovie
여기서의 프레임 수는 글자 그대로 "명령을 실행하였을 때 엔진이 업데이트를 반복해야 하는 숫자" 예를 들어, 여러분이 fps=60, starttime 을 120으로, endtime 을 240 으로 설정했다면 커맨드를 실행시킨 후에는 2초를 기다릴 것입니다. (starttime=120프레임) 그리고 2초간의 프레임을 캡쳐합니다.(120~240) 결과물은 초당 60프레임씩 2초의 길이를 갖게 됩니다. 이런 식이죠
저희는 그냥 마티네를 캡쳐하려고 했습니다. (결국 고정된 타임스텝과 40초로 각 프레임을 캡쳐하려고 하면 게임 플레이가 거의 불가능합니다.) 그래서 SP.PanoramicMovie를 실행할 때는 항상 마티네를 재생시켰습니다. 그렇게 하면 시작과 끝 프레임을 캡쳐하려는 마티네의 재생 시간에 맞추면 쉽게 동작시킬 수 있습니다(간단히 60을 곱하면 답이 나옵니다.)
캡쳐를 한 후
캡쳐를 다 하고 난 뒤에, SP.OutputDir/{date-time}/ 폴더에 Frame_00000 -> Frame_EndFrame 과 Frames.txt 파일이 생성되었을 것입니다. (이 파일이 생성되면 캡쳐가 종료되었다고 볼 수 있습니다.)
위 커스텀 코드를 사용하면 위/아래 로 양안 이미지가 병합되어 있을 것입니다. 인코딩 준비가 끝났죠.
이 이미지들을 영상으로 만들 방법은 많습니다. 저희는 ffmpeg를 이용해서 h.264 60hz 영상을 프레임들로부터 만드는 것을 선호합니다. 노트: ffmpeg는 인터넷에서 무료로 다운로드 받을 수 있습니다.
예를 들어, 아래 커맨드라인은 지정된 폴더 속의 모든 프레임들을 h.264 60hz 영상 포맷을 갖는 MyMovie.mp4로 인코딩 합니다.
"ffmpeg.exe -framerate 60 -i F:/StereoCaptureFrames/2016.04.26-13.04.34/Frame_%%5d.jpg -c:v libx264 -profile:v high -level 4.2 -r 60 -pix_fmt yuv420p -crf 18 -preset slower MyMovie.mp4"
여기에서 ffmpeg에 대해 더 자세히 다루지는 않겠습니다. 여기에 관한 문서들이 아주 많이 있고, 많은 분들이 다른 영상 편집 소프트웨어를 사용하고 싶어하실 것이기 때문입니다.
결과물 영상은 기본적으로 사용할 준비가 끝났습니다. 여러분은 여기에 사운드를 추가하셔야 할 것입니다.
작업 끝!
축하합니다! 이제 여러분은 Gear VR에서 재생 가능하고 YouTube 나 Facebook에 업로드 가능한 완전한 입체 360도 영상 캡쳐를 위한 설정 방법을 습득하셨습니다.
YouTube 업로드시 팁: 업로드시에는 적절한 해상도/종횡비를 포함한 메타데이터를 첨부해야 합니다. 예를 들어 보면 4096x4096 대신에 "4K/UHD/(3840x2160, 16:9)," 같이 지정합니다. 이렇게 하지 않으면 스트리밍해서 볼 때 아주 많이 흐려져 보일 것입니다. 여러분의 소스 프레임을 4096x4096 으로 설정해도 상관은 없지만 영상을 인코딩 하였을 때 (여러분이 어떤 프로그램을 사용하시든) 출력 규격을 4096x4096 1:1 대신에 '표준' UHD 해상도에 16:9로 설정하는 것 잊지 마시기 바랍니다. (그래도 GearVR/Oculus360 에서는 괜찮습니다.)
추가적으로 드리는 팁
제가 공유하고 싶은, 추가로 알려드릴 팁이 조금 있습니다. 몇몇은 보편적으로 적용되고, 이 중 몇몇은 4.11에서 수정되었지만 엔진 구버전에서는 여전히 적용할 수 있을 것입니다.
1. 모든 이펙트가 동작하지는 않습니다.
이 부분을 확인하고 넘어가는 게 좋습니다. 위에서 보았던 정보를 생각해 보면 이치에 맞을 것입니다. 여러분의 씬은 '타일' 형태에서 가장 효과적으로 캡쳐가 됩니다. (줌된 FOV에서), 수평으로 한바퀴 돌며 캡쳐를 하고 수직으로 회전해서 계속 반복합니다. 이렇게 동작하기 때문에 '씬'에 전반적으로 적용되는 스크린스페이스 효과는 (예를 들어 vignette) 제대로 캡쳐되지 않을 것이고, 꺼 두어야 합니다.
이 것은 light shaft 또한 동작하지 않는다는 것을 의미합니다. 여러 개의 '타일'이 인접한 타일과 불일치하는 결과물을 보여줄 것입니다. 이렇게 되면 최종 이미지에서 light shaft가 켜진 블록과 꺼진 블록이 섞여 나오게 됩니다. 그렇기 때문에 안타깝게도(굉장한 효과이기 때문이죠) light shaft 또한 꺼 두어야 합니다.
저희가 작업한 결과물에서는 왜 light shaft가 나오는지에 대해 궁금하실 것입니다. 왜냐하면 저희는 커스텀하게 제작한 world-space participating media solution을 만들었기 때문이고, 잘 동작합니다.
이렇듯, 스크린스페이스 디스토션 또한 동작하지 않을 것입니다. 각 방향에서 정 중앙 스크린만을 가져오기 때문에 스크린의 중심만 디스토션 효과가 나타날 것이기 때문이죠.
월드 스페이스 디스토션도 잘 동작한다는 점을 말씀드려야 겠네요. 그래서인지 파티클에서의 월드스페이스 또한 잘 동작합니다.
일반적으로 말씀드리자면 스크린스페이스 효과는 동작하지 않습니다. 그러므로 이 부분을 염두에 두시고 여러분의 콘텐츠를 살펴보시기 바랍니다.
2. 모든 구성요소가 fixedtimestep을 따르지는 않습니다.
이 부분은 씬의 수직 절편을 만들 때 아주 초기에 알게 된 것입니다. 저희는 게임속 무비에서 얼굴 연기를 했던 부분이 있는데, 영상을 캡쳐 시키면 2프레임만 찍히고 사라지는 것이었습니다. 이렇게 된 이유는 영상이 저희가 원했던 랜더링 당 16ms 가 걸리는 것이 아닌 '일반적인' 속도로 재생되고 있어서 였습니다. 이 것은 프레임이 캡쳐될 때 마다 40초가 지나버렸다는 것을 의미했습니다.
이렇듯, 실제 타임델타 값을 이용해서 GPU에서 연산되는 것들은 ('게임속 시간'과는 별개) 동일하게 동작하지 않을 것입니다. 저희는 예전에 보통의 머티리얼을 사용하는 멋져 보이는 불 효과를 사용하였을 때 GPU-파티클의 'embers' 효과가 굉장히 빠른 속도로 재생되는 등의 재미있는 경우도 있었습니다. 파티클에서 고정된 업데이트 타임(fixed update-time) 을 설정해서 이 부분을 회피할 수 있습니다.
3. 기본값으로 (엔진 릴리즈 버전) 캡쳐는 포스트 프로세스를 캡쳐하지 않습니다!
이 부분에 대해 저희가 작업한 것은 플레이어의 포스트 프로세스 뷰를 사용하여 캡쳐하는 것이었습니다.
쉬운 예제로, USceneCapturer::InitCaptureComponent 로 가서 아래 코드를 추가하면 (RegisterComponentWithWorld를 호출하기 전에) 플레이어의 포스트 프로세스 세팅을 그대로 사용합니다.
//*NEW* 플레이어 카메라 매니저의 포스트 프로세스 설정을 이용
if (GetWorld())
{
APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
if (PlayerController && PlayerController->PlayerCameraManager)
{
CaptureComponent->PostProcessSettings = PlayerController->PlayerCameraManager->CameraCache.POV.PostProcessSettings;
CaptureComponent->PostProcessBlendWeight = PlayerController->PlayerCameraManager->CameraCache.POV.PostProcessBlendWeight;
}
}
// 캡쳐에서 제외할 효과들을 끄기
CaptureComponent->PostProcessSettings.bOverride_GrainIntensity = true;
CaptureComponent->PostProcessSettings.GrainIntensity = 0.0f;
CaptureComponent->PostProcessSettings.bOverride_MotionBlurAmount = true;
CaptureComponent->PostProcessSettings.MotionBlurAmount = 0.0f;
CaptureComponent->PostProcessSettings.bOverride_ScreenSpaceReflectionIntensity = true;
CaptureComponent->PostProcessSettings.ScreenSpaceReflectionIntensity = 0.0f;
CaptureComponent->PostProcessSettings.bOverride_VignetteIntensity = true;
CaptureComponent->PostProcessSettings.VignetteIntensity = 0.0f;
//*NEW*
노트: 캡쳐 컴포넌트가 특정한 포스트 프로세스 이펙트가 동작하도록 ViewState를 갖게끔 강제로 설정해야 할 것입니다. (예를 들어 머티리얼 효과 등)
당연히 초기화는 한 번만 적용하면 됩니다 그러므로 다양한 볼륨을 이동하는 경우 이 코드를 추출하고 프레임마다 호출하거나 instrument FScene::CreateRenderer 가 플레이어의 포스트 프로세스 설정을 여기에서 복사해오도록 설정해 줍니다. (캡쳐 뷰마다, 프레임마다 동작하는 함수)
4. 결과물이 녹색으로 나와요! 도와주세요!
엔진 구버전을 사용하였을 때 저희에게도 이런 현상이 일어났었습니다. 이 현상은 여러개의 캡쳐 컴포넌트를 가지고 있을 때 (SP.ConcurrentCaptures를 떠올려 보세요) 이들의 이름이 모두 같기 때문입니다. 그래서 첫 번째 컴포넌트를 계속 사용하면서도 나머지 컴포넌트에서 순서대로 읽어오기 때문입니다. 녹색이 나오는 이유는 녹색이 바로 '무색'을 의미하기 때문입니다. 그러므로 녹색이 나온다는 것은 랜더링이 되지 않았다는 것을 말합니다.
여기에 관련된 수정 사항은 단순하게 USceneCapturer::InitCaptureComponent에 고유한 이름을 할당하는 것입니다.
4.11에서는 MakeUniqueObjectName가 호출될 때 이 부분이 실행됩니다. 하지만 구버전에서는 여러분이 원하는 다른 방법으로도 할 수 있습니다. (저희는 고유한 이름을 만들기 위해서 고정된 인덱스를 가지고 이를 증가시켰습니다.)
5. 쉐도우가 한쪽 눈에서만 나와요! 도와주세요!
저희도 이런 문제에 봉착했었습니다. 그리고 4.11에서는 해결된 것 같은데, 이 이슈는 쉐도우 버퍼를 한 번만 만들어서 양안에 사용하도록 하는 스테레오 랜더링 패스의 최적화 때문에 생깁니다.
저희는 이 때 쉐도우를 두 번 생성하도록 강제로 조정해서 해결했습니다.
역시 USceneCapturer::InitCaptureComponent에서 CaptureComponent -> CaptureStereoPass 를 코멘트 처리하면 해결될 것입니다.
이 것은 쉐도우 뎁스 랜더링을 더 하게 되지만 랜더링이 워낙 느려서 차이점을 별로 못 느끼실 것입니다.
6. 씬이 180도 돌아가서 보여요! 도와주세요!
이 이슈도 4.11에서 해결된 것 같습니다. 저희도 이 문제를 겪었는데요, 저희는 그냥 수동으로 USceneCapturer::Tick에서 180도를 더했습니다. 아래 코드를 찾아보세요.
"Rotation.Yaw = (bOverrideInitialYaw) ? ForcedInitialYaw : Rotation.Yaw;"
그리고 이 값을 아래와 같이 수정해 줍니다.
"Rotation.Yaw = (bOverrideInitialYaw) ? ForcedInitialYaw : Rotation.Yaw + 180.0f;"
7. 여러 기기에서 분산 캡쳐하기
이 방법이 현재까지 컷씬을 랜더링 하기 위해 시간을 가장 줄일 수 있는 방법입니다.
하지만, 모든 효과가 전부 고정된 것은 아니라는 점을 기억해야 합니다. 예를 들어 파티클 등의 경우에는 각양각색의 효과를 주기 위해 생성점이 랜덤하게 만들어 집니다. 만약 두 대의 기기에서 캡쳐를 했을 때, 한 이미지에서 다른 이미지로 넘기면 팍 튀는 것을 볼 수 있을 것인데, 왜냐하면 이러한 요소가 동일한 위치에 있지 않기 때문입니다.
각 실행마다 동일한 파티클이 생성될 수 있도록 파티클의 생성 위치를 지정할 수 있습니다. 그래도 아주 주의를 많이 기울여야 하는 것이, 한참 캡쳐를 했는데 팍 튀는 효과가 나타날 수도 있어서 입니다.
이렇게 하는 대신에, 저희가 전에 사용했던 방법은 씬에서 카메라가 전환되는 '자연스러운 컷', 페이드 인/아웃이 있는 곳, 또는 콘텐츠에서 팍 튀는 것을 발견하지 못하는 곳을 찾고 각 컷씬을 여러 기기에서 분산 캡쳐하는 것이었습니다.
노트에서 알려드린 점에도 불구하고 분산 캡쳐를 하시려는 경우 반드시 컷의 앞/뒤 추가 '버퍼'를 약간 캡쳐해 두라는 것입니다. 마티네에서 만약 캡쳐가 너무 일찍 멈추는 문제가 생기거나 다음 컷에서 너무 늦게 시작한다던가 하면 컷씬 전체를 처음부터 다시 캡쳐해야 할 수 있습니다. 시간이 너무 낭비되는 것이죠... 일부러 컷의 앞/뒤로 1초정도를 더 캡쳐하면 원하는 모든 프레임을 얻을 수 있다는 것이 보증됩니다.
여기에서 좋은 점 한 가지는 마티네가 한 개 밖에 없는 경우 프레임 번호가 일치한다는 것입니다. 여러분은 특정한 프레임으로 갈 수 있기 때문에 프레임의 이름을(Frame_StartFrame->Frame_EndFrame) 서로 다른 컷씬을 구분하여 지을 수 있고, 이렇게 해서 나중에 나눠진 폴더에서 이어 붙이지 않도록 동일한 폴더에 넣을 수 있습니다.
8. 윈도우 창이 팝업되어서 밤새 에디터/캡쳐했던 걸 느리게 만들었어요, 으아아!!
편집 메뉴 아래에 에디터 개인설정에서 일반-기타 에 보시면 "Use Less CPU when in Background" 메뉴가 있습니다.
이 항목의 체크를 해제합니다. (캡쳐하는 동안에는요) 이렇게 하면 밤새도록 캡쳐를 할 때 누군가가 여러분에게 메시지를 보내는 등의 이벤트에 의해 프로그램이 포커스를 잃어도 에디터가 느려지지 않습니다.
9. 극점에서의 뎁스(depth) 값은 부정확합니다.
불행하게도 이 부부은 캡쳐 기술이 가지고 있는 제약입니다. 일반적으로 말해서 천장을 쳐다볼 때는 잘 모르겠지만 바닥에 서 있을 때 바닥에서는 느낄 수 있을 것입니다.
저희는 이 이슈를 각 프레임마다 바닥 열을 검게 페이드 시켜서 해결했습니다. 이렇게 해서 바닥에 검은 동그라미가 보이게끔 하는 것입니다. 일부러 잘못된 뎁스를 보여주려는 것이 아닌 한 모두에게 권장할 만 한 방법입니다.
10. 어떤 지오메트리가 한쪽/양쪽 눈에서 나타나지 않습니다.
저희는 여기에서 오클루전 쿼리(occulusion queries)를 꺼서 해결했습니다. 문제의 세부 사항은 기억하지 못하지만 엔진이 프레임 사이에서 동시에 진행되는 캡쳐들의 숫자 때문에 마지막 프레임의 오클루전 결과를 사용하는 것과 마지막 프레임 오클루전 정보 업데이트 후로 얼마나 많은 랜더링이 있었는지와 연관되어 있었습니다.
여러분은 캡쳐 전에 간단하게 r.AllowOcclusionQueries 0 으로 설정해서 해결할 수 있습니다.
진짜로 다 됐습니다!
내용이 긴 포스팅이 되었네요, 하지만 여러분이 캡쳐를 시작하시는 것과 캡쳐 중에 겪는 어려움들을 해소하는 데 도움이 되었으면 합니다.
행운을 빕니다. 고품질의 결과물이 최고죠!
~Gav
연습해 볼 Hellblade 프레임들 (6K)
이미지를 클릭하시면 6K 버전을 보실 수 있습니다.