Trang

29/12/12

HỌC IRRLICHT ENGINE QUA CÁC VÍ DỤ



HỌC IRRLICHT ENGINE QUA CÁC VÍ DỤ

Các ví dụ này được tích hợp ngay trong thư mục doc của Irrlicht engine. Việc làm hết lại toàn bộ các ví dụ này là một cách hay nhất để học nhanh irrlicht engine. Đây là các liệt kê những phần bạn cần nhớ khi làm lại (tạo project mới từ VC++ của bạn, chứ không upgrade cái có sẳn của Irrlicht). Điều này giúp bạn nắm rỏ hơn về Irrlicht. Bạn không cần gỏ code lại toàn bộ, bạn chỉ cần chép cái file main.cpp từ ví dụ của Irrlicht sang project của bạn và làm sao cho project của bạn chạy là đạt yêu cầu. Sau đó mới quan sát xem từng dòng code và xem mình cần nhớ những gì của nó.

1)      Hello World :

Cách đặt đường dẫn, cũng như cài đặt làm sao mà VC++ của bạn có thể chạy được Irrlicht (VC++ 2008 và VC++ 2011 khác nhau xa, VC++6.0 bây giờ không còn tương thích từ với Irrlicht 1.6 nữa và nó sẽ không hổ trợ chạy DirectX 9.0 hoàn chỉnh).

Chú ý khi dùng VC++2011 (lúc này thì thẻ VC++ Directories không còn nằm ngay vị trí củ nữa mà nó nằm trong project – property hay property manager. Trong mục  (Microsoft.Cpp.Win32.user) chọn thư mục include chổ này thì nó sẽ tác động tới toàn bộ các project sau đó (cái này mới giống lúc dùng VC++ 2008)

Dùng Irrlicht thì lúc nào cũng phải có dòng ở đầu chương trình.

 #include  

Nếu muốn viết tường minh cho từng engine (có thể bạn dùng nhiều engine khác nhau kết hợp với Irrlich thì nên bỏ qua dòng

using namespace irr ;

Như vậy bạn phải đánh irr :: trước mỗi đối tượng của Irr mà bạn dùng (mình khuyên nên dùng cách này hơi mất công nhưng sau này khi dùng nhiều engine thì sẽ thấy rất có ích. Và như thế thì dòng lệnh hơi có vẻ dài như irr ::core ::vector3df postion(0.0f,0.0f,0.0f) ; để khai báo vector3d. Nhưng lại có sự giúp sức rất tận tình của trình biên dịch và môi trường tương tác VC++ ( do nó tự hiện lên, đó cũng là cách tra hàm và đối tượng nhanh không cần đọc help)

Còn ai mà làm biếng hơn nữa thì có thể không cần đánh thêm như video::, hay core :: hay scene ::.. thì có thê khai báo thêm tên dùng trước là :

using namespace core;

using namespace scene;

using namespace video;

using namespace io;

using namespace gui;

Với cách này thì mọi chuyện trở nên dễ dàng hơn nhiều, khi cần khai báo vector3d thì chỉ cần vector3df postion(0.0f,0.0f,0.0f) ; là xong. Làm cách nào là do thói quen của người lập trình.

#ifdef _IRR_WINDOWS_

#pragma comment(lib, "Irrlicht.lib")

#pragma comment(linker, "/subsystem:windows /ENTRY:mainCRTStartup")

#endif

Đây là phần dành cho người dùng Irrlicht trên hệ điều hành Window. Thứ nhất là không cần phải cài project setting rắc rối, cái thứ hai là cấm không cho hiện cửa sổ console xấu hoắc lên (mặc dù khi debug thì cái này rất có ích, nếu không có thì xem trong output của VC++ cũng có mà)

Nhớ các tham số của hàm createDevice.

Cách hiện dòng text trên titleBar của cửa sở chương trình.

Khai báo các biến cơ bản để dùng engine như device, smgr, guiEnv,…(nó thuộc class nào thì các bạn tự nghiên cứu)

Hiển thị một dòng text tĩnh

Thêm một  model 3D và cách tắt chiếu sáng  cho mô hình.

Thêm một camera vô để làm gì ?

Dòng lặp của Irrlicht engine như thế nào ?

 

2)      Quake3Map :

 
Nghiên cứu thêm tệp tin driver choices xem có hữu ích không, thay nó vô ngay cái dòng lệnh chú thích.

Làm việc với filesystem, và cụ thể là hàm addZipFileArchive() của nó để nạp file nén. Sau đó ta có thể làm việc với fie nén như là một thư mục (ví dụ file nén có chứa nhiều file hay thư mục con) từ đó ta chỉ cần gọi thêm hàm cần thiết. Trong bài này ta gọi hàm getMesh của class IsceneManager. Chú ý tham số trã về của hàm này là IAnimatedSceneNode*. Nhưng cái mô hình của ta là mô hình tĩnh, nên ta ta chỉ làm việc với mesh đầu tiên là đủ. Do đó tham số tối ưu render cho mô hình lớn của hàm addOctreeSceneNode nên chỉ cần getMesh(0) là xong.

Cách ẩn con trỏ mouse bằng hàm getCursorControl()->setVisible(false);

Cách hiện tốc độ khung hình trên giây fps (frame per second) trên thanh titleBar của chương trình.

3)      Custom SceneNode :

 

Cách tạo SceneNode tự tạo của người dùng. Thật ra thì đem bài này vô vị trí thứ 3 mình thấy không hợp lý, ít ra nó phải nằm từ 15 hay 16 trở lên mới đúng. Khi bạn cần Irrlicht dựng hình mesh theo ý bạn. Bài này thì chỉ cần nhớ các vấn đề sau :

Kế thừa class IsceneNode

Các biến tối thiểu là : hộp boundingBox, các bộ đệm chứa mesh....

Các hàm cần phải nạp chồng là : Hàm dựng, hàm hủy, OnRegisterSceneNode(), render(), getBoundingBox(), getMaterialCount(), getMaterial(u32 i)

SceneNode tự tạo phải được tạo bằng hàm new :

CSampleSceneNode *myNode = new CSampleSceneNode(

smgr->getRootSceneNode(), smgr, 666);   

Cách thêm animator đơn giản là loại xoay quanh trục của nó.

 
4)      Movement :

 

Cách nhận các sự kiện phím bấm bằng class tự tạo kế thừa tư class EventReceiver. Chú ý các giá trị trã về là false để còn trao lại quyền điều khiển cho các phần khác của engine.

Chú ý bộ đệm bàn phím ảo được lập ngay trong class này thông qua khai báo :

bool KeyIsDown[KEY_KEY_CODES_COUNT];

Ta học thêm 02 hàm để tạo nên SceneNode dựng sẳn trong Irrlicht đó là : addCubeSceneNode và addSphereSceneNode.

Cách gán chất liệu cho nó, bật tắt chiếu sáng động lên đối tượng.

Tạo hoạt cảnh cho SceneNode bằng Animator là bay theo vòng tròn quanh một điểm định trước và bay theo đường thẳng từ điểm A - > B

createFlyCircleAnimator()

createFlyStraightAnimator()

Chú ý : luôn gọi dropt() với các đối tượng được tạo bằng create

Cách đặt hoạt cảnh cho SceneNode có mesh có sẳn hoạt cảnh bằng tay qua các hàm sau : (cái này rất quan trọng trong lập trình sau này)

setFrameLoop(); //đặt đoạn frame làm việc

setAnimationSpeed(); //tốc độ thực hiện hoạt cảnh

setScale(); //chỉnh lại độ lớn nhân vật cho phù hợp

setRotation(); //quay nhân vật lại cho đúng hướng

Cách hiện logo của Irrlicht một cách đơn giản qua hàm addImage()

Cái hay nhất của bài hướng dẫn này theo mình là phần di chuyển của SceneNode không phụ thuộc tốc độ của khung hình.

Ví dụ ta có tốc độ di chuyển mặc định là :

const f32 MOVEMENT_SPEED = 5.f;

Ta cần thêm phần lấy thời gian sau mỗi lần thực hiện xong vòng lặp toàn cục của Irrlicht (trong vòng lặp while() )

const u32 now = device->getTimer()->getTime();

const f32 frameDeltaTime = (f32)(now - then) / 1000.f;

then = now;

Thời gian : frameDeltaTime chính là cái ta cần, nhân nó với khoảng cách di chuyển của SceneNode thì ta sẽ được khoảng cách thay đổi phù hợp với tốc độ khung hình. (như khi di chuyển ở 1000 fps và 50 fps cũng giống nhau)

Ví dụ SceneNode của ta di chuyển theo trục Y đi lên thì ta có độ dài di chuyển tùy theo tốc độ của khung hình  là :

nodePosition.Y += MOVEMENT_SPEED * frameDeltaTime;

 

5)      User Interface :

 

Bài này dành cho phần giao tiếp người dùng, các phần cần nhớ là :

Tạo cấu trúc để nhận con trỏ trong Contructor (chính xác là viết ngắn lại các tham số) của class kế thừa EventReceiver

struct SAppContext

{

        IrrlichtDevice *device;

        s32                             counter;

        IGUIListBox*    listbox;

};

Cách dùng enum để định nghĩa các hằng số của chương trình

enum //chổ này thiếu tên

{

        GUI_ID_QUIT_BUTTON = 101,

        GUI_ID_NEW_WINDOW_BUTTON,

        GUI_ID_FILE_OPEN_BUTTON,

        GUI_ID_TRANSPARENCY_SCROLL_BAR

};

Hàm dựng khởi tạo luôn biến nội tại của nó nhanh :

MyEventReceiver(SAppContext & context) : Context(context) { }

Biến Context được khai báo cuối class như là biến riêng của class

Nếu xử lý với GUI thì ta chỉ cần chặn lại Event lất từ  

if (event.EventType == EET_GUI_EVENT)

Nếu trong class MyReceiver của ta cần tham chiếu đến các hàm trong class khác, ta chỉ cần nắm có được con trỏ IrrlichtDevice là đủ.

Trong phần xử lý các GUIEvent, phần nào của ta xử lý thì ta trả về là true, còn các phần khác thì phải trả về là false

Hàm cho phép thay đổi kích thước cửa sổ của Irrlicht là :

device->setResizable(true);

Nạp font chữ của người dùng :

IGUISkin* skin = env->getSkin();

        IGUIFont* font = env->getFont("../../media/fonthaettenschweiler.bmp");

        if (font)

                skin->setFont(font);

Và dùng font mặc định để hiện tooltip :

skin->setFont(env->getBuiltInFont(), EGDF_TOOLTIP);

Vì hàm dựng của MyReceiver cần tham số khởi tạo nên ta khởi tạo nó trước :

SAppContext context; // cách hay hơn nếu có hàm dựng của struct này

context.device = device;

context.counter = 0;

context.listbox = listbox;

Khai báo và khởi tạo đối tượng thuộc class MyReceiver

MyEventReceiver receiver(context);

Sau đó cho đối tượng IrrlichtDevice biết là nó nhận sự kiện người dùng chổ nào :

device->setEventReceiver(&receiver);

 

6)      2D Graphic :

 
Hàm để làm cho texture có màu trong suốt (dĩ nhiên là chỉ các một màu duy nhất được làm trong suốt).

makeColorKeyTexture() ;

Để vẽ ảnh 2D đẹp một chút, ta dùng thêm bộ lọc tuyến tính :

getMaterial2D().TextureLayer[0].BilinearFilter=true;

      getMaterial2D().AntiAliasing=video::EAAM_FULL_BASIC;

Phần chế độ khữ răng cưa AntiAliasing các bạn phải đọc thêm tài liệu. Nhưng dịch như thế chắc cũng dễ hiểu lắm rồi.

