마인 크래프트 스타일 게임에서 폐색으로 복셀 기반 조명을 구현하려면 어떻게해야합니까?


13

C #과 XNA를 사용하고 있습니다. 현재 조명에 대한 알고리즘은 재귀 적 방법입니다. 그러나 하나의 8x128x8 청크가 5 초마다 계산되는 지점 까지는 비용많이 듭니다 .

  • 가변 어두운 그림자를 만드는 다른 조명 방법이 있습니까?
  • 아니면 재귀 적 방법이 좋으며 아마도 내가 잘못하고 있습니까?

재귀 적 인 물건은 근본적으로 비싸다 (청크 당 약 25k 블록을 통과해야 함). 광선 추적과 비슷한 방법을 사용하려고 생각했지만 이것이 어떻게 작동하는지 전혀 모릅니다. 내가 시도한 또 다른 것은 광원을 List에 저장하고 각 블록마다 각 광원까지의 거리를 가져 와서 올바른 레벨로 조명하는 데 사용했지만 조명은 벽을 통과합니다.

현재 재귀 코드는 다음과 같습니다. 이것은 햇빛과 횃불을 비운 후 다시 추가 한 후 빛 레벨이 0이 아닌 청크의 어느 곳에서나 호출됩니다.

world.get___at이 청크 외부에서 블록을 가져올 수있는 함수입니다 (청크 클래스 내부에 있음). Location은 내 자신의 구조 Vector3이지만 부동 소수점 값 대신 정수를 사용합니다. light[,,]청크의 라이트 맵입니다.

    private void recursiveLight(int x, int y, int z, byte lightLevel)
    {
        Location loc = new Location(x + chunkx * 8, y, z + chunky * 8);
        if (world.getBlockAt(loc).BlockData.isSolid)
            return;
        lightLevel--;
        if (world.getLightAt(loc) >= lightLevel || lightLevel <= 0)
            return;
        if (y < 0 || y > 127 || x < -8 || x > 16 || z < -8 || z > 16)
            return;
        if (x >= 0 && x < 8 && z >= 0 && z < 8)
            light[x, y, z] = lightLevel;

        recursiveLight(x + 1, y, z, lightLevel);
        recursiveLight(x - 1, y, z, lightLevel);
        recursiveLight(x, y + 1, z, lightLevel);
        recursiveLight(x, y - 1, z, lightLevel);
        recursiveLight(x, y, z + 1, lightLevel);
        recursiveLight(x, y, z - 1, lightLevel);
    }

1
청크 당 2 백만 블록을 수행하는 경우, 특히 8 * 128 * 8 청크에 실제로 8,192 개의 블록 만 있기 때문에 뭔가 잘못 되었습니다 . ~ 244 번 각 블록을 통과하면서 무엇을 할 수 있습니까? (255일까요?)
doppelgreener

1
나는 수학을 잘못했다. 미안 : P. 바꾸다. 그러나 너무 많이 나가야하는 이유는 설정보다 높은 레벨에 도달 할 때까지 모든 블록에서 "버블 아웃"해야하기 때문입니다. 즉, 모든 블록이 실제 조명 수준에 도달하기 전에 5-10 번 덮어 쓸 수 있습니다. 8x8x128x5 = 많은

2
복셀을 어떻게 보관하십니까? 통과 시간을 줄이는 것이 중요합니다.
Samaursa

1
조명 알고리즘을 게시 할 수 있습니까? (당신이 잘못하고 있는지 묻습니다, 우리는 전혀 모릅니다)
doppelgreener

나는 그것들을 "블록"의 배열에 저장하고 있으며, 블록은 머티리얼을위한 열거와 나중에 사용하기위한 메타 데이터 바이트로 구성되어 있습니다.

답변:


6
  1. 모든 라이트에는 정확한 (부동 소수점) 위치와 스칼라 라이트 반경 값으로 정의 된 경계 영역이 LR있습니다.
  2. 모든 복셀의 중심에는 정확한 (부동 소수점) 위치가 있으며 그리드의 위치에서 쉽게 계산할 수 있습니다.
  3. 8192 복셀 각각을 한 번만 실행하고 각각에 대해을 확인하여 각 N 조명의 구형 경계 체적에 속 |VP - LP| < LR하는지 확인하십시오 . 여기서 VP는 원점을 기준으로 복셀의 위치 벡터이며 원점을 기준으로 LP한 광 위치 벡터입니다. 현재 복셀의 반경이있는 각 조명에 대해 조명 중심으로부터의 거리만큼 조명 계수를 증가시킵니다 |VP - LP|. 해당 벡터를 정규화 한 다음 크기를 얻으면 0.0-> 1.0 범위에있게됩니다. 복셀에 도달 할 수있는 최대 조명 수준은 1.0입니다.

