Tài liệu Beginning directx9 (phần 2)

Bạn đã học được cách làm thế nào để tạo các object 3D bằng code và biểu diễn nó

lên màn hình. Có lẽ bạn đang nghĩ rằng đây là một quá trình thật nhàm chán và

chưa có cách nào để có thể tạo tất cả các object bằng code. Bạn nghĩ rất đúng.

Hiện nay 3D đã nhập cuôc. Nó có thể mô tả mọi thứ trong game của bạn rất giống với

thực thể. Những mô hìnhcó thể thể hiện vật thể và đặc điểm xung quanh bạn và cũng có

thể là chính chính nó. Với những đặc điểm này bạn có thể đưa những mô hình này vào

game, bạn có thể biểu diễn nó với đối tượng Mesh và dịch chuyển hoặc điều khiển chúng.

Đây là các phần mà bạn sẽ học trong chương này:

ƒ Direct3D điều khiển mesh như thế nào ?

ƒ Cần những gì để hợp lý hóa một model 3D?

ƒ Định dạng file X là gì?

ƒ Làm thế nào để tạo và lưu giữ mesh?

ƒ Làm thế nào để load một model 3D vào game?

 

pdf 83 trang dienloan 16260
Bạn đang xem 20 trang mẫu của tài liệu "Tài liệu Beginning directx9 (phần 2)", để tải tài liệu gốc về máy hãy click vào nút Download ở trên

Tóm tắt nội dung tài liệu: Tài liệu Beginning directx9 (phần 2)

Tài liệu Beginning directx9 (phần 2)
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 96
CHIA NHỎ VÀ LÀM MỊN 
CÁC ĐỐI TƯỢNG 
ạn đã học được cách làm thế nào để tạo các object 3D bằng code và biểu diễn nó 
lên màn hình. Có lẽ bạn đang nghĩ rằng đây là một quá trình thật nhàm chán và 
chưa có cách nào để có thể tạo tất cả các object bằng code. Bạn nghĩ rất đúng. 
Hiện nay 3D đã nhập cuôc. Nó có thể mô tả mọi thứ trong game của bạn rất giống với 
thực thể. Những mô hìnhcó thể thể hiện vật thể và đặc điểm xung quanh bạn và cũng có 
thể là chính chính nó. Với những đặc điểm này bạn có thể đưa những mô hình này vào 
game, bạn có thể biểu diễn nó với đối tượng Mesh và dịch chuyển hoặc điều khiển chúng. 
Đây là các phần mà bạn sẽ học trong chương này: 
ƒ Direct3D điều khiển mesh như thế nào ? 
ƒ Cần những gì để hợp lý hóa một model 3D? 
ƒ Định dạng file X là gì? 
ƒ Làm thế nào để tạo và lưu giữ mesh? 
ƒ Làm thế nào để load một model 3D vào game? 
Xây dựng một thế giới 3D 
Mô hình 3D giúp chúng bạn thể hiện thế giới ảo mà bạn muốn tạo. Những mô hình này 
được bổ xung bởi gamer và đối thủ của gamer trong môi trường này. Những mô hình này 
được lấy từ đâu? Nếu bạn có một package thiết kế 3D giống như Max hoặc Maya, bạn đã 
có những công cụ cần thiết để tạo mọi thứ cho game của bạn khi cần. Nếu những chương 
trình trên bạn không có thì bạn có thể dùng những package khác như MilkShape 3D, nó 
cũng có thể làm việc tốt. 
Sau khi đã tạo các model, bạn đưa chúng vào một trong những định dạng file 3D hiện có. 
Nhưng bạn cần biết làm thế nào để load một file định dạng 3D vào game của mình. Mục 
đích của cuốn sách này là giúp bạn làm việc với những định dạng file mà Microsoft đã 
tạo ra. 
B 
CHƯƠNG 7 
Chú ý: 
 Bạn có thể tìm MilkShape 3D tại trang 