Vì cái ảnh nền có sẳn toàn bộ hình ảnh cần thiết rồi !, Khi cần vẽ logoIrrlicht (không có alpha, transparent..) ta báo cho engine biết là ta vẽ bình thường

driver->enableMaterial2D();

driver->draw2DImage(images, core::rect(10,10,108,48), core::rect(354,87,442,118));

driver->enableMaterial2D(false);
7)      Collision :

 

Khai các hằng cho biết là SceneNode có thể được chọn, được chiếu sáng…

enum

{

    ID_IsNotPickable = 0, // bằng 00 , không chọn

    IDFlag_IsPickable = 1 << 0, //bằng 01 = 1 //có thể chọn

    IDFlag_IsHighlightable = 1 << 1 //bằng 10 nhị phân = 2  //có thể chọn và làm nổi bật

};

Tham số sau hàm add các SceneNode của đối tượng ISceneManager* ta phải chú ý là ID của nó, các SceneNode ta add sau đó sẽ tùy theo ID cho ta biết là SceneNode này có thể được chọn, được làm nổi bật hay không ?....

Ví dụ : Quake3Map là có thể chọn được, nhưng không được làm nổi bật khi chọn

addOctreeSceneNode(q3levelmesh->getMesh(0), 0, IDFlag_IsPickable);

Trong ví dụ này phần quan trọng là phần kiểm tra va chạm (hay các node có thể được chọn bằng tay hay không và đưa ra các phản ứng thích hợp)

Kiểm tra va chạm tự động trong địa hình 3D ta dùng :

createCollisionResponseAnimator()

Cách dùng các tham số thì mình không bàn ở đây, cái này dùng cho camera chính

createTriangleSelector()

Dùng cho các mô hình nhân vật 3D

getSceneNodeAndCollisionPointFromRay()

Lấy tia va chạm từ Camera đến vật (ngoài ra còn nhiều hàm dùng cho mục đích này như lấy va chạm từ con trỏ mouse…Xem thêm trong ISceneCollisionManager

Các tham số hay trong này là :

idBitMask : chỉ có SceneNode có ID thích hợp mới được chọn – cái này hay đây

collisionRootNode = 0 : chọn toàn bộ SceneNode, còn là ISceneNode* thì chỉ chọn các phần từ SceneNode đó và các con của nó thôi.

noDebugObjects = false : chọn hết, true : những SceneNode được đánh dấu là debug thì sẽ không được chọn.

Vẽ tam giác (hay bất cứ thứ gì thông qua driver) mà nó tương thích với tọa độ màn hình 3D hiện tại :

driver->setTransform(video::ETS_WORLD, core::matrix4());

driver->setMaterial(material);

driver->draw3DTriangle(hitTriangle, video::SColor(0,255,0,0));

Dùng phép AND để chỉ địnhlà SceneNode nào cần làm nổi bật :

if((selectedSceneNode->getID() & IDFlag_IsHighlightable) == IDFlag_IsHighlightable)

 
8)      Special Effect :

 

Để tạo một mặt phẳng mô phổng hiệu ứng mặt nước của Irrlicht thì ta cần một mesh buildin của engine đó là hàm :

addHillPlaneMesh()

sau đó ta gọi hàm để tạo SceneNode có hiệu ứng mặt nước

addWaterSurfaceSceneNode()

Để cho hiệu ứng mặt nước đẹp ta cần đến 2 texture nằm ở 2 layer texture khác nhau là 0 và 1 (0 là nền dưới, còn 1 là nền trên)

node->setMaterialTexture(0, driver->getTexture("../../media/stones.jpg"));

node->setMaterialTexture(1, driver->getTexture("../../media/water.jpg"));

Sau đó là đặt chất liệu là loại phản xạ giữa 2 layer

node->setMaterialType(video::EMT_REFLECTION_2_LAYER);

Để tạo hệ thống hạt ta gọi :

addParticleSystemSceneNode()

Sau đó ta có thể gọi các class con thành viên của hệ thống hạt để tạo loại bố trí các hạt (trong ví dụ này là hệ thống hạt dạng hộp)

createBoxEmitter()

Muốn cho nó có tác dụng thì phải gọi hàm :

setEmitter()

Các tương tác khác ảnh hưởng đến hệ thống hạt ta có thể gọi hàm để tạo hiệu ứng tối dần :

createFadeOutParticleAffector()

sau đó muốn cho có tác dụng thì phải thêm vào hệ thống hạt

addAffector()

Tạo ánh sáng dạng khối hộp (như vòng tròn bao quanh nhân vật…) ta có hàm

addVolumeLightSceneNode()

Để áp dụng một loạt cách ảnh texture (texture động) ta gọi hàm

createTextureAnimator()

Sau đó thêm vào trong SceneNode bằng hàm :

addAnimator()

Để cho SceneNode có thể đổ bóng lên các đối tượn khác ta phải tạo thêm SceneNode con của nó là loại đổ bóng (chỉ áp dụng cho IAnimatedMeshSceneNode mà thôi : vì  chỉ có các SceneNode này mới có hoạt cảnh – bó tay anh Irrlicht). Ta gọi hàm :

addShadowVolumeSceneNode()

Để cho bóng hiện ra đẹp thì ta nên đặt bóng đổ là màu xám.

      smgr->setShadowColor(video::SColor(150,0,0,0));

 

9)      Mesh Viewer :

 

Từ phần này trở đi là phần khá khó

Dùng biến toàn cục để lưu các giá trị cần thiết của chương trình

Khai báo trước các hằng dùng trong chương trình

Phân chia công việc ra làm các phần nhỏ hơn như định camera hoạt động, định độ trong suốt của GUI …

Để camera có thể nhận các sự kiện trực tiếp thì nó phải đặt hàm :

setInputReceiverEnabled(true), ngược lại false sẽ mất tác dụng nhận sự kiện

Đặt một camera là active thì phải gọi hàm :

setActiveCamera(newActive)

Hàm toàn cục phức tạp nhất của ví dụ này là hàm loadModel() có thể nạp cả texture, Quake3Map, hay các mesh3D khác có hay không có animation mà Irrlicht hỗ trợ. Thậm chí nạp cả màn chơi hay mô hình được lưu riêng của Irrlicht là tệp .Irr. Hàm :

getSceneNodesFromType() //chú ý thủ thuật này rất hay

Rất hữu ích trong việc này

Các hàm createToolBox và updateToolBox khá phức tạp, nhưng cũng không khó hiểu lắm.

Class MyEventReceiver cũng rất bình thường, không khó lắm.Chú ý nó xử lý cả các sự kiện GUI và cả bàn phím  luôn. Nó có 02 hàm thành viên là lựa chọn Menu Item và Lựa chọn bộ lọc texture.

setDebugDataVisible() cho hiển thị các trạng thái Debug của SceneNode

Chú ý : hàm addFolderFileArchive() nên thay bằng hàm changeWorkingDirectoryTo()

Cái hay của ví dụ này nữa là cách đọc file .XML trực tiếp (cái này trong lập trình đôi khi cũng cần đến để lưu Data riêng và nạp Data riêng của mình)

Cách đặt hình ảnh logo nằm dưới cùng bên trái màn hình (dùng hàm setAlignment()

img->setAlignment(EGUIA_UPPERLEFT, EGUIA_UPPERLEFT,

                        EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT);

Chương trình chỉ chạy khi cửa sổ của nó đang active, không chạy nền :

Device->isWindowActive()

Nếu không thì nó sẽ ngủ :

Device->yield();

 

10)  Shaders :

 

Biến toàn cục :

IrrlichtDevice* device = 0; //Lưu Device của Irrlicht, nơi có thể truy cập mọi thứ của engine.

bool UseHighLevelShaders = false;

Để chương trình có thể làm việc với Shader ta cần kế thừa class IshaderConstantSetCallBack và viết chồng hàm OnSetConstants()

Vì hướng dẫn này tập trung vào Shader nên ta cũng sẽ khảo sát kỹ cách class và hàm này :

Lấy ma trận đảo của ma trận 3D (ma trận thế giới 3D)

core::matrix4 invWorld = driver->getTransform(video::ETS_WORLD);

invWorld.makeInverse(); //lấy ma trận nghịch đảo từ ma trận hiện tại

Hàm :

setVertexShaderConstant() có 2 hàm khác nhau 1 dùng cho shader bật thấp (như shader assembler hay chương trình ARB_fragment hay ARB_vertex) và 1 dùng cho shader ngôn ngữ bật cao (như GLSL hay HLSL). Ta sẽ xem xét lại hàm này sau khi xem xét các đoạn chương trình Shade.

Lấy ma trận xén :

core::matrix4 worldViewProj;

worldViewProj = driver->getTransform(video::ETS_PROJECTION);

worldViewProj *= driver->getTransform(video::ETS_VIEW);

worldViewProj *= driver->getTransform(video::ETS_WORLD);

Lấy ma trận chuyển đổi của thế giới 3D

core::matrix4 world = driver->getTransform(video::ETS_WORLD);

world = world.getTransposed();

Hàm kiểm tra tính tương thích của card đồ họa của bạn với các tính chất của Shader và bạn cần (hàm truy vấn)

queryFeature()

Để tạo một chất liệu mới từ dạng chất liệu đang có ta phải thực hiện các bước sau:

- Lấy con trỏ của class các dịch vụ và chương trình mà GPU của bạn hỗ trợ :

video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();

Tạo con trỏ đối tượng mới của class mà ta vừa tạo (kế thừa từ class IshaderConstantSetCallBack) bằng hàm new :

Hàm :

addHighLevelShaderMaterialFromFiles() cũng có 2 dạng 1 dùng cho bật thấp và 1 dùng cho ngôn ngữ Shader bật cao (chi tiết hàm này có trong Irrlicht.chm)

Sau đó áp chất liệu mới này vào SceneNode (ví dụ : newMaterialType1)

Sẽ là :

node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType1);

Một hàm ta cũng mới học là tạo đối tượng Text 3D :

addTextSceneNode()

Tạo hộp bầu trời :

addSkyBoxSceneNode()

Tạo ảnh Map cho các đối tượng lớn : setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);

Trả lại bình thường :

setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true);

Thêm : Các đoạn chương trình Shade :

*Mình chỉ dịch đoạn của directX9 thôi, các phần khác các bạn dịch phụ mình hé !

// Một phần của ví dụ dùng Shader của Irrlicth engine

// Những chương trình hiệu ứng điểm ảnh và vector sẽ được nạp bởi chương trình dịch ví dụ về shaders.

//Lưu ý rằng các đoạn chương trình shader này không có gì hữu ích,

// Nó chỉ mô tã shader có thể được dùng trong Irrlicht

//-----------------------------------------------------------------------------

// Các Biến toàn cục//-------------------------------------------------------------------------

float4x4 mWorldViewProj;  // tích của ba ma trận World * View * Projection transformation, chính là ma trận vùng nhìn (có xén)

float4x4 mInvWorld;       // Ma trận nghịch đảo của world matrix

float4x4 mTransWorld;     //Ma trận chuyển đổi của world matrix

float3 mLightPos;         // Vị trí nguồn ánh sáng( trong vì dụ là vị trí camera) float4 mLightColor;       // Màu ánh sáng

// Cấu trúc output của Vertex shader

struct VS_OUTPUT

{

      float4 Position   : POSITION;   // vị trí đỉnh

      float4 Diffuse    : COLOR0;     // màu môi trường của đỉnh

      float2 TexCoord   : TEXCOORD0;  // tọa độ ảnh lát

};

 

VS_OUTPUT vertexMain( in float4 vPosition : POSITION,

                      in float3 vNormal   : NORMAL,

                      float2 texCoord     : TEXCOORD0 )

