1
/** Example 010 Shaders
3
This tutorial shows how to use shaders for D3D8, D3D9, and OpenGL with the
4
engine and how to create new material types with them. It also shows how to
5
disable the generation of mipmaps at texture loading, and how to use text scene
8
This tutorial does not explain how shaders work. I would recommend to read the
9
D3D or OpenGL documentation, to search a tutorial, or to read a book about
12
At first, we need to include all headers and do the stuff we always do, like in
13
nearly all other tutorials:
17
#include "driverChoice.h"
22
#pragma comment(lib, "Irrlicht.lib")
26
Because we want to use some interesting shaders in this tutorials, we need to
27
set some data for them to make them able to compute nice colors. In this
28
example, we'll use a simple vertex shader which will calculate the color of the
29
vertex based on the position of the camera.
30
For this, the shader needs the following data: The inverted world matrix for
31
transforming the normal, the clip matrix for transforming the position, the
32
camera position and the world position of the object for the calculation of the
33
angle of light, and the color of the light. To be able to tell the shader all
34
this data every frame, we have to derive a class from the
35
IShaderConstantSetCallBack interface and override its only method, namely
36
OnSetConstants(). This method will be called every time the material is set.
37
The method setVertexShaderConstant() of the IMaterialRendererServices interface
38
is used to set the data the shader needs. If the user chose to use a High Level
39
shader language like HLSL instead of Assembler in this example, you have to set
40
the variable name as parameter instead of the register index.
43
IrrlichtDevice* device = 0;
44
bool UseHighLevelShaders = false;
46
class MyShaderCallBack : public video::IShaderConstantSetCallBack
50
virtual void OnSetConstants(video::IMaterialRendererServices* services,
53
video::IVideoDriver* driver = services->getVideoDriver();
55
// set inverted world matrix
56
// if we are using highlevel shaders (the user can select this when
57
// starting the program), we must set the constants by name.
59
core::matrix4 invWorld = driver->getTransform(video::ETS_WORLD);
60
invWorld.makeInverse();
62
if (UseHighLevelShaders)
63
services->setVertexShaderConstant("mInvWorld", invWorld.pointer(), 16);
65
services->setVertexShaderConstant(invWorld.pointer(), 0, 4);
69
core::matrix4 worldViewProj;
70
worldViewProj = driver->getTransform(video::ETS_PROJECTION);
71
worldViewProj *= driver->getTransform(video::ETS_VIEW);
72
worldViewProj *= driver->getTransform(video::ETS_WORLD);
74
if (UseHighLevelShaders)
75
services->setVertexShaderConstant("mWorldViewProj", worldViewProj.pointer(), 16);
77
services->setVertexShaderConstant(worldViewProj.pointer(), 4, 4);
79
// set camera position
81
core::vector3df pos = device->getSceneManager()->
82
getActiveCamera()->getAbsolutePosition();
84
if (UseHighLevelShaders)
85
services->setVertexShaderConstant("mLightPos", reinterpret_cast<f32*>(&pos), 3);
87
services->setVertexShaderConstant(reinterpret_cast<f32*>(&pos), 8, 1);
91
video::SColorf col(0.0f,1.0f,1.0f,0.0f);
93
if (UseHighLevelShaders)
94
services->setVertexShaderConstant("mLightColor",
95
reinterpret_cast<f32*>(&col), 4);
97
services->setVertexShaderConstant(reinterpret_cast<f32*>(&col), 9, 1);
99
// set transposed world matrix
101
core::matrix4 world = driver->getTransform(video::ETS_WORLD);
102
world = world.getTransposed();
104
if (UseHighLevelShaders)
105
services->setVertexShaderConstant("mTransWorld", world.pointer(), 16);
107
services->setVertexShaderConstant(world.pointer(), 10, 4);
112
The next few lines start up the engine just like in most other tutorials
113
before. But in addition, we ask the user if he wants to use high level shaders
114
in this example, if he selected a driver which is capable of doing so.
118
// ask user for driver
119
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
120
if (driverType==video::EDT_COUNT)
123
// ask the user if we should use high level shaders for this example
124
if (driverType == video::EDT_DIRECT3D9 ||
125
driverType == video::EDT_OPENGL)
128
printf("Please press 'y' if you want to use high level shaders.\n");
131
UseHighLevelShaders = true;
136
device = createDevice(driverType, core::dimension2d<u32>(640, 480));
139
return 1; // could not create selected driver.
142
video::IVideoDriver* driver = device->getVideoDriver();
143
scene::ISceneManager* smgr = device->getSceneManager();
144
gui::IGUIEnvironment* gui = device->getGUIEnvironment();
147
Now for the more interesting parts. If we are using Direct3D, we want
148
to load vertex and pixel shader programs, if we have OpenGL, we want to
149
use ARB fragment and vertex programs. I wrote the corresponding
150
programs down into the files d3d8.ps, d3d8.vs, d3d9.ps, d3d9.vs,
151
opengl.ps and opengl.vs. We only need the right filenames now. This is
152
done in the following switch. Note, that it is not necessary to write
153
the shaders into text files, like in this example. You can even write
154
the shaders directly as strings into the cpp source file, and use later
155
addShaderMaterial() instead of addShaderMaterialFromFiles().
158
io::path vsFileName; // filename for the vertex shader
159
io::path psFileName; // filename for the pixel shader
163
case video::EDT_DIRECT3D8:
164
psFileName = "../../media/d3d8.psh";
165
vsFileName = "../../media/d3d8.vsh";
167
case video::EDT_DIRECT3D9:
168
if (UseHighLevelShaders)
170
psFileName = "../../media/d3d9.hlsl";
171
vsFileName = psFileName; // both shaders are in the same file
175
psFileName = "../../media/d3d9.psh";
176
vsFileName = "../../media/d3d9.vsh";
180
case video::EDT_OPENGL:
181
if (UseHighLevelShaders)
183
psFileName = "../../media/opengl.frag";
184
vsFileName = "../../media/opengl.vert";
188
psFileName = "../../media/opengl.psh";
189
vsFileName = "../../media/opengl.vsh";
195
In addition, we check if the hardware and the selected renderer is
196
capable of executing the shaders we want. If not, we simply set the
197
filename string to 0. This is not necessary, but useful in this
198
example: For example, if the hardware is able to execute vertex shaders
199
but not pixel shaders, we create a new material which only uses the
200
vertex shader, and no pixel shader. Otherwise, if we would tell the
201
engine to create this material and the engine sees that the hardware
202
wouldn't be able to fullfill the request completely, it would not
203
create any new material at all. So in this example you would see at
204
least the vertex shader in action, without the pixel shader.
207
if (!driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) &&
208
!driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1))
210
device->getLogger()->log("WARNING: Pixel shaders disabled "\
211
"because of missing driver/hardware support.");
215
if (!driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) &&
216
!driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1))
218
device->getLogger()->log("WARNING: Vertex shaders disabled "\
219
"because of missing driver/hardware support.");
224
Now lets create the new materials. As you maybe know from previous
225
examples, a material type in the Irrlicht engine is set by simply
226
changing the MaterialType value in the SMaterial struct. And this value
227
is just a simple 32 bit value, like video::EMT_SOLID. So we only need
228
the engine to create a new value for us which we can set there. To do
229
this, we get a pointer to the IGPUProgrammingServices and call
230
addShaderMaterialFromFiles(), which returns such a new 32 bit value.
233
The parameters to this method are the following: First, the names of
234
the files containing the code of the vertex and the pixel shader. If
235
you would use addShaderMaterial() instead, you would not need file
236
names, then you could write the code of the shader directly as string.
237
The following parameter is a pointer to the IShaderConstantSetCallBack
238
class we wrote at the beginning of this tutorial. If you don't want to
239
set constants, set this to 0. The last paramter tells the engine which
240
material it should use as base material.
242
To demonstrate this, we create two materials with a different base
243
material, one with EMT_SOLID and one with EMT_TRANSPARENT_ADD_COLOR.
248
video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
249
s32 newMaterialType1 = 0;
250
s32 newMaterialType2 = 0;
254
MyShaderCallBack* mc = new MyShaderCallBack();
256
// create the shaders depending on if the user wanted high level
257
// or low level shaders:
259
if (UseHighLevelShaders)
261
// create material from high level shaders (hlsl or glsl)
263
newMaterialType1 = gpu->addHighLevelShaderMaterialFromFiles(
264
vsFileName, "vertexMain", video::EVST_VS_1_1,
265
psFileName, "pixelMain", video::EPST_PS_1_1,
266
mc, video::EMT_SOLID);
268
newMaterialType2 = gpu->addHighLevelShaderMaterialFromFiles(
269
vsFileName, "vertexMain", video::EVST_VS_1_1,
270
psFileName, "pixelMain", video::EPST_PS_1_1,
271
mc, video::EMT_TRANSPARENT_ADD_COLOR);
275
// create material from low level shaders (asm or arb_asm)
277
newMaterialType1 = gpu->addShaderMaterialFromFiles(vsFileName,
278
psFileName, mc, video::EMT_SOLID);
280
newMaterialType2 = gpu->addShaderMaterialFromFiles(vsFileName,
281
psFileName, mc, video::EMT_TRANSPARENT_ADD_COLOR);
288
Now it's time for testing the materials. We create a test cube and set
289
the material we created. In addition, we add a text scene node to the
290
cube and a rotation animator to make it look more interesting and
294
// create test scene node 1, with the new created material type 1
296
scene::ISceneNode* node = smgr->addCubeSceneNode(50);
297
node->setPosition(core::vector3df(0,0,0));
298
node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
299
node->setMaterialFlag(video::EMF_LIGHTING, false);
300
node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType1);
302
smgr->addTextSceneNode(gui->getBuiltInFont(),
303
L"PS & VS & EMT_SOLID",
304
video::SColor(255,255,255,255), node);
306
scene::ISceneNodeAnimator* anim = smgr->createRotationAnimator(
307
core::vector3df(0,0.3f,0));
308
node->addAnimator(anim);
312
Same for the second cube, but with the second material we created.
315
// create test scene node 2, with the new created material type 2
317
node = smgr->addCubeSceneNode(50);
318
node->setPosition(core::vector3df(0,-10,50));
319
node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
320
node->setMaterialFlag(video::EMF_LIGHTING, false);
321
node->setMaterialFlag(video::EMF_BLEND_OPERATION, true);
322
node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType2);
324
smgr->addTextSceneNode(gui->getBuiltInFont(),
325
L"PS & VS & EMT_TRANSPARENT",
326
video::SColor(255,255,255,255), node);
328
anim = smgr->createRotationAnimator(core::vector3df(0,0.3f,0));
329
node->addAnimator(anim);
333
Then we add a third cube without a shader on it, to be able to compare
337
// add a scene node with no shader
339
node = smgr->addCubeSceneNode(50);
340
node->setPosition(core::vector3df(0,50,25));
341
node->setMaterialTexture(0, driver->getTexture("../../media/wall.bmp"));
342
node->setMaterialFlag(video::EMF_LIGHTING, false);
343
smgr->addTextSceneNode(gui->getBuiltInFont(), L"NO SHADER",
344
video::SColor(255,255,255,255), node);
347
And last, we add a skybox and a user controlled camera to the scene.
348
For the skybox textures, we disable mipmap generation, because we don't
354
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
356
smgr->addSkyBoxSceneNode(
357
driver->getTexture("../../media/irrlicht2_up.jpg"),
358
driver->getTexture("../../media/irrlicht2_dn.jpg"),
359
driver->getTexture("../../media/irrlicht2_lf.jpg"),
360
driver->getTexture("../../media/irrlicht2_rt.jpg"),
361
driver->getTexture("../../media/irrlicht2_ft.jpg"),
362
driver->getTexture("../../media/irrlicht2_bk.jpg"));
364
driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true);
366
// add a camera and disable the mouse cursor
368
scene::ICameraSceneNode* cam = smgr->addCameraSceneNodeFPS();
369
cam->setPosition(core::vector3df(-100,50,100));
370
cam->setTarget(core::vector3df(0,0,0));
371
device->getCursorControl()->setVisible(false);
374
Now draw everything. That's all.
380
if (device->isWindowActive())
382
driver->beginScene(true, true, video::SColor(255,0,0,0));
386
int fps = driver->getFPS();
390
core::stringw str = L"Irrlicht Engine - Vertex and pixel shader example [";
391
str += driver->getName();
395
device->setWindowCaption(str.c_str());
406
Compile and run this, and I hope you have fun with your new little shader