GPBasics 02: Irrlicht Hello World

Trong bài này chúng ta sẽ bắt đầu đi vào tìm hiều và sử dụng Irrlicht. Các bài viết mình sẽ chỉ giải thích nguyên lý và hướng làm việc với code, về cách cài đặt và sử dụng thư viện cho một môi trường cụ thể nào đó thì mong người đọc hãy tìm hiểu IDE mà bạn đang dùng để có thể cài Irrlicht cũng như bất cứ một thư viện lập trình nào khác.

Để bắt đầu sử dụng Irrlicht, bạn include file header vào như sau:

#include <irrlicht.h>

Một số khái niệm khi sử dụng Irrlicht:

Mọi thứ đều xoay quanh các Scene Node – đối tượng chính của graphics engine này.

Các Scene Node phổ biến (các tên class mặc định đều bắt đầu bằng chữ I – chữ cái đầu của Irrlicht)

– ICameraSceneNode: Camera trong 3D world, thường thì chỉ có 1 camera chính dành cho người dùng quan sát 3D world, về sau sẽ có thể sử dụng thêm nhiều camera, ví dụ: Game đua xe 2 người chơi, mỗi người nhìn một nửa màn hình thì sẽ phải có 2 camera, ngoài ra những hình ảnh như nhìn qua gương của xe, nhìn ra phía sau xe,…cũng cần nhiều hơn 1 camera.

– ICameraSceneNodeFPS: thừa kế từ ICameraSceneNode, loại camera này có thể điều khiển như trong các game FPS, ngoài ra còn có ICameraSceneNodeMaya, điều khiển giống trong phần mềm đồ họa Maya.

– IMeshSceneNode: phải được xây dựng từ IMesh, scene node này có thể hiển thị đồ họa load từ các file 3D mà Irrlicht hỗ trợ, hoặc bạn cũng có thể tự xây dựng mesh của bạn rồi tạo scene node để hiển thị mesh đó. Nguyên lý là load mesh từ file trước hoặc tự code mesh riêng sau đó mới khởi tạo IMeshSceneNode.

– IAnimatedMeshSceneNode: khác với scene node ở trên là chỉ tĩnh chứ không cử động, scene node này có thể chạy animation load từ các file đồ họa, tương tự vẫn là tạo mesh trước (trường hợp này là IAnimatedMesh) rồi tạo scene node.

Tiếp theo trong hàm main, bạn cần tạo ra một IrrlichtDevice. Nhưng trước khi đi vào phần này thì mình nghĩ nên nói về namespace của Irrlicht trước:

– namespace irr: chứa các namspace khác và một số thứ của thư viện không nằm trong các namespace kia

– namespace scene: chứa những thứ liên quan đến scene, vd như các loại mesh, scene node, camera….

– namespace video: những thứ về hình ảnh nói chung, ví dụ: texture, material,…và các settings về video

– namespace io: về nhập xuất, read write file, XML reader…

– namespace gui: giao diện 2D: text, font,….

– namespace core: trong này là những kiểu dữ liệu và container, ví dụ: vector3df (f là float), vector3di (integer), vector2di, vector2df, line, triangle, plane….

Về việc sử dụng từ khóa using thì tùy vào sở thích của bạn, mình hạn chế dùng using bởi viết rõ ra sẽ giúp bạn hiểu được kiểu dữ liệu nào nằm ở phần nào, sẽ dễ phân biệt và tránh nhầm lẫn. Cũng nên tránh sử dụng using bừa bãi, mình đọc code của game Doom 3 thì thậm chí cả project không hề có một từ khóa using nào :D.

Trở lại với code … chúng ta cần khởi tạo một IrrlichtDevice, nó nằm trong namespace irr. Hàm createDevice tạo ra một device dựa trên các tham số truyền vào và trả về con trỏ tới nó:

