Explorar o código

Support single image splitting for cube maps. Closes #445.

Lasse Öörni %!s(int64=11) %!d(string=hai) anos
pai
achega
f1e25381a3

+ 28 - 2
Docs/Reference.dox

@@ -950,8 +950,34 @@ Textures can have an accompanying XML file which specifies load-time parameters,
 </texture>
 \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
+<cubemap>
+    <face name="PositiveX_TextureName" />
+    <face name="NegativeX_TextureName" />
+    <face name="PositiveY_TextureName" />
+    <face name="NegativeY_TextureName" />
+    <face name="PositiveZ_TextureName" />
+    <face name="NegativeZ_TextureName" />
+</cubemap>
+\endcode
+
+Using a single image texture and a layout is used like this:
+
+\code
+<cubemap>
+    <image name="TextureName" layout="horizontal|horizontalnvidia|horizontalcross|verticalcross|blender" />
+</cubemap>
+\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
 

+ 99 - 10
Source/Engine/Graphics/Direct3D9/D3D9TextureCube.cpp

@@ -42,6 +42,20 @@
 namespace Urho3D
 {
 
+static const char* cubeMapLayoutNames[] = {
+    "horizontal",
+    "horizontalnvidia",
+    "horizontalcross",
+    "verticalcross",
+    "blender",
+    0
+};
+
+static SharedPtr<Image> GetTileImage(Image* src, int tileX, int tileY, int tileWidth, int tileHeight)
+{
+    return SharedPtr<Image>(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<Image>(name));
-        cache->StoreResourceDependency(this, name);
+        CubeMapLayout layout = (CubeMapLayout)GetStringListIndex(imageElem.GetAttribute("layout").CString(), cubeMapLayoutNames, CML_HORIZONTAL);
+        SharedPtr<Image> image = cache->GetTempResource<Image>(name);
+        if (!image)
+            return false;
+        
+        int faceWidth, faceHeight;
+        loadImages_.Resize(MAX_CUBEMAP_FACES);
         
-        faceElem = faceElem.GetNext("face");
+        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<Image>(name));
+            cache->StoreResourceDependency(this, name);
+            
+            faceElem = faceElem.GetNext("face");
+        }
     }
 
     // Precalculate mip levels if async loading

+ 10 - 0
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
 {

+ 100 - 11
Source/Engine/Graphics/OpenGL/OGLTextureCube.cpp

@@ -42,6 +42,20 @@
 namespace Urho3D
 {
 
+static const char* cubeMapLayoutNames[] = {
+    "horizontal",
+    "horizontalnvidia",
+    "horizontalcross",
+    "verticalcross",
+    "blender",
+    0
+};
+
+static SharedPtr<Image> GetTileImage(Image* src, int tileX, int tileY, int tileWidth, int tileHeight)
+{
+    return SharedPtr<Image>(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<Image>(name));
-        cache->StoreResourceDependency(this, name);
+        CubeMapLayout layout = (CubeMapLayout)GetStringListIndex(imageElem.GetAttribute("layout").CString(), cubeMapLayoutNames, CML_HORIZONTAL);
+        SharedPtr<Image> image = cache->GetTempResource<Image>(name);
+        if (!image)
+            return false;
+        
+        int faceWidth, faceHeight;
+        loadImages_.Resize(MAX_CUBEMAP_FACES);
         
-        faceElem = faceElem.GetNext("face");
+        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<Image>(name));
+            cache->StoreResourceDependency(this, name);
+            
+            faceElem = faceElem.GetNext("face");
+        }
+    }
+    
     // Precalculate mip levels if async loading
     if (GetAsyncLoadState() == ASYNC_LOADING)
     {