Como renderizar eficientemente uma grande malha de terreno?

10

Recentemente, estive preso a um problema ao pensar na melhor maneira de gerar um terreno no meu jogo. Em outros projetos, eu normalmente usava mapas de altura, portanto todo o trabalho principal era baseado no mecanismo usado, mas agora isso não pode ser feito porque o terreno possui milhões de polígonos específicos que devem ser desenhados com precisão. Além disso, muitos deles não podem ser analisados ​​a partir do vetor Y (por causa dos polígonos ocultos abaixo), ou seja, um mapa de altura não é útil aqui. Nesse caso, eu tive que usar um objeto COLLADA.

Alguém me disse para dividir manualmente o modelo dentro de um software como o Blender, mas infelizmente isso também não é possível porque esses terrenos são criados em blocos de outro software e carregados depois no jogo (essa é a ideia). Portanto, seria um grande trabalho ser obrigado a cortá-los manualmente toda vez.

Assim, desde uma semana que venho estudando sobre como resolver esse problema e carregar processualmente essa malha, o terreno, de acordo com o frustrum da câmera, economizando o máximo de desempenho possível. Eu me deparei com muitos documentos sobre geração de malhas procedurais e acho que meu problema poderia ser resolvido através do mapeamento da malha em octrees. Este é um trabalho GRANDE, pelo menos para mim, e é por isso que estou aqui, porque não quero correr o risco de seguir o caminho errado sem antes ouvir pessoas experientes.

Em resumo, tenho milhões de vértices e índices que juntos formam o terreno, mas por razões óbvias não posso desenhá-los ao mesmo tempo. É necessário algum tipo de procedimento. Qual é a melhor maneira de fazer isso, tratar uma grande malha como um terreno? Existe algum livro específico sobre isso? Existe a melhor maneira de implementá-lo?

Desculpe por qualquer tipo de erro, sou muito novato nesta área.

Karl Marny
fonte

Respostas:

12

O processamento básico é uma boa maneira de começar. Você pode mudar para estruturas de dados mais sofisticadas, como octrees posteriormente, se necessário. Por enquanto, basta dividir seu terreno em blocos de determinadas dimensões ao carregar o modelo a partir do disco.

Dependendo dos seus dados, você pode dividir seu terreno em pilares em um plano que mede toda a altura ou em cubos no espaço. O código não está completo (fmod, inicialização de vetor, índices, ...), mas deve dar um começo.

// Load vertices from disk
struct point { double x, y, z; };    
vector<point> vertices;

// Create container for chunks
typedef pair<int, int> key;
unordered_map<key, vector<point>> chunks;
const int chunksize = 10;

// For each vertex
for (int i = 0; i < vertices.size(); ++i) {
    // Fetch global coordinates
    int x = vertices[i].x,
        y = vertices[i].y,
        z = vertices[i].z;

    // Find containing chunk
    key k;
    k.first  = x / chunksize;
    k.second = z / chunksize;

    // Calculate local coordinates
    point p;
    p.x = x % chunksize;
    p.y = y;
    p.z = z % chunksize;

    // Add to chunk
    chunks[k].push_back(p);
}

// Create separate buffers for each chunk
// ...

Como você dividiu a malha agora, é possível executar técnicas de LOD e de descarte para ignorar a renderização de pedaços ocultos.

  • A distância da vista é onde você começa. Você renderizaria apenas pedaços dentro de uma determinada distância, por exemplo, a distância de visualização da sua câmera. Quanto menor a distância da vista, mais desempenho você terá, pois menos pedaços do terreno devem ser desenhados.

  • A seleção de frustum é uma técnica comum para renderizar malhas que se cruzam com o frustum de exibição da câmera. Isso provavelmente proporcionará o maior ganho de desempenho.

Experimente o tamanho do pedaço e visualize a distância para obter os melhores resultados. O tamanho do pedaço é uma troca entre seleção precisa e computação fácil. Para otimizar ainda mais, você pode dar uma olhada nessas otimizações mais avançadas.

  • A seleção da oclusão pode ser feita renderizando as malhas na CPU em uma resolução muito baixa. Isso permite detectar precocemente malhas escondidas atrás de outras. Eles não precisam ser enviados à GPU; portanto, você salva muitas execuções de sombreadores de vértices que, de outra forma, teriam sido executadas antes de rejeitar os triângulos.

  • Nível de detalhe significa que você calcula malhas de menor resolução dos seus pedaços. Com base na distância da câmera, você escolhe uma das malhas para desenhar. Isso permite reduzir o número de vértices, pois os pedaços distantes não precisam de muitos detalhes. Essa abordagem funciona bem com octrees porque você pode mesclar vários cubos em uma malha de baixa resolução para áreas distantes da câmera. No entanto, não é trivial mesclar perfeitamente as arestas entre dois blocos de uma resolução diferente.

danijar
fonte