Como remover o jitter da entrada de movimento?


9

Estou escrevendo um mod Minecraft que suporta entradas do Razer Hydra . É um par de controladores de movimento (um para cada mão) que fornece informações de posição e rotação incrivelmente precisas.

Para os fins desta pergunta, girar o controlador direito no eixo Y faz com que o personagem do jogador olhe para a esquerda ou direita (guinada) e girar no eixo X faz o jogador olhar para cima e para baixo (afinação).

A entrada do controlador é mapeada diretamente para o cabeçalho do personagem. Se o controlador for girado 30 graus para a esquerda, o caractere gira 30 graus para a esquerda.

O problema é que a entrada "treme". Se eu tentar manter o controle perfeitamente imóvel, o cabeçalho do personagem se move erraticamente dentro de um cone muito pequeno (talvez 1 grau).

Provavelmente isso se deve às minhas mãos trêmulas, pois os dados do controlador são aparentemente exatos.

Tentei filtrar a entrada calculando a média dos dados dos últimos quadros X, mas isso faz com que a entrada pareça amanteigada.

Minha pergunta é: Como posso filtrar os dados rotacionais para remover a instabilidade sem perder a precisão?


você considerou ignorar mudanças muito pequenas no movimento?
Philipp

11
Este Razer Hyrdra é interessante ... na primeira vez que ouvi falar disso, ainda mais interessante é escrever um mod Minecraft para complementá-lo ... Minha suposição é: assim como imagens pixelizadas, para "reduzir o ruído" necessário para desfocar a imagem ... Basicamente, você pode viver com a imagem pixelizada ou com a imagem borrada ... Sinto que esse é o mesmo princípio, é preciso escolher qual você prefere, jogo instável ou menos precisão ... ... Isso é apenas o que eu penso ...
Luke San Antonio Białecki

11
Você poderia fazer o melhor de ambos. Guarde sempre os últimos 50 quadros, digamos. Mas média apenas em alguns quadros, dependendo da quantidade de movimento da entrada. Para movimentos grandes, conte apenas com os últimos 5 quadros, e para movimentos pequenos (de outra forma instáveis), use os últimos 30 quadros.
danijar

@sharethis Essa é uma ideia interessante. Eu posso acabar implementando algo assim. A instabilidade não é um problema uma vez que a entrada ultrapassa um determinado limite, então eu poderia apenas calcular a média das entradas pequenas para remover a variação e não calcular a média de nada para entradas grandes.
Apples

Respostas:


8

Esse problema de atraso versus capacidade de resposta é a situação de praticamente todos os controladores de movimento, seja o Hydra, o Wii Remote, o Kinect ou o PlayStation Move.

O problema é este:

Quando um fluxo de entrada está chegando, você decide, quadro a quadro, se deve ou não confiar nos dados de entrada; se as tendências que você está vendo agora continuarão nos dados que você receber daqui a uma dúzia de milissegundos. Por exemplo, se houver uma mudança repentina para a direita nesse quadro, você não sabe se são dados reais de entrada (e, portanto, deve agir com base neles) ou se são meramente instabilidade (e, portanto, deve ignorá-los). Qualquer que seja a sua escolha, se mais tarde descobrir que você estava errado, você permitiu que a instabilidade de entrada o fizesse entrar no jogo (no primeiro caso) ou introduzisse um atraso no jogo (no último caso).

Não há uma boa solução para isso. Uma solução "correta" para determinar se a entrada é real ou instável requer saber o que o fluxo de entrada fará no futuro, bem como o que fez no passado. Não podemos fazer isso em jogos, por razões óbvias. Portanto, não há como filtrar dados rotacionais para remover o jitter sem perder a precisão, no contexto de um jogo que está trabalhando com dados de entrada em tempo real.

Eu já vi um grande fabricante recomendar que os desenvolvedores lidem com esse problema fazendo com que os jogadores pressionem um botão enquanto giram o controle, para que o jogo possa desativar seu código anti-instabilidade nesse momento, para que não fique parado. (Eu não recomendo isso, mas é uma abordagem).

