利用Directsound 3D實現遊戲中3D音效

來源:互聯網
上載者:User

 摘要:3D遊戲中,增強3D音效會讓遊戲玩家產生震撼的聽覺效果,本文通過一個樣本示範了如何採用微軟的Directsound 3D 實現3D環繞立體聲。

  關鍵詞:Directsound 3D 遊戲 3D立體聲

  在開始本文前,我要說一句,Directsound 3D可以說是Directsound的精華所在,我個人的感覺,在3D遊戲的開發中,3D音效一般都是採用Directsound 3D來完成的。

  DirectSound 3D是微軟公司所推出的,它利用聲音大小的比例調整與都蔔勒效應,來達到以軟體來類比3D音效的效果,創立了在三維空間定位音效檔案的標準方式。任何應用程式透過它和支援DirectSound 3D的音效卡,便可以獲得所需的效果。由於這是許多音效卡廠商與微軟共同制定的,現在大部分的音效卡都支援這項技術。 本文就是將Directsound 3D技術介紹給大家,你們可以在你們的程式中使用它,相信會給你的程式增色不少。

  下面我們就開始吧,在開始前,我還要先介紹一下Directsound 3D相關的幾個基本概念。然後通過一個例子來示範如何使用Directsound 3D,源碼附在後面,可以下載。

  3D空間 聲源 聽者

  Directsound 3D是通過軟體類比來實現3D音效的,所以要先講一下Dsound 的3D類比空間。這個空間類似現實空間,可以用笛卡兒座標系來描述Dsound 的3D空間,有x,y,z三個座標軸座標軸。

  在這個類比空間中Dsound提供了類比的聲來源物件和傾聽者對象(listener),聲源和聽者的關係可以通過三個變數來描述:在三維空間的位置,以及運動的速度,以及運動方向。

  位置即聲源和聽者在三維空間的所在位置,隨著兩者的相對位置不同,則聽者便會聽到不同的聲音效果。

  速度為聲源和聽者在三維空間中的移動速度,此項特性同樣會改變兩者在空間的座標,以產生不同的聲音效果。

  聲源和聽者相對運動的方向也會影響聽者聽到的聲音效果,因為聲音是具有方向性的。這個下面會談到。

  知道了3D聲源以及3D環境中的聽者,那麼怎麼產生3D音效呢?一般來說,在產生3D音效的時候,主要有下面的幾種情況,1 是聲源不動,而聽者在類比的3D空間進行運動,2 是聽者不動,讓聲源在類比的3D空間進行運動,3 聽者和聲音同時在運動。如:


圖1 聲源不動,聽者移動產生3D音效

 


