123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- /** Example 010 Shaders
- This tutorial shows how to use shaders for D3D9, and OpenGL with the
- engine and how to create new material types with them. It also shows how to
- disable the generation of mipmaps at texture loading, and how to use text scene
- nodes.
- This tutorial does not explain how shaders work. I would recommend to read the
- D3D or OpenGL, documentation, to search a tutorial, or to read a book about
- this.
- At first, we need to include all headers and do the stuff we always do, like in
- nearly all other tutorials:
- */
- #include <irrlicht.h>
- #include <iostream>
- #include "driverChoice.h"
- #include "exampleHelper.h"
- using namespace irr;
- #ifdef _MSC_VER
- #pragma comment(lib, "Irrlicht.lib")
- #endif
- /*
- Because we want to use some interesting shaders in this tutorials, we need to
- set some data for them to make them able to compute nice colors. In this
- example, we'll use a simple vertex shader which will calculate the color of the
- vertex based on the position of the camera.
- For this, the shader needs the following data: The inverted world matrix for
- transforming the normal, the clip matrix for transforming the position, the
- camera position and the world position of the object for the calculation of the
- angle of light, and the color of the light. To be able to tell the shader all
- this data every frame, we have to derive a class from the
- IShaderConstantSetCallBack interface and override its only method, namely
- OnSetConstants(). This method will be called every time the material is set.
- The method setVertexShaderConstant() of the IMaterialRendererServices interface
- is used to set the data the shader needs. If the user chose to use a High Level
- shader language like HLSL instead of Assembler in this example, you have to set
- the variable name as parameter instead of the register index.
- */
- IrrlichtDevice* device = 0;
- bool UseHighLevelShaders = false;
- class MyShaderCallBack : public video::IShaderConstantSetCallBack
- {
- public:
- MyShaderCallBack() : WorldViewProjID(-1), TransWorldID(-1), InvWorldID(-1), PositionID(-1),
- ColorID(-1), TextureID(-1), EmissiveID(-1)
- {
- for ( int i=0; i<4; ++i )
- Emissive[i] = 0.f;
- }
- virtual void OnCreate(video::IMaterialRendererServices* services, s32 userData)
- {
- if (UseHighLevelShaders)
- {
- // Get shader constants id.
- // Constants are "uniforms" in other shading languages.
- // And they are not constant at all but can be changed before every draw call
- // (the naming probably comes from Direct3D where they are called constants)
- WorldViewProjID = services->getVertexShaderConstantID("mWorldViewProj");
- TransWorldID = services->getVertexShaderConstantID("mTransWorld");
- InvWorldID = services->getVertexShaderConstantID("mInvWorld");
- PositionID = services->getVertexShaderConstantID("mLightPos");
- ColorID = services->getVertexShaderConstantID("mLightColor");
- EmissiveID = services->getPixelShaderConstantID("mEmissive");
- // Textures ID are important only for OpenGL interface.
- video::IVideoDriver* driver = services->getVideoDriver();
- if(driver->getDriverType() == video::EDT_OPENGL)
- TextureID = services->getVertexShaderConstantID("myTexture");
- }
- // Set light color
- // That could be set as well in OnSetConstants, but there's some cost to setting shader constants
- // So when we have non-changing shader constants it's more performant to set them only once.
- video::SColorf col(0.0f,1.0f,1.0f,0.0f);
- if (UseHighLevelShaders)
- {
- services->setVertexShaderConstant(ColorID, reinterpret_cast<f32*>(&col), 4);
- // Note: Since Irrlicht 1.9 it's possible to call setVertexShaderConstant
- // from anywhere. To do that save the services pointer here in OnCreate, it
- // won't change as long as you use one IShaderConstantSetCallBack per shader
- // material. But when calling it ouside of IShaderConstantSetCallBack functions
- // you have to call services->startUseProgram()stopUseProgram() before/after doing so.
- // At least for high-level shader constants, low level constants are not attached
- // to programs, so for those it doesn't matter.
- // Doing that sometimes makes sense for performance reasons, like for constants which
- // do only change once per frame or even less.
- }
- else
- services->setVertexShaderConstant(reinterpret_cast<f32*>(&col), 9, 1);
- }
- // Called when any SMaterial value changes
- virtual void OnSetMaterial(const irr::video::SMaterial& material)
- {
- // Remember material values to pass them on to shader in OnSetConstants
- Emissive[0] = material.EmissiveColor.getRed() / 255.0f;
- Emissive[1] = material.EmissiveColor.getGreen() / 255.0f;
- Emissive[2] = material.EmissiveColor.getBlue() / 255.0f;
- Emissive[3] = material.EmissiveColor.getAlpha() / 255.0f;
- // Note: Until Irrlicht 1.8 it was possible to use gl_FrontMaterial in glsl
- // This is no longer supported since Irrlicht 1.9
- // Reason: Passing always every material value is slow, harder to port
- // and generally getting deprecated in newer shader systems.
- }
- virtual void OnSetConstants(video::IMaterialRendererServices* services,
- s32 userData)
- {
- video::IVideoDriver* driver = services->getVideoDriver();
- // set inverted world matrix
- // if we are using highlevel shaders (the user can select this when
- // starting the program), we must set the constants by name.
- core::matrix4 invWorld = driver->getTransform(video::ETS_WORLD);
- invWorld.makeInverse();
- if (UseHighLevelShaders)
- services->setVertexShaderConstant(InvWorldID, invWorld.pointer(), 16);
- else
- services->setVertexShaderConstant(invWorld.pointer(), 0, 4);
- // set clip matrix
- core::matrix4 worldViewProj;
- worldViewProj = driver->getTransform(video::ETS_PROJECTION);
- worldViewProj *= driver->getTransform(video::ETS_VIEW);
- worldViewProj *= driver->getTransform(video::ETS_WORLD);
- if (UseHighLevelShaders)
- services->setVertexShaderConstant(WorldViewProjID, worldViewProj.pointer(), 16);
- else
- services->setVertexShaderConstant(worldViewProj.pointer(), 4, 4);
- // set camera position
- core::vector3df pos = device->getSceneManager()->
- getActiveCamera()->getAbsolutePosition();
- if (UseHighLevelShaders)
- services->setVertexShaderConstant(PositionID, reinterpret_cast<f32*>(&pos), 3);
- else
- services->setVertexShaderConstant(reinterpret_cast<f32*>(&pos), 8, 1);
- // set transposed world matrix
- core::matrix4 world = driver->getTransform(video::ETS_WORLD);
- world = world.getTransposed();
- if (UseHighLevelShaders)
- {
- services->setVertexShaderConstant(TransWorldID, world.pointer(), 16);
- // set texture, for textures you can use both an int and a float setPixelShaderConstant interfaces (You need it only for an OpenGL driver).
- s32 TextureLayerID = 0;
- services->setPixelShaderConstant(TextureID, &TextureLayerID, 1);
- }
- else
- services->setVertexShaderConstant(world.pointer(), 10, 4);
- // Set material values
- if (UseHighLevelShaders)
- {
- services->setPixelShaderConstant(EmissiveID, Emissive, 4);
- }
- }
- private:
- s32 WorldViewProjID;
- s32 TransWorldID;
- s32 InvWorldID;
- s32 PositionID;
- s32 ColorID;
- s32 TextureID;
- s32 EmissiveID;
- irr::f32 Emissive[4];
- };
- /*
- The next few lines start up the engine just like in most other tutorials
- before. But in addition, we ask the user if he wants to use high level shaders
- in this example, if he selected a driver which is capable of doing so.
- */
- int main()
- {
- // ask user for driver
- video::E_DRIVER_TYPE driverType=driverChoiceConsole();
- if (driverType==video::EDT_COUNT)
- return 1;
- // ask the user if we should use high level shaders for this example
- if (driverType == video::EDT_DIRECT3D9 ||
- driverType == video::EDT_OPENGL)
- {
- char i = 'y';
- printf("Please press 'y' if you want to use high level shaders.\n");
- std::cin >> i;
- if (i == 'y')
- {
- UseHighLevelShaders = true;
- }
- }
- // create device
- device = createDevice(driverType, core::dimension2d<u32>(640, 480));
- if (device == 0)
- return 1; // could not create selected driver.
- video::IVideoDriver* driver = device->getVideoDriver();
- scene::ISceneManager* smgr = device->getSceneManager();
- gui::IGUIEnvironment* gui = device->getGUIEnvironment();
- const io::path mediaPath = getExampleMediaPath();
- /*
- Now for the more interesting parts. If we are using Direct3D, we want
- to load vertex and pixel shader programs, if we have OpenGL, we want to
- use ARB fragment and vertex programs. I wrote the corresponding
- programs down into the files d3d9.ps, d3d9.vs, opengl.ps and opengl.vs.
- We only need the right filenames now. This is done in the following switch.
- Note, that it is not necessary to write the shaders into text files,
- like in this example. You can even write the shaders directly as strings
- into the cpp source file, and use later addShaderMaterial() instead of
- addShaderMaterialFromFiles().
- */
- io::path vsFileName; // filename for the vertex shader
- io::path psFileName; // filename for the pixel shader
- switch(driverType)
- {
- case video::EDT_DIRECT3D9:
- if (UseHighLevelShaders)
- {
- psFileName = mediaPath + "d3d9.hlsl";
- vsFileName = psFileName; // both shaders are in the same file
- }
- else
- {
- psFileName = mediaPath + "d3d9.psh";
- vsFileName = mediaPath + "d3d9.vsh";
- }
- break;
- case video::EDT_OPENGL:
- if (UseHighLevelShaders)
- {
- psFileName = mediaPath + "opengl.frag";
- vsFileName = mediaPath + "opengl.vert";
- }
- else
- {
- psFileName = mediaPath + "opengl.psh";
- vsFileName = mediaPath + "opengl.vsh";
- }
- break;
- case video::EDT_BURNINGSVIDEO:
- UseHighLevelShaders = true;
- psFileName = mediaPath + "opengl.frag";
- vsFileName = mediaPath + "opengl.vert";
- break;
- default:
- break;
- }
- /*
- In addition, we check if the hardware and the selected renderer is
- capable of executing the shaders we want. If not, we simply set the
- filename string to 0. This is not necessary, but useful in this
- example: For example, if the hardware is able to execute vertex shaders
- but not pixel shaders, we create a new material which only uses the
- vertex shader, and no pixel shader. Otherwise, if we would tell the
- engine to create this material and the engine sees that the hardware
- wouldn't be able to fulfill the request completely, it would not
- create any new material at all. So in this example you would see at
- least the vertex shader in action, without the pixel shader.
- */
- if (!driver->queryFeature(video::EVDF_PIXEL_SHADER_1_1) &&
- !driver->queryFeature(video::EVDF_ARB_FRAGMENT_PROGRAM_1))
- {
- device->getLogger()->log("WARNING: Pixel shaders disabled "\
- "because of missing driver/hardware support.");
- psFileName = "";
- }
- if (!driver->queryFeature(video::EVDF_VERTEX_SHADER_1_1) &&
- !driver->queryFeature(video::EVDF_ARB_VERTEX_PROGRAM_1))
- {
- device->getLogger()->log("WARNING: Vertex shaders disabled "\
- "because of missing driver/hardware support.");
- vsFileName = "";
- }
- /*
- Now lets create the new materials. As you maybe know from previous
- examples, a material type in the Irrlicht engine is set by simply
- changing the MaterialType value in the SMaterial struct. And this value
- is just a simple 32 bit value, like video::EMT_SOLID. So we only need
- the engine to create a new value for us which we can set there. To do
- this, we get a pointer to the IGPUProgrammingServices and call
- addShaderMaterialFromFiles(), which returns such a new 32 bit value.
- That's all.
- The parameters to this method are the following: First, the names of
- the files containing the code of the vertex and the pixel shader. If
- you would use addShaderMaterial() instead, you would not need file
- names, then you could write the code of the shader directly as string.
- The following parameter is a pointer to the IShaderConstantSetCallBack
- class we wrote at the beginning of this tutorial. If you don't want to
- set constants, set this to 0. The last parameter tells the engine which
- material it should use as base material.
- To demonstrate this, we create two materials with a different base
- material, one with EMT_SOLID and one with EMT_TRANSPARENT_ADD_COLOR.
- The role of the base material is to set the alpha (transparency)
- and blending settings as used in the base material. Avoid the
- EMT_NORMAL_... or EMT_PARALLAX... types as base materials as they
- are internally shaders themselves and will only create conflicts with
- your shaders.
- */
- // create materials
- video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
- s32 newMaterialType1 = 0;
- s32 newMaterialType2 = 0;
- if (gpu)
- {
- /*
- Create one callback instance for each shader material you add.
- Reason is that the getVertexShaderConstantID returns ID's which are
- only valid per added material (The ID's tend to be identical
- as long as the shader code is exactly identical, but it's not good
- style to depend on that).
- */
- MyShaderCallBack* mcSolid = new MyShaderCallBack();
- MyShaderCallBack* mcTransparentAdd = new MyShaderCallBack();
- // create the shaders depending on if the user wanted high level
- // or low level shaders:
- if (UseHighLevelShaders)
- {
- // create material from high level shaders (hlsl, glsl)
- newMaterialType1 = gpu->addHighLevelShaderMaterialFromFiles(
- vsFileName, "vertexMain", video::EVST_VS_1_1,
- psFileName, "pixelMain", video::EPST_PS_1_1,
- mcSolid, video::EMT_SOLID, 0);
- newMaterialType2 = gpu->addHighLevelShaderMaterialFromFiles(
- vsFileName, "vertexMain", video::EVST_VS_1_1,
- psFileName, "pixelMain", video::EPST_PS_1_1,
- mcTransparentAdd, video::EMT_TRANSPARENT_ADD_COLOR, 0);
- }
- else
- {
- // create material from low level shaders (asm or arb_asm)
- newMaterialType1 = gpu->addShaderMaterialFromFiles(vsFileName,
- psFileName, mcSolid, video::EMT_SOLID);
- newMaterialType2 = gpu->addShaderMaterialFromFiles(vsFileName,
- psFileName, mcTransparentAdd, video::EMT_TRANSPARENT_ADD_COLOR);
- }
- mcSolid->drop();
- mcTransparentAdd->drop();
- }
- /*
- Now it's time for testing the materials. We create a test cube and set
- the material we created. In addition, we add a text scene node to the
- cube and a rotation animator to make it look more interesting and
- important.
- */
- // create test scene node 1, with the new created material type 1
- scene::ISceneNode* node = smgr->addCubeSceneNode(50);
- node->setPosition(core::vector3df(0,0,0));
- node->setMaterialTexture(0, driver->getTexture(mediaPath + "wall.bmp"));
- node->setMaterialFlag(video::EMF_LIGHTING, false);
- node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType1);
- smgr->addTextSceneNode(gui->getBuiltInFont(),
- L"PS & VS & EMT_SOLID",
- video::SColor(255,255,255,255), node);
- scene::ISceneNodeAnimator* anim = smgr->createRotationAnimator(
- core::vector3df(0,0.3f,0));
- node->addAnimator(anim);
- anim->drop();
- /*
- Same for the second cube, but with the second material we created.
- */
- // create test scene node 2, with the new created material type 2
- node = smgr->addCubeSceneNode(50);
- node->setPosition(core::vector3df(0,-10,50));
- node->setMaterialTexture(0, driver->getTexture(mediaPath + "wall.bmp"));
- node->setMaterialFlag(video::EMF_LIGHTING, false);
- node->setMaterialFlag(video::EMF_BLEND_OPERATION, true);
- node->setMaterialType((video::E_MATERIAL_TYPE)newMaterialType2);
- node->getMaterial(0).EmissiveColor = irr::video::SColor(0,50,0,50);
- smgr->addTextSceneNode(gui->getBuiltInFont(),
- L"PS & VS & EMT_TRANSPARENT",
- video::SColor(255,255,255,255), node);
- anim = smgr->createRotationAnimator(core::vector3df(0,0.3f,0));
- node->addAnimator(anim);
- anim->drop();
- /*
- Then we add a third cube without a shader on it, to be able to compare
- the cubes.
- */
- // add a scene node with no shader
- node = smgr->addCubeSceneNode(50);
- node->setPosition(core::vector3df(0,50,25));
- node->setMaterialTexture(0, driver->getTexture(mediaPath + "wall.bmp"));
- node->setMaterialFlag(video::EMF_LIGHTING, false);
- smgr->addTextSceneNode(gui->getBuiltInFont(), L"NO SHADER",
- video::SColor(255,255,255,255), node);
- /*
- And last, we add a skybox and a user controlled camera to the scene.
- For the skybox textures, we disable mipmap generation, because we don't
- need mipmaps on it.
- */
- // add a nice skybox
- driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
- smgr->addSkyBoxSceneNode(
- driver->getTexture(mediaPath + "irrlicht2_up.jpg"),
- driver->getTexture(mediaPath + "irrlicht2_dn.jpg"),
- driver->getTexture(mediaPath + "irrlicht2_lf.jpg"),
- driver->getTexture(mediaPath + "irrlicht2_rt.jpg"),
- driver->getTexture(mediaPath + "irrlicht2_ft.jpg"),
- driver->getTexture(mediaPath + "irrlicht2_bk.jpg"));
- driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, true);
- // add a camera and disable the mouse cursor
- scene::ICameraSceneNode* cam = smgr->addCameraSceneNodeFPS();
- cam->setPosition(core::vector3df(-100,50,100));
- cam->setTarget(core::vector3df(0,0,0));
- device->getCursorControl()->setVisible(false);
- /*
- Now draw everything. That's all.
- */
- int lastFPS = -1;
- while(device->run())
- if (device->isWindowActive())
- {
- driver->beginScene(video::ECBF_COLOR | video::ECBF_DEPTH, video::SColor(255,0,0,0));
- smgr->drawAll();
- driver->endScene();
- int fps = driver->getFPS();
- if (lastFPS != fps)
- {
- core::stringw str = L"Irrlicht Engine - Vertex and pixel shader example [";
- str += driver->getName();
- str += "] FPS:";
- str += fps;
- device->setWindowCaption(str.c_str());
- lastFPS = fps;
- }
- }
- device->drop();
- return 0;
- }
- /*
- Compile and run this, and I hope you have fun with your new little shader
- writing tool :).
- **/
|