Java 대 C ++과 관련하여 두 가지 모두에서 voxel 엔진을 작성했습니다 (위의 C ++ 버전). 또한 2004 년부터 보셀 엔진을 작성해 왔습니다 (보그가 아니었을 때). :) C ++ 성능이 훨씬 뛰어나다는 것을 망설이지 않고 말할 수는 있지만 코딩하기가 더 어렵습니다. 계산 속도에 관한 것이 아니라 메모리 관리에 관한 것입니다. 복셀 세계의 데이터만큼 많은 데이터를 할당 / 할당 해제 할 때 C (++)가 이길 언어입니다. 하나목표에 대해 생각해야합니다. 성능이 최우선 순위라면 C ++를 사용하십시오. 최신 성능을 발휘하지 않고 게임을 작성하려는 경우 Minecraft에서 입증 한 바와 같이 Java를 사용할 수 있습니다. 사소한 / 가장자리가 많지만 일반적으로 Java가 (잘 작성된) C ++보다 약 1.75-2.0 배 느리게 실행될 것으로 예상 할 수 있습니다. 제대로 작동하지 않는 오래된 버전의 엔진이 여기 에서 작동하는 것을 볼 수 있습니다 (EDIT : newer version here ). 청크 생성은 느리게 보일 수 있지만 3D 보로 노이 다이어그램을 체적으로 생성하여 무차별 강제 방법으로 CPU의 표면 법선, 조명, AO 및 그림자를 계산한다는 점을 명심하십시오. 나는 다양한 기술을 시험해 보았고 다양한 캐싱 및 인스 턴싱 기술을 사용하여 약 100 배 빠른 청크 생성을 얻을 수 있습니다.
나머지 질문에 대답하기 위해 성능을 향상시키기 위해 할 수있는 일이 많이 있습니다.
- 캐싱. 가능하면 데이터를 한 번 계산해야합니다. 예를 들어 조명을 장면에 굽습니다. 동적 조명 (후 처리로 화면 공간에서)을 사용할 수 있지만 조명에서 베이킹하면 삼각형의 법선을 통과하지 않아도됩니다.
비디오 카드에 가능한 적은 데이터를 전달하십시오. 사람들이 잊어 버리는 경향은 GPU에 더 많은 데이터를 전달할수록 시간이 더 걸린다는 것입니다. 단색과 정점 위치를 전달합니다. 주야간주기를 원한다면 컬러 그레이딩을하거나 태양이 서서히 변할 때 장면을 다시 계산할 수 있습니다.
GPU로 데이터를 전달하는 것은 비용이 많이 들기 때문에 어떤면에서는 더 빠른 소프트웨어로 엔진을 작성할 수 있습니다. 소프트웨어의 장점은 GPU에서는 불가능한 모든 종류의 데이터 조작 / 메모리 액세스를 수행 할 수 있다는 것입니다.
배치 크기로 재생하십시오. GPU를 사용하는 경우 전달하는 각 정점 배열의 크기에 따라 성능이 크게 달라질 수 있습니다. 따라서 청크 크기로 놀아보십시오 (청크를 사용하는 경우). 64x64x64 청크가 꽤 잘 작동한다는 것을 알았습니다. 어쨌든 청크를 입방체로 유지하십시오 (직사각 프리즘 없음). 이를 통해 코딩 및 변환과 같은 다양한 작업이 쉬워지고 경우에 따라 성능이 향상됩니다. 모든 차원의 길이에 대해 하나의 값만 저장하는 경우 계산 중에 스왑되는 레지스터가 2 개 줄어 듭니다.
표시 목록 (OpenGL 용)을 고려하십시오. 비록 그들이 "오래된"방식이지만 더 빠를 수 있습니다. 표시 목록을 변수로 구워야합니다 ... 표시 목록 작성 작업을 실시간으로 호출하면 불필요하게 느려집니다. 표시 목록이 얼마나 빠릅니까? 정점 별 속성 대 상태 만 업데이트합니다. 즉, 최대 6 개의면을 통과 한 다음 1 개의 색상 (복셀의 각 정점에 대한 색상)을 전달할 수 있습니다. GL_QUADS와 3 차 복셀을 사용하는 경우 복셀 당 최대 20 바이트 (160 비트)를 절약 할 수 있습니다! (알파가없는 15 바이트, 일반적으로 사물을 4 바이트로 정렬하려는 경우)
"청크"또는 데이터 페이지를 렌더링하는 무차별 방식을 사용합니다. 이는 일반적인 기술입니다. octrees와는 달리, 데이터를 읽고 / 처리하는 것이 훨씬 쉽고 빠르지 만, 메모리에 덜 친숙합니다 (그러나 요즘에는 $ 200- $ 300에 64 기가 바이트의 메모리를 얻을 수 있습니다). 분명히, 전세계에 하나의 거대한 배열을 할당 할 수는 없습니다 (복셀 당 32 비트 int가 사용되는 경우 1024x1024x1024 복셀 세트는 4 기가 바이트의 메모리입니다). 따라서 뷰어와의 근접성에 따라 많은 작은 배열을 할당 / 할당 해제합니다. 데이터를 할당하고 필요한 표시 목록을 얻은 다음 데이터를 덤프하여 메모리를 절약 할 수도 있습니다. 이상적인 조합은 octrees와 arrays의 하이브리드 접근법을 사용하는 것입니다. 세계의 절차 생성, 조명 등을 수행 할 때 데이터를 배열에 저장합니다.
먼 곳까지 렌더링 ... 클리핑 된 픽셀은 시간이 절약됩니다. gpu는 깊이 버퍼 테스트를 통과하지 않으면 픽셀을 던집니다.
뷰포트에서 청크 / 페이지 만 렌더링합니다 (자체 설명). GPU가 뷰포트 외부에서 폴 지온을 클립하는 방법을 알고 있더라도이 데이터를 전달하는 데 여전히 시간이 걸립니다. 나는 이것을위한 가장 효율적인 구조가 무엇인지 모르지만 ( "부끄럽게도, BSP 트리를 작성한 적이 없다") 청크 단위로 간단한 레이 캐스트조차도 성능을 향상시킬 수 있으며 분명히 관찰 절두체에 대한 테스트는 시간을 절약.
명백한 정보이지만 초보자에게는 : 표면에없는 모든 단일 다각형을 제거합니다. 즉, 복셀이 6 개의면으로 구성된 경우 절대 렌더링되지 않는면을 제거합니다 (다른 복셀을 만지고 있음).
프로그래밍에서 수행하는 모든 작업의 일반적인 규칙 : CACHE LOCALITY! 캐시를 로컬에 유지할 수 있다면 (소량의 시간에도 큰 차이가 생길 수 있습니다.) 이는 동일한 메모리 영역에서 데이터를 합리적으로 유지하고 메모리 영역을 너무 자주 처리하도록 전환하지 않음을 의미합니다. 이상적으로는 스레드 당 하나의 청크에 대해 작업하고 해당 메모리를 스레드 전용으로 유지하십시오. 이는 CPU 캐시에만 적용되지 않습니다. 캐시 계층 구조는 다음과 같습니다 (가장 느리거나 빠른) : 네트워크 (클라우드 / 데이터베이스 / 등) -> 하드 드라이브 (아직 SSD가없는 경우 SSD를 얻음), 램 (삼중 채널 또는 RAM이없는 경우 더 큰 RAM을 얻음), CPU 캐시 (들), 레지스터. 후자이며, 필요 이상으로 바꾸지 마십시오.
스레딩. 해. Voxel 월드는 스레딩에 매우 적합합니다. 각 부분은 (대부분) 다른 부품과 독립적으로 계산할 수 있습니다. 스레딩 루틴.
문자 / 바이트 데이터 형식을 사용하지 마십시오. 또는 반바지. 일반 소비자에게는 최신 AMD 또는 Intel 프로세서가있을 것입니다 (아마도). 이 프로세서에는 8 비트 레지스터가 없습니다. 바이트를 32 비트 슬롯에 넣은 다음 메모리에서 다시 변환하여 바이트를 계산합니다. 컴파일러는 모든 종류의 부두를 수행 할 수 있지만 32 또는 64 비트 숫자를 사용하면 가장 예측 가능하고 빠른 결과를 얻을 수 있습니다. 마찬가지로 "bool"값은 1 비트를 사용하지 않습니다. 컴파일러는 종종 bool에 전체 32 비트를 사용합니다. 데이터에 대해 특정 유형의 압축을 시도하고 싶을 수 있습니다. 예를 들어, 8 개의 복셀이 모두 같은 유형 / 색상 인 경우 단일 숫자 (2 ^ 8 = 256 조합)로 저장할 수 있습니다. 그러나 이것의 결과에 대해 생각해야합니다. 메모리를 많이 절약 할 수 있습니다. 작은 압축 시간으로도 성능이 저하 될 수 있습니다. 그로 인해 소량의 추가 시간도 세계의 크기와 입방체로 확장되기 때문입니다. 레이 캐스트 계산을 상상해보십시오. 레이 캐스트의 모든 단계에 대해 압축 해제 알고리즘을 실행해야합니다 (한 번에 8 개의 복셀 계산을 일반화하는 현명한 방법이 아니라면).
Jose Chavez가 언급했듯이 플라이급 디자인 패턴이 유용 할 수 있습니다. 비트 맵을 사용하여 2D 게임에서 타일을 나타내는 것처럼 여러 3D 타일 (또는 블록) 유형으로 세계를 만들 수 있습니다. 이것의 단점은 텍스처의 반복이지만, 함께 맞는 분산 텍스처를 사용하여이를 개선 할 수 있습니다. 경험상 어디에서나 인스 턴싱을 활용하려고합니다.
지오메트리를 출력 할 때 쉐이더에서 정점 및 픽셀 처리를 피하십시오. 복셀 엔진에는 필연적으로 많은 삼각형이 있으므로 간단한 픽셀 쉐이더조차도 렌더링 시간을 크게 줄일 수 있습니다. 버퍼로 렌더링하는 것이 좋습니다. 그런 다음 픽셀 셰이더를 포스트 프로세스로 사용하십시오. 그렇게 할 수 없다면 정점 셰이더에서 계산을 시도하십시오. 다른 계산은 가능한 정점 데이터로 구워 져야합니다. 모든 지오메트리 (예 : 그림자 매핑 또는 환경 매핑)를 다시 렌더링해야하는 경우 추가 패스가 매우 비쌉니다. 때로는 더 풍부한 디테일을 위해 역동적 인 장면을 포기하는 것이 좋습니다. 게임에 수정 가능한 장면 (예 : 파괴 가능한 지형)이있는 경우 사물이 파괴 될 때 항상 장면을 다시 계산할 수 있습니다. 재 컴파일은 비싸지 않으며 1 초도 걸리지 않습니다.
루프를 풀고 배열을 평평하게 유지하십시오! 이 작업을 수행하지 마십시오 :
for (i = 0; i < chunkLength; i++) {
for (j = 0; j < chunkLength; j++) {
for (k = 0; k < chunkLength; k++) {
MyData[i][j][k] = newVal;
}
}
}
//Instead, do this:
for (i = 0; i < chunkLengthCubed; i++) {
//figure out x, y, z index of chunk using modulus and div operators on i
//myData should have chunkLengthCubed number of indices, obviously
myData[i] = newVal;
}
편집 : 더 광범위한 테스트를 통해 이것이 잘못 될 수 있음을 발견했습니다. 시나리오에 가장 적합한 사례를 사용하십시오. 일반적으로 배열은 평평해야하지만 경우에 따라 다중 색인 루프를 사용하는 것이 더 빠를 수 있습니다