{

      VS_OUTPUT Output;

      // chuyển đổi vị trí sang không gian xén         

Output.Position = mul(vPosition, mWorldViewProj);

      // lấy vector pháp tuyến

      float3 normal = mul(vNormal, mInvWorld); 

      // Đơn giản hóa vector pháp tuyến

      normal = normalize(normal);

      // Vị trí đỉnh trong tọa độ thực

      float3 worldpos = mul(mTransWorld, vPosition);     

      // tính toán light vector, vtxpos - lightpos

      float3 lightVector = worldpos - mLightPos;  

      // Lấy pháp tuyến của light vector

      lightVector = normalize(lightVector);

      // Tính toán màu của ánh sáng

float3 tmp = dot(-lightVector, normal); //Tích vô hướng của nghịch đảo của vị trí light vector với vector pháp

      tmp = lit(tmp.x, tmp.y, 1.0); //tính vector hệ số ánh sáng

      //nếu tmp.x <0 gi="gi" kh="kh" n="n" ng="ng" nguy="nguy" o:p="o:p" th="th" tmp.x="0," u="u">

//nếu tmp.x<0 1.0="1.0" c="c" hay="hay" i="i" l="l" ng="ng" o:p="o:p" th="th" tmp.y="tmp.y">

      tmp = mLightColor * tmp.y; //sau đó nhân với màu ánh sáng

Output.Diffuse = float4(tmp.x, tmp.y, tmp.z, 0);//màu môi trường xuất ra là màu không có màu xanh lục, và có độ trong suốt là tmp.x

      Output.TexCoord = texCoord;

     

      return Output;

}

// Cấu trúc Pixel shader

struct PS_OUTPUT

{

    float4 RGBColor : COLOR0;  // màu điểm ảnh

};

 

sampler2D tex0;

     

PS_OUTPUT pixelMain( float2 TexCoord : TEXCOORD0,

                     float4 Position : POSITION,

                     float4 Diffuse  : COLOR0 )

{

      PS_OUTPUT Output;

 

      float4 col = tex2D( tex0, TexCoord );  // lấy mẫu màu ảnh lát

     

      // multiply with diffuse and do other senseless operations

      Output.RGBColor = Diffuse * col; //nhân với màu môi trường

      Output.RGBColor *= 4.0; //nhân với 4

 

      return Output;

}

 

11)  Per-Pixel Lighting :

 

Hàm dựng của MyEventReceiver() có chứa các con trỏ đến đối tượng IsceneNode, IGUIEnvironment, và IvideoDriver

Tùy theo yêu cầu của chương trình có thể có các hàm dựng linh hoạt, và trong chương trình class kế thừa IEventReceiver sẽ đảm nhật toàn bộ việc xử lý các sự kiện của chương trình

Chỉ có 1 hàm được nạp chồng là onEvent()

Và một hàm riêng của nó là setMaterial()

Ta học thêm về hàm

getMaterialRenderer(u32 idx) là hàm lấy con trỏ trỏ đến chất liệu dựng hình có chỉ mục là idx, trã về là 0 nếu không thành công

getRenderCapability() là hàm thuộc class IMaterial, cho biết khã năng dựng hình của chất liệu này có được chấp nhận trong card đồ họa của bạn hay không.

setTextureCreationFlag() bật tắt cờ tạo Texture

setFog() nhằm đặt kiểu và màu cho loại sương mù đơn giản của Irrlicht (xấu lắm, không bằng làm bằng kiểu hạt đâu), sau đó chỉ cần cho engine biết bạn cần chất liệu nào hiện sương mù là xong (chất liệu gắn với SceneNode) qua hàm setMaterialFlag()

Hàm tạo kiểu ảnh lát phẳng trong bộ đệm mesh của Irrlicht là

makePlanarTextureMapping() là hàm thành viên của class IMeshManipulator chuyên cho việc xử lý mesh.

makeNormalMapTexture() tạo ảnh map dành cho việc hiện nổi bề mặt (pháp tuyến)

Tạo bản sao của Mesh hiện tại với các thông số đường tiếp tuyến (tangent) và 2 hướng pháp tuyến (biNormal). Sau đó ta chỉ làm việc trên Mesh này còn Mesh kia thì quên đi.

createMeshWithTangents()

Sau khi add vô sceneNodeManager xong thì ta không cần dùng nữa và cũng drop() nó đi.

Nhớ là SceneNode của chúng ta dùng đến 2 ảnh Map trong 2 layer (0 : bình thường và 1 dùng cho pháp tuyến tạo ảnh nổi)

setVertexColorAlpha() đặt độ trong suốt của Mesh

Vì làm việc với Mesh nên ta cần phải can thiệp vào việc phóng to thu nhỏ mesh thủ công. Ta sẽ dùng ma trận và hàm chuyển đổi của lớp IMeshManipulator

core::matrix4 m;

m.setScale ( core::vector3df(50,50,50) );

manipulator->transformMesh( tangentSphereMesh, m );

Ta cũng học làm quen với IBbillboardSceneNode

Và cách gán chất liệu, kiểu chất liệu cho nó…

Ôn lại cách tạo các hoạt cảnh dựng sẳn của Irrlicht cho SceneNode và cách tạo hệ thống hạt, cách làm thế nào cho chúng trong đẹp hơn…

Chú ý với loại IBillboard cũng như lại hạt thì ta nên tắt bộ đệm độ sâu cho nó để hình ảnh trong đẹp hơn và hiển thị chính xác hơn :

setMaterialFlag(video::EMF_ZWRITE_ENABLE, false);
 

12)  Terrain Rendering :



Hàm setVisible() có tác dụng làm ẩn hiện (cái này kể cả SceneNode cũng như GUI hàm thành viên nào cũng có)

setMaterialFlag() đặt cờ cho chất liệu

setMaterialType() đặt loại chất liệu

setMaterialTexture() gán ảnh map cho SceneNode…

Hàm cho Camera :

setTarget() // camera sẽ nhìn vào vị trí đó

setFarValue() // tầm nhìn xa của Camera – xa hơn hết thấy

Hàm tạo ITerrainSceneNode của đối tượng IsceneManager

addTerrainSceneNode()

Và nó dùng dạng chất liệu đặc biệt là : EMT_DETAIL_MAP 

scaleTexture() tương tự như hàm makePlanarTextureMapping() cho biết sẽ tạo ảnh map phẳng cho riêng Terrain thôi bao nhiêu lần trên bề mặt (chỉ có tác dụng trên textture layer 1 – phần Detail Map thôi nên có hàm riêng cho nó)

Tương tự

createTerrainTriangleSelector() tạo kiểm tra va chạm cho riêng terrain

Phần này quan trọng mà mình thường bỏ qua là can thiệp vô data của Terrain (cái này rất hay) mình sẽ test nó riêng trên một project khác)

scene::CDynamicMeshBuffer* buffer = new scene::CDynamicMeshBuffer(video::EVT_2TCOORDS, video::EIT_16BIT);

 terrain->getMeshBufferForLOD(*buffer, 0);

 video::S3DVertex2TCoords* data = (video::S3DVertex2TCoords*)buffer->getVertexBuffer().getData();

 // Work on data or get the IndexBuffer with a similar call.

….

 buffer->drop(); // When done drop the buffer again.

addSkyBoxSceneNode() //Tạo SceneNode loại hộp bầu trời

addSkyDomeSceneNode() // Tạo SceneNode loại khối cầu bầu trời

getHeight() cho biết vị trí hiện tại có độ cao bao nhiêu trên terrain.

 

13)  Render to Texture :



Truy vấn đặc điểm card đồ họa xem có tính chất cần tìm hay không :

queryFeature()

Thêm một texture mà nó là texture sinh ra do render một phần hay toàn bộ khung cảnh, con trỏ trã về là loại Itexture nên sau đó ta dùng nó như các loại texture khác:

addRenderTargetTexture()

Điểm đặc biệt của render target texture là ta phải cập nhật nó liên tục từng khung hình : nên nó phải đặt trong vòng lặp của Irrlicht.

if (rt)

{

driver->setRenderTarget(rt, true, true, video::SColor(0,0,0,255));//render vào texture mình chọn ở đây là rt.

test->setVisible(false); //tạm ẩn đối tượng để thực hiện render lên texture của nó, tránh nó bị render luôn (nó hiện nó – ê cái này là hiệu ứng gương)

      smgr->setActiveCamera(fixedCam);//chọn camera cần render

smgr->drawAll(); //render toàn bộ những gì trong tầm camera đó thấy

driver->setRenderTarget(0, true, true, 0);//trã lại bình thường

test->setVisible(true);//hiện lại đối tượng

      smgr->setActiveCamera(fpsCamera);//đặt lại camera chính

}               

 

14)  Win32 Window :



Phần này dính đến lập trình bằng Window SDK nên muốn hiểu nó thì người các bạn phải đọc lại tài liệu về Window SDK

Nhưng cái hướng dẫn của Irrlicht và của mình thì hoàn toàn khác nhau

Sau đây là hướng dẫn của Irrlicht

Sài Window SDK thì phải kèm theo cái này

Và có chú ý thêm là bạn  phải khai báo include thêm thư viện  opengl32.lib trong linker để chạy đó.

-          Nếu làm game chắc là bạn cũng không cần tích hợp thế này.

-          Phần này dành để tạo chương trình có cửa sổ 3D riêng thì rất hay

#include

Khai báo biến toàn cục

HWND hOKButton; //loại dữ liệu này phải đọc mới biết : handle of Window

HWND hWnd;

Hàm chính của chương trình (trên Window – hàm này nhận xử lý các sự kiện của Window). Ở đây khai báo nó là hàm tĩnh

static LRESULT CALLBACK CustomWndProc(HWND hWnd, UINT message,

                  WPARAM wParam, LPARAM lParam)

{

      switch (message)

      {

      case WM_COMMAND:

                  {

                              HWND hwndCtl = (HWND)lParam;

                              int code = HIWORD(wParam);

 

                              if (hwndCtl == hOKButton)

                              {

                                          DestroyWindow(hWnd);

                                          PostQuitMessage(0);

                                          return 0;

                              }

                  }

                  break;

      case WM_DESTROY:

                  PostQuitMessage(0);

                  return 0;

 

      }

 

      return DefWindowProc(hWnd, message, wParam, lParam);

}

Khai báo biến lưu giữ biến thể của chương trình

HINSTANCE hInstance = 0;

Tên cửa sổ chương trình là hằng ký tự:

const char* Win32ClassName = "CIrrlichtWindowsTestDialog";

Đối tượng chuẩn bị tạo cửa sổ :

WNDCLASSEX wcex; //window class extended

Đăng ký nó với hệ điều cho biết chương trình của mình sẽ bao gồm các tham số đã đăng ký – lúc này chưa tạo cửa sổ

RegisterClassEx(&wcex);

CreateWindow() ;//đây mới là hàm dùng để tạo cửa sổ

Lưu cấu trúc mô tả dữ liệu đặc biệt (OpenGL hay Dx) và hệ điều hành

video::SExposedVideoData videodata((key=='b')?hIrrlichtWindow:0);

đối tượng videodata này sẽ dùng làm tham số trong beginScene của Irrlicht

Nếu ta muốn cửa sổ render của Irrlicht nằm trọn trong cửa sổ được tạo sẳn hIrrlichtWindow ( chọn a) thì ta chỉ cần đơn giản hơn nhiều 

irr::SIrrlichtCreationParameters param;

param.DriverType = driverType;

if (key=='a') param.WindowId = reinterpret_cast(hIrrlichtWindow);

irr::IrrlichtDevice* device = irr::createDeviceEx(param);

Nếu chọn driver là OpenGL thì bạn phải khởi động OpenGL bằng tay (DirectX thì Win có sẳn rồi) – cái này nhớ mệt đây – dính đến lập trình bằng OpenGL. Mà mình test rồi muốn hay không muốn chạy OpenGL trong project này thì cũng phải kèm thư viện OpenGL.dll theo mệt thiệt đó.

