动态地形块生成

On-the-fly terrain chunk generation

本文关键字:动态      更新时间:2023-10-16

我正在编写一个可以使用噪音函数生成景观的引擎,并在玩家在地形周围移动时加载新的块。我花了两天的大部分时间来弄清楚如何将这些块放置在正确的位置,这样它们就不会重叠或被放置在现有块的顶部。它在功能上运行得很好,但是当你在离玩家越远的地方生成砖块时,它的性能就会受到很大的影响(例如,如果你在玩家周围3个砖块的半径内生成砖块,它就会快速发光,但如果你将其增加到20个砖块的半径,它就会迅速减速)。

我知道为什么会这样,但是我想不出其他的方法来做这件事。在我进一步讨论之前,这里是我目前使用的代码,希望它的注释足够好,可以理解:

    // Get the player's position rounded to the nearest chunk on the grid.
    D3DXVECTOR3 roundedPlayerPos(SnapToMultiple(m_Dx->m_Camera->GetPosition().x, CHUNK_X), 0, SnapToMultiple(m_Dx->m_Camera->GetPosition().z, CHUNK_Z));
    // Iterate through every point on an invisible grid. At each point, check if it is
    // inside a circle the size of the grid (so we generate chunks in a circle around
    // the player, not a square). At each point that is inside the circle, add a chunk to
    // the ChunksToAdd vector.
    for (int x = -CHUNK_RANGE-1; x <= CHUNK_RANGE; x++)
    {
        for (int z = -CHUNK_RANGE-1; z <= CHUNK_RANGE; z++)
        {
            if (IsInside(roundedPlayerPos, CHUNK_X*CHUNK_RANGE, D3DXVECTOR3(roundedPlayerPos.x+x*CHUNK_X, 0, roundedPlayerPos.z+z*CHUNK_Z)))
            {
                Chunk chunkToAdd;
                chunkToAdd.chunk = 0;
                chunkToAdd.position = D3DXVECTOR3((roundedPlayerPos.x + x*CHUNK_X), 0, (roundedPlayerPos.z + z*CHUNK_Z));
                chunkToAdd.chunkExists = false;
                m_ChunksToAdd.push_back(chunkToAdd);
            }
        }
    }
    // Iterate through the ChunksToAdd vector. For each chunk in this vector, compare it's
    // position to every chunk in the Chunks vector (which stores each generated chunk).
    // If the statement returns true, then there is already a chunk at that location, and
    // we don't need to generate another.
    for (i = 0; i < m_ChunksToAdd.size(); i++)
    {
        for (int j = 0; j < m_Chunks.size(); j++)
        {
            // Check the chunk in the ChunksToAdd vector with the chunk in the Chunks vector (chunks which are already generated).
            if (m_ChunksToAdd[i].position.x == m_Chunks[j].position.x && m_ChunksToAdd[i].position.z == m_Chunks[j].position.z)
            {
                m_ChunksToAdd[i].chunkExists = true;
            }
        }
    }
    // Determine the closest chunk to the player, so we can generate that first.
    // Iterate through the ChunksToAdd vector, and if the vector doesn't exist (if it
    // does exist, we're not going to generate it so ignore it), compare the current (i)
    // chunk against the current closest chunk. If it is larger, move on, and if it is 
    // smaller, store it's position as the new smallest chunk.
    int closest = 0; 
    for (j = 0; j < m_ChunksToAdd.size(); j++)
    {
        if (!m_ChunksToAdd[j].chunkExists)
        {
            // Get the distance from the player to the chunk for the current closest chunk, and
            // the chunk being tested.
            float x1 = ABS(DistanceFrom(roundedPlayerPos, m_ChunksToAdd[j].position));
            float x2 = ABS(DistanceFrom(roundedPlayerPos, m_ChunksToAdd[closest].position));
            // If the chunk being tested is closer to the player, make it the new closest chunk.
            if (x1 <= x2)
                closest = j;
        }
    }
    // After determining the position of the closest chunk, generate the volume and mesh, and add it
    // to the Chunks vector for rendering.
    if (!m_ChunksToAdd[closest].chunkExists) // Only add it if the chunk doesn't already exist in the Chunks vector.
    {
        Chunk chunk;
        chunk.chunk = new chunkClass;
        chunk.chunk->m_Position = m_ChunksToAdd[closest].position;
        chunk.chunk->GenerateVolume(m_Simplex);
        chunk.chunk->GenerateMesh(m_Dx->GetDevice());
        chunk.position = m_ChunksToAdd[closest].position;
        chunk.chunkExists = true;
        m_Chunks.push_back(chunk);
    }
    // Clear the ChunksToAdd vector ready for another frame.
    m_ChunksToAdd.clear();