createDevice(DeviceType, WindowSize, BitDepth, FullScreen, StencilBuffer , VSync , EventReceiverPtr);

giải thích tham số:

DeviceType: một giá trị enum chỉ ra kiểu device, các giá trị bắt đầu bằng irr::video::EDT (E = enum, DT = device type):

EDT_DIRECT3D8

EDT_DIRECT3D9

EDT_OPENGL

EDT_NULL: kiểu device này có thể chạy nhưng làm bất cứ chức năng gì về hiển thị, ví dụ trong game online bạn vẫn cần game server của bạn chạy được nhưng không cần thiết phải render (thực tế thì cũng không có gameserver nào render cả, thường là game server nó như một console application, mọi sự kiện trong game, ví dụ như người chơi di chuyển là đều diễn ra trên gameserver, client chỉ là đang hiển thị lại những dữ liệu mà server gửi về và gửi đi input, nói cách khác, game online thì thực chất là cái server nó đang chạy game) ….

Một số kiểu device khác bạn có thể tìm hiểu thêm trong tài liệu của Irrlicht.

WindowsSize: là một dimension2d và nằm trong namespace core

BitDepth: thường là 16 hoặc 32 bit biểu thị màu cho một pixel, tham số này thường không có tác dụng gì khi chạy chế độ cửa sổ.

FullScreen, StencilBuffer , Vsync: các giá trị bool chỉ thị rằng bạn muốn sử dụng các chức năng này hay không.

EventReceiverPtr: một con trỏ trỏ tới EventReceiver để xử lý input của người chơi, trong các hướng dẫn cơ bản thì thường nó sẽ là NULL, sau này đến phần input mình sẽ có bài viết nói rõ hơn về cách dùng EventReceiver.

irr::IrrlichtDevice* device = irr::createDevice(irr::video::EDT_OPENGL, 
                                  irr::core::dimension2d(800, 600), 
                                  16, false, false, false, 0);
 
if (!device)
    return -1;

Tiếp theo ta cần một ISceneManager để tương tác với 3D Scene, và 1 video driver để tương tác với driver trên hệ thống đang chạy.

irr::video::IVideoDriver* driver = device->getVideoDriver();
irr::scene::ISceneManager* smgr = device->getSceneManager();

Trong hướng dẫn đầu tiên thì chúng ta chỉ tương tác đơn giản, những thành phần khác của Irrlicht thì khi cần thiết dùng thứ gì thì mới viết code thêm.

Trong bài này mọi người thử hiển thị một model được load từ file 3D ở link:

http://devnt.org/female-sci-fi-3d-model/

Sau khi đã có model, chúng ta cần tạo ra Scene Node vì đây là đối tượng cơ bản mà mọi thứ sẽ xoay quanh nó. Và scene node thì cần có mesh. Để load mesh chúng ta dùng hàm getMesh từ Scene Manager.

irr::scene::IMesh* mesh = smgr->getMesh("duong_dan_file_do_hoa");
 
if (!mesh)
{
    device->drop();
    return -1;
}

 Trường hợp này tham số của hàm getMesh là đường dẫn tới file .obj mà các bạn tải về từ link trên.

Các con trỏ của Irrlicht thì bạn không áp dụng delete để giải phóng bộ nhớ, bởi Irrlicht tự viết sẵn reference counting để tự giải phóng, vậy nên chúng ta sẽ sử dụng drop.

Sau khi đã có mesh thì có thể khởi tạo scene node và add vào scene của chúng ta bằng hàm addMeshSceneNode của Scene Manager.

irr::scene::IMeshSceneNode* node = smgr->addMeshSceneNode(mesh);
 
if (node)
{
    node->setMaterialFlag(irr::video::EMF_LIGHTING, false);
    node->setPosition(irr::core::vector3df(0, 0, 0));
}

