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ó 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">0>
//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">0>
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 là
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, yellow
và white
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.
Không có nhận xét nào:
Đăng nhận xét