Como você move um sprite em incrementos de sub-pixel?


11

Os pixels estão ativados ou desativados. A quantidade mínima que você pode mover um sprite é um único pixel. Então, como você faz o sprite se mover mais lento que 1 pixel por quadro?

A maneira como fiz isso foi adicionar a velocidade a uma variável e testar se havia atingido 1 (ou -1). Se isso acontecesse, eu moveria o sprite e redefiniria a variável para 0, assim:

update(dt):
    temp_dx += speed * dt
    temp_dy += speed * dt

    if (temp_dx > 1)
        move sprite
        reset temp_dx to 0
    if (tempy_dy > 1)
        move sprite
        reset temp_dy to 0

Eu não gostei dessa abordagem porque parece boba e o movimento do sprite parece muito irregular. Então, de que maneira você implementaria o movimento sub-pixel?


A única coisa que você pode fazer é a filtragem bilinear.
AturSams

Se você estiver movendo menos de 1 px por quadro, como pode parecer instável?

Respostas:


17

Há várias opções:

  1. Faça como você faz. Você já disse que não parece bom. Existem algumas falhas no seu método atual. Para x, você pode usar o seguinte:

    tempx += speed * dt
    
    while (tempx > 0.5)
      move sprite to x+1
      tempx -= 1
    while (tempx < -0.5)
      move sprite to x-1
      tempx += 1

    isso deveria ser melhor. Mudei as declarações if para usar 0,5, pois quando você passa de 0,5, fica mais próximo do próximo valor que o anterior. Usei while loops para permitir o movimento de mais de 1 pixel por etapa de tempo (não é a melhor maneira de fazer isso, mas cria um código compacto e legível). No entanto, para objetos em movimento lento, ainda será instável, pois não lidamos com a questão fundamental do alinhamento de pixels.

  2. Tenha vários gráficos para o seu sprite e use um diferente, dependendo do deslocamento do subpixel. Para suavizar subpixel apenas em x, por exemplo, você pode criar um gráfico para seu sprite em x+0.5e, se a posição estiver entre x+0.25e x+0.75, use esse sprite em vez do original. Se você deseja um posicionamento mais preciso, basta criar mais gráficos de subpixel. Se você fizer isso xe yseu número de renderizações puder aumentar rapidamente, conforme o número é escalado com o quadrado do número de intervalos: um espaçamento de 0.5exigiria 4 renderizações, 0.25exigiria 16.

  3. Supersample. Essa é uma maneira preguiçosa (e potencialmente muito cara) de criar uma imagem com resolução de subpixel. Essencialmente, duplique (ou mais) a resolução na qual você renderiza sua cena e reduza-a em tempo de execução. Eu recomendaria cuidados aqui, pois o desempenho pode cair rapidamente. Uma maneira menos agressiva de fazer isso seria apenas supersample seu sprite e reduzi-lo em escala menor em tempo de execução.

  4. Conforme sugerido por Zehelvion , pode ser que a plataforma que você está usando já suporte isso. Se você tiver permissão para especificar coordenadas xey não inteiras, pode haver opções para alterar a filtragem de textura. O vizinho mais próximo geralmente é o padrão, e isso causa movimento "espasmódico". Outra filtragem (linear / cúbica) resultaria em um efeito muito mais suave.

A escolha entre 2. 3. e 4. depende de como seus gráficos são implementados. Um estilo de bitmap de gráficos se adapta melhor à pré-renderização, enquanto um estilo de vetor pode se adequar à superamostragem de sprites. Se o seu sistema suportar, 4. pode ser o caminho a seguir.


Por que substituir o limite por 0,5 na opção 1? Também não entendo por que você moveu as declarações para um loop while. As funções de atualização são chamadas uma vez por tick.
bzzr

1
Boa pergunta - esclareci a resposta com base no seu comentário.
Jez

A superamostragem pode ser "preguiçosa", mas em muitos casos o custo de desempenho da superamostragem de 2x ou 4x não seria um problema específico, especialmente se permitir que outros aspectos da renderização sejam simplificados.
Supercat #

Em jogos de grande orçamento, normalmente vejo 3 como uma opção listada nas configurações gráficas como anti-aliasing.
Kevin

1
@ Kevin: Você realmente provavelmente não. A maioria dos jogos usa multisampling , o que é um pouco diferente. Outras variantes também são comumente usadas, com, por exemplo, cobertura variável e amostras de profundidade. A superamostragem quase nunca é feita devido ao custo da execução do shader de fragmento.
imallett

14

Uma maneira pela qual muitos jogos antigos do skool resolveram (ou ocultaram) esse problema foi animar o sprite.

