diff --git a/interface/resources/shaders/model_normal_specular_map.frag b/interface/resources/shaders/model_normal_specular_map.frag
new file mode 100644
index 0000000000..79761446b1
--- /dev/null
+++ b/interface/resources/shaders/model_normal_specular_map.frag
@@ -0,0 +1,47 @@
+#version 120
+
+//
+//  model_normal_specular_map.frag
+//  fragment shader
+//
+//  Created by Andrzej Kapolka on 5/6/14.
+//  Copyright 2014 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+// the diffuse texture
+uniform sampler2D diffuseMap;
+
+// the normal map texture
+uniform sampler2D normalMap;
+
+// the specular map texture
+uniform sampler2D specularMap;
+
+// the interpolated normal
+varying vec4 interpolatedNormal;
+
+// the interpolated tangent
+varying vec4 interpolatedTangent;
+
+void main(void) {
+    vec3 normalizedNormal = normalize(vec3(interpolatedNormal));
+    vec3 normalizedTangent = normalize(vec3(interpolatedTangent));
+    vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent));
+    vec3 localNormal = vec3(texture2D(normalMap, gl_TexCoord[0].st)) * 2.0 - vec3(1.0, 1.0, 1.0);
+
+    // compute the base color based on OpenGL lighting model
+    vec4 viewNormal = vec4(normalizedTangent * localNormal.x +
+        normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0);
+    vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
+        gl_FrontLightProduct[0].diffuse * max(0.0, dot(viewNormal, gl_LightSource[0].position)));
+
+    // compute the specular component (sans exponent)
+    float specular = max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)), viewNormal));
+    
+    // modulate texture by base color and add specular contribution
+    gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) *
+        gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
+}
diff --git a/interface/resources/shaders/model_specular_map.frag b/interface/resources/shaders/model_specular_map.frag
new file mode 100644
index 0000000000..972a8e2de6
--- /dev/null
+++ b/interface/resources/shaders/model_specular_map.frag
@@ -0,0 +1,35 @@
+#version 120
+
+//
+//  model_specular_map.frag
+//  fragment shader
+//
+//  Created by Andrzej Kapolka on 5/6/14.
+//  Copyright 2014 High Fidelity, Inc.
+//
+//  Distributed under the Apache License, Version 2.0.
+//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+// the diffuse texture
+uniform sampler2D diffuseMap;
+
+// the specular texture
+uniform sampler2D specularMap;
+
+// the interpolated normal
+varying vec4 normal;
+
+void main(void) {
+    // compute the base color based on OpenGL lighting model
+    vec4 normalizedNormal = normalize(normal);
+    vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
+        gl_FrontLightProduct[0].diffuse * max(0.0, dot(normalizedNormal, gl_LightSource[0].position)));
+
+    // compute the specular component (sans exponent)
+    float specular = max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)), normalizedNormal));
+    
+    // modulate texture by base color and add specular contribution
+    gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) *
+        gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
+}
diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp
index bf6a868368..08719f0f25 100644
--- a/interface/src/ModelUploader.cpp
+++ b/interface/src/ModelUploader.cpp
@@ -443,6 +443,14 @@ bool ModelUploader::addTextures(const QString& texdir, const FBXGeometry& geomet
                 }
                 added.insert(part.normalTexture.filename);
             }
+            if (!part.specularTexture.filename.isEmpty() && part.specularTexture.content.isEmpty() &&
+                    !added.contains(part.specularTexture.filename)) {
+                if (!addPart(texdir + "/" + part.specularTexture.filename,
+                             QString("texture%1").arg(++_texturesCount), true)) {
+                    return false;
+                }
+                added.insert(part.specularTexture.filename);
+            }
         }
     }
     
diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp
index 6e93fc77af..8d31cdce1d 100644
--- a/interface/src/renderer/GeometryCache.cpp
+++ b/interface/src/renderer/GeometryCache.cpp
@@ -342,7 +342,8 @@ bool NetworkGeometry::isLoadedWithTextures() const {
     foreach (const NetworkMesh& mesh, _meshes) {
         foreach (const NetworkMeshPart& part, mesh.parts) {
             if ((part.diffuseTexture && !part.diffuseTexture->isLoaded()) ||
-                    (part.normalTexture && !part.normalTexture->isLoaded())) {
+                    (part.normalTexture && !part.normalTexture->isLoaded()) ||
+                    (part.specularTexture && !part.specularTexture->isLoaded())) {
                 return false;
             }
         }
@@ -416,6 +417,9 @@ void NetworkGeometry::setLoadPriority(const QPointer<QObject>& owner, float prio
             if (part.normalTexture) {
                 part.normalTexture->setLoadPriority(owner, priority);
             }
+            if (part.specularTexture) {
+                part.specularTexture->setLoadPriority(owner, priority);
+            }
         }
     }
 }
@@ -433,6 +437,9 @@ void NetworkGeometry::setLoadPriorities(const QHash<QPointer<QObject>, float>& p
             if (part.normalTexture) {
                 part.normalTexture->setLoadPriorities(priorities);
             }
+            if (part.specularTexture) {
+                part.specularTexture->setLoadPriorities(priorities);
+            }
         }
     }
 }
@@ -450,6 +457,9 @@ void NetworkGeometry::clearLoadPriority(const QPointer<QObject>& owner) {
             if (part.normalTexture) {
                 part.normalTexture->clearLoadPriority(owner);
             }
+            if (part.specularTexture) {
+                part.specularTexture->clearLoadPriority(owner);
+            }
         }
     }
 }
@@ -566,6 +576,11 @@ void NetworkGeometry::setGeometry(const FBXGeometry& geometry) {
                     _textureBase.resolved(QUrl(part.normalTexture.filename)), true, false, part.normalTexture.content);
                 networkPart.normalTexture->setLoadPriorities(_loadPriorities);
             }
+            if (!part.specularTexture.filename.isEmpty()) {
+                networkPart.specularTexture = Application::getInstance()->getTextureCache()->getTexture(
+                    _textureBase.resolved(QUrl(part.specularTexture.filename)), true, false, part.specularTexture.content);
+                networkPart.specularTexture->setLoadPriorities(_loadPriorities);
+            }
             networkMesh.parts.append(networkPart);
                         
             totalIndices += (part.quadIndices.size() + part.triangleIndices.size());
diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h
index 0ad4f73904..a9b274fedc 100644
--- a/interface/src/renderer/GeometryCache.h
+++ b/interface/src/renderer/GeometryCache.h
@@ -124,6 +124,7 @@ public:
     
     QSharedPointer<NetworkTexture> diffuseTexture;
     QSharedPointer<NetworkTexture> normalTexture;
+    QSharedPointer<NetworkTexture> specularTexture;
     
     bool isTranslucent() const;
 };
diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp
index b8b4f1f2a0..2a9a55abf0 100644
--- a/interface/src/renderer/Model.cpp
+++ b/interface/src/renderer/Model.cpp
@@ -56,13 +56,20 @@ Model::~Model() {
 
 ProgramObject Model::_program;
 ProgramObject Model::_normalMapProgram;
+ProgramObject Model::_specularMapProgram;
+ProgramObject Model::_normalSpecularMapProgram;
 ProgramObject Model::_shadowProgram;
 ProgramObject Model::_skinProgram;
 ProgramObject Model::_skinNormalMapProgram;
+ProgramObject Model::_skinSpecularMapProgram;
+ProgramObject Model::_skinNormalSpecularMapProgram;
 ProgramObject Model::_skinShadowProgram;
 int Model::_normalMapTangentLocation;
+int Model::_normalSpecularMapTangentLocation;
 Model::SkinLocations Model::_skinLocations;
 Model::SkinLocations Model::_skinNormalMapLocations;
+Model::SkinLocations Model::_skinSpecularMapLocations;
+Model::SkinLocations Model::_skinNormalSpecularMapLocations;
 Model::SkinLocations Model::_skinShadowLocations;
 
 void Model::setScale(const glm::vec3& scale) {
@@ -92,7 +99,7 @@ void Model::setOffset(const glm::vec3& offset) {
 }
 
 
-void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations) {
+void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations, int specularTextureUnit) {
     program.bind();
     locations.clusterMatrices = program.uniformLocation("clusterMatrices");
     locations.clusterIndices = program.attributeLocation("clusterIndices");
@@ -100,6 +107,7 @@ void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locati
     locations.tangent = program.attributeLocation("tangent");
     program.setUniformValue("diffuseMap", 0);
     program.setUniformValue("normalMap", 1);
+    program.setUniformValue("specularMap", specularTextureUnit);
     program.release();
 }
 
@@ -162,10 +170,10 @@ void Model::init() {
         _program.setUniformValue("texture", 0);
         _program.release();
         
-        _normalMapProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath()
-                                                  + "shaders/model_normal_map.vert");
-        _normalMapProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath()
-                                                  + "shaders/model_normal_map.frag");
+        _normalMapProgram.addShaderFromSourceFile(QGLShader::Vertex,
+            Application::resourcesPath() + "shaders/model_normal_map.vert");
+        _normalMapProgram.addShaderFromSourceFile(QGLShader::Fragment,
+            Application::resourcesPath() + "shaders/model_normal_map.frag");
         _normalMapProgram.link();
         
         _normalMapProgram.bind();