런타임은입니다 O(s^3 * n). 여기서 s복셀 영역의 측면 길이 (128) n이며 광원의 수입니다. 광원이 정적 인 경우 아무런 문제가 없습니다. 광원이 실시간으로 이동하는 경우 업데이트 할 때마다 전체 shebang을 다시 계산하지 않고 델타에서만 작업 할 수 있습니다.

각 조명에 영향을 미치는 복셀을 해당 조명 내의 참조로 저장할 수도 있습니다. 이런 식으로, 빛이 움직이거나 파괴 될 때, 전체 입방 격자를 다시 가로 지르지 않고 그에 따라 빛 값을 조정하여 그 목록 만 살펴볼 수 있습니다.


내가 그의 알고리즘을 올바르게 이해했다면, 그는 어떤 구석에“가장”가야하더라도 빛이 멀리 떨어진 곳에 도달 할 수있게함으로써 일종의 유사 무선 성을 시도합니다. 즉, 원점 (광원)으로부터의 최대 거리와 거리 (및 광 감쇠)로부터 경계가 정해진 최대 거리를 가진 "빈"(비 고체) 공간의 플러드 필 알고리즘은 다음과 같이 계산됩니다. 원점까지의 최단 경로. 따라서 현재 제안하는 것은 아닙니다.
Martin Sojka

세부 정보 @MartinSojka에 감사드립니다. an은 지능형 홍수 채우기와 비슷합니다. 글로벌 일루미네이션을 시도하면 영리한 최적화로도 비용이 많이 드는 경향이 있습니다. 따라서 2D로 먼저 이러한 문제를 시도하는 것이 좋으며, 심지어 원격으로 비싸더라도 3D로 손에 확실히 도전해야한다는 것을 알고 있습니다.
엔지니어

4

Minecraft 자체는 이런 식으로 햇빛을하지 않습니다.

햇빛을 위에서 아래로 채우면 모든 레이어가 이전 레이어의 인접 복셀에서 빛을 모아 감쇠합니다. 매우 빠름-단일 패스, 목록 없음, 데이터 구조 없음, 재귀 없음.

나중에 패스에서 횃불과 다른 비 홍수 조명을 추가해야합니다.

멋진 방향성 광 전파 등을 포함하여 다른 많은 방법이 있지만, 분명히 느리기 때문에 이러한 처벌에 따라 추가 현실주의에 투자하고 싶은지 알아 내야합니다.


잠깐, 마인 크래프트는 정확히 어떻게합니까? 나는 당신이 무슨 말을했는지 정확히 알 수 없었습니다. "모든 레이어가 감쇠로 이전 레이어의 이웃 복셀에서 빛을 모으고 있습니다"는 무엇을 의미합니까?

2
맨 위 레이어 (고정 슬라이스)로 시작하십시오. 햇빛으로 채우십시오. 그런 다음 아래 레이어로 이동하면 모든 복셀에서 이전 레이어의 가장 가까운 복셀에서 조명을 얻습니다 (위). 단단한 복셀에 제로 라이트를 넣습니다. 위의 복셀의 기여 가중치 인 "커널"을 결정하는 몇 가지 방법이 있습니다. Minecraft는 찾은 최대 값을 사용하지만 전파가 똑바로 내려 가지 않으면 1 씩 줄입니다. 이것은 측면 감쇠이므로 차단되지 않은 복셀의 세로 열은 햇빛을 완전히 전달하고 구석 주위에서 빛이 구부러집니다.
Bjorn Wesen

1
이 방법은 실제 물리학을 기반으로하지 않습니다. 주된 문제는 본질적으로 비 지향성 빛 (대기 산란)을 근사하고 간단한 휴리스틱으로 라디오를 수신 거부하는 것입니다. 꽤 좋아 보인다.
Bjorn Wesen

3
매달려있는 "입술"은 어떻습니까, 빛은 어떻게 올라가나요? 빛은 어떻게 위쪽으로 진행됩니까? 하향식으로 만 이동하면 돌출부를 채우기 위해 위로 올라갈 수 없습니다. 또한 횃불 / 기타 광원. 어떻게 하시겠습니까? (그들은 아래로 내려갈 수 있습니다!)

1
@Felheart : 이제 이걸 보았지만, 본질적으로 최소한 주변 조명 레벨이 돌출부의 밑면에 충분하기 때문에 완전히 검은 색이 아닙니다. 이것을 직접 구현했을 때 다운-> 업에서 두 ​​번째 패스를 추가했지만 앰비언트 방법에 비해 크게 미적 개선이 이루어지지 않았습니다. 토치 / 포인트 라이트는 별도로 처리해야합니다. 벽 중앙에 토치를 놓고 약간 실험하면 MC에서 사용되는 전파 패턴을 볼 수 있다고 생각합니다. 내 테스트에서는 별도의 라이트 필드에 전파 한 다음 추가합니다.
Bjorn Wesen

3