(if it wasn't already obvious, this is run every frame.)
The problem area is to do with the CHUNK_RANGE variable. The larger this value, the more the first two loops are iterated through each frame, slowing the whole thing down tremendously. I need some advice or suggestions on how to do this more efficiently, thanks.

编辑:这里有一些改进的代码:

// Get the player's position rounded to the nearest chunk on the grid.
D3DXVECTOR3 roundedPlayerPos(SnapToMultiple(m_Dx->m_Camera->GetPosition().x, CHUNK_X), 0, SnapToMultiple(m_Dx->m_Camera->GetPosition().z, CHUNK_Z));
// Find if the player has changed into another chunk, if they have, we will scan 
// to see if more chunks need to be generated.
static D3DXVECTOR3 roundedPlayerPosOld = roundedPlayerPos;
static bool playerPosChanged = true;
if (roundedPlayerPosOld != roundedPlayerPos)
{
    roundedPlayerPosOld = roundedPlayerPos;
    playerPosChanged = true;
}
// Iterate through every point on an invisible grid. At each point, check if it is
// inside a circle the size of the grid (so we generate chunks in a circle around
// the player, not a square). At each point that is inside the circle, add a chunk to
// the ChunksToAdd vector.
if (playerPosChanged)
{
    m_ChunksToAdd.clear();
    for (int x = -CHUNK_CREATE_RANGE-1; x <= CHUNK_CREATE_RANGE; x++)
    {
        for (int z = -CHUNK_CREATE_RANGE-1; z <= CHUNK_CREATE_RANGE; z++)
        {
            if (IsInside(roundedPlayerPos, CHUNK_X*CHUNK_CREATE_RANGE, D3DXVECTOR3(roundedPlayerPos.x+x*CHUNK_X, 0, roundedPlayerPos.z+z*CHUNK_Z)))
            {
                bool chunkExists = false;
                for (int j = 0; j < m_Chunks.size(); j++)
                {
                    // Check the chunk in the ChunksToAdd vector with the chunk in the Chunks vector (chunks which are already generated).
                    if ((roundedPlayerPos.x + x*CHUNK_X) == m_Chunks[j].position.x && (roundedPlayerPos.z + z*CHUNK_Z) == m_Chunks[j].position.z)
                    {
                        chunkExists = true;
                        break;
                    }
                }
                if (!chunkExists)
                {
                    Chunk chunkToAdd;
                    chunkToAdd.chunk = 0;
                    chunkToAdd.position = D3DXVECTOR3((roundedPlayerPos.x + x*CHUNK_X), 0, (roundedPlayerPos.z + z*CHUNK_Z));
                    m_ChunksToAdd.push_back(chunkToAdd);
                }
            }
        }
    }
}
playerPosChanged = false;
// If there are chunks to render.
if (m_ChunksToAdd.size() > 0)
{
    // Determine the closest chunk to the player, so we can generate that first.
    // Iterate through the ChunksToAdd vector, and if the vector doesn't exist (if it
    // does exist, we're not going to generate it so ignore it), compare the current (i)
    // chunk against the current closest chunk. If it is larger, move on, and if it is 
    // smaller, store it's position as the new smallest chunk.
    int closest = 0; 
    for (j = 0; j < m_ChunksToAdd.size(); j++)
    {
        // Get the distance from the player to the chunk for the current closest chunk, and
        // the chunk being tested.
        float x1 = ABS(DistanceFrom(roundedPlayerPos, m_ChunksToAdd[j].position));
        float x2 = ABS(DistanceFrom(roundedPlayerPos, m_ChunksToAdd[closest].position));
        // If the chunk being tested is closer to the player, make it the new closest chunk.
        if (x1 <= x2)
            closest = j;
    }
    // After determining the position of the closest chunk, generate the volume and mesh, and add it
    // to the Chunks vector for rendering.
    Chunk chunk;
    chunk.chunk = new chunkClass;
    chunk.chunk->m_Position = m_ChunksToAdd[closest].position;
    chunk.chunk->GenerateVolume(m_Simplex);
    chunk.chunk->GenerateMesh(m_Dx->GetDevice());
    chunk.position = m_ChunksToAdd[closest].position;
    m_Chunks.push_back(chunk);
    m_ChunksToAdd.erase(m_ChunksToAdd.begin()+closest);
}
// Remove chunks that are far away from the player.
for (i = 0; i < m_Chunks.size(); i++)
{
    if (DistanceFrom(roundedPlayerPos, m_Chunks[i].position) > (CHUNK_REMOVE_RANGE*CHUNK_X)*(CHUNK_REMOVE_RANGE*CHUNK_X))
    {
        m_Chunks[i].chunk->Shutdown();
        delete m_Chunks[i].chunk;
        m_Chunks[i].chunk = 0;
        m_Chunks.erase(m_Chunks.begin()+i);
    }
}

您是否尝试分析它以准确地找出瓶颈在哪里?

你是否需要检查所有这些块,或者你是否可以检查玩家正在看的方向,只生成那些在视野中的?

如果你在显示之前每帧生成一次,那么你为什么要先绘制最靠近玩家的块呢?跳过对它们进行排序的阶段可能会释放一些处理能力。

是否有任何理由不能将前两个循环组合起来,以创建一个需要生成的块向量?

听起来你在渲染线程上做了太多的工作(即构建块)。如果你可以快速完成3块半径的工作,你应该将其限制在每帧。在每种情况下,每帧你要生成多少块?

我将假设生成每个块是独立的,因此,您可能可以将工作转移到另一个线程-然后在准备好时显示块。