¿Cómo eliminar el jitter de la entrada de movimiento?

9

Estoy escribiendo un mod de Minecraft que admite la entrada de Razer Hydra . Es un par de controladores de movimiento (uno para cada mano) que proporcionan información de posición y rotación increíblemente precisa.

Para el propósito de esta pregunta, girar el controlador derecho en el eje Y hace que el personaje del jugador mire hacia la izquierda o hacia la derecha (guiñada), y girar sobre el eje X hace que el jugador mire hacia arriba y hacia abajo (cabeceo).

La entrada del controlador se asigna directamente al encabezado del personaje. Si el controlador gira 30 grados a la izquierda, el personaje gira 30 grados a la izquierda.

El problema es que la entrada "fluctúa". Si trato de mantener el controlador perfectamente quieto, el encabezado del personaje se mueve erráticamente dentro de un cono muy pequeño (tal vez 1 grado).

Esto probablemente se deba a mis manos temblorosas, ya que los datos del controlador son aparentemente exactos.

Intenté filtrar la entrada promediando los datos de los últimos X cuadros, pero esto hace que la entrada parezca mantecosa.

Mi pregunta es: ¿Cómo puedo filtrar los datos de rotación para eliminar la fluctuación sin perder precisión?

Las manzanas
fuente
¿consideró ignorar cambios muy pequeños en el movimiento?
Philipp
1
Este Razer Hyrdra es interesante ... la primera vez que escuché sobre él, aún más interesante es escribir un mod de Minecraft para complementarlo ... Mi suposición es: al igual que las imágenes pixeladas, para "reducir el ruido" necesitas para desenfocar la imagen ... Básicamente puedes vivir con la imagen pixelada o vivir con la imagen borrosa ... Siento que este es el mismo principio, debes elegir cuál prefieres, juego inestable o menos precisión ... ... Eso es justo lo que creo ...
Luke San Antonio Bialecki
1
Podrías sacar lo mejor de ambos. Siempre almacene el último decir 50 cuadros. Pero solo promedia unos pocos cuadros, dependiendo de la cantidad de movimiento de entrada. Para movimientos grandes, solo confía en decir los últimos 5 fotogramas y para movimientos pequeños (de otra manera fluctuantes) confía en los últimos 30 fotogramas.
danijar
@sharethis Esa es una idea interesante. Puedo terminar implementando algo así. La fluctuación de fase no es un problema una vez que la entrada supera un cierto umbral, por lo que podría promediar las entradas pequeñas para eliminar la fluctuación de fase y no promediar nada para una entrada grande.
Manzanas

Respuestas:

8

Este problema de retraso vs capacidad de respuesta es la situación con prácticamente todos los controladores de movimiento, ya sea algo como Hydra, Wii Remote, Kinect o PlayStation Move.

El problema es este:

Cuando ingresa una secuencia de entrada, está tomando una decisión cuadro por cuadro sobre si confiar o no en los datos de entrada; si las tendencias que estás viendo ahora continuarán en los datos que recibas en una docena de milisegundos a partir de ahora. Por ejemplo, si hay un cambio repentino a la derecha de este marco, no sabe si se trata de un bit de datos de entrada real (y, por lo tanto, debe actuar sobre él) o si es simplemente jitter (y por lo tanto debe ignorarlo). Independientemente de lo que elija, si luego descubre que estaba equivocado, ha permitido que la fluctuación de entrada entre en su juego (en el primer caso) o que haya introducido retraso en su juego (en el último caso).

No hay una buena solución para esto. Una solución "correcta" para determinar si la entrada es real o inestable requiere saber qué hará el flujo de entrada en el futuro, así como lo que hizo en el pasado. No podemos hacer eso en los juegos, por razones obvias. Entonces, no, no hay forma de filtrar los datos de rotación para eliminar la fluctuación sin perder precisión, en el contexto de un juego que está trabajando con datos de entrada en tiempo real.

He visto a un fabricante importante recomendar que los desarrolladores aborden este problema haciendo que los jugadores mantengan presionado un botón mientras giran el control, para que el juego pueda desactivar su código anti-jitter en ese punto, por lo que no es lento. (No recomiendo esto, pero es un enfoque).

He visto algunas bibliotecas de middleware de entrada de movimiento que tratan este problema mediante la introducción de un retraso artificial en la entrada: hay un búfer de cuarto de segundo en el que entran los datos de entrada, y tu juego solo escucha sobre la entrada un cuarto de segundo después, para que la biblioteca pueda suavizar el jitter por ti, sabiendo lo que sucede tanto antes como después del "presente" desde el punto de vista del juego. Eso funciona muy bien, aparte de introducir un cuarto de segundo de retraso en todo. Pero es una forma de resolver el problema, y ​​puede hacer un trabajo increíble al representar con precisión un movimiento sin la fluctuación de fase, a expensas del retraso constante.

