Gráficos 2D - por que usar planilhas?

62

Eu já vi muitos exemplos de como renderizar sprites a partir de uma planilha, mas ainda não entendi por que é a maneira mais comum de lidar com sprites em jogos 2D.

Comecei com a renderização de sprites em 2D nas poucas aplicações de demonstração que fiz ao lidar com cada quadro de animação para qualquer tipo de sprite como sua própria textura - e essa coleção de texturas é armazenada em um dicionário. Isso parece funcionar para mim e combina muito bem com meu fluxo de trabalho, pois costumo fazer minhas animações como arquivos gif / mng e extrair os quadros para pngs individuais.

Existe uma vantagem notável de desempenho na renderização a partir de uma única folha e não nas texturas individuais? Com um hardware moderno capaz de atrair milhões de polígonos para a tela uma centena de vezes por segundo, isso importa para meus jogos 2D, que lidam apenas com algumas dúzias de retângulos de 50x100px?

Os detalhes de implementação de carregar uma textura na memória gráfica e exibi-la no XNA parecem bastante abstratos. Tudo o que sei é que as texturas são vinculadas ao dispositivo gráfico quando são carregadas e, durante o ciclo do jogo, as texturas são renderizadas em lotes. Portanto, não está claro para mim se minha escolha afeta o desempenho.

Eu suspeito que existem algumas boas razões para a maioria dos desenvolvedores de jogos em 2D parecer usá-los, mas eu não entendo o porquê.

Columbo
fonte
Não, isso realmente não importa muito para seus 50 sprites ímpares. Mas o que perder?
O pato comunista

Respostas:

69

Um argumento forte para o uso de planilhas é que o número de texturas disponíveis em uma placa gráfica pode ser limitado. Portanto, sua biblioteca de gráficos precisaria constantemente remover textura e realocar texturas na GPU. É muito mais eficiente alocar uma textura grande uma vez.

Considere também que os tamanhos de textura costumam ter potência de 2. Portanto, se você tiver um Sprite de 50x100px, alocará texturas com o tamanho de 64x128px ou, na pior das hipóteses, 128x128px. Isso está apenas desperdiçando memória gráfica. Melhor agrupar todos os sprites em uma textura de 1024x1024 px, o que permitiria sprites de 20x10 e você perderá apenas 24 pixels na horizontal e na vertical. Às vezes, até sprites de tamanhos diferentes são combinados em uma enorme folha de sprite para usar a textura o mais eficiente possível.

Adendo: Um motivo muito importante para usar folhas de sprite é reduzir a quantidade de chamadas em sua GPU, o que pode ter um impacto notável no desempenho. Isso foi afirmado em outras respostas e estou adicionando isso por uma questão de integridade, para que isso obtenha mais visibilidade.

bummzack
fonte
11
Obrigado, eu não tinha considerado nenhum desses pontos - Você sabe aproximadamente qual é o limite no número de texturas disponíveis? Eu nunca vi essa estatística mencionada nas especificações gráficas - o limite é basicamente igual à quantidade de RAM gráfica?
Columbo
Normalmente, o limite seria a memória da GPU, sim. Eu pensei que havia algumas restrições no número de texturas em cartões antigos, mas não consigo encontrar uma referência que comprove esse ponto.
bummzack
12
Mesmo se não houver um limite de quantidade, o tráfego de ônibus para mover muitas imagens pequenas não é tão eficiente quanto uma (ou algumas) grandes transferências.
Mordachai
5
Bons pontos, mas os aspectos mais importantes s para minimizar as paradas do pipeline, mantendo a contagem de chamadas baixa. As GPUs preferem uma pequena quantidade de trabalhos grandes, em vez de uma grande quantidade de trabalhos pequenos. Colocar todos os seus sprites em uma pequena quantidade de folhas grandes significa que você pode configurar o estágio do texto uma vez e desenhar muitos spites ao mesmo tempo em um lote. Spritebatch faz isso por você. Se você tem mais de um spritesheet, certifique-se dizer SpriteBatch para classificar por textura
Cubed2D
Adicionando compressão de textura iria transformar qualquer espaço de folga em uma muito pequena pequena quantidade de sobrecarga, em cima de tudo isso
Joe Plante
36

Eu diria que o argumento a ser usado seria a capacidade de renderizar várias coisas em uma única chamada de empate. Tomemos, por exemplo, renderização de fonte, sem uma planilha, você precisará renderizar cada caractere com uma troca de textura separada seguida por uma chamada de desenho. Jogue-os em uma planilha e você poderá renderizar frases inteiras com uma única chamada de desenho (os caracteres de diferença na fonte são escolhidos apenas especificando os diferentes UVs para os cantos). Essa é uma maneira muito mais eficiente de renderizar quando uma sobrecarga muito real de renderizar coisas em muitas plataformas é fazer muitas chamadas de API.

Isso também pode ajudar a economizar espaço, mas depende do que está sendo embalado na planilha, em primeiro lugar.

Roger Perkins
fonte
2
+1 Toca em chamadas de compra. (algo que eu não vi na resposta aceita)
Michael Coleman
11

Cada chamada de empate tem uma certa quantidade de sobrecarga. Ao usar folhas de sprite, você pode agrupar o desenho de itens que não estão usando o mesmo quadro de uma animação (ou, geralmente, tudo o que estiver no mesmo material), melhorando bastante o desempenho. Isso pode não importar muito para PCs modernos, dependendo do seu jogo, mas definitivamente importa, digamos, no iPhone.