Eu já vi algumas bibliotecas de middleware de entrada de movimento que lidam com esse problema introduzindo um atraso artificial na entrada - há um buffer de quarto de segundo no qual os dados são inseridos, e seu jogo apenas ouve um quarto depois, para que a biblioteca possa atenuar o nervosismo para você, sabendo o que acontece antes e depois do "presente" do ponto de vista do jogo. Isso funciona muito bem, além de introduzir um quarto de segundo de atraso em tudo. Mas é uma maneira de resolver o problema, e ele pode fazer um excelente trabalho ao representar com precisão um movimento com a remoção do tremor, às custas do atraso constante.

Mas, sem ir a esse extremo, ainda há algumas coisas que podemos fazer para melhorar o comportamento, mesmo sabendo que sempre haverá "piores cenários" que se comportam de maneira não ideal.

O insight principal é que nos preocupamos apenas com a instabilidade quando o controlador está estacionário e apenas nos preocupamos com o atraso quando o controlador está sendo movido. Portanto, nossa estratégia deve ser tentar lidar com as coisas, para que tenhamos atraso quando o controlador estiver parado e tremeremos quando o controlador estiver em movimento.

Aqui estão duas maneiras possíveis de fazer isso:

Uma abordagem comum é um sistema "bloqueado / desbloqueado", no qual você monitora a orientação do dispositivo e, se ele não mudar por um curto período de tempo (meio segundo mais ou menos), você 'bloqueia' essa orientação, sem levar em conta ação com base na orientação relatada do dispositivo até que seja diferente o suficiente para 'desbloquear' novamente. Isso pode reprimir completamente o tremor baseado na orientação, sem introduzir lag quando a orientação está mudando ativamente. Pode haver uma pitada de atraso antes que o código decida que precisa mudar para o modo "desbloqueado", mas será muito melhor do que ter lag em todos os lugares.

Outra abordagem é calcular a média dos dados de entrada dos quadros. O ponto importante aqui é apenas calcular a média dos dados de entrada dos quadros em que os dados de entrada eram vagamente semelhantes - Isso significa que pequenos tremores ficam borrados e suavizados, mas mudanças maiores não ficam borradas, porque seus dados não são semelhante o suficiente aos dados dos quadros anteriores.

Existem outras maneiras de obter um efeito semelhante também. O insight principal é que você não pode ter tanto instabilidade quanto atraso no seu jogo em tempo real ao mesmo tempo, porque fazer isso exigiria conhecimento do futuro. Portanto, você precisa escolher quando influenciar o comportamento do seu controle para aceitar o jitter e quando influenciar o aceitar o atraso, a fim de tornar a experiência geral o mais ruim possível.


Acabei de publicar uma resposta, você pode opinar sobre a minha solução?
Maçãs

2

Desenvolvo um software que converte a entrada de movimento em uma resposta precisa e precisa do mouse, além de manter um site que tenta ajudar os desenvolvedores a implementar soluções igualmente boas. Eu geralmente desaconselho os limiares de movimento, embora dependa da capacidade de resposta e precisão que os jogadores desejam, fico feliz que esteja trabalhando para você em sua situação. Mas aqui vou oferecer uma solução diferente:

Eu uso algo chamado Smooth Tiered Smoothing . A idéia é que desviaremos a entrada por meio de diferentes algoritmos de suavização, dependendo da magnitude atual da velocidade do giroscópio (na prática, um desses algoritmos de suavização é simplesmente "sem suavização"). Essa é a parte "em camadas". A parte "flexível" é que podemos realmente dividir suavemente a entrada entre diferentes algoritmos de suavização, dependendo de como ela se compara a 2 limites.

Ele preserva o deslocamento corretamente e não adiciona nenhum atraso aos movimentos rápidos.

Na prática, você tem dois limites. Quando a magnitude da velocidade de entrada é menor que o limite mais baixo, estamos usando um algoritmo de suavização simples (média em vários quadros). Quando é maior que o outro limite, não usamos nenhum algoritmo de suavização. Mas, neste caso, ainda estamos passando zeros para o algoritmo de suavização de limite inferior.