@@ -174,27 +182,65 @@ void Model::init() {
         _normalMapTangentLocation = _normalMapProgram.attributeLocation("tangent");
         _normalMapProgram.release();
         
+        _specularMapProgram.addShaderFromSourceFile(QGLShader::Vertex,
+            Application::resourcesPath() + "shaders/model.vert");
+        _specularMapProgram.addShaderFromSourceFile(QGLShader::Fragment,
+            Application::resourcesPath() + "shaders/model_specular_map.frag");
+        _specularMapProgram.link();
+        
+        _specularMapProgram.bind();
+        _specularMapProgram.setUniformValue("diffuseMap", 0);
+        _specularMapProgram.setUniformValue("specularMap", 1);
+        _specularMapProgram.release();
+        
+        _normalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Vertex,
+            Application::resourcesPath() + "shaders/model_normal_map.vert");
+        _normalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Fragment,
+            Application::resourcesPath() + "shaders/model_normal_specular_map.frag");
+        _normalSpecularMapProgram.link();
+        
+        _normalSpecularMapProgram.bind();
+        _normalSpecularMapProgram.setUniformValue("diffuseMap", 0);
+        _normalSpecularMapProgram.setUniformValue("normalMap", 1);
+        _normalSpecularMapProgram.setUniformValue("specularMap", 2);
+        _normalSpecularMapTangentLocation = _normalMapProgram.attributeLocation("tangent");
+        _normalSpecularMapProgram.release();
+        
         _shadowProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/model_shadow.vert");
-        _shadowProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() +
-            "shaders/model_shadow.frag");
+        _shadowProgram.addShaderFromSourceFile(QGLShader::Fragment,
+            Application::resourcesPath() + "shaders/model_shadow.frag");
         _shadowProgram.link();
         
-        _skinProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath()
-                                             + "shaders/skin_model.vert");
-        _skinProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath()
-                                             + "shaders/model.frag");
+        _skinProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/skin_model.vert");
+        _skinProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/model.frag");
         _skinProgram.link();
         
         initSkinProgram(_skinProgram, _skinLocations);
         
-        _skinNormalMapProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath()
-                                                      + "shaders/skin_model_normal_map.vert");
-        _skinNormalMapProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath()
-                                                      + "shaders/model_normal_map.frag");
+        _skinNormalMapProgram.addShaderFromSourceFile(QGLShader::Vertex,
+            Application::resourcesPath() + "shaders/skin_model_normal_map.vert");
+        _skinNormalMapProgram.addShaderFromSourceFile(QGLShader::Fragment,
+            Application::resourcesPath() + "shaders/model_normal_map.frag");
         _skinNormalMapProgram.link();
         
         initSkinProgram(_skinNormalMapProgram, _skinNormalMapLocations);
         