누군가 알아 내면 자신의 질문에 대답했다고 했어요 방법을 알아 냈습니다.

내가하고있는 일은 이것입니다 : 먼저, 청크 위에 오버레이 된 "이미 변경된 블록"의 3D 부울 배열을 만듭니다. 그런 다음 햇빛, 횃불 등을 채우십시오 (블럭을 켜고 홍수로 채우지 마십시오). 무언가를 바꾸었다면, 그 위치에서 "변경된 블록들"을 누르십시오. 또한 모든 솔리드 블록 (따라서 조명을 계산할 필요가 없음)을 "이미 변경됨"으로 변경하십시오.

이제 무거운 물건을 위해 : 16 패스로 전체 덩어리를 통과하지만 (각각의 빛 수준에 대해) '이미 변경'되면 계속하십시오. 그런 다음 주변 블록의 조명 수준을 확인하십시오. 가장 높은 조명 수준을 얻으십시오. 해당 조명 수준이 현재 패스의 조명 수준과 같으면 현재 블록을 현재 수준으로 설정하고 해당 위치의 "이미 변경됨"을 true로 설정하십시오. 계속하다.

나는 복잡한 종류를 알고 최선을 설명하려고 노력했다. 그러나 중요한 사실은 작동하고 빠르다는 것입니다.


2

다중 패스 솔루션 을 원래 재귀 적 방법과 결합하는 알고리즘을 제안하며 둘 중 어느 것보다 훨씬 빠릅니다.

각 조명 수준마다 하나씩 16 개의 목록 (또는 모든 종류의 모음)이 필요합니다. (실제로이 알고리즘을 최적화하여 더 적은 수의 목록을 사용하는 방법이 있지만이 방법을 가장 쉽게 설명 할 수 있습니다.)

먼저 목록을 지우고 모든 블록의 조도를 0으로 설정 한 다음 현재 솔루션에서와 같이 광원을 초기화하십시오. 그 후 (또는 동안) 0이 아닌 조명 수준의 블록을 해당 목록에 추가하십시오.

이제 조명 수준이 16 인 블록 목록을 살펴 봅니다. 인접한 블록의 조명 수준이 15보다 작 으면 조명 수준을 15로 설정하고 적절한 목록에 추가합니다. (이미 다른 목록에있는 경우 해당 목록에서 제거 할 수 있지만 그렇지 않은 경우에도 아무런 영향을 미치지 않습니다.)

그런 다음 밝기가 감소하는 순서로 다른 모든 목록에 대해 동일하게 반복하십시오. 목록의 블록이 해당 목록에있는 것보다 높은 광 레벨을 이미 가지고있는 경우, 블록이 이미 처리 된 것으로 가정하고 이웃을 확인하지 않아도됩니다. (따라서 이웃을 확인하는 것이 더 빠를 수도 있습니다. 얼마나 자주 발생하는지에 따라 다릅니다. 두 방법 모두 시도하고 어느 쪽이 더 빠른지 확인해야합니다.)

목록을 저장하는 방법을 지정하지 않았 음을 알 수 있습니다. 주어진 블록을 삽입하고 임의의 블록을 추출하는 것이 모두 빠른 작업이라면 실제로는 거의 모든 합리적인 구현이 그렇게해야합니다. 연결된 목록은 작동하지만 가변 길이 배열의 중간 정도의 구현도 마찬가지입니다. 가장 적합한 것을 사용하십시오.


부록 : 대부분의 조명이 자주 움직이지 않고 벽도 움직이지 않으면 조명이 켜진 각 블록마다 조명 수준을 결정하는 광원에 대한 포인터 (또는 여러 개가 묶인 경우). 이렇게하면 전역 조명 업데이트를 거의 완전히 피할 수 있습니다. 새로운 광원을 추가하거나 기존 광원을 밝게 비추는 경우 주변 블록에 대해 단일 재귀 조명 패스 만 수행하면되며, 제거하면 흐리게 표시되는 경우 해당 블록을 업데이트하기 만하면됩니다.

이 방법으로 벽 변경을 처리 할 수도 있습니다. 벽이 제거되면 해당 블록에서 새로운 재귀 조명 패스를 시작하면됩니다. 하나가 추가되면 새로 벽으로 둘러싸인 블록과 동일한 광원을 가리키는 모든 블록에 대해 조명 재 계산을 수행하십시오.

(조명 및 추가로 계산되는 조명이 이동 한 경우와 같이 여러 조명 변경이 동시에 발생하는 경우 위의 알고리즘을 사용하여 업데이트를 단일 업데이트로 결합해야합니다. 기본적으로 모든 조명 수준을 0으로 설정합니다. 제거 된 광원을 가리키는 블록, 주변의 조명이있는 블록 및 새 광원 (또는 제로화 된 영역의 기존 광원)을 해당 목록에 추가하고 위와 같이 업데이트를 실행하십시오.)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.