Ou seja, se o seu sprite mover menos de um pixel por quadro (ou, especialmente, se a proporção de pixels / quadro for algo estranha como 2 pixels em 3 quadros), você poderá ocultar a instabilidade criando um n loop de animação de quadros que, sobre esses n quadros, acabou movendo o sprite em alguns k < n pixels.

O ponto é que, desde que o sprite sempre se mova de alguma forma em cada quadro, nunca haverá um único quadro em que todo o sprite subitamente se "empurre" para frente.


Eu não consegui encontrar um sprite real de um videogame antigo para ilustrar isso (embora eu ache que, por exemplo, algumas das animações de Lemmings eram assim), mas acontece que o padrão de "planador" do Jogo da Vida de Conway faz um ilustração muito legal:

insira a descrição da imagem aqui
Animação de Kieff / Wikimedia Commons , usada sob a licença CC-By-SA 3.0 .

Aqui, os pequenos blocos de pixels pretos rastejando rastejando para baixo e para a direita são os planadores. Se você olhar com cuidado, perceberá que eles pegam quatro quadros de animação para rastrear um pixel na diagonal, mas, como se movem de alguma forma em cada um desses quadros, o movimento não parece tão instável (bem, pelo menos não mais) espasmódico do que qualquer coisa olha para essa taxa de quadros, de qualquer maneira).


2
Essa é uma excelente abordagem se a velocidade for fixa, ou se for menor que 1/2 pixel por quadro, ou se a animação for "grande" o suficiente e rápida o suficiente para obscurecer as variações de movimento.
Supercat #

Essa é uma boa abordagem, embora se uma câmera tiver que seguir o sprite, você ainda terá problemas porque não se moverá bem.
Elden Abob

6

A posição do sprite deve ser mantida como uma quantidade de ponto flutuante e arredondada para um número inteiro apenas antes da exibição.

Você também pode manter o sprite em super resolução e reduzir o tamanho do sprite antes da exibição. Se você mantivesse o sprite na resolução de tela 3x, teria 9 sprites reais diferentes, dependendo da posição do subpixel do sprite.


3

A única solução real aqui é usar a filtragem bilinear. A idéia é permitir que a GPU calcule o valor de cada pixel com base nos quatro pixels sprite que se sobrepõem. É uma técnica comum e eficaz. Você simplesmente precisa colocar o sprite em uma planície 2d (um outdoor) como textura; depois use a GPU para renderizar essas planícies. Isso funciona bem, mas espera-se obter resultados um pouco embaçados e perder a aparência de 8 ou 16 bits, se você estiver buscando isso.

profissionais:

Solução já implementada, muito rápida, baseada em hardware.

contras:

Perda de fidelidade de 8 bits / 16 bits.


1

O ponto flutuante é a maneira normal de fazer isso, especialmente se você estiver renderizando para um destino GL, onde o hardware está bastante satisfeito com um polígono sombreado com coordenadas flutuantes.

No entanto, há outra maneira de fazer o que você está fazendo atualmente, mas um pouco menos brusca: ponto fixo. Um valor de posição de 32 bits pode representar os valores de 0 a 4.294.967.295, mesmo que sua tela quase certamente tenha menos de 4k pixels ao longo de ambos os eixos. Portanto, coloque um "ponto binário" fixo (por analogia com ponto decimal) no meio e você poderá representar as posições de pixel de 0-65536 com outros 16 bits de resolução de subpixel. Você pode adicionar números normalmente, basta lembrar de converter 16 bits à direita, sempre que converter para o espaço da tela ou quando multiplicar dois números juntos.

(Eu escrevi um mecanismo 3D em ponto fixo de 16 bits no dia em que eu tinha um Intel 386 sem unidade de ponto flutuante. Ele atingiu a velocidade ofuscante de 200 polígonos sombreados de Phong por quadro.)


1

Além das outras respostas aqui, você pode usar o pontilhamento até certo ponto. O pontilhamento é o local em que a borda dos pixels de um objeto é mais clara / mais escura para combinar com o fundo, criando uma borda mais suave. Por exemplo, digamos que você tenha um quadrado de 4 pixels com o qual vou abordar:

OO
OO

Se você movesse esse pixel de 1/2 para a direita, nada realmente se moveria. Mas, com alguma hesitação, você pode criar um pouco de ilusão de movimento. Considere O como preto e o como cinza; portanto, você pode fazer:

oOo
oOo

Em um quadro e

 OO
 OO

Na próxima. O "quadrado" real com as bordas acinzentadas teria na verdade 3 pixels de largura, mas, como são cinza mais claro que o preto, parecem menores. No entanto, isso pode ser difícil de codificar, e não estou realmente ciente de nada que faça isso agora. Na época em que começaram a usar o pontilhamento, as resoluções rapidamente se tornaram melhores e não havia tanta necessidade, exceto em coisas como manipulação de imagem.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.