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)
{