Tutorial 28: CubeMapping

Shows usage of cubemap textures and how to do some simple environment mapping. Cubemap textures have images for all 6 directions of a cube in a single texture. Environment is used to reflect the environment around an object onto the object. Cubemaps only work with shader materials which are written to support cube mapping.

Author
  Michael Zeilfelder, based on EnvCubeMap example from irrSpintz engine.

Start with the usual includes.

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif
#include <irrlicht.h>
#include "driverChoice.h"
#include "exampleHelper.h"
using namespace irr;

A callback class for our cubemap shader. We need a shader material which maps the cubemap texture to the polygon vertices of objects.

class CubeMapReflectionCallback : public video::IShaderConstantSetCallBack
{
public:
CubeMapReflectionCallback(scene::ISceneManager* smgr, int styleUVW)
: SceneMgr(smgr)
, StyleUVW(styleUVW), Roughness(0.f)
, styleUvwID(-1) , worldViewProjID(-1), worldID(-1), cameraPosID(-1)
{}

Setting the style to map vertex UV-coordinates to the cubemap textures.

- Specular style is typically used for mirrors and highlight reflections.
- Diffuse style is commonly used in image based lighting calculations and often in combination with a higher roughness. Think of it as the sum of all light which reaches a point on your object.
- Using model vertices directly for UV's is just nice for testing sometimes. Maybe has more uses? Experiment around :-)
  void SetStyleUVW(int style)
  {
  StyleUVW = style;
  }
  int GetStyleUVW() const
  {
  return StyleUVW;
  }
  We could also call this sharpness as the rougher a material the less sharp the reflections of a cubemap are (light for rough materials spreads out more while smooth materials reflect it more like a mirror). Roughness is calculated using the mipmaps of the cubemap texture. Note that rendertarget cubemap textures won't have mipmaps, so unfortunately it won't work for those. Also currently only OpenGL is able to interpolate seamless over cubemap borders. On Direct3D9 you will only smooth per side, but not over side-borders.
  void SetRoughness(float roughness)
  {
  Roughness = roughness;
  }
  float getRoughness() const
  {
  return Roughness;
  }
  Typical code which passes a few values from c++ to shader.
  virtual void OnSetMaterial(const video::SMaterial& material)
  {}
  virtual void OnSetConstants(video::IMaterialRendererServices* services, s32 userData)
  {
  video::IVideoDriver* driver = services->getVideoDriver();
  if ( worldViewProjID < 0 ) // first update
  {
  styleUvwID = services->getVertexShaderConstantID("StyleUVW");
  if( driver->getDriverType() == video::EDT_DIRECT3D9 )
  {
  worldViewProjID = services->getVertexShaderConstantID("WorldViewProj");
  }
  worldID = services->getVertexShaderConstantID("World");
  cameraPosID = services->getVertexShaderConstantID("CameraPos");
  roughnessID = services->getPixelShaderConstantID("Roughness");
  }
  services->setVertexShaderConstant(styleUvwID, &StyleUVW, 1 );
  irr::core::matrix4 world = driver->getTransform(irr::video::ETS_WORLD);
  services->setVertexShaderConstant(worldID, world.pointer(), 16);
  if( driver->getDriverType() == video::EDT_DIRECT3D9 )
  {
  irr::core::matrix4 worldViewProj;
  worldViewProj = driver->getTransform(irr::video::ETS_PROJECTION);
  worldViewProj *= driver->getTransform(irr::video::ETS_VIEW);
  worldViewProj *= world;
  services->setVertexShaderConstant(worldViewProjID, worldViewProj.pointer(), 16);
  }
  core::vector3df cameraPos = SceneMgr->getActiveCamera()->getAbsolutePosition();
  services->setVertexShaderConstant(cameraPosID, &cameraPos.X, 3 );
  services->setPixelShaderConstant(roughnessID, &Roughness, 1 );
  }
  private:
  scene::ISceneManager* SceneMgr;
  int StyleUVW; // 0 = specular, 1=diffuse, 2 = use model vertex coordinates for uvw.
  float Roughness; // cubemap 0 = specular ... highest value depends on number of mipmaps in the texture
  irr::s32 styleUvwID;
  irr::s32 worldViewProjID;
  irr::s32 worldID;
  irr::s32 cameraPosID;
  irr::s32 roughnessID;
  };
  To keep the example compact our event-receiver acts also like a main application class. So it handles user input, updates the dynamic parts of the UI and it keeps some 3d nodes around.
  class MyEventReceiver : public IEventReceiver
  {
  public:
  MyEventReceiver() : Driver(0), Shader(0)
  ,BackgroundSkybox(0), BackgroundCube(0)
  , CubemapUpdates(2)
  , CurrentStyleUVW(0), CurrentRoughness(0)
  , NeedCubemapUpdate(true)
  {
  StyleNamesUVW.push_back( L"specular" );
  StyleNamesUVW.push_back( L"diffuse" );
  StyleNamesUVW.push_back( L"model coordinates" );
  }
  // Handle the key input
  virtual bool OnEvent(const SEvent& event)
  {
  if (event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown == false)
  {
  switch(event.KeyInput.Key )
  {
  case KEY_SPACE:
  // Switch between different texture mapping styles
  if ( Shader )
  {
  Shader->SetStyleUVW((Shader->GetStyleUVW()+1)%StyleNamesUVW.size());
  updateStyleUVW();
  }
  break;
  case KEY_KEY_B:
  // Switch between our 2 different backgrounds
  if ( BackgroundSkybox && BackgroundCube )
  {
  if ( BackgroundSkybox->isVisible() )
  {
  BackgroundSkybox->setVisible(false);
  BackgroundCube->setVisible(true);
  }
  else
  {
  BackgroundSkybox->setVisible(true);
  BackgroundCube->setVisible(false);
  }
  NeedCubemapUpdate = true;
  }
  break;
  case KEY_KEY_I:
  // Show/hide the info text nodes
  for (u32 i=0; i<InfoTextNodes.size(); ++i )
  InfoTextNodes[i]->setVisible(!InfoTextNodes[i]->isVisible());
  break;
  case KEY_KEY_S:
  // Enable/disable seamless smoothing of mipmaps over cube borders
  if ( Driver )
  {
  Driver->disableFeature(video::EVDF_TEXTURE_CUBEMAP_SEAMLESS, Driver->queryFeature(video::EVDF_TEXTURE_CUBEMAP_SEAMLESS) );
  updateSeamless();
  }
  break;
  case KEY_KEY_U:
  // Switch dynamic cubemap updates on/off.
  CubemapUpdates = (CubemapUpdates+1) % 3;
  updateCubemapUpdates();
  break;
  case KEY_PLUS:
  case KEY_ADD:
  // Make material rougher
  if ( Shader )
  {
  Shader->SetRoughness( Shader->getRoughness() + 0.5f );
  updateRoughness();
  }
  break;
  case KEY_MINUS:
  case KEY_SUBTRACT:
  {
  // Make material smoother
  if ( Shader )
  {
  float roughness = Shader->getRoughness() - 0.5f;
  if ( roughness >= 0.f )
  {
  Shader->SetRoughness(roughness);
  updateRoughness();
  }
  }
  break;
  }
  default:
  break;
  }
  }
  return false;
  }
  // Some helper functions to update the UI
  void updateStyleUVW()
  {
  if ( CurrentStyleUVW && Shader)
  CurrentStyleUVW->setText(StyleNamesUVW[Shader->GetStyleUVW()].c_str());
  }
  void updateRoughness()
  {
  if ( CurrentRoughness && Shader )
  {
  CurrentRoughness->setText( irr::core::stringw(Shader->getRoughness()).c_str() );
  }
  }
  void updateSeamless()
  {
  if ( CurrentSeamlessCubemap && Driver )
  {
  CurrentSeamlessCubemap->setText( Driver->queryFeature(video::EVDF_TEXTURE_CUBEMAP_SEAMLESS) ? L"ON" : L"OFF" );
  }
  }
  void updateCubemapUpdates()
  {
  if ( CurrentCubemapUpdates )
  {
  switch ( CubemapUpdates )
  {
  case 0: CurrentCubemapUpdates->setText( L"static"); break;
  case 1: CurrentCubemapUpdates->setText( L"dynamic" ); break;
  case 2: CurrentCubemapUpdates->setText( L"dynamic+mips" ); break;
  }
  }
  }
  // Check if the cubemap textures should be updated with new screenshots
  // return 0 for no update, 1 for update, 2 for update and fix mip-maps
  int checkCubemapUpdate()
  {
  if ( NeedCubemapUpdate || CubemapUpdates == 2)
  {
  NeedCubemapUpdate = false;
  return 2;
  }
  return CubemapUpdates;
  }
  // Add some text-node floating above it's parent node.
  void addInfoTextNode(irr::gui::IGUIFont* font, const wchar_t* text, irr::scene::ISceneNode* parent)
  {
  if ( parent )
  {
  const video::SColor infoTextCol(250, 70, 90, 90);
  core::dimension2du dim(font->getDimension(text));
  core::dimension2df dimf((f32)dim.Width, (f32)dim.Height);
  scene::IBillboardTextSceneNode* infoNode = parent->getSceneManager()->addBillboardTextSceneNode( font, text, parent, dimf, core::vector3df(0, 120, 0), -1, infoTextCol, infoTextCol);
  InfoTextNodes.push_back(infoNode);
  }
  }
  irr::video::IVideoDriver* Driver;
  CubeMapReflectionCallback* Shader;
  scene::ISceneNode* BackgroundSkybox;
  scene::ISceneNode* BackgroundCube;
  irr::core::array<scene::ISceneNode*> InfoTextNodes;
  int CubemapUpdates; // 0 = static, 1 = dynamic, 2 = dynamic with rtt
  irr::core::array<irr::core::stringw> StyleNamesUVW;
  irr::gui::IGUIStaticText* CurrentStyleUVW;
  irr::gui::IGUIStaticText* CurrentRoughness;
  irr::gui::IGUIStaticText* CurrentSeamlessCubemap;
  irr::gui::IGUIStaticText* CurrentCubemapUpdates;
  private:
  bool NeedCubemapUpdate;
  };
  Workaround for OpenGL's upside-down images. Texture origins (0,0) in OpenGL are usually at the left-bottom instead of the more common left-top image formats. Irrlicht internally uses textures with left-top origin and then corrects the texture-matrices in the fixed-function pipeline. For shader materials it's left to the users to handle those UV-flips for the texture-matrix. Render target textures (RTT's) in OpenGL are rendered with left-bottom origin and Irrlicht can't change that, so all RTT textures in memory are upside-down (unlike all other Irrlicht textures). In the fixed function pipeline Irrlicht handles this by flipping the RTT's texture matrix once more and for shaders it's again left to the users to handle it. Cubemap textures are different from other textures in OpenGL. Each cube side has left-top as the origin. So not flipping Irrlicht textures for those would be fine. Except - OpenGL RTT's still render left-bottom - even when the target is a cubemap RTT. I found no good way around this so far - it just seems messed up as we get a left-handed/right handed coordinate system change that way.

So... the following 2 defines are two different workarounds I found. Both are ugly, which one is better in reality depends probably on the scene. Only use one of those: CUBEMAP_UPSIDE_DOWN_GL_PROJECTION is relatively fast as it just changes the project matrix. The problem is that changing the projection matrix means changing front/backside culling. So every node rendered has to flip the material flags for those.

CUBEMAP_USPIDE_DOWN_RTT will change the texture memory itself and flip the image upside-down. While easier to do, this involves texture-locking and is very slow.

#define CUBEMAP_UPSIDE_DOWN_GL_PROJECTION
//#define CUBEMAP_USPIDE_DOWN_RTT
// Flip frontface/backface culling for all nodes
#ifdef CUBEMAP_UPSIDE_DOWN_GL_PROJECTION
void flipCullingFlags(const core::array<scene::ISceneNode*>& nodes)
{
for ( irr::u32 n=0; n < nodes.size(); ++n )
{
scene::ISceneNode* node = nodes[n];
const irr::u32 matCount = node->getMaterialCount();
for ( irr::u32 m=0; m < matCount; ++m)
{
video::SMaterial& mat = node->getMaterial(m);
mat.BackfaceCulling = !mat.BackfaceCulling;
mat.FrontfaceCulling = !mat.FrontfaceCulling;
}
}
}
#endif

Render the environment around a node into a cubemap texture.

void renderEnvironmentCubeMap(irr::video::IVideoDriver* driver, irr::scene::ICameraSceneNode* cubeMapCamera, irr::scene::ISceneNode* cubeCenterNode, video::IRenderTarget* cubeMapRT, video::ITexture* dynamicCubeMapRTT, video::ITexture* depthStencilRTT)
{
// Change to the cubemap camera which has a few specific render-settings
scene::ISceneManager* smgr = cubeMapCamera->getSceneManager();
scene::ICameraSceneNode * oldCam = smgr->getActiveCamera();
smgr->setActiveCamera( cubeMapCamera );

We want to see everything around the center node, so hide the node itself, otherwise it would be in the way. Then set the camera to that node's position.

cubeCenterNode->setVisible( false );
const core::vector3df center( cubeCenterNode->getAbsolutePosition() );
cubeMapCamera->setPosition( center );

Render all 6 directions. Which means simple setting the camera target/up vector to all 6 directions and then render the full scene each time. So yeah - updating an environment cube-map means 6 full renders for each object which needs an environment map. In other words - you generally only want to do that in pre-processing, not in realtime.

const core::vector3df targetVecs[6] = {
core::vector3df(1.f, 0.f, 0.f),
core::vector3df(-1.f, 0.f, 0.f),
core::vector3df(0.f, 1.f, 0.f),
core::vector3df(0.f, -1.f, 0.f),
core::vector3df(0.f, 0.f, 1.f),
core::vector3df(0.f, 0.f, -1.f)
};
const core::vector3df upVecs[6] = {
core::vector3df( 0,1,0 ),
core::vector3df( 0,1,0 ),
core::vector3df( 0,0,-1 ),
core::vector3df( 0,0,1 ),
core::vector3df( 0,1,0 ),
core::vector3df( 0,1,0 )
};
for ( int s=0; s<6; ++s )
{
cubeMapCamera->setUpVector( upVecs[s] );
cubeMapCamera->setTarget( center + targetVecs[s] );
// Here we tell into which side of the cubemap texture we want to write
cubeMapRT->setTexture(dynamicCubeMapRTT, depthStencilRTT, (video::E_CUBE_SURFACE)(video::ECS_POSX + s));
driver->setRenderTargetEx(cubeMapRT, video::ECBF_ALL);
smgr->drawAll();
#ifdef CUBEMAP_USPIDE_DOWN_RTT
// This works because the lock for rtt's always flips in Irrlicht.
// So in this case lock() unlock will result in a flipped texture
// But be warned - it's very, very slow!
driver->setRenderTarget(0); // to avoid accessing active rt
dynamicCubeMapRTT->lock(video::ETLM_READ_WRITE, 0, s, video::ETLF_FLIP_Y_UP_RTT);
dynamicCubeMapRTT->unlock();
#endif
}
//dynamicCubeMapRTT->regenerateMipMapLevels(); // Unfortunately we can't seem to have mipmaps for rtt's
driver->setRenderTarget(0);
cubeCenterNode->setVisible( true );
smgr->setActiveCamera( oldCam );
}

Typical setup at the main start.

int main()
{
// Ask user for driver
video::E_DRIVER_TYPE driverType = driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;
// Create device
MyEventReceiver eventReceiver;
const core::dimension2d<u32> dimDevice(1024, 768);
IrrlichtDevice* device = createDevice( driverType, dimDevice, 32, false, false, false, &eventReceiver );
if (!device)
return 1;
const io::path mediaPath = getExampleMediaPath();
video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();
gui::IGUIEnvironment* env = device->getGUIEnvironment();
eventReceiver.Driver = driver;
// Set window title
core::stringw strCaption(L"Cubemap example - Irrlicht Engine [");
strCaption += driver->getName();
strCaption += L"]";
device->setWindowCaption(strCaption.c_str());
// set a nicer font
gui::IGUISkin* skin = env->getSkin();
gui::IGUIFont* font = env->getFont(mediaPath + "fonthaettenschweiler.bmp");
if (font)
skin->setFont(font);

Create a shader material for cube mapping

video::IGPUProgrammingServices* gpu = driver->getGPUProgrammingServices();
s32 cubeMapReflectionMaterial = 0;
if( gpu )
{
// Decide on shader to use based on active driver
irr::io::path vsFileName;
irr::io::path psFileName;
switch( driverType )
{
case video::EDT_DIRECT3D9:
vsFileName = mediaPath + "cubeMapReflectionVS.hlsl";
psFileName = mediaPath + "cubeMapReflectionPS.hlsl";
break;
case video::EDT_OPENGL:
vsFileName = mediaPath + "cubeMapReflection.vert";
psFileName = mediaPath + "cubeMapReflection.frag";
break;
}
CubeMapReflectionCallback* cubeMapCB = new CubeMapReflectionCallback(smgr, 2);
cubeMapReflectionMaterial = gpu->addHighLevelShaderMaterialFromFiles(
vsFileName, "VS", video::EVST_VS_1_1,
psFileName, "PS", video::EPST_PS_3_0,
cubeMapCB, video::EMT_SOLID );
if ( cubeMapReflectionMaterial >= 0 )
eventReceiver.Shader = cubeMapCB;
cubeMapCB->drop();
}
// add fps camera
scene::ICameraSceneNode* camera = smgr->addCameraSceneNodeFPS(0, 100.f, 1.f);
camera->setPosition( core::vector3df( 0,10,-200 ) );
device->getCursorControl()->setVisible(false);

Get 6 images forming a cubemap. The coordinate system used in those images seemed to be different than the one in Irrlicht. I decided to leave it like that because it's pretty common that way. If you get cubemap textures which seem to have x/y/z axis named different you'll just have to experiment until you figured out the correct order.

core::array<video::IImage*> cubeMapImages;
cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_posx.jpg" ));
cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_negx.jpg" ));
cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_posy.jpg" ));
cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_negy.jpg" ));
cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_posz.jpg" ));
cubeMapImages.push_back(driver->createImageFromFile( mediaPath + "cubemap_negz.jpg" ));

Create a cubemap texture from those images. Note that 6 images become a single texture now.

video::ITexture* cubeMapStaticTex = 0;
cubeMapStaticTex = driver->addTextureCubemap("cm", cubeMapImages[0], cubeMapImages[1], cubeMapImages[2], cubeMapImages[3], cubeMapImages[4], cubeMapImages[5]);
for ( u32 i=0; i<cubeMapImages.size(); ++i )
if ( cubeMapImages[i] )
cubeMapImages[i]->drop();
cubeMapImages.clear();

Create a render target, cubemap render-target-textures and a camera with settings for cube mapping

video::IRenderTarget* cubeMapRT = driver->addRenderTarget();
video::ITexture* dynamicCubeMapRTT = 0;
video::ITexture* depthStencilRTT = 0;
video::ITexture* dynamicCubeMapRTT_intermediate = 0; // just for rendering, but not used in material
video::ITexture* dynamicCubeMapTex = 0; // dynamic and with mipmaps
scene::ICameraSceneNode* cubeMapCamera = 0;
if( driver->queryFeature( video::EVDF_RENDER_TO_TARGET ) )
{
// Create cube map textures and render target cubemap textures.
const u32 dynamicCubeMapSize = 512;
dynamicCubeMapRTT = driver->addRenderTargetTextureCubemap(dynamicCubeMapSize, "cube_rtr");
depthStencilRTT = driver->addRenderTargetTexture(irr::core::dimension2du(dynamicCubeMapSize, dynamicCubeMapSize), "cubemap_ds", irr::video::ECF_D24S8);
dynamicCubeMapRTT_intermediate = driver->addRenderTargetTextureCubemap(dynamicCubeMapSize, "cube_rtr");
dynamicCubeMapTex = driver->addTextureCubemap(dynamicCubeMapSize, "cube_tex");
// Camera for creating an environment cubemap
cubeMapCamera = smgr->addCameraSceneNode();
cubeMapCamera->setFOV(core::PI* 0.5f); // 90° view angle
cubeMapCamera->setAspectRatio(1.f); // it's a cube... all sides have the same length
smgr->setActiveCamera( camera );
}

Add sphere-nodes which will be using the cubemaps as materials. You may also want to experiment with other node-types here!

scene::ISceneNode* sphereNode = 0;
scene::ISceneNode* sphereNode2 = 0;
scene::ISceneNode* sphereNode3 = 0;
scene::IMesh* sphereMesh = smgr->getGeometryCreator()->createSphereMesh(100.f);
if( sphereMesh )
{
// Nothing really special here except they need the shader material to display cubemaps.
sphereNode = smgr->addMeshSceneNode( sphereMesh );
sphereNode->setPosition( core::vector3df(-250,0,0) );
sphereNode->updateAbsolutePosition();
sphereNode->setMaterialFlag( video::EMF_LIGHTING, false );
sphereNode->setMaterialTexture( 0, dynamicCubeMapRTT );
sphereNode->setMaterialType( (video::E_MATERIAL_TYPE)cubeMapReflectionMaterial );
eventReceiver.addInfoTextNode(font, L"Cubemap dynamic rtt, no mip-maps", sphereNode);
if ( dynamicCubeMapTex )
{
sphereNode3 = smgr->addMeshSceneNode( sphereMesh );
sphereNode3->setPosition( core::vector3df(0,0,250) );
sphereNode3->updateAbsolutePosition();
sphereNode3->setMaterialFlag( video::EMF_LIGHTING, false );
sphereNode3->setMaterialTexture( 0, dynamicCubeMapTex );
sphereNode3->getMaterial(0).TextureLayer[0].TrilinearFilter = false; // this is default anyway. It would be faster - but you can only access integer mip-levels - no filtering between mip-levels.
sphereNode3->setMaterialType( (video::E_MATERIAL_TYPE)cubeMapReflectionMaterial );
eventReceiver.addInfoTextNode(font, L"Cubemap dynamic with mip-maps", sphereNode3);
}
if ( cubeMapStaticTex )
{
sphereNode2 = smgr->addMeshSceneNode( sphereMesh );
sphereNode2->setPosition( core::vector3df(250,0,0) );
sphereNode2->updateAbsolutePosition();
sphereNode2->setMaterialFlag( video::EMF_LIGHTING, false );
sphereNode2->setMaterialTexture( 0, cubeMapStaticTex );
sphereNode2->getMaterial(0).TextureLayer[0].TrilinearFilter = true; // this way smoothing happens between different mip-levels.
sphereNode2->setMaterialType( (video::E_MATERIAL_TYPE)cubeMapReflectionMaterial );
eventReceiver.addInfoTextNode(font, L"Cubemap fixed images", sphereNode2);
}
sphereMesh->drop();
}

Add some background which will show up in the environment maps. For first one we use the same textures as used in the spheres. Note the difference between a skybox and a cubemap is that the skybox really uses 6 different textures. While the cubemap uses a single texture created from 6 images.

eventReceiver.BackgroundSkybox = smgr->addSkyBoxSceneNode(
driver->getTexture(mediaPath + "cubemap_posy.jpg"), // top
driver->getTexture(mediaPath + "cubemap_negy.jpg"), // bottom
driver->getTexture(mediaPath + "cubemap_posz.jpg"), // left
driver->getTexture(mediaPath + "cubemap_negz.jpg"), // right
driver->getTexture(mediaPath + "cubemap_posx.jpg"), // front
driver->getTexture(mediaPath + "cubemap_negx.jpg")); // back

Another background for comparison and to make it more obvious when the spheres reflect the environment and when they use static cubemaps.

scene::IMesh * cubeMesh = smgr->getGeometryCreator()->createCubeMesh( core::vector3df(10.f, 10.f, 10.f), scene::ECMT_6BUF_4VTX_NP);
smgr->getMeshManipulator()->scale(cubeMesh, core::vector3df(-1, 1, 1));
if( cubeMesh )
{
smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(0), video::SColor(255, 240, 10, 10) );
smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(1), video::SColor(255, 240, 130, 10) );
smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(2), video::SColor(255, 50, 250, 10) );
smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(3), video::SColor(255, 70, 10, 250) );
smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(4), video::SColor(255, 240, 250, 10) );
smgr->getMeshManipulator()->setVertexColors( cubeMesh->getMeshBuffer(5), video::SColor(255, 85, 250, 250) );
eventReceiver.BackgroundCube = smgr->addMeshSceneNode( cubeMesh );
cubeMesh->drop();
eventReceiver.BackgroundCube->setScale( core::vector3df( 200, 200, 200 ) );
eventReceiver.BackgroundCube->setMaterialFlag( video::EMF_LIGHTING, false );
eventReceiver.BackgroundCube->setVisible(false);
}
#ifdef CUBEMAP_UPSIDE_DOWN_GL_PROJECTION
if ( driverType == video::EDT_OPENGL )
{
// Flip projection matrix (note this also flips front/backface culling)
core::matrix4 matProj = cubeMapCamera->getProjectionMatrix();
matProj[4] = -matProj[4];
matProj[5] = -matProj[5];
matProj[6] = -matProj[6];
matProj[7] = -matProj[7];
cubeMapCamera->setProjectionMatrix(matProj);
}
#endif

