¿Cómo se mueve un sprite en incrementos de subpíxel?

11

Los píxeles están activados o desactivados. La cantidad mínima que puede mover un sprite es un solo píxel. Entonces, ¿cómo haces que el sprite se mueva más lento que 1 píxel por fotograma?

La forma en que lo hice fue agregar la velocidad a una variable y probar si había alcanzado 1 (o -1). Si lo hiciera, movería el sprite y restablecería la variable a 0, así:

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

No me gustó este enfoque porque se siente tonto y el movimiento del sprite se ve muy desigual. Entonces, ¿de qué manera implementaría el movimiento subpíxel?

bzzr
fuente
Lo único que podría hacer es el filtrado bilineal.
AturSams
Si se mueve menos de 1 px por fotograma, ¿cómo puede verse desigual?

Respuestas:

17

Hay varias opciones:

  1. Haz lo que haces Ya has dicho que no se ve suave. Sin embargo, hay algunos defectos con su método actual. Para x, puede usar lo siguiente:

    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

    Esto debería ser mejor. He cambiado las declaraciones if para usar 0.5, ya que cuando pasas 0.5 estás más cerca del siguiente valor que el anterior. He usado los bucles while para permitir el movimiento de más de 1 píxel por paso de tiempo (no es la mejor manera de hacerlo, pero es un código compacto y legible). Sin embargo, para los objetos que se mueven lentamente todavía estará nervioso, ya que no hemos abordado el problema fundamental de la alineación de píxeles.

  2. Tenga varios gráficos para su sprite y use uno diferente dependiendo del desplazamiento de subpíxeles. Para suavizar los subpíxeles solo en x, por ejemplo, puede crear un gráfico para su sprite en x+0.5, y si la posición está entre x+0.25y x+0.75, use este sprite en lugar del original. Si desea un posicionamiento más fino, simplemente cree más gráficos de subpíxeles. Si hace esto xy ysu número de representaciones puede aumentar rápidamente, ya que el número se escala con el cuadrado del número de intervalos: un espaciado 0.5requeriría 4 representaciones, 0.25requeriría 16.

  3. Supersample Esta es una forma perezosa (y potencialmente muy costosa) de crear una imagen con una resolución de subpíxeles. Esencialmente, duplique (o más) la resolución con la que renderiza su escena, luego reduzca la escala en tiempo de ejecución. Recomendaría tener cuidado aquí, ya que el rendimiento puede disminuir rápidamente. Una forma menos agresiva de hacer esto sería supermuestrear su sprite y reducirlo en tiempo de ejecución.

  4. Según lo sugerido por Zehelvion , puede ser que la plataforma que está utilizando ya sea compatible con esto. Si se le permite especificar coordenadas x e y no enteras, puede haber opciones para cambiar el filtrado de textura. El vecino más cercano suele ser el predeterminado, y esto provocará un movimiento "desigual". Otro filtrado (lineal / cúbico) daría como resultado un efecto mucho más suave.

La elección entre 2. 3. y 4. depende de cómo se implementen sus gráficos. Un estilo de mapa de bits de gráficos se adapta mucho mejor a la representación previa, mientras que un estilo vectorial puede adaptarse al supermuestreo de sprites. Si su sistema lo admite, 4. puede ser el camino a seguir.

Jez
fuente
¿Por qué reemplazar el umbral por 0.5 en opt 1? Tampoco entiendo por qué moviste las declaraciones a un ciclo while. Las funciones de actualización se llaman una vez por marca.
bzzr
1
Buena pregunta: he aclarado la respuesta en función de tu comentario.
Jez
El supermuestreo puede ser "vago", pero en muchos casos el costo de rendimiento del sobremuestreo 2x o incluso 4x no sería un problema particular, especialmente si permitiera simplificar otros aspectos del renderizado.
supercat
En los juegos de gran presupuesto, generalmente veo 3 como una opción en la configuración de gráficos como suavizado.
Kevin
1
@Kevin: Probablemente no lo hagas. La mayoría de los juegos usan multimuestreo , que es algo diferente. También se usan comúnmente otras variantes, por ejemplo, con cobertura variable y muestras de profundidad. El supermuestreo casi nunca se realiza debido al costo de la ejecución del sombreador de fragmentos.
imallett
14

Una forma en que muchos juegos antiguos resolvieron (u ocultaron) este problema fue animar el sprite.

Es decir, si su sprite iba a moverse menos de un píxel por fotograma (o, especialmente, si la relación píxeles / fotograma fuera algo extraño, como 2 píxeles en 3 fotogramas), podría ocultar la sacudida haciendo un n bucle de animación de cuadro que, sobre esos n cuadros, terminó moviendo el sprite unos k < n píxeles.