if (driverType==video::EDT_OPENGL)

{

      HDC HDc=GetDC(hIrrlichtWindow);

      PIXELFORMATDESCRIPTOR pfd={0};

      pfd.nSize=sizeof(PIXELFORMATDESCRIPTOR);

      int pf = GetPixelFormat(HDc);

      DescribePixelFormat(HDc, pf, sizeof(PIXELFORMATDESCRIPTOR), &pfd);

      pfd.dwFlags |= PFD_DOUBLEBUFFER | PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW;

      pfd.cDepthBits=16;

      pf = ChoosePixelFormat(HDc, &pfd);

      SetPixelFormat(HDc, pf, &pfd);

      videodata.OpenGLWin32.HDc = HDc;

      videodata.OpenGLWin32.HRc=wglCreateContext(HDc);

      wglShareLists((HGLRC)driver-getExposedVideoData().OpenGLWin32.HRc, (HGLRC)videodata.OpenGLWin32.HRc);

}

Cho hiển thị và thực thi chương cửa sổ chương trình :

ShowWindow(hWnd , SW_SHOW);

UpdateWindow(hWnd);

Một cái quan trọng không kém là hàm

driver->beginScene(true, true, 0, videodata); //có tham số cuối cùng là đối tượng cấu trúc videodata.

Còn sau đây là chương trình của mình : hoàn toàn bằng WindowSDK :

Dĩ nhiên do dùng WindowSDK nên không có console cho bạn lựa chọn chạy driver OpenGL hay DirectX cái đó bạn phải tự làm. Và khi cửa sổ chính thay đổi thì phải thông báo cho cửa sổ của Irrlicht biết mà thay đổi kích thước theo cho phù hợp – Cái này cũng xem là bài tập cho các bạn  :

//Win32 Irrlicht chạy cửa sổ con của cửa sổ chính :

/** Example 014 Win32 Window

#include

#ifndef _IRR_WINDOWS_

#error Windows only example

#else

#include // this example only runs with windows

using namespace irr;

#pragma comment(lib, "irrlicht.lib")

HWND hOKButton;

HWND hWnd;

#define Win32ClassName "CIrrlichtWindowsTestDialog"

static LRESULT CALLBACK CustomWndProc(HWND hWnd, UINT message,

                  WPARAM wParam, LPARAM lParam)

{

      switch (message)

      {

      case WM_COMMAND:

                  {

                              HWND hwndCtl = (HWND)lParam;

                              int code = HIWORD(wParam);

 

                              if (hwndCtl == hOKButton)

                              {

                                          DestroyWindow(hWnd);

                                          PostQuitMessage(0);

                                          return 0;

                              }

                  }

                  break;

      case WM_DESTROY:

                  PostQuitMessage(0);

                  return 0;

 

      }

 

      return DefWindowProc(hWnd, message, wParam, lParam);

}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hpre, LPSTR cmd, int cc)

{

      //HINSTANCE hInstance = 0;

      // create dialog

      WNDCLASSEX wcex;

      wcex.cbSize                             = sizeof(WNDCLASSEX);

      wcex.style                                = CS_HREDRAW | CS_VREDRAW;

      wcex.lpfnWndProc      = (WNDPROC)CustomWndProc;

      wcex.cbClsExtra                     = 0;

      wcex.cbWndExtra                   = DLGWINDOWEXTRA;

      wcex.hInstance                        = hInstance;

      wcex.hIcon                              = NULL;

      wcex.hCursor              = LoadCursor(NULL, IDC_ARROW);

      wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW);

      wcex.lpszMenuName   = 0;

      wcex.lpszClassName = TEXT(Win32ClassName);

      wcex.hIconSm             = 0;

 

      RegisterClassEx(&wcex);

 

      DWORD style = WS_SYSMENU | WS_BORDER | WS_CAPTION |

                  WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SIZEBOX;

 

      int windowWidth = 440;

      int windowHeight = 380;

 

      hWnd = CreateWindow( TEXT(Win32ClassName), TEXT("Irrlicht Win32 window example"),

                  style, 100, 100, windowWidth, windowHeight,

                  NULL, NULL, hInstance, NULL);

 

      RECT clientRect;

      GetClientRect(hWnd, &clientRect);

      windowWidth = clientRect.right;

      windowHeight = clientRect.bottom;

 

      // create ok button

 

      hOKButton = CreateWindow(TEXT("BUTTON"),TEXT("OK - Close"), WS_CHILD | WS_VISIBLE | BS_TEXT,

                  windowWidth - 160, windowHeight - 40, 150, 30, hWnd, NULL, hInstance, NULL);

 

      // create some text

 

      CreateWindow(TEXT("STATIC"), TEXT("This is Irrlicht running inside a standard Win32 window.\n Also mixing with MFC and .NET Windows.Forms is possible."),

                  WS_CHILD | WS_VISIBLE, 20, 20, 400, 40, hWnd, NULL, hInstance, NULL);

 

      // create window to put irrlicht in

 

      HWND hIrrlichtWindow = CreateWindow(TEXT("BUTTON"), TEXT(""),

                              WS_CHILD | WS_VISIBLE | BS_OWNERDRAW,

                              50, 80, 320, 220, hWnd, NULL, hInstance, NULL);

 

      /*

      So now that we have some window, we can create an Irrlicht device

      inside of it. We use Irrlicht createEx() function for this. We only

      need the handle (HWND) to that window, set it as windowsID parameter

      and start up the engine as usual. That's it.

      */

      // create irrlicht device in the button window

 

      irr::SIrrlichtCreationParameters param;

      param.WindowId = reinterpret_cast(hIrrlichtWindow); // hColorButton

      param.DriverType = video::EDT_DIRECT3D9;

 

      irr::IrrlichtDevice* device = irr::createDeviceEx(param);

 

      // setup a simple 3d scene

 

      irr::scene::ISceneManager* smgr = device->getSceneManager();

      video::IVideoDriver* driver = device->getVideoDriver();

 

      scene::ICameraSceneNode* cam = smgr->addCameraSceneNode();

      cam->setTarget(core::vector3df(0,0,0));

 

      scene::ISceneNodeAnimator* anim =

                  smgr->createFlyCircleAnimator(core::vector3df(0,15,0), 30.0f);

      cam->addAnimator(anim);

      anim->drop();

 

      scene::ISceneNode* cube = smgr->addCubeSceneNode(20);

 

      cube->setMaterialTexture(0, driver->getTexture("../../Irrlicht-1.7.2/media/wall.bmp"));

      cube->setMaterialTexture(1, driver->getTexture("../../Irrlicht-1.7.2/media/water.jpg"));

      cube->setMaterialFlag( video::EMF_LIGHTING, false );

      cube->setMaterialType( video::EMT_REFLECTION_2_LAYER );

 

      smgr->addSkyBoxSceneNode(

      driver->getTexture("../../Irrlicht-1.7.2/media/irrlicht2_up.jpg"),

      driver->getTexture("../../Irrlicht-1.7.2/media/irrlicht2_dn.jpg"),

      driver->getTexture("../../Irrlicht-1.7.2/media/irrlicht2_lf.jpg"),

      driver->getTexture("../../Irrlicht-1.7.2/media/irrlicht2_rt.jpg"),

      driver->getTexture("../../Irrlicht-1.7.2/media/irrlicht2_ft.jpg"),

      driver->getTexture("../../Irrlicht-1.7.2/media/irrlicht2_bk.jpg"));

 

      // show and execute dialog

 

      ShowWindow(hWnd , SW_SHOW);

      UpdateWindow(hWnd);

      /*

      while (device->run())

      {

                  driver->beginScene(true, true, 0);

                  smgr->drawAll();

                  driver->endScene();

      }

      */

      MSG msg;

      while (true)

      {

                  if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))

                  {

                              TranslateMessage(&msg);

                              DispatchMessage(&msg);

 

                              if (msg.message == WM_QUIT)

                                          break;

                  }

 

                  // advance virtual time

                  device->getTimer()->tick();

 

                  // draw engine picture

                  driver->beginScene(true, true, 0);

                  smgr->drawAll();

                  driver->endScene();

      }

      device->closeDevice();

      device->drop();

 

}

#endif // if windows

//Win32 Irrlicht chạy trên cửa sổ chính :

#include

#ifndef _IRR_WINDOWS_

#error Windows only example

#endif // if windows

#include // this example only runs with windows

using namespace irr;

using namespace core;

using namespace scene;

using namespace video;

using namespace io;

using namespace gui;

 

#pragma comment(lib, "irrlicht.lib")

#define Win32ClassName "CIrrlichtWindowsTestDialog"

HWND hWnd;

IrrlichtDevice* device;

 

static LRESULT CALLBACK CustomWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)

{

      switch (message)

      {

                  case WM_DESTROY:

                  PostQuitMessage(0);

                  return 0;

      }

      return DefWindowProc(hWnd, message, wParam, lParam);

}

 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hpre, LPSTR cmd, int cc)

{

      // create dialog

     

 

      WNDCLASSEX wcex;

      wcex.cbSize                             = sizeof(WNDCLASSEX);

      wcex.style                                = CS_HREDRAW | CS_VREDRAW;

      wcex.lpfnWndProc      = (WNDPROC)CustomWndProc;

      wcex.cbClsExtra                     = 0;

      wcex.cbWndExtra                   = DLGWINDOWEXTRA;

      wcex.hInstance                        = hInstance;

      wcex.hIcon                              = NULL;

      wcex.hCursor              = LoadCursor(NULL, IDC_ARROW);

      wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW);

      wcex.lpszMenuName   = 0;

      wcex.lpszClassName   = TEXT(Win32ClassName);

      wcex.hIconSm             = 0;

 

      RegisterClassEx(&wcex);

 

      DWORD style = WS_SYSMENU | WS_BORDER | WS_CAPTION |

                  WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SIZEBOX;

 

      int windowWidth = 640;

      int windowHeight = 480;

 

      hWnd = CreateWindow( TEXT(Win32ClassName),TEXT("Irrlicht Win32 window example"),

                  style, 0, 0, windowWidth, windowHeight,

                  NULL, NULL, hInstance, NULL);

 

      RECT clientRect;

      GetClientRect(hWnd, &clientRect);

      windowWidth = clientRect.right;

      windowHeight = clientRect.bottom;

 

      // create irrlicht device in the button window

      irr::SIrrlichtCreationParameters param;

      param.WindowId = reinterpret_cast(hWnd); // hColorButton

      param.DriverType = video::EDT_OPENGL;

 

      device = irr::createDeviceEx(param);

 

      // setup a simple 3d scene

     

      irr::scene::ISceneManager* smgr = device->getSceneManager();

      video::IVideoDriver* driver = device->getVideoDriver();

 

      scene::ICameraSceneNode* cam = smgr->addCameraSceneNode();

      cam->setTarget(core::vector3df(0,0,0));

 

      scene::ISceneNodeAnimator* anim =

                  smgr->createFlyCircleAnimator(core::vector3df(0,15,0), 30.0f);

      cam->addAnimator(anim);

      anim->drop();

 

      scene::ISceneNode* cube = smgr->addCubeSceneNode(20);

 

      cube->setMaterialTexture(0, driver->getTexture("../../Irrlicht-1.7.2/media/wall.bmp"));

      cube->setMaterialTexture(1, driver->getTexture("../../Irrlicht-1.7.2/media/water.jpg"));

      cube->setMaterialFlag( video::EMF_LIGHTING, false );

      cube->setMaterialType( video::EMT_REFLECTION_2_LAYER );

 

      smgr->addSkyBoxSceneNode(

      driver->getTexture("../../Irrlicht-1.7.2/media/irrlicht2_up.jpg"),

      driver->getTexture("../../Irrlicht-1.7.2/media/irrlicht2_dn.jpg"),

      driver->getTexture("../../Irrlicht-1.7.2/media/irrlicht2_lf.jpg"),

      driver->getTexture("../../Irrlicht-1.7.2/media/irrlicht2_rt.jpg"),

      driver->getTexture("../../Irrlicht-1.7.2/media/irrlicht2_ft.jpg"),

      driver->getTexture("../../Irrlicht-1.7.2/media/irrlicht2_bk.jpg"));

      // show and execute dialog

      ShowWindow(hWnd , SW_SHOW);

      UpdateWindow(hWnd);

      // do message queue

      while (device->run())

      {

                  driver->beginScene(true, true, 0);

                  smgr->drawAll();

                  driver->endScene();

      }

      device->closeDevice();

      device->drop();

      return 0;

}

 