Add some moving node to show the difference between static/dynamic environment maps

scene::IMeshSceneNode * movingNode = smgr->addCubeSceneNode(30.f);
movingNode->getMaterial(0).Lighting = false;
smgr->getMeshManipulator()->setVertexColors( movingNode->getMesh()->getMeshBuffer(0), video::SColor(255, 230, 200, 150));
scene::ISceneNodeAnimator* circleAnimator = smgr->createFlyCircleAnimator(core::vector3df(-125, -50.f, 125), 300.f, 0.0005f);
movingNode->addAnimator(circleAnimator);
circleAnimator->drop();

Add some UI

if ( eventReceiver.Shader )
{
skin->setColor(gui::EGDC_3D_FACE, video::SColor(50, 160, 120, 120));
u32 top = dimDevice.Height - 200;
const u32 left = dimDevice.Width - 350;
const u32 right = dimDevice.Width - 10;
irr::gui::IGUIStaticText * stextUVW = env->addStaticText(L" Style of generating texture coordinates:\n Change with (space)", core::recti(left, top, right, top+35), false, true, 0, -1, true);
top += 40;
stextUVW->setTextAlignment(gui::EGUIA_UPPERLEFT, gui::EGUIA_UPPERLEFT);
eventReceiver.CurrentStyleUVW = env->addStaticText(L"", core::recti(240,0, 400, 20), false, false, stextUVW);
eventReceiver.updateStyleUVW();
irr::gui::IGUIStaticText * stextRoughness = env->addStaticText(L" Roughness:\n Change with (+) and (-)", core::recti(left, top, right, top+35), false, true, 0, -1, true);
top += 40;
eventReceiver.CurrentRoughness = env->addStaticText( L"", core::recti(240,0, 400, 20), false, false, stextRoughness);
eventReceiver.updateRoughness();
irr::gui::IGUIStaticText * stextSeamlessCupemap = env->addStaticText(L" Seamless cubemap (with roughness):\n Change with (s)", core::recti(left, top, right, top+35), false, true, 0, -1, true);
top += 40;
eventReceiver.CurrentSeamlessCubemap = env->addStaticText( L"", core::recti(240,0, 400, 20), false, false, stextSeamlessCupemap);
eventReceiver.updateSeamless();
irr::gui::IGUIStaticText * stextUpdates = env->addStaticText(L" Cubemap updates:\n Change with (u)", core::recti(left, top, right, top+35), false, true, 0, -1, true);
top += 40;
eventReceiver.CurrentCubemapUpdates = env->addStaticText( L"", core::recti(240,0, 400, 20), false, false, stextUpdates);
eventReceiver.updateCubemapUpdates();
env->addStaticText(L" Change background with (b)", core::recti(left, top, right, top+15), false, true, 0, -1, true);
top += 20;
env->addStaticText(L" Show/hide info nodes with (i)", core::recti(left, top, right, top+15), false, true, 0, -1, true);
}

