¿Cómo simulo un efecto Doppler en un juego?

14

Estoy tratando de simular el efecto Doppler en un juego (un juego de carreras de autos). No estoy usando una biblioteca de sonido específica que simule el efecto, solo tengo una función de devolución de llamada donde mezclo los datos.

Ya descubrí cómo cambiar la frecuencia de una muestra en la función del mezclador.

Lo que no sé es cuánto debería cambiar la frecuencia dependiendo de la posición y velocidad del jugador y del emisor.

Esto es lo que tengo en el juego:

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

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

1) Según Wikipedia , la relación entre la frecuencia emitida y la frecuencia observada viene dada por:

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

donde c es una constante, la velocidad en el medio (generalmente un gran número) vs y vr son las velocidades de origen y receptor en relación con el medio.

así que supongo:

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

pero creo que está mal, no producirá ningún cambio en la frecuencia, por ejemplo: si vr = 0(el jugador no se mueve) y el emisor tienen una velocidad constante, entonces vry vsno cambiarán (mientras deberían).

¿Tal vez debería calcular la velocidad del jugador en relación con la velocidad del emisor?

Me gusta esto :

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

entonces, ¿cómo vry vsdebe ser alimentado?


2) Wikipedia también da otra fórmula para simular el efecto de un vehículo que el vehículo pasa por el 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); ?

sin embargo, esta fórmula supone que el receptor no se mueve, lo cual no es el caso aquí. Si el jugador y el emisor se mueven a la misma velocidad (o una pequeña diferencia), no debería haber efecto Doppler. Esta función también es específica para un caso, supongo que la fórmula final debería ser la misma sin importar la situación.


EDITAR: estoy tratando de encontrar la fórmula correcta, usando la publicación de SkimFlux:

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 aquellos interesados, aquí está la 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: utiliza proyección vectorial, descrita aquí :

fórmula de proyección

entonces vr,sy vs,rdebería inyectarse en la primera fórmula de Wikipedia:

ingrese la descripción de la imagen aquí

Lo probé y funciona con éxito, proporcionando excelentes resultados.

tigrou
fuente
3
Puede adaptar la fórmula que supone que el receptor no se está moviendo reemplazando el movimiento real de la fuente con su movimiento relativo al receptor.
yoozer8

Respuestas:

9

1) Asume que ambos objetos se mueven en la misma línea - (esto se explica en la página de wikipedia que ha vinculado) su conclusión es correcta, en esta situación, con velocidades constantes, el cambio de frecuencia es constante. Para que el cambio de frecuencia cambie, las velocidades relativas deben cambiar, de ahí la fórmula 2), para la situación donde Vses constante pero no colineal con el eje SR.

Sin embargo, la fórmula 2) es engañosa: Vrdebe leerse como Vs,r, es decir, el componente radial / relativo de la velocidad de la fuente.

Tenga en cuenta que el efecto Doppler depende solo de las velocidades, solo necesita las posiciones para encontrar el eje SR.

Editar : esto debería ayudarlo a calcular las velocidades, debe usar las cantidades Vs,ry Vr,rcon la fórmula 1:

Velocidades relativas para el cambio Doppler

SkimFlux
fuente
ok gracias por tu respuesta (y foto), ayuda mucho. ahora todo está claro, debería combinar las fórmulas 1 y 2 juntas. Como explicaste, la fórmula2 será útil cuando los objetos no están en la misma línea. La última parte es averiguar vr, r y 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)); // ¿hay alguna forma más fácil / rápida de descubrirlos? // nota vr.vel y vs.vel son vectores, las flechas verde y roja en la imagen de SkimFlux.
tigrou
Edité la primera publicación y agregué la fórmula con el formato correcto. ¿Puedes revisarlos? (la primera vez que uso gamedev stackexchange. No sabía que no mantendría los retornos de línea en respuesta, y ese comentario se bloquea después de 5 minutos ...)
tigrou
@ user1083855 Sí, esos se ven bien. Una forma de hacerlo más simple / rápido sería seguir la sugerencia de Jim y usar la fórmula 2) con el movimiento relativo entre ambos. No creo que sea realmente lo mismo porque el efecto Doppler real depende de las velocidades de ambas entidades en relación con el medio de sonido (el aire), pero en una situación de juego probablemente estará lo suficientemente cerca y le ahorrará una operación costosa.
SkimFlux
bueno, en realidad encontré una manera mucho más fácil de encontrar vr, r vs, r: en.wikipedia.org/wiki/Vector_projection
tigrou
0

Para XACT, existe la variable escalar de tono doppler, es decir, velocidad relativa, donde 1.0 es la misma velocidad, pero <1.0 es más lento y> 1.0 es más rápido

Gracias a todos por el código, que he transferido a esta pieza de C #, donde se calcula un sonido entre la posición de la pantalla y una señal. Trabaja con precisión

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

Por cierto.

Oleg Skripnyak
fuente