15)  Load Scene from .Irr file :



Lưu và nạp toàn bộ Scene thành tệp tin .Irr hay .xml cũng vậy thôi (do thằng .irr hoàn toàn là thằng .xml thôi)

Những cái mình cần quan tâm là :

 if (argc>1) //nếu chương trình có tham số khi chạy thì nạp tham số đó

      smgr->loadScene(argv[1]);

else //còn không thì nạp Scene mặc định

      smgr->loadScene("../../media/example.irr");

Kiểm tra va chạm cho nhều đối tượng ta phải dùng meta triangle selector chứ không dùng đơn giản như trước (có thể kiểm tra va chạm cho nhiều loại kiểm tra khác nhau)

scene::IMetaTriangleSelector * meta = smgr->createMetaTriangleSelector();

Thủ thuật sau đây rất hay đây (lấy mảng các con trỏ của toàn bộ SceneNode trong khung cảnh) :

core::array nodes;

smgr->getSceneNodesFromType(scene::ESNT_ANY, nodes); //toàn bộ SceneNode

Sau đó ta duyệt qua từng SceneNode cụ thể, với từng loại ta áp dụng từng loại kiểm tra va chạm khác nhau, sau đó từng loại đó được thêm vào danh sách meta triangle selector và sau khi đã thêm vào, không cần nữa thì ta drop ngay đối tượng Selector đó : xem dòng code :

for (u32 i=0; i < nodes.size(); ++i)

{

scene::ISceneNode * node = nodes[i];

      scene::ITriangleSelector * selector = 0;

switch(node->getType())

      {

      case scene::ESNT_CUBE:

case scene::ESNT_ANIMATED_MESH:

      selector = smgr->createTriangleSelectorFromBoundingBox(node);

      break;

      case scene::ESNT_MESH:

      case scene::ESNT_SPHERE:

selector = smgr->createTriangleSelector(((scene::IMeshSceneNode*)node)->getMesh(), node);

      break;

case scene::ESNT_TERRAIN:

      selector = smgr->createTerrainTriangleSelector((scene::ITerrainSceneNode*)node);

      break;

case scene::ESNT_OCTREE:

selector = smgr->createOctreeTriangleSelector(((scene::IMeshSceneNode*)node)->getMesh(), node);

break;

default://những cái còn lại không tạo kiểm tra va chạm

break;

} //hết câu lệnh switch

if(selector)

{

meta->addTriangleSelector(selector);

      selector->drop();

}

}//hết vòng lặp for

Tham số ta cần chú ý khi đưa kiểm tra va chạm tự động là : meta (quản lý toàn bộ các kiểm tra va chạm mà ta vừa thêm vào) chứ không phải loại kiểm tra đơn

scene::ISceneNodeAnimator* anim = smgr->createCollisionResponseAnimator(

 meta, camera, core::vector3df(5,5,5),core::vector3df(0,0,0));

Lấy ngay một SceneNode đầu tiênkhi biết loại của nó qua hàm :

getSceneNodeFromType()

 


16)  Quake3Map Shader support :



Nhớ kỹ các hằng số khai bằng #define ở đầu chương trình, cũng như các chỉ hướng biên dịch

Class tạo ScreenShot cho chương trình (có thể lấy cái này làm mẫu cũng OK lắm). Nó sẽ lấy tên tệp tin (nếu là đường dẫn thì thay / hay \\ bằng khoảng trắng cộng thêm chữ shot và số thứ tự của screenshot (4 số))

Sau đây là toàn bộ class đó (các bạn tự đọc hiểu nghe):

class CScreenShotFactory : public IEventReceiver

{

public:

      CScreenShotFactory( IrrlichtDevice *device, const c8 * templateName, ISceneNode* node )

                  : Device(device), Number(0), FilenameTemplate(templateName), Node(node)

      {

                  FilenameTemplate.replace ( '/', '_' );

                  FilenameTemplate.replace ( '\\', '_' );

      }

      bool OnEvent(const SEvent& event)

      {

                  // check if user presses the key F9

                  if ((event.EventType == EET_KEY_INPUT_EVENT) &&

                                          event.KeyInput.PressedDown)

                  {

                              if (event.KeyInput.Key == KEY_F9)

                              {

                                          video::IImage* image = Device->getVideoDriver()->createScreenShot();

                                          if (image)

                                          {

                                                      c8 buf[256];

                                                      snprintf(buf, 256, "%s_shot%04d.jpg",

                                                                              FilenameTemplate.c_str(),

                                                                              ++Number);

                                                      Device->getVideoDriver()->writeImageToFile(image, buf, 85 );

                                                      image->drop();

                                          }

                              }

                              else

                              if (event.KeyInput.Key == KEY_F8)

                              {

                                          if (Node->isDebugDataVisible())

                                                      Node->setDebugDataVisible(scene::EDS_OFF);

                                          else

                                                      Node->setDebugDataVisible(scene::EDS_BBOX_ALL);

                              }

                  }

                  return false;

      }

 

private:

      IrrlichtDevice *Device;

      u32 Number;

      core::stringc FilenameTemplate;

      ISceneNode* Node;

};

Chuyển thư mục làm việc sang thư mục media nên thay bằng changeWorkingDirectoryTo()

device->getFileSystem()->addFolderFileArchive("../../media/");

Nếu có tham số chương trình sẽ nạp Quake3Map từ tham số đó, nếu không thì sẽ nạp tự động.

QUAKE3_STORAGE_FORMAT chính là addZipFileArchive . Chú ý coi chừng nhầm

Cho phép Quake3 shader truy cập bộ đệm độ sâu Zbuffer

smgr->getParameters()->setAttribute(scene::ALLOW_ZWRITE_ON_TRANSPARENT, true);

Nạp mesh là Quake3Mesh (dùng type casting)

scene::IQ3LevelMesh* const mesh =

                  (scene::IQ3LevelMesh*) smgr->getMesh(mapname);

Dùng OcttreeSceneNode để tối ưu tốc độ dựng hình

scene::IMesh * const geometry = mesh->getMesh(quake3::E_Q3_MESH_GEOMETRY);

                  node = smgr->addOctreeSceneNode(geometry, 0, -1, 4096);

Nạp thêm các Item từ Quake3Map (nếu có, có thể không được tối ưu)

const scene::IMesh * const additional_mesh = mesh->getMesh(quake3::E_Q3_MESH_ITEMS);

Sau đây là đoạn code nạp Shader của Quake3

for ( u32 i = 0; i!= additional_mesh->getMeshBufferCount(); ++i )

{

const IMeshBuffer* meshBuffer = additional_mesh->getMeshBuffer(i);

      const video::SMaterial& material = meshBuffer->getMaterial();

      // Chỉ mục shader được lưu trong tham  số của chất liệu

const s32 shaderIndex = (s32) material.MaterialTypeParam2;

//meshbuffer có thể được dựng hình không cần hổ trợ phụ thêm, hay là nó không có shader

      const quake3::IShader *shader = mesh->getShader(shaderIndex);

      if (0 == shader)

      {

                  continue; //lổi – tiếp tục

      }

node = smgr->addQuake3SceneNode(meshBuffer, shader);

}

Tìm vị trí thích hợp để đặt camera (vị trí lưu của nhân vật)

Xem đoạn code sau :

quake3::tQ3EntityList &entityList = mesh->getEntityList();

quake3::IEntity search;

search.name = "info_player_deathmatch";

s32 index = entityList.binary_search(search);

if (index >= 0)

{

      s32 notEndList;

      do

      {

                  const quake3::SVarGroup *group = entityList[index].getGroup(1);

                  u32 parsepos = 0;

                  const core::vector3df pos =

                  quake3::getAsVector3df(group->get("origin"), parsepos);

                  parsepos = 0;

                  const f32 angle = quake3::getAsFloat(group->get("angle"), parsepos);

                  core::vector3df target(0.f, 0.f, 1.f);

                  target.rotateXZBy(angle);

                  camera->setPosition(pos);

                  camera->setTarget(pos + target);

                  ++index;

                  notEndList = index == 2;

      } while ( notEndList );

}

Hiện các tham số render của SceneManager để tiện theo dõi quá trình :

str += " Cull:";

str += attr->getAttributeAsInt("calls");

str += "/";

str += attr->getAttributeAsInt("culled");

str += " Draw: ";

str += attr->getAttributeAsInt("drawn_solid");

str += "/";

str += attr->getAttributeAsInt("drawn_transparent");

str += "/";

str += attr->getAttributeAsInt("drawn_transparent_effect");

Mình thật sự cũng chưa nắm hết các phần này, phải xem lại thêm mới được.


17)  Hello World mobile :



(Phần này không dành cho game trên Window nên mình bỏ qua)

 

18)  Split Screen :



Chia màn hình ra làm nhiều màn hình con (như trong game đua xe, hay các chương trình 3D) – phần này không khó lắm

Khai báo các biến toàn cục – mình có thể lấy các giá trị này lúc chạy chương trình cũng được bằng hàm - getScreenSize()

const int ResX=800;

const int ResY=600;

const bool fullScreen=false;

bool SplitScreen=true;

//khai báo dùng 4 camera – camera thứ 4 là loại FPS

Phần tạo class MyEventReceiver rất đơn giản, không bàn đến đây (chú ý là các sự kiện đều gởi đến cái CameraFPS – do chỉ có mình nó có nhận tác động phím, còn các camera khác không có)

Phần khởi tạo ta cũng khởi tạo 3 camera đầu là 3 camera bình thường, còn camera thứ 4 là FPS, xem đoạn code :

//Front

camera[0] = smgr->addCameraSceneNode(0, vector3df(50,0,0), vector3df(0,0,0));

//Top

camera[1] = smgr->addCameraSceneNode(0, vector3df(0,50,0), vector3df(0,0,0));

//Left

camera[2] = smgr->addCameraSceneNode(0, vector3df(0,0,50), vector3df(0,0,0));

//FPS camera

camera[3] = smgr->addCameraSceneNodeFPS();

//Di chuyển nó đi để có thể nhìn thấy mô hình cần render

if (camera[3]) camera[3]->setPosition(core::vector3df(-50,0,-50));

Nguyên tắc làm việc theo kiểu chia màn hình con như sau (thật ra là render nhiều lần, mỗi lần vào một Viewport yêu cầu):

-          Đặt Viewport cho toàn màn hình

-          BeginScene (new scene – clear screen)

-          Với mỗi Viewport ta làm như sau :

o       Đặt ViewPort ở vùng cần render

o       Kích hoạt camera liên kết với vùng viewport đó

o       Render tất cả các đối tượng mà camera nhình thấy

-          Nếu có sài GUI thì phải :

o       Đặt viewport cho toàn màn hình (vì GUI tính trên toàn màn hình)

o       Vẽ GUI

Thế cũng không quá khó hé các bạn : sau đây là code:

driver->setViewPort(rect(0,0,ResX,ResY));

driver->beginScene(true,true,SColor(255,100,100,100));