Main loop

while(device->run())
{
if (device->isWindowActive())
{
driver->beginScene(true, true, video::SColor(255, 127, 127, 255));

Check if we want to update the environment maps. Usually not something you'll do every frame, but either once at the star or maybe updating an environment map once in a while.

int updateCubemaps = eventReceiver.checkCubemapUpdate();
if( dynamicCubeMapRTT && sphereNode && updateCubemaps > 0 )
{
#ifdef CUBEMAP_UPSIDE_DOWN_GL_PROJECTION
core::array<scene::ISceneNode*> allNodes;
if ( driverType == video::EDT_OPENGL )
{

Flipping projection matrix flips front/backface culling. We only have a skybox so in this case this still would be fast, with more objects it's getting more ugly.

smgr->getSceneNodesFromType(scene::ESNT_ANY, allNodes);
flipCullingFlags(allNodes);
}
#endif

If rendered just once then this node has still a white (or even undefined) texture at this point Just hiding it and render the background when rendering the cubemap for the other node is less noticable than having a big white dot in the environment texture. Render order can matter if you want several environment maps in your scene.

if (sphereNode3)
sphereNode3->setVisible(false);
renderEnvironmentCubeMap(driver, cubeMapCamera, sphereNode, cubeMapRT, dynamicCubeMapRTT, depthStencilRTT);
if ( sphereNode3)
{
if ( updateCubemaps == 2 )
{

Our rtt's unfortunately don't have mipmaps (sorry, not sure if we can get that somehow...) So if we want mipmaps in the dynamic cubemap we have to copy it to a non-rtt texture. Warning: Very, very slow. Far slower than just creating an environment map as this will copy the texture from GPU to main memory - copy it to a new texture, create mip-maps and upload the result back to the GPU.

renderEnvironmentCubeMap(driver, cubeMapCamera, sphereNode3, cubeMapRT, dynamicCubeMapRTT_intermediate, depthStencilRTT);
for ( int i=0; i<6; ++i)
{
void * rtData = dynamicCubeMapRTT_intermediate->lock(video::ETLM_READ_ONLY, 0, i, video::ETLF_NONE);
void * tData = dynamicCubeMapTex->lock(video::ETLM_READ_WRITE, 0, i);
memcpy(tData, rtData, dynamicCubeMapTex->getPitch()*dynamicCubeMapTex->getSize().Width);
dynamicCubeMapRTT_intermediate->unlock();
dynamicCubeMapTex->unlock();
dynamicCubeMapTex->regenerateMipMapLevels();
}
}
sphereNode3->setVisible(true);
}
#ifdef CUBEMAP_UPSIDE_DOWN_GL_PROJECTION
if ( driverType == video::EDT_OPENGL )
{
flipCullingFlags(allNodes);
}
#endif
}
smgr->drawAll();
env->drawAll();
driver->endScene();
}
}
device->drop();
return 0;
}