diff --git a/Docs/Reference.dox b/Docs/Reference.dox index 1ee8804c3..58ee98faf 100644 --- a/Docs/Reference.dox +++ b/Docs/Reference.dox @@ -950,8 +950,34 @@ Textures can have an accompanying XML file which specifies load-time parameters, \endcode -The sRGB flag controls both whether the texture should be sampled with sRGB to linear conversion, and if used as a rendertarget, pixels should be converted back to sRGB when writing to it. -To control whether the backbuffer should use sRGB conversion on write, call \ref Graphics::SetSRGB "SetSRGB()" on the Graphics subsystem. +The sRGB flag controls both whether the texture should be sampled with sRGB to linear conversion, and if used as a rendertarget, pixels should be converted back to sRGB when writing to it. To control whether the backbuffer should use sRGB conversion on write, call \ref Graphics::SetSRGB "SetSRGB()" on the Graphics subsystem. + +\section Materials_CubeMapTextures Cube map textures + +Using cube map textures requires an XML file to define the cube map face textures or layout. In this case the XML file *is* the texture resource name in material scripts or in LoadResource() calls. + +Individual face textures are defined in the XML like this: (see Bin/Data/Textures/Skybox.xml for an example) + +\code + + + + + + + + +\endcode + +Using a single image texture and a layout is used like this: + +\code + + + +\endcode + +For the layout definitions, see http://www.cgtextures.com/content.php?action=tutorial&name=cubemaps and http://en.wikibooks.org/wiki/Blender_3D:_Noob_to_Pro/Build_a_skybox \section Materials_Techniques Techniques and passes diff --git a/Source/Engine/Graphics/Direct3D9/D3D9TextureCube.cpp b/Source/Engine/Graphics/Direct3D9/D3D9TextureCube.cpp index d99377a9f..e97530d58 100644 --- a/Source/Engine/Graphics/Direct3D9/D3D9TextureCube.cpp +++ b/Source/Engine/Graphics/Direct3D9/D3D9TextureCube.cpp @@ -42,6 +42,20 @@ namespace Urho3D { +static const char* cubeMapLayoutNames[] = { + "horizontal", + "horizontalnvidia", + "horizontalcross", + "verticalcross", + "blender", + 0 +}; + +static SharedPtr GetTileImage(Image* src, int tileX, int tileY, int tileWidth, int tileHeight) +{ + return SharedPtr(src->GetSubimage(IntRect(tileX * tileWidth, tileY * tileHeight, (tileX + 1) * tileWidth, (tileY + 1) * tileHeight))); +} + TextureCube::TextureCube(Context* context) : Texture(context), lockedLevel_(-1) @@ -96,21 +110,96 @@ bool TextureCube::BeginLoad(Deserializer& source) loadImages_.Clear(); XMLElement textureElem = loadParameters_->GetRoot(); - XMLElement faceElem = textureElem.GetChild("face"); - while (faceElem) + XMLElement imageElem = textureElem.GetChild("image"); + // Single image and multiple faces with layout + if (imageElem) { - String name = faceElem.GetAttribute("name"); - - String faceTexPath, faceTexName, faceTexExt; - SplitPath(name, faceTexPath, faceTexName, faceTexExt); + String name = imageElem.GetAttribute("name"); // If path is empty, add the XML file path - if (faceTexPath.Empty()) + if (GetPath(name).Empty()) name = texPath + name; - loadImages_.Push(cache->GetTempResource(name)); - cache->StoreResourceDependency(this, name); + CubeMapLayout layout = (CubeMapLayout)GetStringListIndex(imageElem.GetAttribute("layout").CString(), cubeMapLayoutNames, CML_HORIZONTAL); + SharedPtr image = cache->GetTempResource(name); + if (!image) + return false; - faceElem = faceElem.GetNext("face"); + int faceWidth, faceHeight; + loadImages_.Resize(MAX_CUBEMAP_FACES); + + switch (layout) + { + case CML_HORIZONTAL: + faceWidth = image->GetWidth() / MAX_CUBEMAP_FACES; + faceHeight = image->GetHeight(); + loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 0, 0, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 1, 0, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 2, 0, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 3, 0, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 4, 0, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 5, 0, faceWidth, faceHeight); + break; + + case CML_HORIZONTALNVIDIA: + faceWidth = image->GetWidth() / MAX_CUBEMAP_FACES; + faceHeight = image->GetHeight(); + for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i) + loadImages_[i] = GetTileImage(image, i, 0, faceWidth, faceHeight); + break; + + case CML_HORIZONTALCROSS: + faceWidth = image->GetWidth() / 4; + faceHeight = image->GetHeight() / 3; + loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 1, 0, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 0, 1, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 1, 1, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 2, 1, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 3, 1, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 1, 2, faceWidth, faceHeight); + break; + + case CML_VERTICALCROSS: + faceWidth = image->GetWidth() / 3; + faceHeight = image->GetHeight() / 4; + loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 1, 0, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 0, 1, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 1, 1, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 2, 1, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 1, 2, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 1, 3, faceWidth, faceHeight); + if (loadImages_[FACE_NEGATIVE_Z]) + loadImages_[FACE_NEGATIVE_Z]->FlipVertical(); + break; + + case CML_BLENDER: + faceWidth = image->GetWidth() / 3; + faceHeight = image->GetHeight() / 2; + loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 0, 0, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 1, 0, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 2, 0, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 0, 1, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 1, 1, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 2, 1, faceWidth, faceHeight); + break; + } + } + // Face per image + else + { + XMLElement faceElem = textureElem.GetChild("face"); + while (faceElem) + { + String name = faceElem.GetAttribute("name"); + + // If path is empty, add the XML file path + if (GetPath(name).Empty()) + name = texPath + name; + + loadImages_.Push(cache->GetTempResource(name)); + cache->StoreResourceDependency(this, name); + + faceElem = faceElem.GetNext("face"); + } } // Precalculate mip levels if async loading diff --git a/Source/Engine/Graphics/GraphicsDefs.h b/Source/Engine/Graphics/GraphicsDefs.h index 2c684ce38..da7405d82 100644 --- a/Source/Engine/Graphics/GraphicsDefs.h +++ b/Source/Engine/Graphics/GraphicsDefs.h @@ -186,6 +186,16 @@ enum CubeMapFace MAX_CUBEMAP_FACES }; +/// Cubemap single image layout modes. +enum CubeMapLayout +{ + CML_HORIZONTAL = 0, + CML_HORIZONTALNVIDIA, + CML_HORIZONTALCROSS, + CML_VERTICALCROSS, + CML_BLENDER +}; + /// Update mode for render surface viewports. enum RenderSurfaceUpdateMode { diff --git a/Source/Engine/Graphics/OpenGL/OGLTextureCube.cpp b/Source/Engine/Graphics/OpenGL/OGLTextureCube.cpp index 60d1cc89c..f5102c7c7 100644 --- a/Source/Engine/Graphics/OpenGL/OGLTextureCube.cpp +++ b/Source/Engine/Graphics/OpenGL/OGLTextureCube.cpp @@ -42,6 +42,20 @@ namespace Urho3D { +static const char* cubeMapLayoutNames[] = { + "horizontal", + "horizontalnvidia", + "horizontalcross", + "verticalcross", + "blender", + 0 +}; + +static SharedPtr GetTileImage(Image* src, int tileX, int tileY, int tileWidth, int tileHeight) +{ + return SharedPtr(src->GetSubimage(IntRect(tileX * tileWidth, tileY * tileHeight, (tileX + 1) * tileWidth, (tileY + 1) * tileHeight))); +} + TextureCube::TextureCube(Context* context) : Texture(context) { @@ -97,23 +111,98 @@ bool TextureCube::BeginLoad(Deserializer& source) loadImages_.Clear(); XMLElement textureElem = loadParameters_->GetRoot(); - XMLElement faceElem = textureElem.GetChild("face"); - while (faceElem) + XMLElement imageElem = textureElem.GetChild("image"); + // Single image and multiple faces with layout + if (imageElem) { - String name = faceElem.GetAttribute("name"); - - String faceTexPath, faceTexName, faceTexExt; - SplitPath(name, faceTexPath, faceTexName, faceTexExt); + String name = imageElem.GetAttribute("name"); // If path is empty, add the XML file path - if (faceTexPath.Empty()) + if (GetPath(name).Empty()) name = texPath + name; - loadImages_.Push(cache->GetTempResource(name)); - cache->StoreResourceDependency(this, name); + CubeMapLayout layout = (CubeMapLayout)GetStringListIndex(imageElem.GetAttribute("layout").CString(), cubeMapLayoutNames, CML_HORIZONTAL); + SharedPtr image = cache->GetTempResource(name); + if (!image) + return false; - faceElem = faceElem.GetNext("face"); + int faceWidth, faceHeight; + loadImages_.Resize(MAX_CUBEMAP_FACES); + + switch (layout) + { + case CML_HORIZONTAL: + faceWidth = image->GetWidth() / MAX_CUBEMAP_FACES; + faceHeight = image->GetHeight(); + loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 0, 0, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 1, 0, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 2, 0, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 3, 0, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 4, 0, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 5, 0, faceWidth, faceHeight); + break; + + case CML_HORIZONTALNVIDIA: + faceWidth = image->GetWidth() / MAX_CUBEMAP_FACES; + faceHeight = image->GetHeight(); + for (unsigned i = 0; i < MAX_CUBEMAP_FACES; ++i) + loadImages_[i] = GetTileImage(image, i, 0, faceWidth, faceHeight); + break; + + case CML_HORIZONTALCROSS: + faceWidth = image->GetWidth() / 4; + faceHeight = image->GetHeight() / 3; + loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 1, 0, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 0, 1, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 1, 1, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 2, 1, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 3, 1, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 1, 2, faceWidth, faceHeight); + break; + + case CML_VERTICALCROSS: + faceWidth = image->GetWidth() / 3; + faceHeight = image->GetHeight() / 4; + loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 1, 0, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 0, 1, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 1, 1, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 2, 1, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 1, 2, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 1, 3, faceWidth, faceHeight); + if (loadImages_[FACE_NEGATIVE_Z]) + loadImages_[FACE_NEGATIVE_Z]->FlipVertical(); + break; + + case CML_BLENDER: + faceWidth = image->GetWidth() / 3; + faceHeight = image->GetHeight() / 2; + loadImages_[FACE_NEGATIVE_X] = GetTileImage(image, 0, 0, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Z] = GetTileImage(image, 1, 0, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_X] = GetTileImage(image, 2, 0, faceWidth, faceHeight); + loadImages_[FACE_NEGATIVE_Y] = GetTileImage(image, 0, 1, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_Y] = GetTileImage(image, 1, 1, faceWidth, faceHeight); + loadImages_[FACE_POSITIVE_Z] = GetTileImage(image, 2, 1, faceWidth, faceHeight); + break; + } } - + // Face per image + else + { + XMLElement faceElem = textureElem.GetChild("face"); + while (faceElem) + { + String name = faceElem.GetAttribute("name"); + + // If path is empty, add the XML file path + if (GetPath(name).Empty()) + name = texPath + name; + + loadImages_.Push(cache->GetTempResource(name)); + cache->StoreResourceDependency(this, name); + + faceElem = faceElem.GetNext("face"); + } + } + // Precalculate mip levels if async loading if (GetAsyncLoadState() == ASYNC_LOADING) {