if (SplitScreen)

{

      smgr->setActiveCamera(camera[0]);//front camera

      driver->setViewPort(rect(0,0,ResX/2,ResY/2));

      smgr->drawAll();

      smgr->setActiveCamera(camera[1]);//top camera

      driver->setViewPort(rect(ResX/2,0,ResX,ResY/2));

      smgr->drawAll();

      smgr->setActiveCamera(camera[2]);//left camera

      driver->setViewPort(rect(0,ResY/2,ResX/2,ResY));

      smgr->drawAll();

      driver->setViewPort(rect(ResX/2,ResY/2,ResX,ResY));//để dành cho cameraFPS nếu dùng slitScreen

}

smgr->setActiveCamera(camera[3]);

smgr->drawAll();

driver->endScene();

Bạn có quan sát là Camera[3] không được đặt trong vòng lặp vì : nó là camera chính khi bạn không sài slit Screen thì nó sẽ render toàn bộ màn hình, và khi bạn dùng slit screen thì nó chỉ vẽ màn hình bên phải dưới cùng (cách này khá hay)

 


19)  Mouse and Joystick :



Làm quen với điều khiển dùng Joystick và Mouse

Class MyEventReceiver kế thừa EventReceiver này cũng không khó hiểu lắm, mình sẽ cho qua không bình luận gì thêm. Lưu ý là có thêm phần khai báo cấu trúc nhận sự kiện Mouse và biến nội tại chứa sự kiện của JoyStick. Có 02 hàm nhận sự kiện và 02 hàm lấy trạng thái của Mouse và JoySitck.

Cấu trúc chứa thông tin của JoyStick :

core::array joystickInfo;

Kiểm tra xem có bao nhiêu JoyStick đang gắn vào PC, và phát sinh sự kiện cho nó. Xem đoạn code hiển thị thông tin sơ bộ của các JoyStick hiện có :

if(device->activateJoysticks(joystickInfo))

{

std::cout << "Joystick support is enabled and " << joystickInfo.size() << " joystick(s) are present." << std::endl;

for(u32 joystick = 0; joystick < joystickInfo.size(); ++joystick)

      {

                  std::cout << "Joystick " << joystick << ":" << std::endl;

                  std::cout << "\tName: '" << joystickInfo[joystick].Name.c_str() << "'" << std::endl;

                  std::cout << "\tAxes: " << joystickInfo[joystick].Axes << std::endl;

                  std::cout << "\tButtons: " << joystickInfo[joystick].Buttons << std::endl;

std::cout << "\tHat is: ";

switch(joystickInfo[joystick].PovHat)

                  {

                  case SJoystickInfo::POV_HAT_PRESENT:

                              std::cout << "present" << std::endl;

                              break;

 

                  case SJoystickInfo::POV_HAT_ABSENT:

                              std::cout << "absent" << std::endl;

                              break;

                  case SJoystickInfo::POV_HAT_UNKNOWN:

                  default:

                              std::cout << "unknown" << std::endl;

                              break;

                        }

                }

        }

        else

        {

            std::cout << "Joystick support is not enabled." << std::endl;

        }

Tạo một ScreenNode có Mesh là một mũi tên (cái này có sẳn trong Irrlicht)

scene::ISceneNode * node = smgr->addMeshSceneNode(

smgr->addArrowMesh( "Arrow",video::SColor(255, 255, 0, 0),     video::SColor(255, 0, 255, 0),16,16, 2.f, 1.3f, 0.1f, 0.6f));

Phần JoyStick mình cũng không thể kiểm tra (do không có JoyStick) nhưng đọc cũng không quá khó hiểu

Phần Mouse thì chỉ có dùng hàm getRayFromScreenCoordinates() trã về tia dò 3D thông qua vị trí 2D (như vị trí Mouse)

Sau đó lập cái mặt phẳng, và tìm giao điểm của tia quét với mặt phẳng cho ra vị trí 3D của Mouse trong không gian, vị trí đó sẽ là vị trí mà mũi tên đi đến. Mình sẽ chú thích cụ thể trong đoạn code :

core::vector3df toMousePosition(mousePosition - nodePosition);//vị trí hướng đến mouse bằng vector vị trí mouse trừ đi vị trí Node mũi tên)

const f32 availableMovement = MOVEMENT_SPEED * frameDeltaTime;

if(toMousePosition.getLength() <= availableMovement)//khã năng di chuyển, nhỏ hơn số này có thể xem là đã di chuyển đến đích rồi

nodePosition = mousePosition; // Đến đích rồi

else

nodePosition += toMousePosition.normalize() * availableMovement; // Lấy vector đơn vị của vector toMousePosition nhân với khã năng di chuyển

* Nếu Camera cũng dùng theo cách này và có thêm tham số kiểm soát góc quay, hay là đích ngắm thì ta sẽ có camera truy tìm theo đích

 

20)  Managed Lights :



Hướng dẫn  này khá mới nên mình sẽ dịch và bình luận chung

Hướng dẫn này được viết bởi Colin MacDonald. Hướng dẫn này giải thích cách dùng của trình quản lý các nguồn sáng trong Irrlicht. Nó cho phép dùng nhiều nguồn sáng động hơn là khã năng hỗ trợ thật sự của phần cứng.Các ứng dụng thêm của trình quản lý nguồn sáng, như các callback của SceneNode, không còn là một ví dụ đơn giản nữa.

#include

#include "driverChoice.h"

 

using namespace irr;

using namespace core;

 

#if defined(_MSC_VER)

#pragma comment(lib, "Irrlicht.lib")

#endif // MSC_VER

Thông tường bạn có giới hạn 8 nguồn sáng trong một cảnh : đó là giới hạn của phần cứng. Nếu bạn muốn có nhiều hơn nguồn sáng trong một cảnh của bạn. Bạn  có thể đăng ký lựa chọn trình quản lý nguồn sáng mà nó cho phép bạn bật hay tắt các điểm đặc biệt trong quá trình dựng hình. Bạn vẫn bị giới hạn 8 nguồn sáng, nhưng lúc này là giới hạn trong một SceneNode chứ không phải toàn Scene như trước nữa.

Điều này hoàn toàn là một lựa chọn : nếu bạn không đăng ký trình quản lý nguồn sáng. Khi đó lược đồ dựa trên khoảng cách cơ bản mặc định sẽ dựa trên độ ưu tiên của nguồn sáng so với camera đang hoạt động.

-          NO_MANAGEMENT : sẽ tắt LightManager và chuyển sang chế độ mặc định của Irrlicht. 8 nguồn sáng gần camera nhất sẽ được bật, còn các nguồn sáng khác sẽ tắt. Trong ví dụ này nó tạo ra cái nhìn hiện đại nhưng ánh sáng hiển thị không liên tục.

-          LIGHTS_NEAREST_NODE: trình bày cách cài đặt để bật một số giới hạn nguồn sáng trên một node. Nếu tìm thấy 3 nguồn sáng gần với Node đang được dựng hình nhất thì 3 nguồn sáng đó sẽ được bật, và các nguồn sáng khác sẽ tắt. Công việc này, nhưng vì nó vận hành trên mỗi nguồn sáng cho mỗi Node, nó không có độ co giản tốt khi có nhiều nguồn sáng. Ánh sáng bập bùng bạn có thể thấy trong Demo này dính đến khã năng chuyển đổi của nguồn sáng dựa vào vị trí tương đối của nó so với các khối hộp (một mô tã có chủ ý cho kỹ thuật này)

-          LIGHTS_IN_ZONE : trình bày kỹ thuật bật các nguồn sáng dựa trên “vùng”. Mỗi Empty SceneNode được coi là cha (parent) của một vùng. Khi các Node được dựng hình. Nó sẽ tắt toàn bộ các nguồn sáng, sau đó nó sẽ tìm cha của « vùng » đó bật tất cả các nguồn sáng nằm trong vùng đó (dĩ nhiên là tối đa là 8) như là sự truy tìm ngược của nó trong cấu trúc đồ họa của Scene. Điều này sản sinh ra sự chiếu sáng nội tại thật cho từng SceneNode trong ví dụ này. Bạn có thể dùng kỹ thuật tương tự để làm cục bộ các nguồn sáng trong toàn bộ Mesh (như trong) một cái phòng và không chia sẽ nguồn sáng này cho các phòng khác.

LightManager còn có một bộ nhận sự kiện. Nó chỉ đơn giản dùng trong ví dụ này chứ không thực sự được khuyến cáo dùng trong ứng dụng thực sự. (cái này tùy ứng dụng mà mình sẽ viết khác nhau)

class CMyLightManager : public scene::ILightManager, public IEventReceiver

{

//khai báo các hằng nội tại trong class

     typedef enum

     {

      NO_MANAGEMENT, //không dùng lightmanager

      LIGHTS_NEAREST_NODE,//chỉ dùng 3 light gần nhất

      LIGHTS_IN_ZONE //dùng theo kiểu Zone

     }

     LightManagementMode;

     LightManagementMode Mode; //kiểu hiện tại của lightmanager

     LightManagementMode RequestedMode; //kiểu yêu cầu

// những dữ liệu này thay mặt cho thông tin trạng thái của trình quản lý nguồn sáng này là rất thú vị

   scene::ISceneManager * SceneManager;// quản lý các SceneNode

   core::array * SceneLightList;

   scene::E_SCENE_NODE_RENDER_PASS CurrentRenderPass;//đang render tới loại node nào