圖2 聽者不動,聲源移動產生3D音效

  Directsound給我們提供了聽者和聲來源物件的介面,我們可以通過上面提到的三種方式設定改變聲源或者聽者的位置,運動速度和方向就可以形成3D音效了,

  在3D環境中,我們通過IDirectSound3DBuffer8介面來表述聲源,這個介面只有建立時設定DSBCAPS_CTRL3D標誌的Directsound buffer才支援這個介面,這個介面提供的一些函數用來設定和擷取聲源的一些屬性。在一個虛擬3D環境中,我們可以通過主要緩衝區來擷取IDirectSound3DListener8介面,通過這個介面我們可以控制著聲學環境中的多數參數,比如多普勒變換的數量,音量衰減的比率。

  介面很簡單,但是大量的計算工作Directsound都在內部協助我們完成了。
 最大最小距離

  當聽者越接近聲源,那麼聽到的聲音就越大,距離減少一半,音量會增加一倍。但是,當你繼續接近到聲源,當距離縮短到一定距離後,音量就不會持續增加。這就是聲源的最小距離。

  聲源的最小距離,就是聲音的音量開始隨著距離大幅度衰減的起始點。例如,對于飛機,這個最小距離也許是100m,但是對於蜜蜂,這個最小距離是2 cm,根據這個最小距離,當聽者距離飛機400m時,聲音的音量就要衰減一半,對於蜜蜂來說,當超過4cm的時候,音量衰減一半。下面這個圖表現了,最小和最大距離對飛機和蜜蜂音量的影響。

  Directsound的預設的最小距離DS3D_DEFAULTMINDISTANCE 定義為1個單位,或者是1米。我們規定,聲音在1米處的音量是full volume,在2米處衰減一半,4米處衰減為1/4,一次類推。對於大多數聲音來說,我們要設定一個比較大的最小距離,這樣,當聲音運動的時候,不至於衰減的這麼快。

  最大距離,就是就是聲源的音量不再衰減的距離,我們稱為聲源的最大距離。對於Directsound 3D buffer預設的最大的距離DS3D_DEFAULTMAXDISTANCE 是1 billion。 也就是說,當聲音超出我們的聽覺範圍以外的時候,衰減還是在繼續。在VXD驅動下,為了避免不必要的計算處理,我們在建立buffer的時候就要設定一個合理的最大距離。

  最大距離同時也用來避免某種聲音聽不到。例如,如果你將某種聲音的最小距離設定為100m,那麼聲音可能在1000m的處衰減的可能就聽不到了,你可以將最大的距離設定為800m,這樣你就可以保證聲音在無論多遠處都為原音量的1/10。
記得,預設的單位是m。


圖3 最大最小距離

  處理模式

  Sound buffers 有三種處理模式,normal, head-relative, and disabled。

  在正常模式下,聲源的位置和方向是真實世界中的絕對值,這種模式適合聲源相對於聽者不動的情形。

  在head reative 模式下,聲源的所有3D特性都跟聽者的當前的位置,速度,以及方向有關,當聽者移動,或者轉動方向,3D buffer 就會自動的重新調整 world space 。這種模式可以應用實現一種在聽者頭部不停的嗡嗡叫的聲音,那些一直跟隨著聽者的聲音,根本沒有必要用3D聲音來實現。

  在 disable 模式下,3 D 聲音失效,所有的聲音聽起來好像發自聽者的頭部。

  聲音的錐效應

  沒有方向的聲音在各個方向上的振幅都相同,有方向的聲音在該方向上的振幅最大,聲音的錐效應分為內部的錐效應,和外部的錐效應,錐的外部的角度應該大於等於錐的內部角度。

  在錐的內部,在考慮到buffer中的基本的音量,以及距離聽者的距離遠近,何聽者的方向,聲音的音量就跟沒有錐效應一樣。
在錐的外部,正常的音量被削弱了,從0到負的百分之幾分貝。

  在錐體的內部和外部之間,是一個過渡帶,從內部的volume 到外部的volume,這個音量的逐漸的降低。下面的圖表顯示了聲音的錐效應。


圖4 聲音錐

  每一個的3D buffer都有一個聲音錐,但是預設的情況下,每一個3D聲音 buffer都像一個沒有衰減的 聲源,因為錐體內外都沒有聲音的音量的降低,錐的角度內外的角度是360度,除非我們的應用程式改變這個設定,否則的話,3D聲音沒有方向感。

  利用3D聲音,可以給你的程式添加比較奇異的特技,例如,你可以將聲音源放到房間的中間,將聲源的方向朝向門的方向,然後將聲音的錐體角度包含門的寬度,將錐體的外部設定的更寬一些,然後將錐體的外部音量設定為0。這樣,當聽者只有在門的附近才能聽到聲音,在正對門的位置,聲音才更響。

  關於Directsound 3D的一些相關的概念先介紹到這裡,下面我們就可以開始我們的3D音效之旅了。我提供了一個demo,下面我們分析如何編程來實現3D音效。Demo的介面如下:


圖5 demo介面

  這裡要說明一下,這個demo實現3D音效的方法是採用的第二種方法,即聽者不動,使聲源運動的方法來產生3D音效。這裡設定聲源在一個平面上來運動,設定y軸為零。

  象聲源一樣,3D世界中的聽者也有位置,速度和方向。 如


圖6 聽者的座標系

  向上的向量指向頭的頂部,前向的向量指向聽者的面部正前方,預設時,front向量是(0.0, 0.0, 1.0),,頂部的向量是(0.0, 1.0, 0.0).,如果需要的話,Directsound可以調整front 向量,因為它在top向量的右向。所以,我們這個demo設定的buffer運動軌跡在一個平面內,如,我們選擇的平面是y=0,所以,我們在這個demo聽起來,就好像聲音在繞著我們的頭部不停的在轉動。
 下面一步一步跟我來做吧。

  第1步 聲明一些我們要用到的變數。

LPDIRECTSOUND8 g_pDsd = NULL; //Directsound 對象指標
LPDIRECTSOUNDBUFFER g_pDSBuffer = NULL; //輔助緩衝區指標
LPDIRECTSOUND3DBUFFER g_pDS3DBuffer = NULL; // 3D 聲來源物件指標
LPDIRECTSOUND3DLISTENER g_pDSListener = NULL; // 3D 聽者對象指標
DS3DBUFFER g_dsBufferParams; // 3D buffer properties
DS3DLISTENER g_dsListenerParams; // Listener properties
CWaveFile *g_pWaveFile= NULL; //讀寫wave檔案用到的一個類對象
BOOL g_bPlaying = FALSE; //是否現正播放

  第2步初始化Directsound,建立主要緩衝區,設定主要緩衝區的音頻格式,並通過主要緩衝區擷取聽者(listener)介面,這些工作可以在對話方塊的初始化中來做。

//初始化Dsound
HRESULT hr;
if(FAILED(hr = DirectSoundCreate8(NULL,&g_pDsd,NULL)))
 return FALSE;
if(FAILED(hr = g_pDsd->SetCooperativeLevel(m_hWnd,DSSCL_PRIORITY)))
 return FALSE;
//初始化Directsound 的主要緩衝區,並設定格式
LPDIRECTSOUNDBUFFER pDSBPrimary = NULL;
DSBUFFERDESC dsbdesc ;
ZeroMemory(&dsbdesc,sizeof(DSBUFFERDESC));
dsbdesc.dwSize = sizeof(DSBUFFERDESC);
dsbdesc.dwFlags = DSBCAPS_CTRL3D | DSBCAPS_PRIMARYBUFFER;//一定不要忘記建立主要緩衝區的時候要設定DSBCAPS_CTRL3D標誌。只有指定了這個標誌,後面你才能從這個輔助緩衝區中請求到3D的buffer指標。
if(FAILED(hr = g_pDsd->CreateSoundBuffer(&dsbdesc,&pDSBPrimary ,NULL)))
 return FALSE;
 //擷取3D 類比世界中的聽者指標。
if( FAILED( hr = pDSBPrimary->QueryInterface( IID_IDirectSound3DListener, (VOID**)&g_pDSListener ) ) )
 return FALSE;

WAVEFORMATEX wfx;
ZeroMemory( &wfx, sizeof(WAVEFORMATEX) );
wfx.wFormatTag = (WORD) WAVE_FORMAT_PCM;
wfx.nChannels = WAVECHANNEL ;
wfx.nSamplesPerSec = WAVESAMPLEPERSEC ;
wfx.wBitsPerSample = WAVEBITSPERSAMPLE ;
wfx.nBlockAlign = (WORD) (wfx.wBitsPerSample / 8 * wfx.nChannels);
wfx.nAvgBytesPerSec = (DWORD) (wfx.nSamplesPerSec * wfx.nBlockAlign);
if( FAILED( hr = pDSBPrimary->SetFormat(&wfx) ) ) //設定緩衝區的音頻格式。
 return FALSE;

  第3步,建立輔助緩衝區,通過輔助緩衝區擷取3dbuffer指標