soft/index.html. 
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 97
Mesh là gì? 
Code của bạn điều khiển những mô hình 3D được load vào trong game cũng được xem 
như là một Mesh. Một Mesh là một code container mà chứa mọi thứ liên quan đến đối 
tượng 3D. Nó bào gồm các vectơ, tọa độ texture và các dữ liệu khác. Bằng cách sử dụng 
dữ liệu có trong mesh object bạn có thể biểu diễn các mô hình 3D lên màn hình . 
Direct3D định nghĩa một Mesh như thế nào? 
Hầu hết các mesh trong Direct3D đều dựa trên ID3DXBaseMesh interface. Interface này 
cung cấp kho dự trữ dành cho các model của bạn, giúp các methods có hiệu lực để tăng 
tốc độ truy cập tới dữ liệu trong mesh. Ví dụ method GetVertexBuffer luôn sẵn có trong 
ID3DXBaseMesh interface để giúp bạn truy cập trực tiếp tới vectơ buffer của đối tượng 
mesh. 
Dưới đây là một số kiểu Mesh khác nhau: 
ƒ ID3DXMesh – Đây là dạng mesh interface chuẩn mà bạn sẽ sử dụng. 
ƒ ID3DXPMesh – Interface này cho phép bạn sử dụng mesh tiến hành. 
ƒ ID3DXSPMesh – interface này điều khiển sự đơn giản hóa các mesh object. 
ƒ ID3DXPatchMesh - Interface này cung cấp Patch mesh theo chức năng. 
Mỗi một kiểu mesh có thể lưu giữ tất cả các vectơ của một model trong vectơ buffer và 
cung cấp cho bạn thông tin vể model, ví dụ như số phần tử hoặc là số vectơ. 
Tạo Mesh 
Bước đầu tiên khi sử dụng các mesh trong game đó là sự khởi tạo mesh object. Mesh 
object là kho dự trữ, cất giữ tất cả các thông tin cần thiết dùn để mô tả model của bạn 
trong Direct3D. Sau khi bạn đã tạo ra mesh, bạn dễ dàng copy tất cả thông tin mà model 
của bạn cần. 
Hai hàm trong Direct3D dùng để tạo mesh: D3DXCreaetMesh và D3DXCreateMeshFVF. 
Vì mỗi hàm này tạo mesh bằng các cách khác nhau, chúng ta sẽ làm rõ cả hai hàm dưới đây. 
D3DXCreateMesh 
Giao diện ID3DXMesh là một giao diện đơn giản nhất trong mesh interface, dễ dàng tổ chức 
và thực hiện một cách nhanh chóng. Trong phần này, bạn sẽ học cách làm thế nào để tạo 
mesh trong ID3DXMesh interface bằng hàm D3DXCreateMesh. 
D3DXCreateMesh( 
 DWORD NumFaces, 
 DWORD NumVertices, 
 DWORD Options, 
 CONST LPD3DVERTEXELEMENT9 *pDeclaration, 
 LPDIRECT3DDEVICE9 pDevice, 
 LPD3DXMESH *ppMesh 
); 
Chú ý: 
 Thư viện tổng hợp D3DX chứa tất cả mọi thứ mà bạn cần để sử dụng mesh trong Direct3D 
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 98
Hàm D3DcreateMesh có 6 tham số: 
ƒ NumFaces. Số phần tử mà mesh sẽ chứa. 
ƒ NumVertices. Số vectơ mà mesh sẽ chứa. 
ƒ Options. Giá trị từ bảng liệt kê D3DXMESH . 
ƒ pDeclaration. Ma trận thuộc đối tượng D3DVERTEXELEMENT9. Những object 
 này mô tả FVF cho mesh. 
ƒ pDevice. Một Direct3D device hợp lệ. 
ƒ ppMesh. Con trỏ trỏ tới đối tượng ID3DMesh hợp lệ. 
Đoạn code sau sẽ chỉ ra cách làm thế nào để tạo một đối tượng mesh mà chứa đầy đủ bộ 
vectơ, dùng để tạo một khối lập phương. 
HRESULT hr; 
// biến giữ một mesh được tạo mới 
LPD3DXMESH boxMesh; 
// ma trận D3DVERTEXELEMENT9 
D3DVERTEXELEMENT9 Declaration [MAX_FVF_DECL_SIZE]; 
// tạo khai báo cần thiết cho hàm D3DXCreateMesh 
D3DXDeclaratorFromFVF (D3DFVF_CUSTOMVERTEX, Declaration); 
hr= D3DXCreateMesh(12, //số phần tử của mesh 
 8, //số vectơ 
 D3DXMESH_MANAGED, //sử dụng bộ nhớ cần thiết cho mesh 
 Declaration, //ma trận kiểu đối tượng D3DVERTEXELEMENT9 
 pd3dDevice, //the Direct3D device 
 &boxMesh); //biến giữ mesh 
//kiểm tra giá trị trả về để chắc chắn rằng bạn đã cómột đối tượng mesh hợp lệ. 
if FAILD (hr) 
 Return false; 
Như bạn đã thấy, đoạn code trên tạo ra một mesh chứa 12 phần tử và 8 vectơ và tất cả chúng 
được đưa vào trong biến boxMesh. Biến thứ ba là D3DXMESH_MANAGED thông báo 
Direct3D là cần sử dụng bộ nhớ cần thiết cho cả vectơ và index buffer để tạo mesh. 
Bạn nên chú ý là hàm D3DXDeclaratorFromFVF phải được gọi trước hàm 
D3DXCreateMesh. D3DXDeclaratorFromFVF tạo đối tượng cần thiết 
D3DVERTEXELEMENT9 cho tham số thứ 4 bằng cách sử dụng Flexible Vertex Format 
(định dạng véctơ linh hoạt) mà model của bạn sử dụng. 
Khi bạn sử dụng hàm D3DXDeclaratorFromFVF, bạn không cần thiết phải tạo ngay các đối 
tượng D3DVERTEXELEMENT9. 
D3DXCreatMeshFVF 
Hàm D3DXCreateMeshFVF không giống với D3DcreateMesh ở một điểm là nó dựa vào 
sự kiến tạo mesh trên Định Dạng Véctơ Linh Hoạt (Flexible Vertex Format) thay vì dùng 
Declarator. Mesh object này cũng giống với mesh object được tạo bởi hàm 
D3DXCreateMesh ở trong phần trước. 
Hàm D3DXCreatMeshFVF được định nghĩa như sau: 
HRESULT D3DXCreateMeshFVF( 
 DWORD Numface, 
 DWORD NumVertices, 
 DWORD Options, 
 DWORD FVF, 
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 99
 LPDIRECT3DDEVICE9 pDevice, 
 LPD3DXMESH *ppMesh 
); 
Hàm D3DXCreatMeshFVF cần 6 tham số: 
ƒ Numface - số phần tử mà mesh sẽ có 
ƒ NumVertices – số véctơ mà mà mesh sẽ có 
ƒ Options – các giá trị từ bảng liệt kê D3DXMESH 
ƒ FVF – Flexible Vertex Format của các véctơ. 
ƒ pDevice – Direct3D device hợp lệ. 
ƒ ppMesh – con trỏ trỏ tới đối tượng ID3DXMESH 
Dưới đây là một chương trình mẫu gọi hàm D3DXCreateMeshFVF. 
//biến giữ giá trị trả về 
HRESULT hr; 
//biến giữ mesh đựơc tạo mới 
LPD3DXMESH boxMesh; 
//tạo mesh bằng cách gọi gàm D3DXCreateMeshFVF 
hr= D3DXCreateMeshFVF (12, //Numfaces 
 8, // NumVertices 
 D3DFVF_MANAGED, // Options 
 D3DFVF_CUSTOMVERTEX, // FVF 
 pd3dDevice, // pDevice 
 &boxMesh); // ppMesh 