   scene::ISceneNode * CurrentSceneNode; //node hiện tại

 

public:

CMyLightManager(scene::ISceneManager* sceneManager): Mode(NO_MANAGEMENT), RequestedMode(NO_MANAGEMENT),

SceneManager(sceneManager),  SceneLightList(0),

CurrentRenderPass(scene::ESNRP_NONE), CurrentSceneNode(0){} //hàm dựng

virtual ~CMyLightManager(void) { } //hàm hủy

//giao tiếp nhập của phần nhận sự kiện, mà nó chỉ hoán chuyển chiến lược của trình quản lý nguồn sáng

bool OnEvent(const SEvent & event)

{

  bool handled = false;

  if (event.EventType == irr::EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)

  {

    handled = true;

    switch(event.KeyInput.Key)

    {

      case irr::KEY_KEY_1:

      RequestedMode = NO_MANAGEMENT;

      break;

      case irr::KEY_KEY_2:

      RequestedMode = LIGHTS_NEAREST_NODE;

      break;

      case irr::KEY_KEY_3:

      RequestedMode = LIGHTS_IN_ZONE;

      break;

      default: //phím khác

      handled = false;

      break;

    }

    if(NO_MANAGEMENT == RequestedMode)

      SceneManager->setLightManager(0); // chuyển về trạng thái mặc định của Irrlicht (8 light gần camera nhất sẽ ON còn lại OFF)

    else

      SceneManager->setLightManager(this); //đăng ký LightManager

  }

  return handled; // chỉ trã về true khi là phím 1,2,3 được nhấn

}

// Hàm này được gọi khi SceneNode đầu tiên được dựng hình.

virtual void OnPreRender(core::array & lightList)

{

// Cập nhật kiểu;thay đổi nó ở đây để chắc rằng duy nhất trong suốt quá trình dựng hình

  Mode = RequestedMode;

// Lưu trữ danh sách nguồn sáng. Tôi tự do thao tác với danh sách này cho đến khi hết hàm OnPostRender().

  SceneLightList = &lightList;

}

// Hàm này được gọi sau khi SceneNode cuối cùng được dựng hình        

virtual void OnPostRender()

{

 //Khi trình quản lý nguồn sáng có khã năng tắt bằng điều khiển sự kiện của nó, chúng ta sẽ bật toàn bộ nguồn sáng để chắc rằng chúng cùng một trạng thái. Bạn sẽ không phải thường xuyên làm điều này khi dùng light manager, Sau đó bạn chỉ cần tự mình làm việc tiếp tục với light management.

  for(u32 i = 0; i < SceneLightList->size(); i++)

   (*SceneLightList)[i]->setVisible(true);

}

 

//Hàm này được gọi khi quá trình dựng hình Node bắt đầu

virtual void OnRenderPassPreRender(scene::E_SCENE_NODE_RENDER_PASS renderPass)

{

  // Chỉ cần nhớ dựng hình đã qua

  CurrentRenderPass = renderPass;

}

//hàm này được gọi sau khi quá trình chỉ địnhdựng hình trong OnRenderPassPreRender()kết thúc

virtual void OnRenderPassPostRender(scene::E_SCENE_NODE_RENDER_PASS renderPass)

{

// Chỉ có các node tô đầy được quan tâm, Vì thế sau khi dựng hình tô đầy xong, tắt hết các nguồn sáng

  if(scene::ESNRP_SOLID == renderPass)

  {

for(u32 i = 0; i < SceneLightList->size(); ++i)  (*SceneLightList)[i]->setVisible(false);

  }

}

// Hàm này được gọi khi node chỉ địnhđược dựng hình

virtual void OnNodePreRender(scene::ISceneNode* node)

{

  CurrentSceneNode = node;

//Trình quản lý nguồn sáng này chỉ làm việc với các đối tượng được tô đầy, Nhưng bạn tự do vận dụng các nguồn sáng cho mục đích khác, tùy theo yêu cần của bạn

  if (scene::ESNRP_SOLID != CurrentRenderPass) //nếu đang dựng hình đối tượng khác lại SOLID thì thoát

  return;

// Và trong ví dụ này chỉ quan tâm đến các hình hộp.Bạn có thể làm như thế cho ít nhất là chiếu sáng cho IAnimatedMeshSceneNode cũng tốt vậy thôi.

  if (node->getType() != scene::ESNT_CUBE) return;

  if (LIGHTS_NEAREST_NODE == Mode)

  {

  // Cái đặt đơn giản này dùng độ ưu tiên về khoảng cách của các nguồn sáng trong cảnh với Node đang được dựng hình.Điều này thỉnh thoảng làm cho nguồn sáng bị chập chờn khi các nguồn sáng xoay quanh khối hộp hơn là dùng kỹ thuật nguồn sáng theo 'zone'.

  const vector3df nodePosition = node->getAbsolutePosition();

  // Sắp xếp danh sách nguồn sáng dựa trên độ ưu tiên là khoảng cách của nó với Node đang được dựng hình

//Đây là kỹ thuật sắp xếp của Irrlicht mà chúng ta cần quan tâm – sẽ dùng rất nhiều sao này trong các ứng dụng

  array sortingArray;

  sortingArray.reallocate(SceneLightList->size()); //bằng kích thước với danh sách nguồn sáng

  u32 i;

  for(i = 0; i < SceneLightList->size(); ++i)

  {

    scene::ILightSceneNode* lightNode = (*SceneLightList)[i];

    f64 distance = lightNode->getAbsolutePosition().getDistanceFromSQ(nodePosition);//lấy khoảng cách nguồn sáng đến Node

    sortingArray.push_back(LightDistanceElement(lightNode, distance)); //thêm vào trong danh sách sắp xếp – chú ý chổ này dùng class con có toán tử so sánh nhỏ hơn “<”

  }

  sortingArray.sort(); //sort bằng Heap – cái này là mặc định của Irrlicht

// Bây giờ danh sách nguồn sáng đã được sắp xếp theo độ ưu tiên so với Node.

// Bật 3 nguồn sáng gần Node nhất các cái khác thì tắt.

  for(i = 0; i < sortingArray.size(); ++i)

  sortingArray[i].node->setVisible(i < 3);

 }

 else if(LIGHTS_IN_ZONE == Mode)

 {

// Các SceneNode trống dùng để thay mặc cho ‘zones'. Với mỗi node tô đầy được render, tắt toàn bộ nguồn sáng, sau đó tìm cha của các ‘zone', và bật toàn bộ các nguồn sáng là thuộc cấu trúc đồ thị của Zone đó. Đây chỉ dùng thuật toán tổng quát mà không dùng kỹ thuật đặc biệt nào để quản lý cấu đồ thị của Scene.

  for (u32 i = 0; i < SceneLightList->size(); ++i)

  {

    scene::ILightSceneNode* lightNode = (*SceneLightList)[i];

    video::SLight & lightData = lightNode->getLightData();

    if (video::ELT_DIRECTIONAL != lightData.Type)

       lightNode->setVisible(false);

   }

  scene::ISceneNode * parentZone = findZone(node);

  if (parentZone)

    turnOnZoneLights(parentZone);

  }

} //hết hàm

// Hàm này được gọi sau khi Node chỉ địnhđược dựng hình       

virtual void OnNodePostRender(scene::ISceneNode* node)

{

 //Không cần bất cứ trình quản lý nguồn sáng nào sau khi cá nhân Node được dựng hình

}

private:

// Tìm toàn bộ Empty SceneNode là cha của SceneNode chỉ định

scene::ISceneNode * findZone(scene::ISceneNode * node)

{

  if(!node)return 0;

  if(node->getType() == scene::ESNT_EMPTY)return node;

   return findZone(node->getParent());//gọi đệ quy đến cha của Node hiện tại

 }

 // Bật tất cả các nguồn sáng là con (trực tiếp và gián tiếp) của Node chỉ định.

void turnOnZoneLights(scene::ISceneNode * node)

{

   core::list const & children = node->getChildren();

   for (core::list::ConstIterator child = children.begin(); child != children.end();++child)

   {

       if((*child)->getType() == scene::ESNT_LIGHT)

          static_cast(*child)->setVisible(true);

       else // Đảm bảo rằng nguồn sáng không có các con cũng là nguồn sáng

     turnOnZoneLights(*child);//duyệt đệ quy

     }

} //hết hàm

// Một class tiện ích để hổ trợ cho việc sắp xếp SceneNode vựa vào khoảng cách với cái khác

class LightDistanceElement

{

  public:

    LightDistanceElement() {}; //hàm dựng mặc định

    LightDistanceElement(scene::ILightSceneNode* n, f64 d)

    : node(n), distance(d) { } //hàm dựng

     scene::ILightSceneNode* node;

     f64 distance;

// Khoảng cách nhỏ hơn phần tử khác được sắp xếp để bắt đầu của mảng

  bool operator < (const LightDistanceElement& other) const

  {

    return (distance < other.distance);

    }

}; //hết class

};//hết class CmyLightManager

//Đến phần hàm main

int main(int argumentCount, char * argumentValues[])

{

        // Hỏi người dùng driver

        video::E_DRIVER_TYPE driverType=driverChoiceConsole();

        if (driverType==video::EDT_COUNT) return 1;

        IrrlichtDevice *device = createDevice(driverType, dimension2d(640, 480), 32);

        if(!device)  return -1; //thoát khi không thành công

        f32 const lightRadius = 60.f; // Đủ để đạt tới khoảng cách xa nhất của mỗi 'zone'

        video::IVideoDriver* driver = device->getVideoDriver();

        scene::ISceneManager* smgr = device->getSceneManager();

        gui::IGUIEnvironment* guienv = device->getGUIEnvironment();

        gui::IGUISkin* skin = guienv->getSkin();

        if (skin)

        {

               skin->setColor(gui::EGDC_BUTTON_TEXT, video::SColor(255, 255, 255, 255));

                gui::IGUIFont* font = guienv->getFont("../../media/fontlucida.png");

                if(font)

                        skin->setFont(font);

        }

        guienv->addStaticText(L"1 - No light management", core::rect(10,10,200,30));

        guienv->addStaticText(L"2 - Closest 3 lights", core::rect(10,30,200,50));

        guienv->addStaticText(L"3 - Lights in zone", core::rect(10,50,200,70));

//Thêm nhiều “Zone”, Bạn có dùng kỹ thuật này để giới hạn nguồn sáng riêng từng phòng như là ví dụ.

for(f32 zoneX = -100.f; zoneX <= 100.f; zoneX += 50.f)

   for(f32 zoneY = -60.f; zoneY <= 60.f; zoneY += 60.f)

   {

      // Bắt đầu với empty SceneNode, dùng dại diện cho một “zone”.

      scene::ISceneNode * zoneRoot = smgr->addEmptySceneNode();

      zoneRoot->setPosition(vector3df(zoneX, zoneY, 0));

     // Mỗi “Zone” chứa một khối hộp tự quay

      scene::IMeshSceneNode * node = smgr->addCubeSceneNode(15, zoneRoot);

      scene::ISceneNodeAnimator * rotation = smgr->createRotationAnimator(vector3df(0.25f, 0.5f, 0.75f));

      node->addAnimator(rotation);

      rotation->drop();

// Mỗi khối hộp có 3 nguồn sáng trực thuộc.

//Các nguồn sáng này trực thuộc với các billboards để chúng ta có thể thấy chúng ở đâu.Các billboard này lại được trực thuộc với khối hộp vì thế các nguồn sáng là con cháu gián tiếp của cùng enmpty SceneNode như là khối hộp

scene::IBillboardSceneNode * billboard = smgr->addBillboardSceneNode(node);

billboard->setPosition(vector3df(0, -14, 30));

billboard->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );

billboard->setMaterialTexture(0, driver->getTexture("../../media/particle.bmp"));

billboard->setMaterialFlag(video::EMF_LIGHTING, false);

scene::ILightSceneNode * light = smgr->addLightSceneNode(billboard, vector3df(0, 0, 0), video::SColorf(1, 0, 0), lightRadius);

 

billboard = smgr->addBillboardSceneNode(node);

billboard->setPosition(vector3df(-21, -14, -21));

billboard->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );

billboard->setMaterialTexture(0, driver->getTexture("../../media/particle.bmp"));

billboard->setMaterialFlag(video::EMF_LIGHTING, false);

light = smgr->addLightSceneNode(billboard, vector3df(0, 0, 0), video::SColorf(0, 1, 0), lightRadius);

 

billboard = smgr->addBillboardSceneNode(node);

billboard->setPosition(vector3df(21, -14, -21));

billboard->setMaterialType(video::EMT_TRANSPARENT_ADD_COLOR );

billboard->setMaterialTexture(0, driver->getTexture("../../media/particle.bmp"));

billboard->setMaterialFlag(video::EMF_LIGHTING, false);

light = smgr->addLightSceneNode(billboard, vector3df(0, 0, 0), video::SColorf(0, 0, 1), lightRadius);

// Mỗi khối hộp có một khối hộp nhỏ hơn quay quanh nó, để biểu diễn  rằng các khối hộp đang được chiếu sáng trong “zone” của chúng, không chỉ có nguồn sáng là ánh sáng con trực tiếp

node = smgr->addCubeSceneNode(5, node);

node->setPosition(vector3df(0, 21, 0));

 } //hết vòng lặp for

smgr->addCameraSceneNode(0, vector3df(0,0,-130), vector3df(0,0,0));

CMyLightManager * myLightManager = new CMyLightManager(smgr);

smgr->setLightManager(0); // Mặc định : chúng ta không cần trình quản lý nguồn sáng đến khi chúng ta gọi nó

device->setEventReceiver(myLightManager);

int lastFps = -1;

while(device->run())

        {

                driver->beginScene(true, true, video::SColor(255,100,101,140));

                smgr->drawAll();

                guienv->drawAll();

                driver->endScene();

 

                int fps = driver->getFPS();

                if(fps != lastFps)

                {

                        lastFps = fps;

                        core::stringw str = L"Managed Lights [";

                        str += driver->getName();

                        str += "] FPS:";

                        str += fps;

                        device->setWindowCaption(str.c_str());

                }

        }

        myLightManager->drop(); // thả tham chiếu ngầm của mình

        device->drop();

        return 0;

}