DSBUFFERDESC dsbd;
ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) );
dsbd.dwSize = sizeof(DSBUFFERDESC);
dsbd.dwFlags = DSBCAPS_CTRL3D| DSBCAPS_GLOBALFOCUS | DSBCAPS_CTRLPOSITIONNOTIFY |DSBCAPS_GETCURRENTPOSITION2;
//看看建立輔助緩衝區的buffer時設定的標誌,3D的標誌自然是需要的,還有一個標誌需要注意,DSBCAPS_CTRLPOSITIONNOTIFY,如果你採用的流(stream)buffer的話,就需要邊播放,邊向buffer中填充資料,就需要設定這個標誌,這樣,在directsound播放至指定位置時,就會觸發事件。
//dsbd.dwBufferBytes =MAX_AUDIO_BUF * BUFFERNOTIFYSIZE ;//如果採用流buffer,可以設定適當的buffer大小。
dsbd.dwBufferBytes =g_pWaveFile->GetSize(); // 如果採用靜態buffer,那麼buffer的大小就是檔案的大小了。
dsbd.guid3DAlgorithm = guid3DAlgorithm;
dsbd.lpwfxFormat = g_pWaveFile->m_pwfx;
if(FAILED(hr = g_pDsd->CreateSoundBuffer(&dsbd,&g_pDSBuffer,NULL)))
 return ;
 //通過輔助緩衝區,來擷取3D buffer的指標。
if(FAILED(hr = g_pDSBuffer->QueryInterface(IID_IDirectSound3DBuffer, (VOID**)&g_pDS3DBuffer )))
 return ;

g_dsBufferParams.dwSize = sizeof(DS3DBUFFER);
g_pDS3DBuffer->GetAllParameters( &g_dsBufferParams );

//設定3Dbuffer的屬性。
g_dsBufferParams.dwMode = DS3DMODE_HEADRELATIVE;
g_pDS3DBuffer->SetAllParameters( &g_dsBufferParams, DS3D_IMMEDIATE );

  第4步 設定多普勒因子以及最小最大距離。

FLOAT fDopplerFactor;
FLOAT fRolloffFactor;
FLOAT fMinDistance;
FLOAT fMaxDistance;

CSliderCtrl *pDopplerSlider = (CSliderCtrl*)GetDlgItem(IDC_DOPPLER_SLIDER);
fDopplerFactor = pDopplerSlider->GetPos();

CSliderCtrl *pRolloffSlider= (CSliderCtrl*)GetDlgItem(IDC_ROLLOFF_SLIDER);
fRolloffFactor = pRolloffSlider->GetPos();

CSliderCtrl *pMinDistSlider = (CSliderCtrl*)GetDlgItem(IDC_MINDISTANCE_SLIDER );
fMinDistance = pMinDistSlider->GetPos();
CSliderCtrl *pMaxDistSlider = (CSliderCtrl*)GetDlgItem(IDC_MAXDISTANCE_SLIDER );
fMaxDistance = pMaxDistSlider->GetPos();

g_dsListenerParams.flDopplerFactor = fDopplerFactor;
g_dsListenerParams.flRolloffFactor = fRolloffFactor;

if( g_pDSListener )
{
 g_pDSListener->SetAllParameters( &g_dsListenerParams, DS3D_DEFERRED );
 g_pDSListener->CommitDeferredSettings();
}

g_dsBufferParams.flMinDistance = fMinDistance;
g_dsBufferParams.flMaxDistance = fMaxDistance;

if( g_pDS3DBuffer )
 g_pDS3DBuffer->SetAllParameters( &g_dsBufferParams, DS3D_DEFERRED );

  第5步 然後就是通過調用輔助緩衝區的play方法,來進行播放音頻檔案了。

DWORD res;
LPVOID lplockbuf;
DWORD len;
DWORD dwWrite;