Quando a velocidade de entrada está entre os dois limites, dividimos a entrada entre os dois algoritmos de acordo.

Aqui está um trecho do artigo acima:

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 apenas retorna o que é dado a ele, mas é para mostrar que outro algoritmo de suavização pode ser usado aqui. GetSmoothedInput pega uma velocidade e retorna uma velocidade suavizada.

Com Suavização em camadas suaves, não há suavização aplicada a movimentos obviamente intencionais (acima do limite maior), há suavização aplicada para encobrir pequenas quantidades de tremulação, o que também afetará movimentos muito pequenos, mas quando você atinge seus limites corretamente, não é muito perceptível. E há uma transição muito suave entre os dois (sem a qual, o jitter pode realmente ser amplificado).

Enquanto as outras respostas têm razão em dizer que é difícil reconhecer a instabilidade no instante em que uma entrada é recebida, também é verdade que a instabilidade é quase sempre uma velocidade muito baixa, e o atraso na entrada que vem com a suavização é muito menos perceptível nas entradas de baixa velocidade .

Como o artigo menciona, isso é usado em alguns lugares da minha ferramenta de código aberto JoyShockMapper , um mapeador de entrada que transforma a entrada de giroscópio em entrada de mouse. Mesmo para pessoas que usam outras ferramentas de remapeamento, como Steam ou reWASD, algumas usam o JoyShockMapper ao mesmo tempo apenas para seus controles de giroscópio.

Esta resposta assume que a entrada é dada em velocidade angular (que é comum em controladores que possuem controles de movimento), e não em orientação absoluta (que parece que o Razer Hydra está fornecendo a você). Com orientação absoluta, minha esperança é que você possa usar a diferença entre a orientação atual e a orientação relatada anteriormente para obter uma velocidade, mas não sei ao certo se funcionará tão bem quanto com os controladores que relatam a velocidade angular .

Uma solução de suavização comum quando você está lidando com uma posição / orientação absoluta, em vez de velocidades, é interpolar para a orientação da meta ao longo do tempo - isso é descrito em detalhes muito úteis neste artigo do Gamasutra. Isso também pode funcionar com suavização em camadas suaves. Você calculará a magnitude da velocidade usando a diferença entre esta entrada e a entrada relatada anteriormente. Você aplicará a diferença de orientação entre esse quadro e o último quadro multiplicado pelo valor "directWeight", conforme calculado no snippet acima. A última etapa é adicionar a entrada suavizada, mas devido à maneira como a suavização da orientação interpolada funciona, basta aplicar a alteração da orientação interpolada conforme o normal - ela não precisa considerar "directWeight". Apenas defina a orientação do alvo (é para isso que você está interpolando com a suavização descrita nesse artigo do Gamasutra) para qualquer orientação que estiver recebendo do dispositivo e interpole a orientação para ele, conforme descrito no artigo.


1

É estranho responder minha própria pergunta, mas acho que encontrei minha solução.

//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);
    }
}

Em vez de modificar diretamente o rumo do jogador, simplesmente "empurro" um cone de um determinado ângulo (no meu caso, 2,5 graus). Fiz uma pequena demonstração em HTML5 dessa técnica.

Quando você começa a pressionar o cone, há um atraso zero e precisão total. No entanto, se você empurrar o cone para a esquerda e depois apontar para a direita, terá que se mover em todo o ângulo do cone para ver um efeito.

Por isso, resolve os problemas de atraso temporal e suavização horrível, mas introduz o novo problema de um limiar de movimento. No entanto, se o cone estabilizador estiver ajustado corretamente, o limiar será imperceptível.


Essa parece outra maneira razoável de fazer isso. Costumava haver filmadoras (caras) que estabilizavam sua imagem usando essa abordagem; fornece precisão à medida que você continua um movimento em uma direção, mas fica lento quando você muda de direção. Se isso funciona bem para o seu jogo, então vá em frente. Você não encontrará uma solução única que funcione melhor para todos os jogos; é sempre uma troca em que você precisa pesar as desvantagens de cada abordagem em relação às necessidades específicas do jogo em particular que você está criando. :)
Trevor Powell
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.