Generalmente i motori audio di gioco utilizzano un sistema piuttosto semplice per il rendering dell'audio: 1. posizionare una sorgente audio nello spazio 2. applica effetti sulla clip audio in base all'area in cui si trova l'oggetto 3. sposta il volume / fase / pan in base all'angolo e alla distanza dall'ascoltatore
Ma nella vita reale l'audio si propaga in onde attraverso ogni mezzo e si riflette anche. Per esempio: se urli contro un muro, sarai molto più strong di se urlerai all'orizzonte (esempio grezzo, lo so).
Quindi la mia idea è di creare un motore audio che funzioni piuttosto come il raytracing nella grafica: Ogni passaggio (100 volte al secondo - > 441 campioni)
- Hai una sorgente audio che invia il suono Dry (che significa non modificato) in una quantità fissa di direzioni, distribuite uniformemente sulla sfera dell'angolo.
- Ogni raggio si propaga per un periodo di tempo limitato nello spazio.
- Se si colpisce un muro, viene creato un nuovo (più piccolo) set di raggi e si propaga con meno ampiezza (a seconda delle proprietà impostate sul muro)
- Quando questo processo viene eseguito e tutti i raggi raggiungono la loro lunghezza massima, ciascun raggio viene trattato come una funzione lineare e viene calcolata la distanza minima tra il raggio e ciascun canale di ascolto (ad esempio L / R).
- Lo spostamento di fase e l'ampiezza di ciascun raggio ora sono calcolati nel modo seguente
L'ampiezza è anti-proporzionale e lo sfasamento è proporzionale alla distanza percorsa dal raggio e alla distanza tra il raggio e l'ascoltatore, mentre questa distanza diventa meno efficace più il raggio ha percorso.
Mi dispiace se non è stato abbastanza chiaro - forse questo pseudocodice può aiutarti a capire cosa intendo.
// Code example in C# style
// C++ would be more performant but this is easier to get
// Base classes for clarity
class AudioRay
{
Vector3 Source;
Vector3 Direction;
float DistanceOffset;
float MaxLength;
float Volume = 1;
public AudioRay(Vector3 src, Vector3 dir, float off, float maxLen, float vol);
public struct Intersection
{
Vector3 Point;
Vector3 InAngel;
Vector3 OutAngle;
float MaterialMuffling;
}
public Intersection GetIntersection()
{
// Todo - just concept - I don't really know how to calculate that
}
}
static class RayCaster
{
static Vector3 GetNearestPoint(Vector3 targetPoint, Vector3 source, Vector3 direction)
{
// Nearest point on a line to a point...
}
static List<AudiorRay> Cast(AudioRay ray, float maxLength)
{
List<AudiorRay> rays = new List<AudiorRay>();
AudioRay.Intersection intersection = ray.GetIntersection();
if(intersection!= null) // Only cast new rays when the old one intersected with a wall
{
float len = ray.DistanceOffset + (intersection.Point - ray.Source).Length;
if(len < maxLength) // ... and if they are below the max length
{
List<AudioRay> newRays = new List<AudioRay>();
// Add the new rays --> This is only the central one but the others will be evenly distributed
newRays.Add(new AudioRay(intersection.Point, interse.OutAngle, len, maxLenght, vol * intersection.MaterialMuffling));
// Cast the new rays but
newRays.ForEach(t => rays.AddRange(Cast(t)));
}
}
}
}
// Main class
class AudioSource
{
Vector3 Position = new Vector3(0, 0, 0);
Vector3 ListenerPos = new Vector3(10, 20, 30);
int StartRayCount = 162;
int BounceRayCount = 11;
float BounceRayAngle = 25;
float MaxRayLength = 1000;
// The dry wave parameter is not the buffer but the whole file so we can adress
// every sample all over the file.
// I suspect a 512 sample out buffer (~12 ms)
// wavePositionOffset = position in the track
public float[] Pass(float[] dryWave, int wavePositionOffset)
{
List<AudioRay> rays = new List<AudioRay>();
int samples = StartRayCount;
float offset = 2.0f / (float)samples;
float increment = Math.Pi * ( 3.0f - Math.Sqrt(5.0f));
// Create source rays
for(int i = 0; i < samples; i++)
{
float y = ((i * offset) - 1f) + (offset / 2f);
float r = Math.Sqrt(1f - Math.Pow(y, 2f));
float phi = (i % samples) * increment;
float x = Math.Cos(phi) * r
float z = Math.Sin(phi) * r
AudioRay ray = new AudioRay();
ray.Source = Position;
ray.Direction = new Vector3(x, y, z).Normalized;
ray.DistanceOffset = 0f;
ray.MaxLength = MaxRayLength;
rays.Add(ray);
rays.AddRange(RayCaster.Cast(ray, MaxRayLength));
}
// Create the out buffer
float[] oB = new float[512]();
float distanceVolumeFalloff = -0.01; // Complete silence after 100 m
float phaseShift = 20; // 20 samples / m shift
// calculate all rays phases and volumes
foreach(AudioRay ray in rays)
{
for(int i = 0; i < 512; i++)
{
Vector3 nearestPoint = GetNearestPoint(ListenerPos, ray.Position, ray.Direction);
float dist = ray.DistanceOffset + (nearestPoint - ray.Position);
// vol can't go below 0
// Respect the material properties stored in RayVolume
float vol = Math.Max(1 - dist * distanceVolumeFalloff, 0f) * ray.Volume;
int sampleOffset = (int)(dist * phaseShift);
int pos = wavePositionOffset-sampleOffset
if(pos > 0 && pos < dryWave.Length)
oB[i] += dryWave[pos] * vol;
}
}
return oB;
}
}
La mia domanda qui è: perché i motori non supportano ancora questo modello audio? Non penso ci sarebbe troppo sovraccarico della CPU poiché il riverbero è essenzialmente lo stesso e ho "implementato" i valori delle prestazioni (ad esempio StartRayCount e MaxRayLenght). L'unica cosa che potrebbe essere (rendimento saggio) difficile da fare è il raycasting. Ma la tua GPU fa milioni di volte e parlava forse centinaia di volte dicono 100 volte al secondo. Questo è ancora molto meno di quello che fa una scena video 3D.
Pixel / secondo: 1920 x * 1080 y * 60 FPS = 124.416.000 solo Full HD - Quadrupel per 4K
Raggi / secondo: dì circa 200 a 1000? * 100 raggi / secondo = da 200.000 a 1.000.000
Prima che qualcuno gridi: ho fatto la mia ricerca e ho letto questo .