Pero sin llegar a ese extremo, todavía hay algunas cosas que podemos hacer para mejorar enormemente el comportamiento, aunque sabemos que siempre habrá "peores escenarios" que se comporten de manera no ideal.

La idea central es que solo nos preocupamos por el jitter cuando el controlador está mayormente estacionario, y solo nos importa el retraso cuando el controlador se está moviendo. Por lo tanto, nuestra estrategia debería ser tratar de lidiar con las cosas de modo que tengamos un retraso cuando el controlador esté estacionario, y tengamos nerviosismo cuando el controlador esté en movimiento.

Aquí hay dos formas posibles de hacerlo:

Un enfoque común es un sistema "bloqueado / desbloqueado", en el que realiza un seguimiento de la orientación del dispositivo, y si no cambia por un corto tiempo (medio segundo más o menos), 'bloquea' esa orientación, sin tomar acción basada en la orientación informada del dispositivo hasta que difiera lo suficiente como para 'desbloquear' nuevamente. Esto puede silenciar completamente el jitter basado en la orientación, sin introducir retraso cuando la orientación está cambiando activamente. Puede haber un indicio de retraso antes de que el código decida que necesita cambiar al modo "desbloqueado", pero será mucho mejor que tener un retraso en todas partes.

Otro enfoque es promediar juntos los datos de entrada de los marcos. El punto importante aquí es promediar solo los datos de entrada de los fotogramas donde los datos de entrada fueron vagamente similares: esto significa que las pequeñas fluctuaciones se desenfocarán y suavizarán, pero los cambios más grandes no se desenfocan, porque sus datos no son suficientemente similar a los datos de cuadros anteriores.

Hay otras formas de obtener un efecto similar también. La idea central es que no puedes tener tanto jitter como no lag en tu juego en tiempo real al mismo tiempo, porque hacerlo requeriría conocimiento del futuro. Por lo tanto, debe elegir cuándo sesgar el comportamiento de su control para aceptar el jitter y cuándo sesgarlo para aceptar el retraso, para que la experiencia general sea lo más mala posible.

Trevor Powell
fuente
Acabo de publicar una respuesta, ¿puede dar su opinión sobre mi solución?
Manzanas
2

Desarrollo un software que convierte la entrada de movimiento en una entrada de mouse precisa y receptiva, así como también mantengo un sitio web que intenta ayudar a los desarrolladores a implementar soluciones igualmente buenas. Generalmente desaconsejo los umbrales de movimiento, aunque depende de la capacidad de respuesta y precisión que deseen los jugadores, me alegra que eso funcione para usted en su situación. Pero aquí ofreceré una solución diferente:

Yo uso algo llamado Suavizado en niveles suaves . La idea es que desviamos la entrada a través de diferentes algoritmos de suavizado dependiendo de la magnitud actual de la velocidad del giroscopio (en la práctica, uno de esos algoritmos de suavizado es simplemente "sin suavizado"). Esa es la parte "escalonada". La parte "blanda" es que en realidad podemos dividir suavemente la entrada entre diferentes algoritmos de suavizado dependiendo de cómo se compara con 2 umbrales.

Conserva el desplazamiento correctamente y no agrega ningún retraso a los movimientos rápidos.

En la práctica, tienes dos umbrales. Cuando la magnitud de la velocidad de entrada es menor que el umbral inferior, estamos utilizando un algoritmo de suavizado simple (promedio en varios cuadros). Cuando es mayor que el otro umbral, no utilizamos ningún algoritmo de suavizado. Pero en este caso, todavía estamos pasando ceros al algoritmo de suavizado de umbral inferior.

Cuando la velocidad de entrada está entre los dos umbrales, dividimos la entrada entre los dos algoritmos en consecuencia.

Aquí hay un fragmento del artículo anterior:

GetSoftTieredSmoothedInput(Vec2 input, float threshold1, float threshold2) {
    // this will be length(input) for vectors
    float inputMagnitude = Abs(input);

    float directWeight = (inputMagnitude - threshold1) / (threshold2 - threshold1);
    directWeight = clamp(directWeight, 0, 1);

    return GetDirectInput(input * directWeight) +
        GetSmoothedInput(input * (1.0 - directWeight));
}