+        _skinSpecularMapProgram.addShaderFromSourceFile(QGLShader::Vertex,
+            Application::resourcesPath() + "shaders/skin_model.vert");
+        _skinSpecularMapProgram.addShaderFromSourceFile(QGLShader::Fragment,
+            Application::resourcesPath() + "shaders/model_specular_map.frag");
+        _skinSpecularMapProgram.link();
+        
+        initSkinProgram(_skinSpecularMapProgram, _skinSpecularMapLocations);
+        
+        _skinNormalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Vertex,
+            Application::resourcesPath() + "shaders/skin_model_normal_map.vert");
+        _skinNormalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Fragment,
+            Application::resourcesPath() + "shaders/model_normal_specular_map.frag");
+        _skinNormalSpecularMapProgram.link();
+        
+        initSkinProgram(_skinNormalSpecularMapProgram, _skinNormalSpecularMapLocations, 2);
+        
         _skinShadowProgram.addShaderFromSourceFile(QGLShader::Vertex,
             Application::resourcesPath() + "shaders/skin_model_shadow.vert");
         _skinShadowProgram.addShaderFromSourceFile(QGLShader::Fragment,
@@ -1331,15 +1377,29 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) {
         ProgramObject* program = &_program;
         ProgramObject* skinProgram = &_skinProgram;
         SkinLocations* skinLocations = &_skinLocations;
+        GLenum specularTextureUnit = 0;
         if (mode == SHADOW_RENDER_MODE) {
             program = &_shadowProgram;
             skinProgram = &_skinShadowProgram;
             skinLocations = &_skinShadowLocations;
             
         } else if (!mesh.tangents.isEmpty()) {
-            program = &_normalMapProgram;
-            skinProgram = &_skinNormalMapProgram;
-            skinLocations = &_skinNormalMapLocations;
+            if (mesh.hasSpecularTexture()) {
+                program = &_normalSpecularMapProgram;
+                skinProgram = &_skinNormalSpecularMapProgram;
+                skinLocations = &_skinNormalSpecularMapLocations;
+                specularTextureUnit = GL_TEXTURE2;
+                
+            } else {
+                program = &_normalMapProgram;
+                skinProgram = &_skinNormalMapProgram;
+                skinLocations = &_skinNormalMapLocations;
+            }
+        } else if (mesh.hasSpecularTexture()) {
+            program = &_specularMapProgram;
+            skinProgram = &_skinSpecularMapProgram;
+            skinLocations = &_skinSpecularMapLocations;
+            specularTextureUnit = GL_TEXTURE1;
         }
         
         const MeshState& state = _meshStates.at(i);
@@ -1427,13 +1487,23 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) {
                 glBindTexture(GL_TEXTURE_2D, !diffuseMap ?
                     Application::getInstance()->getTextureCache()->getWhiteTextureID() : diffuseMap->getID());
                 
+                
                 if (!mesh.tangents.isEmpty()) {
+                    specularTextureUnit = GL_TEXTURE2;                    
                     glActiveTexture(GL_TEXTURE1);                
                     Texture* normalMap = networkPart.normalTexture.data();
                     glBindTexture(GL_TEXTURE_2D, !normalMap ?
                         Application::getInstance()->getTextureCache()->getBlueTextureID() : normalMap->getID());
                     glActiveTexture(GL_TEXTURE0);
                 }
+                
+                if (specularTextureUnit) {
+                    glActiveTexture(specularTextureUnit);
+                    Texture* specularMap = networkPart.specularTexture.data();
+                    glBindTexture(GL_TEXTURE_2D, !specularMap ?
+                        Application::getInstance()->getTextureCache()->getWhiteTextureID() : specularMap->getID());
+                    glActiveTexture(GL_TEXTURE0);
+                }
             }
             glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset);
             offset += part.quadIndices.size() * sizeof(int);
@@ -1456,7 +1526,13 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) {
             
             activeProgram->disableAttributeArray(tangentLocation);
         }