El punto es que, siempre y cuando el sprite siempre se mueva de alguna manera en cada cuadro, nunca habrá un solo cuadro en el que todo el sprite de repente "se mueva" hacia adelante.


No pude encontrar un sprite real de un viejo videojuego para ilustrar esto (aunque creo que, por ejemplo, algunas de las animaciones de excavación de Lemmings eran así), pero resulta que el patrón de "planeador" del juego de la vida de Conway hace un Muy buena ilustración:

ingrese la descripción de la imagen aquí
Animación de Kieff / Wikimedia Commons , utilizada bajo la licencia CC-By-SA 3.0 .

Aquí, los pequeños bloques de píxeles negros que se arrastran hacia abajo y hacia la derecha son los planeadores. Si observa detenidamente, notará que toman cuatro cuadros de animación para arrastrar un píxel en diagonal, pero como se mueven de alguna manera en cada uno de esos cuadros, el movimiento no se ve tan irregular (bueno, al menos ya no más). desigual de todo lo que se ve en esa velocidad de fotogramas, de todos modos).

Ilmari Karonen
fuente
2
Este es un enfoque excelente si la velocidad es fija, o si es inferior a 1/2 píxel por fotograma, o si la animación es lo suficientemente "grande" y lo suficientemente rápida como para ocultar las variaciones de movimiento.
supercat
Este es un buen enfoque, aunque si una cámara tiene que seguir el sprite, todavía se encontrará con problemas porque no se moverá sin problemas.
Elden Abob
6

La posición del sprite debe mantenerse como una cantidad de punto flotante y redondearse a un número entero justo antes de la visualización.

También puede mantener el sprite a una resolución súper y reducir la muestra del sprite antes de mostrarlo. Si mantuviste el sprite con una resolución de pantalla de 3x, tendrías 9 sprites reales diferentes dependiendo de la posición de subpíxel del sprite.

ddyer
fuente
3

La única solución real aquí es utilizar el filtrado bilineal. La idea es dejar que la GPU calcule el valor de cada píxel en función de los cuatro píxeles de sprite que se superponen con él. Es una técnica común y efectiva. Simplemente necesita colocar el sprite en un 2d-plain (una valla publicitaria) como textura; luego use la GPU para renderizar estas llanuras. Esto funciona bien, pero espera obtener resultados algo borrosos y perder el aspecto de 8 bits o 16 bits si lo buscaras.

pros:

Solución ya implementada, muy rápida, basada en hardware.

contras:

Pérdida de fidelidad de 8 bits / 16 bits.

AturSams
fuente
1

El punto flotante es la forma normal de hacer esto, especialmente si finalmente está renderizando a un objetivo GL donde el hardware está bastante contento con un polígono sombreado con coordenadas flotantes.

Sin embargo, hay otra forma de hacer lo que estás haciendo actualmente, pero un poco menos bruscamente: punto fijo. Un valor de posición de 32 bits puede representar valores de 0 a 4,294,967,295, aunque su pantalla casi seguramente tiene menos de 4k píxeles a lo largo de cualquier eje. Por lo tanto, coloque un "punto binario" fijo (por analogía con el punto decimal) en el medio y puede representar posiciones de píxeles de 0-65536 con otros 16 bits de resolución de subpíxeles. Luego puede agregar números de manera normal, solo debe recordar realizar la conversión desplazando a la derecha 16 bits cada vez que realice la conversión al espacio de la pantalla o cuando haya multiplicado dos números juntos.

(Escribí un motor 3D en punto fijo de 16 bits en el día en que tenía un Intel 386 sin unidad de punto flotante. Logró la velocidad cegadora de 200 polígonos sombreados por Phong por cuadro).

pjc50
fuente
1

Además de las otras respuestas aquí, puede usar el tramado hasta cierto punto. El tramado es donde el borde de los píxeles de un objeto es de color más claro / más oscuro para que coincida con el fondo y así obtener un borde más suave. Por ejemplo, supongamos que tenía un cuadrado de 4 píxeles que aproximaré con:

OO
OO

Si movieras este 1/2 píxel a la derecha, nada se movería realmente. Pero con un poco de vacilación, podrías hacer un poco de ilusión de movimiento. Considere O como negro y o como gris, por lo que podría hacer:

oOo
oOo

En un marco y

 OO
 OO

En el proximo. El "cuadrado" real con los bordes grises en realidad tomaría 3 píxeles de ancho, pero como son de un gris más claro que el negro, parecen más pequeños. Sin embargo, esto puede ser difícil de codificar, y no estoy al tanto de nada que haga esto en este momento. En el momento en que comenzaron a usar el dithering para las cosas, las resoluciones rápidamente mejoraron y no hubo tanta necesidad, excepto en cosas como la manipulación de imágenes.

Serpardum
fuente