GetDirectInput solo devuelve lo que se le da, pero es para mostrar que otro algoritmo de suavizado podría usarse aquí. GetSmoothedInput toma una velocidad y devuelve una velocidad suavizada.

Con Soft Smokered Smoothing no se aplica suavizado a los movimientos obviamente intencionales (por encima del umbral mayor), se aplica suavizado para cubrir pequeñas cantidades de jitter, lo que también afectará los movimientos muy pequeños, pero cuando obtienes tus umbrales correctos, no es muy perceptible. Y hay una transición muy suave entre los dos (sin los cuales, la fluctuación de fase puede amplificarse).

Si bien las otras respuestas son correctas al decir que es difícil reconocer la fluctuación de fase en el instante en que se recibe una entrada, también es cierto que la fluctuación de fase es casi siempre de muy baja velocidad, y el retraso de entrada que viene con el suavizado es mucho menos notable para las entradas de baja velocidad .

Como menciona el artículo, esto se usa en un par de lugares en mi herramienta de código abierto JoyShockMapper , un mapeador de entrada que convierte la entrada del giroscopio en entrada del mouse. Incluso para las personas que usan otras herramientas de reasignación como Steam o reWASD, algunos usan JoyShockMapper al mismo tiempo solo por sus controles de giroscopio.

Esta respuesta asume que la entrada se da en velocidad angular (que es común con los controladores que tienen controles de movimiento), no en orientación absoluta (que parece que Razer Hydra te está dando). Con orientación absoluta, espero que pueda usar la diferencia entre la orientación actual y la orientación informada previamente para obtener una velocidad, pero no estoy seguro si funcionará tan bien como con los controladores que informan la velocidad angular .

Una solución de suavizado común cuando se trata de una posición / orientación absoluta en lugar de velocidades es interpolar hacia la orientación de su objetivo a lo largo del tiempo; esto se describe con detalles muy útiles en este artículo de Gamasutra. Esto también puede funcionar con Soft Smokered Smoothing. Calculará la magnitud de la velocidad utilizando la diferencia entre esta entrada y la entrada informada anterior. Aplicará la diferencia de orientación entre este cuadro y el último cuadro multiplicado por su valor "directWeight" como se calcula en el fragmento anterior. El último paso es agregar la entrada suavizada, pero debido a la forma en que funciona el suavizado de orientación interpolada, simplemente aplica el cambio de orientación interpolado de la manera normal; no es necesario considerar "directWeight" en absoluto. Simplemente configure su orientación objetivo (esto es a lo que se está interpolando con el suavizado descrito en ese artículo de Gamasutra) a cualquier orientación que obtenga del dispositivo, e interpole su orientación hacia él como se describe en ese artículo.

Jibb Smart
fuente
1

Resulta extraño responder mi propia pregunta, pero creo que he encontrado mi solución.

//Pseudo-Java

update()
{
    //deltaYaw is the change in yaw of the controller since last update
    //yawBuffer is initialized to zero, and only modified here
    //coneAngle is the stabilizing cone

    deltaYaw = getData().yaw;

    yawBuffer += deltaYaw;
    if (abs(yawBuffer) >= coneAngle)
    {
        player.yaw += (abs(yawBuffer)-coneAngle) * sign(yawBuffer);
        yawBuffer = coneAngle * sign(yawBuffer);
    }
}

En lugar de modificar el rumbo del jugador directamente, simplemente "empujo" un cono de un ángulo dado (en mi caso, 2.5 grados). Hice una pequeña demostración HTML5 de esta técnica.

Una vez que comience a empujar el cono, hay cero retraso y precisión total. Sin embargo, si empujas el cono hacia la izquierda y luego quieres apuntar hacia la derecha, debes moverte a través del ángulo completo del cono para ver un efecto.

Por lo tanto, resuelve los problemas de retraso temporal y suavización horrible, pero presenta el nuevo problema de un umbral de movimiento. Sin embargo, si el cono estabilizador está sintonizado correctamente, el umbral es imperceptible.

Las manzanas
fuente
Esta parece otra forma razonable de hacerlo. Solía ​​haber videocámaras (caras) que estabilizaban su imagen usando este enfoque; le brinda precisión a medida que continúa un movimiento en una dirección, pero tiene un retraso cuando cambia de dirección. Si eso funciona bien para tu juego, entonces hazlo. No vas a encontrar una solución única que funcione mejor para cada juego; siempre es una compensación donde debes sopesar las desventajas de cada enfoque contra las necesidades específicas del juego en particular que estás haciendo. :)
Trevor Powell