diff --git a/components/nif/property.hpp b/components/nif/property.hpp
index cd1e0a5d1..00cdc0e00 100644
--- a/components/nif/property.hpp
+++ b/components/nif/property.hpp
@@ -109,6 +109,17 @@ public:
* 5 - Bump map texture
* 6 - Decal texture
*/
+ enum TextureType
+ {
+ BaseTexture = 0,
+ DarkTexture = 1,
+ DetailTexture = 2,
+ GlossTexture = 3,
+ GlowTexture = 4,
+ BumpTexture = 5,
+ DecalTexture = 6
+ };
+
Texture textures[7];
void read(NIFStream *nif)
diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp
index 89f55fbd4..c5dc7fbb9 100644
--- a/components/nifogre/ogrenifloader.cpp
+++ b/components/nifogre/ogrenifloader.cpp
@@ -555,11 +555,14 @@ static std::string findTextureName(const std::string &filename)
* texture file name references were kept as .tga.
*/
static const char path[] = "textures\\";
+ static const char path2[] = "textures/";
+
std::string texname = filename;
Misc::StringUtils::toLower(texname);
- if(texname.compare(0, sizeof(path)-1, path) != 0)
+ if(texname.compare(0, sizeof(path)-1, path) != 0
+ && texname.compare(0, sizeof(path2)-1, path2) != 0)
texname = path + texname;
Ogre::String::size_type pos = texname.rfind('.');
@@ -575,7 +578,8 @@ static std::string findTextureName(const std::string &filename)
{
texname = filename;
Misc::StringUtils::toLower(texname);
- if(texname.compare(0, sizeof(path)-1, path) != 0)
+ if(texname.compare(0, sizeof(path)-1, path) != 0
+ && texname.compare(0, sizeof(path2)-1, path2) != 0)
texname = path + texname;
}
}
@@ -590,7 +594,8 @@ static Ogre::String getMaterial(const Nif::NiTriShape *shape, const Ogre::String
const Nif::NiAlphaProperty *alphaprop,
const Nif::NiVertexColorProperty *vertprop,
const Nif::NiZBufferProperty *zprop,
- const Nif::NiSpecularProperty *specprop)
+ const Nif::NiSpecularProperty *specprop,
+ bool &needTangents)
{
Ogre::MaterialManager &matMgr = Ogre::MaterialManager::getSingleton();
Ogre::MaterialPtr material = matMgr.getByName(name);
@@ -634,6 +639,7 @@ static Ogre::String getMaterial(const Nif::NiTriShape *shape, const Ogre::String
warn("Found internal texture, ignoring.");
}
}
+ needTangents = !texName[Nif::NiTexturingProperty::BumpTexture].empty();
// Alpha modifiers
if(alphaprop)
@@ -741,7 +747,8 @@ static Ogre::String getMaterial(const Nif::NiTriShape *shape, const Ogre::String
new sh::Vector4(specular.x, specular.y, specular.z, glossiness)));
}
- instance->setProperty("diffuseMap", sh::makeProperty(texName[0]));
+ instance->setProperty("diffuseMap", sh::makeProperty(texName[Nif::NiTexturingProperty::BaseTexture]));
+ instance->setProperty("normalMap", sh::makeProperty(texName[Nif::NiTexturingProperty::BumpTexture]));
for(int i = 1;i < 7;i++)
{
if(!texName[i].empty())
@@ -1022,11 +1029,20 @@ class NIFMeshLoader : Ogre::ManualResourceLoader
}
}
+ bool needTangents=false;
std::string matname = NIFMaterialLoader::getMaterial(shape, mesh->getName(), mGroup,
texprop, matprop, alphaprop,
- vertprop, zprop, specprop);
+ vertprop, zprop, specprop, needTangents);
if(matname.length() > 0)
sub->setMaterialName(matname);
+
+ // build tangents if the material needs them
+ if (needTangents)
+ {
+ unsigned short src,dest;
+ if (!mesh->suggestTangentVectorBuildParams(Ogre::VES_TANGENT, src,dest))
+ mesh->buildTangentVectors(Ogre::VES_TANGENT, src,dest);
+ }
}
bool findTriShape(Ogre::Mesh *mesh, const Nif::Node *node,
diff --git a/extern/shiny/Docs/Macros.dox b/extern/shiny/Docs/Macros.dox
index 0578c447f..c04ebd374 100644
--- a/extern/shiny/Docs/Macros.dox
+++ b/extern/shiny/Docs/Macros.dox
@@ -107,6 +107,23 @@
\section properties Property retrieval / binding
+ \subsection shPropertyHasValue shPropertyHasValue
+
+ Usage: \@shPropertyHasValue(property)
+
+ Gets replaced by 1 if the property's value is not empty, or 0 if it is empty.
+ Useful for checking whether an optional texture is present or not.
+
+ Example:
+ \code
+ #if @shPropertyHasValue(specularMap)
+ // specular mapping code
+ #endif
+ #if @shPropertyHasValue(normalMap)
+ // normal mapping code
+ #endif
+ \endcode
+
\subsection shUniformProperty shUniformProperty
Usage: \@shUniformProperty<4f|3f|2f|1f|int> (uniformName, property)
@@ -130,15 +147,11 @@
Example:
\code
- #if @shPropertyBool(has_normal_map)
+ #if @shPropertyBool(has_vertex_colors)
...
#endif
\endcode
- \subsection shPropertyNotBool shPropertyNotBool
-
- Same as shPropertyBool, but inverts the result (i.e. when shPropertyBool would return 0, this returns 1 and vice versa)
-
\subsection shPropertyString shPropertyString
Retrieve a string property of the pass that this shader belongs to
diff --git a/extern/shiny/Main/MaterialInstance.hpp b/extern/shiny/Main/MaterialInstance.hpp
index 000f9d60c..36ba37ddb 100644
--- a/extern/shiny/Main/MaterialInstance.hpp
+++ b/extern/shiny/Main/MaterialInstance.hpp
@@ -25,6 +25,7 @@ namespace sh
public:
virtual void requestedConfiguration (MaterialInstance* m, const std::string& configuration) = 0; ///< called before creating
virtual void createdConfiguration (MaterialInstance* m, const std::string& configuration) = 0; ///< called after creating
+ virtual ~MaterialInstanceListener(){}
};
/**
diff --git a/extern/shiny/Main/ShaderInstance.cpp b/extern/shiny/Main/ShaderInstance.cpp
index 1539128ab..b44c63c32 100644
--- a/extern/shiny/Main/ShaderInstance.cpp
+++ b/extern/shiny/Main/ShaderInstance.cpp
@@ -194,13 +194,6 @@ namespace sh
bool val = retrieveValue(value, properties->getContext()).get();
replaceValue = val ? "1" : "0";
}
- else if (cmd == "shPropertyNotBool") // same as above, but inverts the result
- {
- std::string propertyName = args[0];
- PropertyValuePtr value = properties->getProperty(propertyName);
- bool val = retrieveValue(value, properties->getContext()).get();
- replaceValue = val ? "0" : "1";
- }
else if (cmd == "shPropertyString")
{
std::string propertyName = args[0];
@@ -214,6 +207,14 @@ namespace sh
std::string value = retrieveValue(properties->getProperty(propertyName), properties->getContext()).get();
replaceValue = (value == comparedAgainst) ? "1" : "0";
}
+ else if (isCmd(source, pos, "@shPropertyHasValue"))
+ {
+ assert(args.size() == 1);
+ std::string propertyName = args[0];
+ PropertyValuePtr value = properties->getProperty(propertyName);
+ std::string val = retrieveValue(value, properties->getContext()).get();
+ replaceValue = (val.empty() ? "0" : "1");
+ }
else
throw std::runtime_error ("unknown command \"" + cmd + "\"");
source.replace(pos, (end+1)-pos, replaceValue);
diff --git a/extern/shiny/Main/ShaderSet.cpp b/extern/shiny/Main/ShaderSet.cpp
index 413d7d1a2..628e0acee 100644
--- a/extern/shiny/Main/ShaderSet.cpp
+++ b/extern/shiny/Main/ShaderSet.cpp
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
#include "Factory.hpp"
@@ -26,6 +27,10 @@ namespace sh
std::ifstream stream(sourceFile.c_str(), std::ifstream::in);
std::stringstream buffer;
+ boost::filesystem::path p (sourceFile);
+ p = p.branch_path();
+ mBasePath = p.string();
+
buffer << stream.rdbuf();
stream.close();
mSource = buffer.str();
@@ -52,6 +57,12 @@ namespace sh
size_t start = currentToken.find('(')+1;
mGlobalSettings.push_back(currentToken.substr(start, currentToken.find(')')-start));
}
+ else if (boost::starts_with(currentToken, "@shPropertyHasValue"))
+ {
+ assert ((currentToken.find('(') != std::string::npos) && (currentToken.find(')') != std::string::npos));
+ size_t start = currentToken.find('(')+1;
+ mPropertiesToExist.push_back(currentToken.substr(start, currentToken.find(')')-start));
+ }
else if (boost::starts_with(currentToken, "@shPropertyEqual"))
{
assert ((currentToken.find('(') != std::string::npos) && (currentToken.find(')') != std::string::npos)
@@ -135,6 +146,11 @@ namespace sh
{
boost::hash_combine(seed, retrieveValue(currentGlobalSettings->getProperty(*it), NULL).get());
}
+ for (std::vector::iterator it = mPropertiesToExist.begin(); it != mPropertiesToExist.end(); ++it)
+ {
+ std::string v = retrieveValue(properties->getProperty(*it), properties->getContext()).get();
+ boost::hash_combine(seed, static_cast(v != ""));
+ }
boost::hash_combine(seed, static_cast(Factory::getInstance().getCurrentLanguage()));
return seed;
}
diff --git a/extern/shiny/Main/ShaderSet.hpp b/extern/shiny/Main/ShaderSet.hpp
index a423b6779..b21278ac9 100644
--- a/extern/shiny/Main/ShaderSet.hpp
+++ b/extern/shiny/Main/ShaderSet.hpp
@@ -53,6 +53,10 @@ namespace sh
std::vector mGlobalSettings; ///< names of the global settings that affect the shader source
std::vector mProperties; ///< names of the per-material properties that affect the shader source
+ std::vector mPropertiesToExist;
+ ///< same as mProperties, however in this case, it is only relevant if the property is empty or not
+ /// (we don't care about the value)
+
ShaderInstanceMap mInstances; ///< maps permutation ID (generated from the properties) to \a ShaderInstance
void parse(); ///< find out which properties and global settings affect the shader source
diff --git a/files/materials/objects.mat b/files/materials/objects.mat
index 5e18a666a..00b43a9d0 100644
--- a/files/materials/objects.mat
+++ b/files/materials/objects.mat
@@ -6,6 +6,7 @@ material openmw_objects_base
emissive 0.0 0.0 0.0
vertmode 0
diffuseMap black.png
+ normalMap
is_transparent false // real transparency, alpha rejection doesn't count here
scene_blend default
@@ -22,6 +23,7 @@ material openmw_objects_base
{
vertexcolor_mode $vertmode
is_transparent $is_transparent
+ normalMap $normalMap
}
diffuse $diffuse
@@ -38,6 +40,11 @@ material openmw_objects_base
direct_texture $diffuseMap
create_in_ffp true
}
+
+ texture_unit normalMap
+ {
+ direct_texture $normalMap
+ }
texture_unit shadowMap0
{
diff --git a/files/materials/objects.shader b/files/materials/objects.shader
index c8616e9d1..73968a6b3 100644
--- a/files/materials/objects.shader
+++ b/files/materials/objects.shader
@@ -14,13 +14,15 @@
#define NEED_DEPTH
#endif
+#define NORMAL_MAP @shPropertyHasValue(normalMap)
+
+// if normal mapping is enabled, we force pixel lighting
+#define VERTEX_LIGHTING (!@shPropertyHasValue(normalMap))
#define UNDERWATER @shGlobalSettingBool(render_refraction)
#define VERTEXCOLOR_MODE @shPropertyString(vertexcolor_mode)
-#define VERTEX_LIGHTING 1
-
#define VIEWPROJ_FIX @shGlobalSettingBool(viewproj_fix)
#ifdef SH_VERTEX_SHADER
@@ -42,6 +44,16 @@
shVertexInput(float2, uv0)
shOutput(float2, UV)
shNormalInput(float4)
+
+#if NORMAL_MAP
+ shTangentInput(float4)
+ shOutput(float3, tangentPassthrough)
+#endif
+
+#if !VERTEX_LIGHTING
+ shOutput(float3, normalPassthrough)
+#endif
+
#ifdef NEED_DEPTH
shOutput(float, depthPassthrough)
#endif
@@ -52,6 +64,10 @@
shColourInput(float4)
#endif
+#if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING
+ shOutput(float4, colourPassthrough)
+#endif
+
#if VERTEX_LIGHTING
shUniform(float, lightCount) @shAutoConstant(lightCount, light_count)
shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_space_array, @shGlobalSettingString(num_lights))
@@ -94,6 +110,15 @@
{
shOutputPosition = shMatrixMult(wvp, shInputPosition);
UV = uv0;
+#if NORMAL_MAP
+ tangentPassthrough = tangent.xyz;
+#endif
+#if !VERTEX_LIGHTING
+ normalPassthrough = normal.xyz;
+#endif
+#if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING
+ colourPassthrough = colour;
+#endif
#ifdef NEED_DEPTH
@@ -188,15 +213,31 @@
#endif
SH_BEGIN_PROGRAM
- shSampler2D(diffuseMap)
- shInput(float2, UV)
+ shSampler2D(diffuseMap)
+
+#if NORMAL_MAP
+ shSampler2D(normalMap)
+#endif
+
+ shInput(float2, UV)
+
+#if NORMAL_MAP
+ shInput(float3, tangentPassthrough)
+#endif
+#if !VERTEX_LIGHTING
+ shInput(float3, normalPassthrough)
+#endif
#ifdef NEED_DEPTH
shInput(float, depthPassthrough)
#endif
shInput(float3, objSpacePositionPassthrough)
-
+
+#if VERTEXCOLOR_MODE != 0 && !VERTEX_LIGHTING
+ shInput(float4, colourPassthrough)
+#endif
+
#if FOG
shUniform(float3, fogColour) @shAutoConstant(fogColour, fog_colour)
shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params)
@@ -222,23 +263,98 @@
#if (UNDERWATER) || (FOG)
shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix)
- shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position)
+ shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position)
#endif
#if UNDERWATER
- shUniform(float, waterLevel) @shSharedParameter(waterLevel)
+ shUniform(float, waterLevel) @shSharedParameter(waterLevel)
shUniform(float, waterEnabled) @shSharedParameter(waterEnabled)
#endif
#if VERTEX_LIGHTING
shInput(float4, lightResult)
shInput(float3, directionalResult)
+#else
+ shUniform(float, lightCount) @shAutoConstant(lightCount, light_count)
+ shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_view_space_array, @shGlobalSettingString(num_lights))
+ shUniform(float4, lightDiffuse[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightDiffuse, light_diffuse_colour_array, @shGlobalSettingString(num_lights))
+ shUniform(float4, lightAttenuation[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightAttenuation, light_attenuation_array, @shGlobalSettingString(num_lights))
+ shUniform(float4, lightAmbient) @shAutoConstant(lightAmbient, ambient_light_colour)
+ shUniform(float4x4, worldView) @shAutoConstant(worldView, worldview_matrix)
+ #if VERTEXCOLOR_MODE != 2
+ shUniform(float4, materialAmbient) @shAutoConstant(materialAmbient, surface_ambient_colour)
+ #endif
+ #if VERTEXCOLOR_MODE != 2
+ shUniform(float4, materialDiffuse) @shAutoConstant(materialDiffuse, surface_diffuse_colour)
+ #endif
+ #if VERTEXCOLOR_MODE != 1
+ shUniform(float4, materialEmissive) @shAutoConstant(materialEmissive, surface_emissive_colour)
+ #endif
#endif
SH_START_PROGRAM
{
shOutputColour(0) = shSample(diffuseMap, UV);
-
+
+#if NORMAL_MAP
+ float3 normal = normalPassthrough;
+ float3 binormal = cross(tangentPassthrough.xyz, normal.xyz);
+ float3x3 tbn = float3x3(tangentPassthrough.xyz, binormal, normal.xyz);
+
+ #if SH_GLSL
+ tbn = transpose(tbn);
+ #endif
+
+ float3 TSnormal = shSample(normalMap, UV.xy).xyz * 2 - 1;
+
+ normal = normalize (shMatrixMult( transpose(tbn), TSnormal ));
+#endif
+
+#if !VERTEX_LIGHTING
+ float3 viewPos = shMatrixMult(worldView, float4(objSpacePositionPassthrough,1)).xyz;
+ float3 viewNormal = normalize(shMatrixMult(worldView, float4(normal.xyz, 0)).xyz);
+
+ float3 lightDir;
+ float d;
+ float4 lightResult = float4(0,0,0,1);
+ @shForeach(@shGlobalSettingString(num_lights))
+ lightDir = lightPosition[@shIterator].xyz - (viewPos * lightPosition[@shIterator].w);
+ d = length(lightDir);
+ lightDir = normalize(lightDir);
+
+#if VERTEXCOLOR_MODE == 2
+ lightResult.xyz += colourPassthrough.xyz * lightDiffuse[@shIterator].xyz
+ * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d)))
+ * max(dot(viewNormal.xyz, lightDir), 0);
+#else
+ lightResult.xyz += materialDiffuse.xyz * lightDiffuse[@shIterator].xyz
+ * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d)))
+ * max(dot(viewNormal.xyz, lightDir), 0);
+#endif
+
+#if @shIterator == 0
+ float3 directionalResult = lightResult.xyz;
+#endif
+
+ @shEndForeach
+
+
+#if VERTEXCOLOR_MODE == 2
+ lightResult.xyz += lightAmbient.xyz * colourPassthrough.xyz + materialEmissive.xyz;
+ lightResult.a *= colourPassthrough.a;
+#endif
+#if VERTEXCOLOR_MODE == 1
+ lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + colourPassthrough.xyz;
+#endif
+#if VERTEXCOLOR_MODE == 0
+ lightResult.xyz += lightAmbient.xyz * materialAmbient.xyz + materialEmissive.xyz;
+#endif
+
+#if VERTEXCOLOR_MODE != 2
+ lightResult.a *= materialDiffuse.a;
+#endif
+#endif
+
// shadows only for the first (directional) light
#if SHADOWS
float shadow = depthShadowPCF (shadowMap0, lightSpacePos0, invShadowmapSize0);
@@ -275,7 +391,7 @@
#if FOG
float fogValue = shSaturate((depthPassthrough - fogParams.y) * fogParams.w);
-
+
#if UNDERWATER
shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, UNDERWATER_COLOUR, shSaturate(length(waterEyePos-worldPos) / VISIBILITY));