//kiểm tra giá trị trả về có hợp lệ không 
if FAILED (hr) 
 return false; 
Một lần nữa bạn tạo mesh bằng cách sử dụng bộ nhớ quản lý cho véctơ, index buffer và 
việc chỉ định giá trị D3DXMESH_MANAGED. Khi việc gọi hàm kết thúc, biến boxMessh 
sẽ giữ đối tượng mesh hợp lệ. 
Hàm D3DXCreateMeshFVF là hàm dễ sủ dụng nhất trong hai hàm tạo mesh. 
Filling the Mesh. 
Bạn đã tạo được mesh object, bạn cần bổ xung thêm dữ liệu để mô tả model mà bạn muốn 
thể hiện. Ở đây, bạn có một kho rỗng với kích thước thích hợp để chứa dữ liệu cần thiết 
cho việc tạo khối lập phương. 
Để xác định một khối lập phương, trước tiên bạn cần lock véctơ buffer lại và bổ xung vào 
nó 8 véctơ mà khối lập phương cần. Tiếp theo bạn cần lock index buffer và copy toàn bộ 
index vào trong đó. 
Hàm SetupMesh chỉ ra dưới đây sẽ giúp bạn từng bước hoàn thành mesh với các thông 
tin cần thiết để tạo một khối lập phương. 
/************************************************************************************** 
* SetupMesh 
* Set up the vertex buffer and index buffer of a mesh 
**************************************************************************************/ 
HRESULT SetupMesh() 
{ 
 HRESULT hr; //biến giữ giá trị trả về 
 /////////////////////////////////////////////////////////////////////////////// 
 //các véctơ của vertex buffer 
 CUSTOMVERTEX g_Vertices[ ]={ 
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 100
 // X Y Z 
 {-1.0f, -1.0f, -1.0f, D3DCOLOR_ARGB(0,255,0,0)}, //0 
 {-1.0f, 1.0f, -1.0f, D3DCOLOR_ARGB(0,0,0,255)}, //1 
 { 1.0f, 1.0f, -1.0f, D3DCOLOR_ARGB(0,0,255,0)}, //2 
 { 1.0f, -1.0f, -1.0f, D3DCOLOR_ARGB(0,0,0,255)}, //3 
 {-1.0f, -1.0f, 1.0f, D3DCOLOR_ARGB(0,0,255,0)}, //4 
 { 1.0f, -1.0f, 1.0f, D3DCOLOR_ARGB(0,0,0,255)}, //5 
 { 1.0f, 1.0f, 1.0f, D3DCOLOR_ARGB(0,0,255,0)}, //6 
 {-1.0f, 1.0f, 1.0f, D3DCOLOR_ARGB(0,0,0,255)} //7 
 }; 
 //Copy các véctơ vào trong vertex buffer 
 VOID* pVertices; 
 // lock vertex buffer 
 hr = boxMesh->LockVertexBuffer(D3DLOCK_DISCARD, (void**)&pVertices); 
 //kiểm tra lại để chắc chắn là các vertex buffer đã lock 
 if FAILED (hr) 
 return hr; 
 ///////////////////////////////////////////////////////////////////////////////// 
 //index buffer data 
 //index buffer xác định các phần tử của khối lập phương, 
 //hai phần tử ở mỗi mặt của cube 
 WORD IndexData[ ] = { 
 0,1,2, //0 
 2,3,0, //1 
 4,5,6, //2 
 6,7,4, //3 
 0,3,5, //4 
 5,4,0. //5 
 3,2,6, //6 
 6,5,3, //7 
 2,1,7, //8 
 7,6,2, //9 
 1,0,4, //10 
 4,7,1, //11 
 } 
 //copy các véctơ vào buffer 
 memcpy(pVertices, g_Vertices, sizeof(g_Vertices) ); 
 //mở khóa véctơ buffer 
 boxMesh->UnlockVertexBuffer(); 
 //chuẩn bị copy các index vào index buffer 
 VOID* IndexPtr; 
 //khóa index buffer 
 hr-boxMesh->LockIndexBufffer( 0, &IndexPtr); 
 //kiểm tra xem index buffer đã khóa chưa 
 if FAILED (hr) 
 return hr; 
 //copy các index vào buffer 
 memcpy(IndexPtr, IndexData, sizeof( IndexData)*sizeof(WORD)); 
 //mở khóa buffer 
 boxMesh->UnlockIndexBuffer(); 
 return S_OK; 
} 
Điều đầu tiên mà hàm SetupMesh làm đó là tạo ma trận g_Vertices. Ma trận này chứa 
các véctơ và véctơ màu mà bạn cần để xác định một khối lập phương. Tiếp đó, vectơ 
buffer bị locked như đã thấy ở ví dụ trên. Việc gọi hàm memcpy là để copy tất cả các 
véctơ vào trong vertex buffer và sau đó thực hiện unlock buffer. 
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 101
Tiếp theo bạn cần điền vào index buffer. Giống như vectơ buffer, bạn phải lock nó trước 
khi dữ liệu được copy vào nó. Các index mà index buffer sử dụng sẽ được xác định trong 
ma trận IndexData. Chú ý rằng ma trận IndexData có kiểu WORD, điều đó có nghĩa là 
chúng là những value 16-bit. 
Sau khi bạn đã có các giá trị xác định, bạn lock index buffer và copy vào các index này 
bằng việc gọi hàm memcpy rồi unlock index buffer. 
Hiển thị Mesh 
Bạn đã tạo ra một mesh và đưa dữ liệu vào trong đó, như vậy bạn đã sẵn sàng đưa nó ra 
màn hình. Hàm drawMesh dưới đây sẽ chỉ ra việc biểu diễn một mesh sẽ cần những gì. 
Hàm này sẽ làm khối lập phương quay trên màn hình. 
/*************************************************************************** 
*void drawMesh (LPD3DXMESH mesh, D3DMATERIAL9 material) 
* Draws the mesh 
****************************************************************************/ 
void drawMesh (LPD3DXMESH mesh, D3DMATERIAL9 *marterial) 
{ 
//quay mesh 
D3DXMATRIX matRot; 
D3DXMATRIX matView; 
D3DXMATRIX matWorld; 
//tạo ma ma trận quay 
D3DXMatrixMultiply(&matWorld, &matRot, &matView); 
//lấy sự biến đổi vào kết quả của ma trận matWorld 
pd3dDecice ->SetTransform( D3DTS_WORLDM, &matWorld); 
//đưa giữ liệu vào sử dụng 
pd3dDevice->SetMaterial(material); 
//vẽ mesh 
mesh->DrawSubset(0); 
} 
Phần đầu tiên của hàm drawMesh sẽ là quay và dịch chuyển khối lập phương trên màn 
hình. Sau đó giữ liệu mà khối lập phương sẽ sử dụng được nạp vào qua việc gọi 
SetMaterial. Phần quan trọng nhất của hàm drawMesh là gọi DrawSubset. 
Hàm DrawSubset thông báo Direct3D phần nào của mesh bạn muốn hiện thị lên màn 
hình. Vì mesh mà bạn tạo ra ở phần trước chỉ chứa một nhóm đơn thuần, giá trị 0 đã được 
truyền cho DrawSubset. 
Trên hình 7.1 sẽ chỉ ra kết quả mesh đựợc hiển thị trên màn hình. Bạn sẽ tìm thấy đầy đủ 
source code trong chapter7\example1 trên đia CD-ROM. 
Chú ý: 
Cả vectơ và index buffer đều cần thiết để tạo một đối tượng mesh hợp lệ 
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 102
Hình 7.1 hình ảnh mesh của cube trên màn hình. 
Tối ưu hóa Mesh. 
Khi một mesh được tạo, nó không có định dạng tốt ưu để Direct3D vẽ. Ví dụ mesh của 
bạn phải chứa các véctơ bị lặp lại và được sử dụng cho nhiều phần tử, hoặc các véctơ và 
các phần tử không được nằm trong một thứ tự có hiệu quả. Khi tối ưu hóa mesh bằng 
cách sử dụng các “véctơ được dùng chung” và cho phép các véctơ và các phần tử sắp xếp 
lại, bạn có thể tăng quá trình thực hiện khi kết xuất một mesh. 
Thư viện tổng hợp D3DX cung cấp 2 hàm để tối ưu mesh: Optimize và OptimizeInplace. 
Mỗi một hàm này về cơ bản thực hiện cùng một công việc nhưng với các key khác nhau. 
Optimize tạo ra output mesh, ngược lại OptimizeInplace làm thay đổi ở input mesh. Tôi sẽ 
giải thich cụ thể hai hàm này được sử dụng với mesh như thế nào. 
Hàm Optimize định nghĩa nhu sau, lấy một input mesh, tối ưu nó và khởi tạo một output mesh. 
HRESULT Optimize( 
 DWORD Flags, 
 CONST DWORD *pAdjacencyIn, 
 DWORD *pAdjacencyOut, 
 DWORD *pFaceRemap, 
 LPD3DXBUFFER *ppVerTexRemap, 
 LPD3DXMESH *ppOptMesh 
); 
Hàm Optimize có 6 tham số: 
ƒ Flags – là dấu hiệu chỉ ra dạng tối ưu cho việc thi hành. Bạn càn tìm flag cho tham 
số này trong bảng liệt kê D3DXMESHOPT. 
Chú ý: 
Mesh có thể chứa rất nhiều nhóm đặc tính khác. Những nhóm khác đó lưu giữ một tập hợp 
tất cả các phần tử được dùng để mô tả sự phân chia của mesh. Ví dụ như nếu bạn có 2 
vùng mesh mà cần 2 texture khác nhau, mỗi vùng trong đó sẽ đặt vào một nhóm đặc tính 
riêng rẽ. Bằng cách này, bạn có thể điều chỉnh lại texture và giữ liệu mà Dicrect3D sử dụng 
trước khi kết xuất mỗi nhóm 
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 103
ƒ pAdjacencyIn – con trỏ trỏ tới ma trận đang lưu dữ liệu liền kề hiện tại cho input mesh. 
ƒ pAdjacencyOut – con trỏ trỏ tới ma trận đang lưu giữ dữ liệu liền kề cho output 
 mesh đựơc tối ưu. 