-                
+        
+        if (specularTextureUnit) {
+            glActiveTexture(specularTextureUnit);
+            glBindTexture(GL_TEXTURE_2D, 0);
+            glActiveTexture(GL_TEXTURE0);
+        }
+        
         if (state.clusterMatrices.size() > 1) {
             skinProgram->disableAttributeArray(skinLocations->clusterIndices);
             skinProgram->disableAttributeArray(skinLocations->clusterWeights);  
diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h
index 6a79772ca7..1a469c8122 100644
--- a/interface/src/renderer/Model.h
+++ b/interface/src/renderer/Model.h
@@ -318,12 +318,17 @@ private:
 
     static ProgramObject _program;
     static ProgramObject _normalMapProgram;
+    static ProgramObject _specularMapProgram;
+    static ProgramObject _normalSpecularMapProgram;
     static ProgramObject _shadowProgram;
     static ProgramObject _skinProgram;
     static ProgramObject _skinNormalMapProgram;
+    static ProgramObject _skinSpecularMapProgram;
+    static ProgramObject _skinNormalSpecularMapProgram;
     static ProgramObject _skinShadowProgram;
     
     static int _normalMapTangentLocation;
+    static int _normalSpecularMapTangentLocation;
     
     class SkinLocations {
     public:
@@ -335,9 +340,11 @@ private:
     
     static SkinLocations _skinLocations;
     static SkinLocations _skinNormalMapLocations;
+    static SkinLocations _skinSpecularMapLocations;
+    static SkinLocations _skinNormalSpecularMapLocations;
     static SkinLocations _skinShadowLocations;
     
-    static void initSkinProgram(ProgramObject& program, SkinLocations& locations);
+    static void initSkinProgram(ProgramObject& program, SkinLocations& locations, int specularTextureUnit = 1);
 };
 
 Q_DECLARE_METATYPE(QPointer<Model>)
diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp
index 40a7d56c0d..d637526067 100644
--- a/libraries/fbx/src/FBXReader.cpp
+++ b/libraries/fbx/src/FBXReader.cpp
@@ -54,6 +54,15 @@ void Extents::addPoint(const glm::vec3& point) {
     maximum = glm::max(maximum, point);
 }
 
+bool FBXMesh::hasSpecularTexture() const {
+    foreach (const FBXMeshPart& part, parts) {
+        if (!part.specularTexture.filename.isEmpty()) {
+            return true;
+        }
+    }
+    return false;
+}
+
 QStringList FBXGeometry::getJointNames() const {
     QStringList names;
     foreach (const FBXJoint& joint, joints) {
@@ -976,6 +985,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
     QHash<QString, Material> materials;
     QHash<QString, QString> diffuseTextures;
     QHash<QString, QString> bumpTextures;
+    QHash<QString, QString> specularTextures;
     QHash<QString, QString> localRotations;
     QHash<QString, QString> xComponents;
     QHash<QString, QString> yComponents;
@@ -1330,6 +1340,9 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
                         } else if (type.contains("bump") || type.contains("normal")) {
                             bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
                         
+                        } else if (type.contains("specular")) {
+                            specularTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));    
+                            
                         } else if (type == "lcl rotation") {
                             localRotations.insert(getID(connection.properties, 2), getID(connection.properties, 1));
                             
@@ -1546,6 +1559,12 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
                     generateTangents = true;
                 }
                 
+                FBXTexture specularTexture;
+                QString specularTextureID = specularTextures.value(childID);
+                if (!specularTextureID.isNull()) {
+                    specularTexture = getTexture(specularTextureID, textureFilenames, textureContent);
+                }
+                
                 for (int j = 0; j < extracted.partMaterialTextures.size(); j++) {
                     if (extracted.partMaterialTextures.at(j).first == materialIndex) {
                         FBXMeshPart& part = extracted.mesh.parts[j];
@@ -1558,6 +1577,9 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
                         if (!normalTexture.filename.isNull()) {
                             part.normalTexture = normalTexture;
                         }
+                        if (!specularTexture.filename.isNull()) {
+                            part.specularTexture = specularTexture;
+                        }
                     }
                 }
                 materialIndex++;
diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h
index a4b04825ef..51e7380181 100644
--- a/libraries/fbx/src/FBXReader.h
+++ b/libraries/fbx/src/FBXReader.h
@@ -129,6 +129,7 @@ public:
     
     FBXTexture diffuseTexture;
     FBXTexture normalTexture;
+    FBXTexture specularTexture;
 };
 
 /// A single mesh (with optional blendshapes) extracted from an FBX document.
@@ -150,6 +151,8 @@ public:
     bool isEye;
     
     QVector<FBXBlendshape> blendshapes;
+    
+    bool hasSpecularTexture() const;
 };
 
 /// A single animation frame extracted from an FBX document.