setMaterialFlag: đặt các thuộc tính cho material, nếu không có dòng này thì scene node của chúng ta sẽ hoàn toàn màu đen do không có ánh sáng trong scene, vì vậy cần set flag ánh sáng về false, các Material Flag đều bắt đầu bằng EMF (E = Enum, MF = Material Flag).

Hàm setPosition đặt vị trí của scene node và nhận một vector3df làm tham số, chúng ta đặt node này ở vị trí (0, 0, 0) trong hệ tọa độ 3D.

Để quan sát được scene thì cần ít nhất một cái camera:

irr::scene::ICameraSceneNode* camera = smgr->addCameraSceneNode();
camera->setPosition(irr::core::vector3df(0, 2, 2));
camera->setTarget(irr::core::vector3df(0, 1, 0));

Vị trí camera: 0, 2, 2: cao hơn scene node 2 đơn vị và lùi ra sau 2 đơn vị để có thể quan sát được node.

Hàm setTarget của camera đặt điểm nhìn cho camera đó.

Mọi thiết lập scene chúng ta đã làm xong, phần tiếp theo là render.

Về rendering với irrlicht thì bạn phải làm trong vòng lặp, và đương nhiên là nếu dùng trong game loop thì nó sẽ chỉ là các câu lệnh của một vòng lặp render chứ không phải là toàn bộ vòng lặp render này rồi.

while (device->run())
{
    driver->beginScene(true, true, irr::video::SColor(255, 100, 101, 140));
 
    smgr->drawAll();
 
    driver->endScene();
}

Hai tham số đầu tiên của hàm beginScene nghĩa là có clear back buffer và z-buffer hay không. Về cơ bản thì trong rendering thường sử dụng 2 buffer, font và back, trong lúc hiển thị những gì đã render thì ở sau sẽ render vào một buffer và đổi chỗ buffer này thành buffer hiển thị (swap) và quá trình lặp đi lại, nếu clear back buffer = false thì tham số thứ 3 không có ý nghĩa vì nó là màu sẽ đặt cho back buffer khi bị clear (SColor định nghĩa màu ARGB). Về z-buffer thì nó là buffer lưu thông số depth của các pixel đã được render, vô hiệu khi chỉ render 2D. Mọi câu lệnh render khác đều nằm giữa 2 lệnh beginScene và endScene của Video Driver.

Hàm drawAll() của Scene Manager render tất cả các scene đã được add vào và có thể hiển thị được. Tương tự sau này nếu sử dụng giao diện 2D, chúng ta cũng sẽ cần gọi drawAll cho nó…

Sau khi kết thúc vòng lặp (xảy ra nếu người dùng thoát chương trình, hoặc sau này có thể tự thêm các điều kiện khác), bạn chỉ cần drop Irrlicht Device và return 0;

device->drop();
 
return 0;

Kết quả của chương trình sẽ như video dưới đây:

Phần tìm hiểu mở rộng: Irrlicht sử dụng hệ tọa độ 3D xác định bằng tay trái nên bạn thử tìm hiểu về “Left handed coordinate system” nhé, sẽ rất có ích trong việc sử dụng các vector và tọa độ trong lập trình game.

Comments

Related Posts

GPBasics 06: Thực hành điều khiển nhân vật

Chào các bạn, hôm nay chúng ta sẽ cùng thực hành về điều khiển nhân vật, kết quả của bài này các bạn có thể xem tại video: Đầu tiên các bạn tạo một class mới như sau: #ifndef NINJA_H #define NINJA_H   #include <irrlicht.h>   // liệt kê các trạng thái của nhân vật […]

Read More

GPBasics 05: Căn bản về điều khiển nhân vật

Chào các bạn, trong tutorial này chúng ta sẽ làm việc với một số vấn đề trong điều khiển nhân vật. Chúng ta sẽ làm quen với khái niệm mới: Framerate independent movement. Trước khi đọc bài này thì chúng ta phải sử dụng các kiến thức về game loop (tài liệu về phần này […]

Read More