ƒ pFaceRemap – con trỏ trỏ tới buffer đang lưu dữ index mới cho output mesh. 
ƒ ppVertexRemap – địa chỉ gửi đến con trỏ của giao diện ID3DXBuffer dành cho 
 output mesh. 
ƒ ppOptmesh – một giao diện ID3DXMesh đang lưu giữ output mesh được tạo mới. 
Hàm OptimizeInplace làm thay đổi input mesh, được xác định như sau: 
HRESULT Optimize( 
 DWORD Flags, 
 CONST DWORD *pAdjacencyIn, 
 DW ... ATIC 
Bộ đệm được đặt trên bộ nhớ phần 
cứng on-board 
DSBCAPS_STICKYFOCUS 
Khi bạn chuyển focus sang ứng dụng 
khác, bạn vẫn có thể nghe bộ đệm có 
stickyfocus. Bộ đệm thường sẽ ko 
kêu khi khi điều này xảy ra 
Tạo bộ đệm thứ cấp(secondary buffer) 
Giờ bạn đã tạo cấu trúc DSBUFFERDESC, bạn đã sẵn sàng tạo bộ đệm 
thứ cấp thực sự. Bộ đệm thứ cấp được tạo với lời gọi CreatSoundBuffer, 
định nghĩa như sau: 
HRESULT CreateSoundBuffer( 
LPCDSBUFFERDESC pcDSBufferDesc, 
LPDIRECTSOUNDBUFFER * ppDSBuffer, 
LPUNKNOWN pUnkOuter 
); 
Hàm này chỉ cần 3 tham số: 
- pcDSBufferDesc. Địa chỉ một cấu trúc đã xác định DSBUFFERDESC. 
- ppDSBuffer. Địa chỉ biến sẽ chứa bộ đệm mới tạo. 
- pUnkOuter. Địa chỉ tới điều khiển giao tiếp IunKnown của đối tượng. 
Giá trị này nên để là NULL. 
Một lời gọi mẫu của hàm như sau: 
// định nghĩa một cấu trúc WAVEFORMATEX 
WAVEFORMATEX wfx; 
// Khởi trị cấu trúc tất cả về 0. 
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 171
ZeroMemory( &wfx, sizeof(WAVEFORMATEX) ); 
// Thiết lập định dạng là WAVE_FORMAT_PCM 
wfx.wFormatTag = (WORD) WAVE_FORMAT_PCM; 
// Thiết lập số kênh âm thanh là 2 
wfx.nChannels = 2; 
// Thiết lập tốc độ mẫu là 22050 
wfx.nSamplesPerSec = 22050; 
// Tính giá trị nBlockAlign 
wfx.wBitsPerSample = 16; 
wfx.nBlockAlign = (WORD) (wfx.wBitsPerSample / 8 * wfx.nChannels); 
// Tính giá trị the nAvgBytesPerSec 
wfx.nAvgBytesPerSec = (DWORD) (wfx.nSamplesPerSec * 
wfx.nBlockAlign); 
// Định nghĩa một cấu trúc DSBUFFERDESC 
DSBUFFERDESC dsbd; 
// Khởi trị cấu trúc tất cả về 0 
ZeroMemory( &dsbd, sizeof(DSBUFFERDESC) ); 
// Thiết lập kích thước cấu trúc 
dsbd.dwSize = sizeof(DSBUFFERDESC); 
// Thiết lập các cờ 
dsbd.dwFlags = 0; 
// kích thước bộ đệm 
dsbd.dwBufferBytes = 64000; 
// GUID của thuật toán 
dsbd.guid3DAlgorithm = GUID_NULL; 
// địa chỉ cấu trúc WAVEFORMATEX 
dsbd.lpwfxFormat = &wfx; 
// Định nghĩa biến lưu bộ đệm mới tạo 
LPDIRECTSOUNDBUFFER DSBuffer = NULL; 
// Tạo bộ đệm mới 
hr = g_pDS->CreateSoundBuffer( &dsbd, &DSBuffer, NULL ); 
// Kiểm tra code trả về để chắc chắc lời gọi hàm CreatSoundBuffer thành 
công 
if FAILED (hr) 
return NULL; 
Nếu lời gọi hàm CreatSoundBuffer thành công, biến DSBuffer sẽ là một bộ 
đệm DS hợp lệ. Trong ví dụ trên, định dạng của cấu trúc 
WAVEFORMATEX đã được hard-coded, buộc tất cả các files được load 
vào bộ đệm này phải có kiểu định dạng xác định và chỉ tối đa dài 64000 
bytes. 
Load một file âm thanh vào bộ đệm 
Giờ bạn đã tạo bộ đệm, bạn cần load dữ liệu âm thanh vào nó. Load dữ 
liệu âm thanh vào bộ đệm cần bạn trước hết phải mở file chứa dữ liêu đó, 
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 172
rồi copy nội dung của nó vào bộ đệm đã tạo. Với bộ đệm tĩnh(static buffer), 
tất cả dữ liệu được copy vào bộ đệm. 
Bởi bộ đệm âm thanh là một diện tích bộ nhớ điều khiển bởi DS, bạn phải 
khoá(lock) nó trước khi viết lên nó. Khoá bộ đệm sẽ chuẩn bị bộ nhớ để 
được viết vào. Sau khi khoá bộ đệm, chương trình có thể load âm thanh 
vào nó. Khi bạn hoàn thành việc load, bạn phải nhớ mở khoá(unlock) nó. 
Mở khoá bộ đệm cho phép DS chế tác nội dung của nó một lần nữa. 
Khoá(Lock) bộ đệm 
Khoá bộ đệm âm thanh cho code một cơ hội chế tác, thay đổi dữ liệu âm 
thanh trong bộ đệm. Khoá bộ đệm cần hàm Lock, như sau: 
HRESULT Lock( 
 DWORD dwOffset, 
 DWORD dwBytes, 
 LPVOID * ppvAudioPtr1, 
 LPDWORD pdwAudioBytes1, 
 LPVOID * ppvAudioPtr2, 
 LPDWORD pdwAudioBytes2, 
 DWORD dwFlags 
); 
Hàm Lock yêu cầu 7 tham số: 
- dwOffset. Biến này chỉ định vị trí trong bộ đệm nên được bắt đầu 
lock. 
- dwBytes. Số bytes bên trong bộ đệm để lock. 
- ppvAudioPtr1. Biến này nhận một con trỏ tới phần đầu của bộ đệm bị 
lock. 
- pdwAudioBytes1. Biến này nhận số bytes trong khối nhớ của con trỏ 
ppvAudioPtr1. 
- ppvAudioPtr2. Biến này nhận một con trỏ tới phần thứ hai của bộ 
đệm bị lock. Nếu bạn điền vào cả bộ đệm với dữ liệu âm thanh, biến này 
nên để NULL. 
- pdwAudioBytes2. Biến này nhận số bytes trong khối nhớ của con trỏ 
ppvAudioPtr2. Biến này nên để NULL nếu bạn điền vào cả bộ đệm với dữ 
liệu âm thanh. 
- dwFlags. Đây là các cờ(Flags) chỉ định lock nên diễn ra như thế nào: 
. DSBLOCK_FROMWRITECURSOR. Bắt đầu lock từ con trỏ ghi(write 
cursor). 
. DSBLOCK_ENTIREBUFFER. Lock cả bộ đệm. Nếu cờ này được bật, 
biến dwBytes sẽ được bỏ qua. 
 Mở khoá(unlock) bộ đệm 
Lúc này, bạn được tuỳ ý đọc dữ liệu âm thanh và load nó vào bộ đệm. Sau 
khi quá trình này hoàn thành, bạn có thể mở khoá(unlock) bộ đệm bằng 
hàm Unlock, như sau: 
HRESULT Unlock( 
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 173
 LPVOID pvAudioPtr1, 
 DWORD dwAudioBytes1, 
 LPVOID pvAudioPtr2, 
 DWORD dwAudioBytes2 
); 
Hàm Unlock cần 4 tham số: 
- pvAudioPtr1. Địa chỉ của giá trị tham số ppvAudioPtr1 dùng trong 
hàm Lock. 
- dwAudioBytes1. Số bytes viết vào pvAudioPtr1. 
- pvAudioPtr2. Địa chỉ của giá trị tham số ppvAudioPtr2 dùng trong 
hàm Lock. 
- dwAudioBytes2. Số bytes viết vào pvAudioPtr2. 
Đọc từ dữ liệu âm thanh vào bộ đệm 
Đọc dữ liệu âm thanh vào bộ đệm thứ cấp (secondary buffer) có thể rất 
phức tạp. Để giải thích dễ hiểu hơn, tỗi sẽ giải thích quá trình này qua lớp 
CWaveFile trong các lớp DS framework. DS Framework cung cấp một cách 
đơn giản để load dữ liệu âm thanh sử dụng định dạng file WAV. Đây là 
định dạng âm thanh mặc định của Windows, có đuôi là WAV. 
Chú ý: 
Các lớp DS framework được khai báo trong file dsutil.cpp và dsutil.h, cung 
cấp các hàm chung gắn liền với DS. Bạn có thể tìm thấy chúng trong thư 
mục Samples\C++\Common\Src và Samples\C++\Common\Inc, trong 
folder mà bạn đã cài DirectX Software Development Kit (SDK). 
Bước đầu tiên trong việc load một file WAV vào bộ đệm là tạo một đối 
tượng CWaveFile. Đối tượng này cung cấp cho bạn các phương thức để 
mở, đóng và đọc một files WAV. Dòng code sau chỉ ra cách tạo một đối 
tượng CWaveFile. 
CWaveFile wavFileObj = new CWaveFile( ); 
 Tiếp theo, sử dụng phương thức Open được cung cấp bởi lớp CWaveFile, 
bạn có thê nhận quyền truy cập vào file WAV bạn muốn dùng. Đoạn code 
sau sử dụng hàm Open và kiểm tra xem file WAV có chứa dữ liệu ko? 
// Mở 1 file dạng WAV có tên test.wav 
wavFile->Open(“test.wav”, NULL, WAVEFILE_READ ); 
// Kiểm tra để chắc chắn kích thước dữ liệu trong file là hợp lệ 
if( wavFile->GetSize( ) == 0 ) 
return false; 
Đoạn code trên mở một file tên là test.wav để đọc. Sau đó nó kiểm tra kích 
thước của dữ liệu bên trong file, nếu file ko chứa dữ liệu, nó dừng việc đọc. 
Bước tiếp theo là tạo một bộ đệm thứ cấp (secondary buffer) để chứa dữ 
liệu. Bước này đã được hướng dẫn ở trên. Sau khi tạo bộ đệm, bạn phải 
khoá(lock) nó trước khi bạn có thể viết dữ liệu WAV vào nó. Đoạn code tiếp 
theo giải thích cách dùng hàm Lock để chuẩn bị một bộ đệm đọc toàn bộ 
một file WAV. 
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 174
HRESULT hr; 
VOID* pDSLockedBuffer = NULL; // con trỏ tới vùng nhớ bộ đệm bị khoá 
DWORD dwDSLockedBufferSize = 0; // kích thước bộ đệm DS bị khoá 
// Bắt đầu từ phần đầu của bộ đệm 
hr = DSBuffer->Lock( 0, 
 // Nhận một bộ đệm 64000 bytes 
64000, 
 // Biến này lưu một con trỏ tới phần mở đầu 
 // của bộ đệm 
 &pDSLockedBuffer, 
 // Biến này lưu kích thước của bộ đệm bị khoá 
 &dwDSLockedBufferSize, 
 NULL,// Ko cần cái thứ hai 
 NULL, // Ko cần cái thứ hai 
 // Khoá cả bộ đệm 
 DSBLOCK_ENTIREBUFFER); 
 // Kiểm tra code trả về để chắc chắc lock 
 // thành công 
if FAILED (hr) 
 return NULL; 
Đoạn code trên lock một bộ đệm bằng cờ DSBLOCK_ENTIREBUFFER. 
Cờ này khiến bộ đệm bị lock toàn bộ. Biến DSBuffer phải là một 
DirectSoundBuffer hợp lệ. 
Giờ bộ đệm đã được lock đúng, bạn có thể ghi dữ liệu WAV vào nó. Một 
lần nữa tôi lại dùng phương thức có trong lớp CWaveFile. Trước khi bạn 
đọc dữ liệu WAV vào bộ đệm, bạn phải reset lại dữ liệu WAV về phần đầu. 
Bạn làm điều này bằng cách dùng phương thức ResetFile. Tiếp theo bạn 
dùng phương thức Read để đưa dữ liệu WAV vào bộ đệm. Đoạn code tiếp 
theo reset lại file WAV để đọc và đưa dữ liệu vào bộ đệm. 
HRESULT hr; // biến lưu kết quả trả lại 
DWORD dwWavDataRead = 0; // biến chứa lượng dữ liệu đọc từ file 
 // WAV 
wavFile->ResetFile( ); // Reset lại file WAV về đầu file 
// Đọc file 
hr = wavFile->Read( ( BYTE* ) pDSLockedBuffer, 
dwDSLockedBufferSize, 
&dwWavDataRead ); 
// Kiểm tra để chắc chắn đã thành công 
if FAILED (hr) 
return NULL; 
Biến wavFile phải chứa một đối tượng CWaveFile hợp lệ trước khi sử 
dụng. Đầu tiên, hàm ResetFile được gọi, tiếp theo là lời gọi hàm Read. 
Hàm Read cần ba tham số. Tham số thứ nhất là con trỏ tới vùng bộ nhớ 
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 175
chứa bộ đệm để copy dữ liệu vào. Tham số tiếp theo là kích thước của 
vùng bộ đệm đã khoá, tham số cuối nhận khối lượng dữ liệu đọc từ file 
WAV, tính bằng bytes. 
Sau khi gọi hàm Read, bộ đệm được điền bằng dữ liệu từ file WAV. Bạn 
giờ đây có thể an toàn mở khoá bộ đệm. 
Play âm thanh trong bộ đệm 
Giờ bạn đã có dữ liệu âm thanh hợp lệ chứa trong DirectSoundBuffer, bạn 
có thể play dữ liệu mà nó chứa. Sau toàn bộ các công việc cần để tạo bộ 
đệm và điền dữ liệu vào đó, giừo việc play nó rất đơn giản. Một hàm đơn 
giản là Play hoàn thành việc này. Hàm này là một phương thức cung cấp 
bởi đối tượng DirectSoundBuffer. Nó như sau: 
HRESULT Play( 
 DWORD dwReserved1, 
 DWORD dwPriority, 
 DWORD dwFlags 
); 
Hàm này cần ba tham số: 
- dwReserved1. Một giá trị để dành trước phải là 0. 
- dwPriority. Mức độ ưu tiên để play âm thanh. Nó có thể là bất cứ giá 
trị nào trong khoảng 0 và 0xFFFFFFFF. Bạn phải đặt mức độ ưu tiên về 0 
nếu cờ DSBCAPS_LOCDEFER chưa được bật khi bộ đệm được tạo ra. 
- dwFlags. Các cờ chỉ rõ âm thanh sẽ được play thế nào? Cờ duy nhất 
tôi sẽ giải thích ở đây là DSBPLAY_LOOPING. Cờ này khiến âm thanh lặp 
lại khi đạt đến cuối bộ đệm. Nếu âm thanh chỉ nên play một lần, tahm số 
dwFlags nên được truyền giá trị 0. 
Đoạn code dưới đây khiến 1 bộ đệm âm thanh play nội dung của nó: 
DSBuffer->Play( 0, 0, DSBPLAY_LOOPING); 
Biến DSBuffer phải chứa một đối tượng DirectSoundBuffer hợp lệ, chứa dữ 
liệu. Trong trường hợp này, cờ DSBPLAY_LOOPING được truyền vào hàm 
khiến âm thanh lặp lại sau khi hoàn thành một lượt. 
Dừng âm thanh 
Thường sau khi play âm thanh, bạn ko cần lo gì nữa, trừ khi bạn bảo âm 
thanh lặp lại. Trong trường hợp này, bạn cần ra lệnh dừng âm thanh. Bạn 
làm điều này nhờ phương thức Stop, cung cấp bởi đối tượng 
DirectSoundBuffer, như sau: 
HRESULT Stop( ); 
Hàm này ko cần tham số. Nó chỉ trả lại một kết quả cho biết hàm được gọi 
thành cồng hay ko? 
Bạn có thể tìm thấy một ví dụ đầy đủ về load một file âm thanh và play nó 
trong thư mục chapter10\example2 trên đĩa. 
Sử dụng các điều khiển của bộ đệm(buffer control) 
Như đã đề cập, bộ đệm DS có thể điều khiển diện mạo của những âm 
thanh trong nó. Ví dụ, qua một bộ đệm, bạ có thể thay đổi âm lượng, tần 
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 176
số, hoặc pan(chỉnh âm lượng giữa hai loa) một âm thanh. Trong đoạn này 
bạn sẽ học cách dùng chúng. 
Thay đổi âm lượng 
Bạn có thể thay đổi âm lượng của âm thanh qua bộ đệm chứa nó. Bạn có 
thể điều chỉnh âm lượng trong khoảng giá trị DSBVOLUME_MIN và 
DSBVOLUMEMAX. Giá trị DSBVOLUME_MIN miêu tả im lặng và 
DSBVOLUME_MAX miêu tả âm lượng gốc của âm thanh. 
Chú ý: 
DS ko hỗ trợ khuếch đại âm, vì vậy bạn ko thể tăng âm lượng được. 
Bạn có thể điều chỉnh âm lượng của âm thanh qua hàm SetVolume như 
sau: 
HRESULT SetVolume ( 
 LONG lVolume 
); 
Hàm này chỉ cần một tham số: lVolume. Bạn có thể cho giá trị của nó từ -
10000(DSBVOLUME_MIN) và 0(DSBVOLUME_MAX). 
Bạn cũng có thể nhận giá trị âm lượng mà một âm thanh đang được play 
bằng hàm Getvolume. Hàm này như sau: 
HRESULT GetVolume ( 
 LPLONG plVolume 
); 
Hàm này cũng chỉ cần một tham số, là một con trỏ tới một biến sẽ nhận giá 
trị âm thanh hiện tại. 
Chú ý: 
Trước khi dùng hai hàm trên, bạn phải thiết lập bộ đệm cho phép dùng 
chúng, bằng cách bật cờ DSBCAPS_CTRLVOLUME trong cấu trúc 
DSBUFFERDESC khi bạn tạo bộ đệm thứ cấp (secondary buffer). 
Panning âm thanh(chỉnh âm lượng giữa hai loa) 
Bộ đệm DS cho phép một âm thanh được pan giữa hai loa. Pan là giảm âm 
lượng một loa và tăng ở bên kia. Âm thanh nghe như di chuyển vậy. Pan 
dùng một tư tưởng chung như hàm SetVolume. Loa trái và loa phải có thể 
tăng hoặc giảm âm lượng phụ thuộc hai giá trị: DSBPAN_LEFT và 
DSBPAN_RIGHT. 
Giá trị đầu DSBPAN_LEFT, tương đương -10000, tăng âm lượng của loa 
trái tới full, trong khi làm im loa kia. Và giá trị DSBPAN_RIGHT, tương 
đương 10000, tăng âm lượng loa phải trong khi làm câm loa trái. Bằng 
cách dùng giá trị giữa DSBPAN_LEFT và DSBPAN_RIGHT, âm thanh có 
thể bị pan từ một loa sang loa kia. 
Một giá trị thứ ba, là DSBPAN_CENTER, bằng 0, chỉnh cả hai tới full âm 
lượng. 
Giá trị pan âm thanh có thể được đặt bằng hàm SetPan, như sau: 
HRESULT SetPan( 
 LONG lPan 
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 177
); 
Hàm này chỉ cần một tham số: lPan, nhận giá trị giữa DSBPAN_LEFT và 
DSBPAN_RIGHT(-10000->10000). 
Nếu bạn muốn biết giá trị pan hiện tại, bạn dùng hàm GetPan: 
HRESULT GetPan( 
 LPLONG plPan 
); 
Hàm này cũng chỉ cần một tham số: plPan, là con trỏ tới một biến LONG 
nhận giá trị pan hiện tại. 
Chú ý: 
Trước khi dung hai hàm này, bạn phải thiết lập bộ đệm cho phép dùng. 
Bạn cần đặt cờ DSBCAPS_CTRLPAN trong cấu trúc DSBUFFERDESC khi 
tạo bộ đệm. 
Tổng kết chương 
Dùng những gì vừa học, bạn có thể play nhạc nền hoặc các hiệu ứng âm 
thanh đơn giản cho game. Bạn có thể mở rộng bài học này để play nhiều 
âm thanh đồng thời, hay tạo các nhạc động, có thể sửa và điều khiển trong 
game. 
Trong chương sau, chúng ta sẽ đặt những thứ đã học với nhau để tạo một 
game đơn giản sử dụng mỗi phần đã nói trong sách. 
Những thứ bạn đã học 
Trong chương này, bạn đã học: 
- DS dùng thế nào? 
- Có những kiểu bộ đệm nào? 
- Liệt kê các thiết bị âm thanh đã có trong hệ thống. 
- Load và Play một file WAV. 
- Điều khiển sự phát lại(playback) của một file âm thanh. 
Câu hỏi ôn tập 
Bạn có thể tìm câu trả lời trong phụ lục A “Answers to End-of-Chapter 
Exercises”. 
1. Khi nào bạn cần dùng hàm DirectSoundEnumerate? 
2. Ba mẩu dữ liệu quan trọng nào được truyền cho hàm callback liệt 
kê?(enumeration callback function)? 
3. Định dạng của một bộ đệm có cần giống định dạng của dữ liệu nó 
chứa ko? 
4. Mục đích bộ đệm sơ cấp (primary buffer)? 
5. Giá trị nào được truyền cho hàm DirectSoundCreate8 để chỉ rõ 
thiết bị âm thanh mặc định được dùng? 
Về phần bạn 
1. Viết một cí dụ cho phéo điều chỉnh âm lượng âm thanh khi đang 
play nó? 
2. Viết một ví dụ nhỏ cho phép âm thanh pan sử dụng các phím mũi 
tên. 
Beginning DirectX9 Dịch bởi TransTeam diễn đàn Gamedev.VN
 178

File đính kèm:

  • pdftai_lieu_beginning_directx9_phan_2.pdf