main.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510
  1. /** Example 010 Shaders
  2. This tutorial shows how to use shaders for D3D9, and OpenGL with the
  3. engine and how to create new material types with them. It also shows how to
  4. disable the generation of mipmaps at texture loading, and how to use text scene
  5. nodes.
  6. This tutorial does not explain how shaders work. I would recommend to read the
  7. D3D or OpenGL, documentation, to search a tutorial, or to read a book about
  8. this.
  9. At first, we need to include all headers and do the stuff we always do, like in
  10. nearly all other tutorials:
  11. */
  12. #include <irrlicht.h>
  13. #include <iostream>
  14. #include "driverChoice.h"
  15. #include "exampleHelper.h"
  16. using namespace irr;
  17. #ifdef _MSC_VER
  18. #pragma comment(lib, "Irrlicht.lib")
  19. #endif
  20. /*
  21. Because we want to use some interesting shaders in this tutorials, we need to
  22. set some data for them to make them able to compute nice colors. In this
  23. example, we'll use a simple vertex shader which will calculate the color of the
  24. vertex based on the position of the camera.
  25. For this, the shader needs the following data: The inverted world matrix for
  26. transforming the normal, the clip matrix for transforming the position, the
  27. camera position and the world position of the object for the calculation of the
  28. angle of light, and the color of the light. To be able to tell the shader all
  29. this data every frame, we have to derive a class from the
  30. IShaderConstantSetCallBack interface and override its only method, namely
  31. OnSetConstants(). This method will be called every time the material is set.
  32. The method setVertexShaderConstant() of the IMaterialRendererServices interface
  33. is used to set the data the shader needs. If the user chose to use a High Level
  34. shader language like HLSL instead of Assembler in this example, you have to set
  35. the variable name as parameter instead of the register index.
  36. */
  37. IrrlichtDevice* device = 0;
  38. bool UseHighLevelShaders = false;
  39. class MyShaderCallBack : public video::IShaderConstantSetCallBack
  40. {
  41. public:
  42. MyShaderCallBack() : WorldViewProjID(-1), TransWorldID(-1), InvWorldID(-1), PositionID(-1),
  43. ColorID(-1), TextureID(-1), EmissiveID(-1)
  44. {
  45. for ( int i=0; i<4; ++i )
  46. Emissive[i] = 0.f;
  47. }
  48. virtual void OnCreate(video::IMaterialRendererServices* services, s32 userData)
  49. {
  50. if (UseHighLevelShaders)
  51. {
  52. // Get shader constants id.
  53. // Constants are "uniforms" in other shading languages.
  54. // And they are not constant at all but can be changed before every draw call
  55. // (the naming probably comes from Direct3D where they are called constants)
  56. WorldViewProjID = services->getVertexShaderConstantID("mWorldViewProj");
  57. TransWorldID = services->getVertexShaderConstantID("mTransWorld");
  58. InvWorldID = services->getVertexShaderConstantID("mInvWorld");
  59. PositionID = services->getVertexShaderConstantID("mLightPos");
  60. ColorID = services->getVertexShaderConstantID("mLightColor");
  61. EmissiveID = services->getPixelShaderConstantID("mEmissive");
  62. // Textures ID are important only for OpenGL interface.
  63. video::IVideoDriver* driver = services->getVideoDriver();
  64. if(driver->getDriverType() == video::EDT_OPENGL)
  65. TextureID = services->getVertexShaderConstantID("myTexture");
  66. }
  67. // Set light color
  68. // That could be set as well in OnSetConstants, but there's some cost to setting shader constants
  69. // So when we have non-changing shader constants it's more performant to set them only once.
  70. video::SColorf col(0.0f,1.0f,1.0f,0.0f);
  71. if (UseHighLevelShaders)
  72. {
  73. services->setVertexShaderConstant(ColorID, reinterpret_cast<f32*>(&col), 4);
  74. // Note: Since Irrlicht 1.9 it's possible to call setVertexShaderConstant
  75. // from anywhere. To do that save the services pointer here in OnCreate, it
  76. // won't change as long as you use one IShaderConstantSetCallBack per shader
  77. // material. But when calling it ouside of IShaderConstantSetCallBack functions
  78. // you have to call services->startUseProgram()stopUseProgram() before/after doing so.
  79. // At least for high-level shader constants, low level constants are not attached
  80. // to programs, so for those it doesn't matter.
  81. // Doing that sometimes makes sense for performance reasons, like for constants which
  82. // do only change once per frame or even less.
  83. }
  84. else
  85. services->setVertexShaderConstant(reinterpret_cast<f32*>(&col), 9, 1);
  86. }
  87. // Called when any SMaterial value changes
  88. virtual void OnSetMaterial(const irr::video::SMaterial& material)
  89. {
  90. // Remember material values to pass them on to shader in OnSetConstants
  91. Emissive[0] = material.EmissiveColor.getRed() / 255.0f;
  92. Emissive[1] = material.EmissiveColor.getGreen() / 255.0f;
  93. Emissive[2] = material.EmissiveColor.getBlue() / 255.0f;
  94. Emissive[3] = material.EmissiveColor.getAlpha() / 255.0f;
  95. // Note: Until Irrlicht 1.8 it was possible to use gl_FrontMaterial in glsl
  96. // This is no longer supported since Irrlicht 1.9
  97. // Reason: Passing always every material value is slow, harder to port
  98. // and generally getting deprecated in newer shader systems.
  99. }
  100. virtual void OnSetConstants(video::IMaterialRendererServices* services,
  101. s32 userData)
  102. {
  103. video::IVideoDriver* driver = services->getVideoDriver();
  104. // set inverted world matrix
  105. // if we are using highlevel shaders (the user can select this when
  106. // starting the program), we must set the constants by name.
  107. core::matrix4 invWorld = driver->getTransform(video::ETS_WORLD);
  108. invWorld.makeInverse();
  109. if (UseHighLevelShaders)
  110. services->setVertexShaderConstant(InvWorldID, invWorld.pointer(), 16);
  111. else
  112. services->setVertexShaderConstant(invWorld.pointer(), 0, 4);
  113. // set clip matrix
  114. core::matrix4 worldViewProj;
  115. worldViewProj = driver->getTransform(video::ETS_PROJECTION);
  116. worldViewProj *= driver->getTransform(video::ETS_VIEW);
  117. worldViewProj *= driver->getTransform(video::ETS_WORLD);
  118. if (UseHighLevelShaders)
  119. services->setVertexShaderConstant(WorldViewProjID, worldViewProj.pointer(), 16);
  120. else
  121. services->setVertexShaderConstant(worldViewProj.pointer(), 4, 4);
  122. // set camera position
  123. core::vector3df pos = device->getSceneManager()->
  124. getActiveCamera()->getAbsolutePosition();
  125. if (UseHighLevelShaders)
  126. services->setVertexShaderConstant(PositionID, reinterpret_cast<f32*>(&pos), 3);
  127. else
  128. services->setVertexShaderConstant(reinterpret_cast<f32*>(&pos), 8, 1);
  129. // set transposed world matrix
  130. core::matrix4 world = driver->getTransform(video::ETS_WORLD);
  131. world = world.getTransposed();
  132. if (UseHighLevelShaders)
  133. {
  134. services->setVertexShaderConstant(TransWorldID, world.pointer(), 16);
  135. // set texture, for textures you can use both an int and a float setPixelShaderConstant interfaces (You need it only for an OpenGL driver).
  136. s32 TextureLayerID = 0;
  137. services->setPixelShaderConstant(TextureID, &TextureLayerID, 1);
  138. }
  139. else
  140. services->setVertexShaderConstant(world.pointer(), 10, 4);
  141. // Set material values
  142. if (UseHighLevelShaders)
  143. {
  144. services->setPixelShaderConstant(EmissiveID, Emissive, 4);
  145. }
  146. }
  147. private:
  148. s32 WorldViewProjID;
  149. s32 TransWorldID;
  150. s32 InvWorldID;
  151. s32 PositionID;
  152. s32 ColorID;
  153. s32 TextureID;
  154. s32 EmissiveID;
  155. irr::f32 Emissive[4];
  156. };
  157. /*
  158. The next few lines start up the engine just like in most other tutorials
  159. before. But in addition, we ask the user if he wants to use high level shaders
  160. in this example, if he selected a driver which is capable of doing so.
  161. */
  162. int main()
  163. {
  164. // ask user for driver
  165. video::E_DRIVER_TYPE driverType=driverChoiceConsole();
  166. if (driverType==video::EDT_COUNT)
  167. return 1;
  168. // ask the user if we should use high level shaders for this example
  169. if (driverType == video::EDT_DIRECT3D9 ||
  170. driverType == video::EDT_OPENGL)
  171. {
  172. char i = 'y';
  173. printf("Please press 'y' if you want to use high level shaders.\n");
  174. std::cin >> i;
  175. if (i == 'y')
  176. {
  177. UseHighLevelShaders = true;
  178. }
  179. }
  180. // create device
  181. device = createDevice(driverType, core::dimension2d<u32>(640, 480));
  182. if (device == 0)
  183. return 1; // could not create selected driver.
  184. video::IVideoDriver* driver = device->getVideoDriver();
  185. scene::ISceneManager* smgr = device->getSceneManager();
  186. gui::IGUIEnvironment* gui = device->getGUIEnvironment();
  187. const io::path mediaPath = getExampleMediaPath();
  188. /*
  189. Now for the more interesting parts. If we are using Direct3D, we want
  190. to load vertex and pixel shader programs, if we have OpenGL, we want to
  191. use ARB fragment and vertex programs. I wrote the corresponding
  192. programs down into the files d3d9.ps, d3d9.vs, opengl.ps and opengl.vs.
  193. We only need the right filenames now. This is done in the following switch.
  194. Note, that it is not necessary to write the shaders into text files,
  195. like in this example. You can even write the shaders directly as strings
  196. into the cpp source file, and use later addShaderMaterial() instead of
  197. addShaderMaterialFromFiles().
  198. */
  199. io::path vsFileName; // filename for the vertex shader
  200. io::path psFileName; // filename for the pixel shader
  201. switch(driverType)
  202. {
  203. case video::EDT_DIRECT3D9:
  204. if (UseHighLevelShaders)
  205. {
  206. psFileName = mediaPath + "d3d9.hlsl";
  207. vsFileName = psFileName; // both shaders are in the same file
  208. }
  209. else
  210. {
  211. psFileName = mediaPath + "d3d9.psh";
  212. vsFileName = mediaPath + "d3d9.vsh";
  213. }
  214. break;
  215. case video::EDT_OPENGL:
  216. if (UseHighLevelShaders)
  217. {
  218. psFileName = mediaPath + "opengl.frag";
  219. vsFileName = mediaPath + "opengl.vert";
  220. }
  221. else
  222. {
  223. psFileName = mediaPath + "opengl.psh";
  224. vsFileName = mediaPath + "opengl.vsh";
  225. }
  226. break;
  227. case video::EDT_BURNINGSVIDEO:
  228. UseHighLevelShaders = true;
  229. psFileName = mediaPath + "opengl.frag";
  230. vsFileName = mediaPath + "opengl.vert";
  231. break;
  232. default:
  233. break;
  234. }
  235. /*
  236. In addition, we check if the hardware and the selected renderer is
  237. capable of executing the shaders we want. If not, we simply set the
  238. filename string to 0. This is not necessary, but useful in this
  239. example: For example, if the hardware is able to execute vertex shaders
  240. but not pixel shaders, we create a new material which only uses the
  241. vertex shader, and no pixel shader. Otherwise, if we would tell the
  242. engine to create this material and the engine sees that the hardware
  243. wouldn't be able to fulfill the request completely, it would not
  244. create any new material at all. So in this example you would see at
  245. least the vertex shader in action, without the pixel shader.
  246. */
  247. if (!driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) &&
  248. !driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1))
  249. {
  250. device->getLogger()->log("WARNING: Pixel shaders disabled "\
  251. "because of missing driver/hardware support.");
  252. psFileName = "";
  253. }
  254. if (!driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) &&
  255. !driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1))
  256. {
  257. device->getLogger()->log("WARNING: Vertex shaders disabled "\
  258. "because of missing driver/hardware support.");
  259. vsFileName = "";
  260. }
  261. /*
  262. Now lets create the new materials. As you maybe know from previous
  263. examples, a material type in the Irrlicht engine is set by simply
  264. changing the MaterialType value in the SMaterial struct. And this value
  265. is just a simple 32 bit value, like video::EMT_SOLID. So we only need
  266. the engine to create a new value for us which we can set there. To do
  267. this, we get a pointer to the IGPUProgrammingServices and call
  268. addShaderMaterialFromFiles(), which returns such a new 32 bit value.
  269. That's all.
  270. The parameters to this method are the following: First, the names of
  271. the files containing the code of the vertex and the pixel shader. If
  272. you would use addShaderMaterial() instead, you would not need file
  273. names, then you could write the code of the shader directly as string.
  274. The following parameter is a pointer to the IShaderConstantSetCallBack
  275. class we wrote at the beginning of this tutorial. If you don't want to
  276. set constants, set this to 0. The last parameter tells the engine which
  277. material it should use as base material.
  278. To demonstrate this, we create two materials with a different base
  279. material, one with EMT_SOLID and one with EMT_TRANSPARENT_ADD_COLOR.
  280. The role of the base material is to set the alpha (transparency)
  281. and blending settings as used in the base material. Avoid the
  282. EMT_NORMAL_... or EMT_PARALLAX... types as base materials as they
  283. are internally shaders themselves and will only create conflicts with
  284. your shaders.
  285. */
  286. // create materials
  287. video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
  288. s32 newMaterialType1 = 0;
  289. s32 newMaterialType2 = 0;
  290. if (gpu)
  291. {
  292. /*
  293. Create one callback instance for each shader material you add.
  294. Reason is that the getVertexShaderConstantID returns ID's which are
  295. only valid per added material (The ID's tend to be identical
  296. as long as the shader code is exactly identical, but it's not good
  297. style to depend on that).
  298. */
  299. MyShaderCallBack* mcSolid = new MyShaderCallBack();
  300. MyShaderCallBack* mcTransparentAdd = new MyShaderCallBack();
  301. // create the shaders depending on if the user wanted high level
  302. // or low level shaders:
  303. if (UseHighLevelShaders)
  304. {
  305. // create material from high level shaders (hlsl, glsl)
  306. newMaterialType1 = gpu->addHighLevelShaderMaterialFromFiles(
  307. vsFileName, "vertexMain", video::EVST_VS_1_1,
  308. psFileName, "pixelMain", video::EPST_PS_1_1,
  309. mcSolid, video::EMT_SOLID, 0);
  310. newMaterialType2 = gpu->addHighLevelShaderMaterialFromFiles(
  311. vsFileName, "vertexMain", video::EVST_VS_1_1,
  312. psFileName, "pixelMain", video::EPST_PS_1_1,
  313. mcTransparentAdd, video::EMT_TRANSPARENT_ADD_COLOR, 0);
  314. }
  315. else
  316. {
  317. // create material from low level shaders (asm or arb_asm)
  318. newMaterialType1 = gpu->addShaderMaterialFromFiles(vsFileName,
  319. psFileName, mcSolid, video::EMT_SOLID);
  320. newMaterialType2 = gpu->addShaderMaterialFromFiles(vsFileName,
  321. psFileName, mcTransparentAdd, video::EMT_TRANSPARENT_ADD_COLOR);
  322. }
  323. mcSolid->drop();
  324. mcTransparentAdd->drop();
  325. }
  326. /*
  327. Now it's time for testing the materials. We create a test cube and set
  328. the material we created. In addition, we add a text scene node to the
  329. cube and a rotation animator to make it look more interesting and
  330. important.
  331. */
  332. // create test scene node 1, with the new created material type 1
  333. scene::ISceneNode* node = smgr->addCubeSceneNode(50);
  334. node->setPosition(core::vector3df(0,0,0));
  335. node->setMaterialTexture(0, driver->getTexture(mediaPath + "wall.bmp"));
  336. node->setMaterialFlag(video::EMF_LIGHTING, false);
  337. node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType1);
  338. smgr->addTextSceneNode(gui->getBuiltInFont(),
  339. L"PS & VS & EMT_SOLID",
  340. video::SColor(255,255,255,255), node);
  341. scene::ISceneNodeAnimator* anim = smgr->createRotationAnimator(
  342. core::vector3df(0,0.3f,0));
  343. node->addAnimator(anim);
  344. anim->drop();
  345. /*
  346. Same for the second cube, but with the second material we created.
  347. */
  348. // create test scene node 2, with the new created material type 2
  349. node = smgr->addCubeSceneNode(50);
  350. node->setPosition(core::vector3df(0,-10,50));
  351. node->setMaterialTexture(0, driver->getTexture(mediaPath + "wall.bmp"));
  352. node->setMaterialFlag(video::EMF_LIGHTING, false);
  353. node->setMaterialFlag(video::EMF_BLEND_OPERATION, true);
  354. node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType2);
  355. node->getMaterial(0).EmissiveColor = irr::video::SColor(0,50,0,50);
  356. smgr->addTextSceneNode(gui->getBuiltInFont(),
  357. L"PS & VS & EMT_TRANSPARENT",
  358. video::SColor(255,255,255,255), node);
  359. anim = smgr->createRotationAnimator(core::vector3df(0,0.3f,0));
  360. node->addAnimator(anim);
  361. anim->drop();
  362. /*
  363. Then we add a third cube without a shader on it, to be able to compare
  364. the cubes.
  365. */
  366. // add a scene node with no shader
  367. node = smgr->addCubeSceneNode(50);
  368. node->setPosition(core::vector3df(0,50,25));
  369. node->setMaterialTexture(0, driver->getTexture(mediaPath + "wall.bmp"));
  370. node->setMaterialFlag(video::EMF_LIGHTING, false);
  371. smgr->addTextSceneNode(gui->getBuiltInFont(), L"NO SHADER",
  372. video::SColor(255,255,255,255), node);
  373. /*
  374. And last, we add a skybox and a user controlled camera to the scene.
  375. For the skybox textures, we disable mipmap generation, because we don't
  376. need mipmaps on it.
  377. */
  378. // add a nice skybox
  379. driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
  380. smgr->addSkyBoxSceneNode(
  381. driver->getTexture(mediaPath + "irrlicht2_up.jpg"),
  382. driver->getTexture(mediaPath + "irrlicht2_dn.jpg"),
  383. driver->getTexture(mediaPath + "irrlicht2_lf.jpg"),
  384. driver->getTexture(mediaPath + "irrlicht2_rt.jpg"),
  385. driver->getTexture(mediaPath + "irrlicht2_ft.jpg"),
  386. driver->getTexture(mediaPath + "irrlicht2_bk.jpg"));
  387. driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true);
  388. // add a camera and disable the mouse cursor
  389. scene::ICameraSceneNode* cam = smgr->addCameraSceneNodeFPS();
  390. cam->setPosition(core::vector3df(-100,50,100));
  391. cam->setTarget(core::vector3df(0,0,0));
  392. device->getCursorControl()->setVisible(false);
  393. /*
  394. Now draw everything. That's all.
  395. */
  396. int lastFPS = -1;
  397. while(device->run())
  398. if (device->isWindowActive())
  399. {
  400. driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(255,0,0,0));
  401. smgr->drawAll();
  402. driver->endScene();
  403. int fps = driver->getFPS();
  404. if (lastFPS != fps)
  405. {
  406. core::stringw str = L"Irrlicht Engine - Vertex and pixel shader example [";
  407. str += driver->getName();
  408. str += "] FPS:";
  409. str += fps;
  410. device->setWindowCaption(str.c_str());
  411. lastFPS = fps;
  412. }
  413. }
  414. device->drop();
  415. return 0;
  416. }
  417. /*
  418. Compile and run this, and I hope you have fun with your new little shader
  419. writing tool :).
  420. **/