Como simulo um efeito doppler em um jogo?

14

Estou tentando simular o efeito doppler em um jogo (um jogo de corrida de carros). Não estou usando uma biblioteca de sons específica que simula o efeito, só tenho uma função de retorno de chamada onde misturo os dados.

Eu já descobri como alterar a frequência de uma amostra na função mixer.

O que eu não sei é quanto a frequência deve mudar dependendo da posição e velocidade do player e do emissor.

Aqui está o que eu tenho no jogo:

//player 
vec3 p.pos; 
vec3 p.vel;

//emitter 
vec3 e.pos;
vec3 e.vel;

1) Segundo a Wikipedia , a relação entre frequência emitida e frequência observada é dada por:

float f = (c + vr) / (c + vs) * fo

onde c é uma constante, a velocidade no meio (geralmente um grande número) vs e vr são as velocidades da fonte e do receptor em relação ao meio.

então eu acho :

float vr = p.vel.length; //player speed 
float vs = e.vel.length; //emitter speed

mas acho errado, ele não produzirá nenhuma alteração na frequência, por exemplo: se vr = 0(o jogador não se move) e o emissor tem velocidade constante, então vre vsnão muda (enquanto deveria).

talvez eu deveria calcular a velocidade do jogador em relação à velocidade do emissor?

como isso :

relative_speed = distance(p.pos + p.vel, e.pos + e.vel) -
distance(p.pos, e.pos);

então como vre vsdeve ser alimentado?


2) a Wikipedia também fornece outra fórmula para simular o efeito de um veículo que o veículo passa pelo observador:

vr = vs * cos(theta);

//theta is angle between observer and emitter
//theta = atan2(e.pos.y-p.pos.y, e.pos.x-p.pos.x); ?

no entanto, essa fórmula supõe que o receptor não se mova, o que não é o caso aqui. se o jogador e o emissor se moverem na mesma velocidade (ou pequena diferença), não haverá efeito doppler. essa função também é específica para um caso, suponho que a fórmula final deva ser a mesma, independentemente da situação.


EDIT: estou tentando encontrar a fórmula correta, usando o SkimFlux post:

vr,r = vr.vel * cos(shortest_angle_between ( vr.vel , vs.pos - vr.pos)); 
vs,r = vs.vel * cos(shortest_angle_between ( vs.vel , vr.pos - vs.pos)); 

//is there a easier/faster way to find them out ? 
//note: vr.vel and vs.vel are vectors, the green and red arrows on SkimFlux picture. 

EDIT2:

Para os interessados, aqui está a fórmula final:

vec2 dist = vs.pos - vr.pos;

vr,r = dotproduct(vr.vel, dist) / length(dist)
vs,r = dotproduct(vs.vel, dist) / length(dist)

NOTA: usa projeção vetorial, descrita aqui :

fórmula de projeção

então vr,se vs,rdeve ser injetado na primeira fórmula da wikipedia:

insira a descrição da imagem aqui

Eu testei e funciona com sucesso, fornecendo ótimos resultados.

tigrou
fonte
3
Você pode adaptar a fórmula que assume que o receptor não está se movendo, substituindo o movimento real da fonte pelo movimento relativo ao receptor.
precisa saber é o seguinte

Respostas:

9

1) Assume que ambos os objetos estão se movendo na mesma linha - (isso é explicado na página da wikipedia que você vinculou) - sua conclusão está correta; nessa situação, com velocidades constantes, a mudança de frequência é constante. Para que o deslocamento de frequência mude, as velocidades relativas precisam mudar, daí a fórmula 2), para a situação em que Vsé constante, mas não colinear, com o eixo SR.

A Fórmula 2) é enganosa, porém: Vrdeve ser lida como Vs,r, isto é, o componente radial / relativo da velocidade da fonte.

Observe que o efeito Doppler depende apenas das velocidades, você só precisa das posições para encontrar o eixo SR.

Edit : isso deve ajudá-lo a descobrir as velocidades, você precisa usar as quantidades Vs,re Vr,rcom a fórmula 1:

Velocidades relativas para desvio Doppler

SkimFlux
fonte
ok obrigado por sua resposta (e foto), isso ajuda muito. agora está tudo claro, devo combinar a fórmula 1 e 2 juntos. como você explicou, a fórmula2 será útil quando os objetos não estiverem na mesma linha. a última parte é descobrir vr, re vs vs, r. vr, r = vr.vel * cos (shortest_angle_between (vr.vel, vs.pos - vr.pos)); vs, r = vs.vel * cos (shortest_angle_between (vs.vel, vr.pos - vs.pos)); // existe uma maneira mais fácil / rápida de descobrir? // note que vr.vel e vs.vel são vetores, as setas verde e vermelha na imagem do SkimFlux.
tigrou
Editei o primeiro post e adicionei a fórmula com a formatação correta. Você pode verificá-los? (primeira vez que eu usar gamedev Stackexchange eu não sabia que costuma manter linha retorna em resposta, e que o comentário é bloqueado após 5 min ....)
tigrou
@ user1083855 Sim, parece certo. Uma maneira de simplificar / acelerar seria seguir a sugestão de Jim e usar a fórmula 2) com o movimento relativo entre ambos. Eu não acho que seja o mesmo, porque o efeito Doppler real depende das velocidades de ambas as entidades em relação ao meio sonoro (o ar), mas em uma situação de jogo, provavelmente estará perto o suficiente e economizando uma operação cos cara.
SkimFlux
Bem, na verdade eu encontrei maneira muito mais fácil de encontrar vr, r vs, r: en.wikipedia.org/wiki/Vector_projection
tigrou
0

Para XACT, existe a variável escalar do pitch doppler que deve ser especificada, ou seja, velocidade relativa, onde 1,0 é a mesma velocidade, mas <1,0 é mais lento e> 1,0 é mais rápido

Obrigado pelo código, que eu transferi para este pedaço de C #, onde um som é calculado entre a posição da tela e uma sugestão. Funciona precisamente

soundElements.ForEach(e =>
            {
                var cuePosition = new Vector3(e.PhysicPosition, 0);
                var distance = cuePosition - ScreenCenter;
                var distanceLength = distance.Length();
                e.Cue.SetVariable("Distance", distanceLength);
                var dopplerPitchScalar = 1.0f;
                if (e.AssociatedBody != null)
                {
                    ///gamedev/23583/how-do-i-simulate-a-doppler-effect-in-a-game
                    var screenVelocity = Vector3.Dot(ScreenVelocity, distance) / distanceLength;
                    var cueVelocity = Vector3.Dot(new Vector3(e.AssociatedBody.LinearVelocity, 0), distance) / distanceLength;
                    var relativeVelocity = screenVelocity - cueVelocity;
                    dopplerPitchScalar = (1f + relativeVelocity / SoundEffect.SpeedOfSound) / (1f - relativeVelocity / SoundEffect.SpeedOfSound);
                    //Console.WriteLine($"C: {ScreenCenter}, V: {ScreenVelocity}, D: {dopplerPitchScalar}");
                }
                e.Cue.SetVariable("DopplerPitchScalar", dopplerPitchScalar);
            });

Btw.

Oleg Skripnyak
fonte