//Hy vọng là các bạn có thể hiểu nó, cũng không khó lắm phải không ?

21)  Quake3 Explorer : (Đây là ví dụ theo mình là khó nhất – nên mình sẽ dịch làm cái hướng dẫn 23 trước ) – đã xong



 

22)  Material viewer :

 

 

23)  SmeshBufferHandling :



 

Cái đầu tiên cần nghiên cứu là loại định nghĩa trước loại hàm. Đây là một chức năng rất hay của typedef

Định nghĩa hàm làm việc với màu sắc là loại hàm trã về Scolor và có 3 tham số là dạng f32

typedef SColor colour_func(f32 x, f32 y, f32 z);

Tiếp theo là 3 hàm có cùng dạng như thế : đó là grey, yellowwhite

Ta có các định nghĩa cụ thể của các hàm này như sau :

//màu xám

SColor grey(f32, f32, f32 z)

{

      u32 n = (u32)(255.f * z);

      return SColor(255, n, n, n);

}

//màu vàng

SColor yellow(f32 x, f32 y, f32)

{

      return SColor(255, 128 + (u32)(127.f * x), 128 + (u32)(127.f * y), 255);

}

//màu trắng

SColor white(f32, f32, f32)

{

return SColor(255, 255, 255, 255);

}

Định nghĩa hàm để tạo heightMap, và tham số x, và y của nó từ -0.5 đến 0.5 và s là hệ số co giản của heightMap

typedef f32 generate_func(s16 x, s16 y, f32 s);

Các hàm mẫu của nó :

//Hộp trứng

f32 eggbox(s16 x, s16 y, f32 s)

{

      const f32 r = 4.f*sqrtf((f32)(x*x + y*y))/s;

      const f32 z = expf(-r * 2) * (cosf(0.2f * x) + cosf(0.2f * y));

      return 0.25f+0.25f*z;

}

// Hàm sin

f32 moresine(s16 x, s16 y, f32 s)

{

      const f32 xx=0.3f*(f32)x/s;

      const f32 yy=12*y/s;

      const f32 z = sinf(xx*xx+yy)*sinf(xx+yy*yy);

      return 0.25f + 0.25f * z;

}

//hàm mủ (x^2 + y^2) x cos(x^2 + y^2)

f32 justexp(s16 x, s16 y, f32 s)

{

      const f32 xx=6*x/s;

      const f32 yy=6*y/s;

      const f32 z = (xx*xx+yy*yy);

      return 0.3f*z*cosf(xx*yy);

}

Tạo một lớp đơn giản thay thế cho heightMap :

Có 3 biến thành viên riêng là : Width, Height và Scale.

Hàm dựng lúc khởi tạo : s = sqrtf(Width^2 + Height^2)

Hàm generate() có tác dụng tô toàn bộ HeightMap bằng giá trị trã về từ hàm f (là dạng hàm generate_func). Câu lệnh chính của nó chỉ là :

set(i++, calc(f, x, y));

gọi hàm set() và hàm calc()

Định nghĩa hàm calc như sau :

f32 calc(generate_func f, u16 x, u16 y) const

{

    const f32 xx = (f32)x - Width*0.5f;

    const f32 yy = (f32)y - Height*0.5f;

    return f((u16)xx, (u16)yy, s);

}

Và hàm set thì có 2 hàm thành viên giống tên khác tham số :

Theo tọa độ :

void set(u16 x, u16 y, f32 z) { data[y * Width + x] = z; }

theo chỉ mục :

void set(u32 i, f32 z) { data[i] = z; }

Và hàm dùng để lấy độ cao z của HeightMap :

f32 get(u16 x, u16 y) const { return data[y * Width + x]; }

Phần khó nhất trong class này là lấy vector pháp tuyến của HeightMap :

Chính là tích của các vector đường chéo của các điểm liền kề giữa phương ngang và phương đứng.

Ta sẽ phân tích kỹ hàm này :

vector3df getnormal(u16 x, u16 y, f32 s) const

{

    const f32 zc = get(x, y); //zc chứa độ cao ngay tại điểm x,y

    f32 zl, zr, zu, zd;

    if (x == 0)

   {

zr = get(x + 1, y);

      zl = zc + zc - zr; //x = 0 thì zl = zc + zc – zr

   }

   else if (x == Width - 1)

  {

     zl = get(x - 1, y);

     zr = zc + zc - zl; //nếu x = Width-1 thì zr = zc + zc – zl

   }

   else

  {

      zr = get(x + 1, y); // zr độ cao tại (x+1,y)

      zl = get(x - 1, y); //  zl độ cao tại (x-1,y)

  }

 if (y == 0)

{

      zd = get(x, y + 1); //tương tư với zl

      zu = zc + zc - zd;

}

else if (y == Height - 1)

{

      zu = get(x, y - 1); //tương tự với zr

      zd = zc + zc - zu;

}

else

{

      zd = get(x, y + 1); //zd độ cao tại (x,y+1)

      zu = get(x, y - 1);  //zu độ cao tại (x,y-1)

}

// vector đơn vị của vector(s * 2 * (zl – zr), 4, s * 2 * (zd-zu))

return vector3df(s * 2 * (zl - zr), 4, s * 2 * (zd - zu)).normalize();

}

};

Đến cái class sinh Mesh từ HeightMap :

Nó cũng có 3 biến riêng thành viên : Width, Height, Scale.

Và một biến Public : Mesh thuộc con trỏ Smesh

Hàm dựng : chỉ có khởi tạo Mesh : Mesh = new SMesh()

Và hủy : cũng chỉ có một câu lệnh : Mesh->drop() (Biến Mesh không còn tác dụng nữa)

Hàm sinh ra Mesh từ bản đồ heightMap hiện có (đây là hàm rất hay – nó tự tách ra làm nhiều MeshBuffer khi mà độ lớn của Mesh sinh ra lớn)

Chi tiết của hàm này :

void init(const HeightMap &hm, f32 scale, colour_func cf, IVideoDriver *driver)

      {

                  Scale = scale;

 

                  const u32 mp = driver -> getMaximalPrimitiveCount(); //lấy giá trị lớn nhất các đỉnh mà driver của bạn có thể vẽ trong một lần gọi hàm vẽ.

                  Width = hm.width(); //độ rộng

                  Height = hm.height(); //độ cao

                 

                  const u32 sw = mp / (6 * Height); // độ rộng của từng bộ đệm MeshBuffer. Vậy nó chia theo chiều cao, ra tối đa làm 6 phần

                  u32 i=0;

                  for(u32 y0 = 0; y0 < Height; y0 += sw)

                  {

                              u16 y1 = y0 + sw;

                              if (y1 >= Height)

                                          y1 = Height - 1; // cái cuối cùng có thể hẹp hơn

                              addstrip(hm, cf, y0, y1, i);  //sẽ giải thích hàm này sau

                              ++i;

                  }

                  if (igetMeshBufferCount())

                  {

                              // Xóa phần còn lại

                              for (u32 j=i; jgetMeshBufferCount(); ++j)

                              {

                                          Mesh->getMeshBuffer(j)->drop();

                              }

                              Mesh->MeshBuffers.erase(i,Mesh->getMeshBufferCount()-i); //xóa phần chưa dùng

                  }

 

                  Mesh->recalculateBoundingBox();//tính toán lại hộp giới hạn

      }

Hàm tiếp theo ta phải khảo sát là hàm addstrip(), sau đây là chi tiết hàm này :

(chức năng hàm này là sinh bộ đệm SmeshBuffer mà nó thay thế tất cả các đỉnh và các chỉ mục cho các giá trị y bằng giá trị nằm trong khoảng y0 và y1, và thêm nó vào mesh.

void addstrip(const HeightMap &hm, colour_func cf, u16 y0, u16 y1, u32 bufNum)

      {

                  SMeshBuffer *buf = 0; //khởi tạo bằng 0

//Chỉ vào vị trí đã có

                  if (bufNumgetMeshBufferCount())

                  {

                              buf = (SMeshBuffer*)Mesh->getMeshBuffer(bufNum);

                  }

                  else

                  {

                              // Tạo bộ đệm mới

                              buf = new SMeshBuffer();

                              Mesh->addMeshBuffer(buf);

                              // Để đơn giản lúc này ta bỏ không quản lý nó, nhưng ta tiếp tục dùng nó làm việc khác. Lúc này Mesh đã quản lý được nó rồi

buf->drop();

                  }

                  //Đặt độ lớn của số đỉnh được lưu trong buf

                  buf->Vertices.set_used((1 + y1 - y0) * Width);

 

                  u32 i=0;

                  for (u16 y = y0; y <= y1; ++y)

                  {

                              for (u16 x = 0; x < Width; ++x)

                              {

                                          const f32 z = hm.get(x, y); //độ cao tại x,y

                                          const f32 xx = (f32)x/(f32)Width;

                                          const f32 yy = (f32)y/(f32)Height;

 

                                          S3DVertex& v = buf->Vertices[i++]; //đỉnh thứ i

                                          v.Pos.set(x, Scale * z, y); //đặt vị trí

v.Normal.set(hm.getnormal(x, y, Scale)); //đặt vector pháp

                                          v.Color=cf(xx, yy, z); //màu sắc

                                          v.TCoords.set(xx, yy);//tạo độ ảnh Map

                              }

                  }

//đặt tiếp số chỉ mục là 6 lần độ rộng nhân (y-y0) – các công thức này //tự chứng minh –ta có thể lấy cái này làm chương trình mẫu.

                  buf->Indices.set_used(6 * (Width - 1) * (y1 - y0));

                  i=0;

                  for(u16 y = y0; y < y1; ++y)

                  {

                              for(u16 x = 0; x < Width - 1; ++x)

                              {

                                          const u16 n = (y-y0) * Width + x;

                                          buf->Indices[i]=n;

                                          buf->Indices[++i]=n + Height;

                                          buf->Indices[++i]=n + Height + 1;

                                          buf->Indices[++i]=n + Height + 1;

                                          buf->Indices[++i]=n + 1;

                                          buf->Indices[++i]=n;

                                          ++i;

                              }

                  }

 

                  buf->recalculateBoundingBox(); //tính toán lại hộp giới hạn

      }

Class sự kiện không có gì để bàn ! Quá dễ.

Đến hàm main chính của chương trình :

Nguyên tắc dùng các class trên như sau :

TMesh mesh; //khai báo biến thuộc TMesh

HeightMap hm = HeightMap(255, 255); //tạo heightMap

hm.generate(eggbox); //dùng hàm nào để sinh heightMap

mesh.init(hm, 50.f, grey, driver);//tạo SMesh

 

// Thêm Mesh vào cây đồ thị

IMeshSceneNode* meshnode = smgr -> addMeshSceneNode(mesh.Mesh);

//đặt cờ chất liệu – không kiểm tra mặt khuất sau.

meshnode->setMaterialFlag(video::EMF_BACK_FACE_CULLING, false);

Do ta không có drop Mesh đi sau  khi tạo SceneNode nên tac có toàn quyền thay đổi Mesh của SceneNode :

Như khi nhấn phím số 1 thì Mesh là loại eggBox

Nhấn phím số 2 thì Mesh là loại  hình Sin

Nhấn phím số 3 thì là loại hàm mũ.. Nếu ta tạo thêm thì nó có thêm.

Chú ý : hàm generate() của heightMap và hàm init() của Tmesh đều có dùng tham số là kiểu hàm định nghĩa trước – cái này quá hay – để tiết kiệm thời gian, nếu viết bình thường thì chắc dùng nhiều câu kiểm tra điều kiện đây.

- Vậy là xong, chắc là mình bắt tay vô làm game được rồi.

Bài đăng phổ biến