Tetrad
fonte
2
Definitivamente ainda importa. O hardware gráfico foi otimizado para empurrar toneladas de polígonos para um pequeno número de modelos detalhados (por exemplo, personagens), porque a maioria dos jogos tende nessa direção. O resultado é que você precisa enviar o maior número possível de polígonos em cada uma das dezenas de chamadas de empate. Os mecanismos de jogo que reproduzem grandes cenas como as cidades geralmente combinam várias texturas em uma, para reduzir as chamadas de empate.
Jhocking 09/04
Definitivamente, estou me perguntando se a textura u, v mapeada em um monte de hardware 3D, além de operações pouco atrativas, se beneficia da folha de sprite.
Joe Plante
7

Além das considerações de desempenho, as folhas de sprite podem ser úteis ao criar a arte; cada caractere pode estar em sua própria folha e você pode ver todos os quadros apenas rolando ao redor. Isso me ajuda a manter uma aparência consistente em todos os quadros.

Além disso, codifico no AS3 e cada arquivo de imagem requer uma instrução de incorporação separada. Prefiro ter uma única incorporação de uma planilha inteira do que 24 incorporações para todos os quadros, mesmo se estiver usando uma classe de recurso separada.

Gregory Avery-Weir
fonte
11
+1. Embora o OP não esteja usando o Flash, a incorporação ou o carregamento também é um ponto a favor das planilhas. Também é relevante quando os ativos são carregados de um servidor da Web em que 1 solicitação é preferível a várias centenas de solicitações (para cada "quadro").
bummzack
Definitivamente, você deseja reduzir as solicitações do servidor para um jogo online. Consideramos se é melhor colocar as imagens em um arquivo .swf ou em uma planilha, pois ambas atingem esse objetivo principal (decidimos que as planilhas são menos trabalhosas para os nossos artistas).
Jhocking
7

Outro motivo para usar planilhas com o XNA é que, no desenvolvimento do Xbox360, há um problema conhecido com tempos de implantação / carregamento lentos em relação ao número de arquivos que você possui no seu projeto. Portanto, combinar muitos arquivos de imagem pequenos em um único arquivo de imagem ajudará a combater esse problema.

George Clingerman
fonte
5

Não vou mencionar memória / GPU aqui, pois há respostas suficientes assim.

Em vez disso, pode esclarecer sua arte enquanto você trabalha nela - e depois. Em vez de ter que percorrer 10 imagens para ver o ciclo de caminhada, você pode ver todos os quadros de uma só vez. Se você quiser distribuir isso, terá apenas 1 arquivo para lidar em vez de 10.

E também é mais limpo (do ponto de vista da organização) ter character.png e enemy.png sobre characterwalk01 até characterwalk10, depois characterattack01 a characterattack05, e continua.

O Pato Comunista
fonte
4

A memória gráfica não possui um gerenciamento de memória inchado, como sistemas de arquivos em discos; o contrário é o caso: é mantido simples e rápido .

Um desses gerenciadores de memória simples e rápido pode ser como ter apenas um único sprite gigante de 4096 x 4096 cortado em vários sprites menores pela metade de sua largura e / ou altura. (Existe uma técnica unidimensional de gerenciamento de memória unidimensional, mas esqueci o nome.) Eventualmente, a desfragmentação deve ser executada.

Para pular esse gerenciamento de memória em tempo de execução , os desenvolvedores preenchem sprites grandes únicos com vários sprites menores automaticamente no tempo de compilação ou mesmo durante o processo de criação dos gráficos.

comonad
fonte
3

Além disso, não se esqueça das operações de E / S que podem ser salvas durante a inicialização. Se você estiver carregando a partir de uma unidade de CD, cada arquivo é uma chamada de E / S extra e pode demorar um pouco para ser procurado (os HDDs modernos são cerca de 15 ms por arquivo, os CDs podem ser de 50 a 100ms?). Isso pode economizar muito tempo de inicialização, pois apenas um arquivo precisa ser buscado. Ele também permite que seu sistema operacional armazene em cache essas operações de E / S, caso seu aplicativo esteja fazendo outra coisa (como aguardar GPU).

Marcação
fonte
11
No outro extremo, se você quiser resolver o problema de busca de arquivos, poderá ter todos os seus sprites compactados em um único arquivo, usando um formato personalizado. A maioria dos jogos faz isso.
tigrou
0

Além de ser mais eficiente em termos de desempenho, acho mais fácil. É necessário um pouco de trabalho extra para garantir que suas imagens estejam sempre dentro dos limites ao criar a arte, mas é muito mais fácil ver todas as imagens com as quais você está lidando no momento.

Também é mais fácil codificar, na minha opinião. Eu simplesmente tenho uma variedade de objetos como:

sheet = {
    img: (the actual image),
    rects: [
        {x:0, y:0, w:32, h:32},
        {x:32, y:0, w:32, h:32},
        {x:64, y:0, w:32, h:32}
    ]
};

sheets.push(sheet);

Então, para cada item, dou dois valores, sheet e rect. A planilha seria o índice na matriz de objetos e o rect seria o índice na matriz de rects dessa planilha.

bridgerrholt
fonte