g_pDSBuffer->Lock(0,0,&lplockbuf,&len,NULL,NULL,DSBLOCK_ENTIREBUFFER);
g_pWaveFile->Read((BYTE*)lplockbuf,len,&dwWrite);
g_pDSBuffer->Unlock(lplockbuf,len,NULL,0);
g_pDSBuffer->SetCurrentPosition(0);
g_pDSBuffer->Play(0,0,DSBPLAY_LOOPING);

  第6步最後一個很重要的地方,就是我們在程式中設定了一個時鐘,這個時鐘事件是用來觸發Directsound聲源的運動如下:

  聲源的運動軌跡根據我們設定的fxscale 和fyscale 的大小,就是我們拖動豎直和水平的兩個slider,是一個橢圓形狀,當然,如果fxscale = fyscale時,就是個圓形軌跡了。我們可以調整,然後通過ontimer事件來不斷調整buffer的運動軌跡。


圖7聲源運動的軌跡

void CDsound3DPlayDemoDlg::OnTimer(UINT nIDEvent )
{
 FLOAT fXScale;
 FLOAT fYScale;

 fXScale = (((CSliderCtrl*)GetDlgItem(IDC_HORIZONTAL_SLIDER ))->GetPos())/100.0f;

 fYScale = (((CSliderCtrl*)GetDlgItem(IDC_VERTICAL_SLIDER ))->GetPos())/100.0f;
 FLOAT t = timeGetTime()/1000.0f;

 // Move the sound object around the listener. The maximum radius of the
 // orbit is 27.5 units.
 D3DVECTOR vPosition;
 vPosition.x = ORBIT_MAX_RADIUS * fXScale * (FLOAT)sin(t);
 vPosition.y = 0.0f;
 vPosition.z = ORBIT_MAX_RADIUS * fYScale * (FLOAT)cos(t);

 D3DVECTOR vVelocity;
 vVelocity.x = ORBIT_MAX_RADIUS * fXScale * (FLOAT)sin(t+0.05f);
 vVelocity.y = 0.0f;
 vVelocity.z = ORBIT_MAX_RADIUS * fYScale * (FLOAT)cos(t+0.05f);

 memcpy( &g_dsBufferParams.vPosition, &vPosition, sizeof(D3DVECTOR) );
 memcpy( &g_dsBufferParams.vVelocity, &vVelocity, sizeof(D3DVECTOR) );

 UpdateGrid( vPosition.x, vPosition.z );

 if( g_pDS3DBuffer )
  g_pDS3DBuffer->SetAllParameters( &g_dsBufferParams, DS3D_IMMEDIATE );
 //隨著類比buffer的運動,我們要不斷地改變3Dbuffer的參數。
}

  好了,介紹到這裡,基本上都介紹清楚了,如果你還有什麼疑惑的話,請參看demo的代碼,對於程式員來說,看代碼,比看文章能理解的更清楚些。

  結束語

  本文通過一個執行個體來示範了如何利用Directsound 實現遊戲開發中的3D立體聲音效,隨文提供了一個demo源碼,你可以下載,本文的demo在windowx xp,2000 + VC.7.0下調試。關於更詳細的資料,請參考msdn上directx部分。

  最後提醒兩個問題:

  1 在我的demo程式中,關於播放音頻檔案,我提供了兩種方法,一種是比較簡單的靜態buffer,適合比較小的wave檔案,另一種方法是採用Streaming buffer,開一個比較小的緩衝,然後一邊播放,一邊將向緩衝中填充資料,這個適合比較大的wave檔案。當然後一種方法比較麻煩,需要單獨起一個線程。我在程式中都作了詳盡的注釋,如果你足夠仔細,你肯定會發現。

  2 一定要記住,3D音效只支援單聲道的wave檔案,不要試圖對一個多聲道的wave檔案實現三維立體聲效果,不會成功的。

  最後留下email,aooang@hotmail.com有什麼問題聯絡我。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.