diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index ebf6046ff..b421de5a2 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include namespace @@ -36,6 +38,8 @@ namespace Terrain : mShaders(shaders) , mShadows(false) , mSplitShadows(false) + , mNormalMapping(true) + , mParallaxMapping(true) { } @@ -85,7 +89,7 @@ namespace Terrain { assert(mLayerList.size() == mBlendmapList.size()+1); std::vector::iterator blend = mBlendmapList.begin(); - for (std::vector::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer) + for (std::vector::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer) { Ogre::Pass* pass = technique->createPass(); pass->setLightingEnabled(false); @@ -117,7 +121,7 @@ namespace Terrain } // Add the actual layer texture on top of the alpha map. - tus = pass->createTextureUnitState("textures\\" + *layer); + tus = pass->createTextureUnitState(layer->mDiffuseMap); if (!first) tus->setColourOperationEx(Ogre::LBX_BLEND_DIFFUSE_ALPHA, Ogre::LBS_TEXTURE, @@ -156,6 +160,10 @@ namespace Terrain p->mShaderProperties.setProperty ("display_composite_map", sh::makeProperty(new sh::BooleanValue(true))); p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue("0"))); p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue("0"))); + p->mShaderProperties.setProperty ("normal_map_enabled", sh::makeProperty (new sh::BooleanValue(false))); + p->mShaderProperties.setProperty ("parallax_enabled", sh::makeProperty (new sh::BooleanValue(false))); + p->mShaderProperties.setProperty ("normal_maps", + sh::makeProperty (new sh::IntValue(0))); sh::MaterialInstanceTextureUnit* tex = p->createTextureUnit ("compositeMap"); tex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(mCompositeMap))); @@ -210,6 +218,11 @@ namespace Terrain } } ++neededTextureUnits; // layer texture + + // Check if this layer has a normal map + if (mNormalMapping && !mLayerList[layerOffset].mNormalMap.empty()) + ++neededTextureUnits; // normal map + if (neededTextureUnits <= remainingTextureUnits) { // We can fit another! @@ -238,6 +251,8 @@ namespace Terrain p->mShaderProperties.setProperty ("num_layers", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numLayersInThisPass)))); p->mShaderProperties.setProperty ("num_blendmaps", sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(numBlendTextures)))); + p->mShaderProperties.setProperty ("normal_map_enabled", + sh::makeProperty (new sh::BooleanValue(false))); // blend maps // the index of the first blend map used in this pass @@ -254,17 +269,39 @@ namespace Terrain } // layer maps + bool anyNormalMaps = false; + bool anyParallax = false; + size_t normalMaps = 0; for (int i = 0; i < numLayersInThisPass; ++i) { + const LayerInfo& layer = mLayerList[layerOffset+i]; + // diffuse map sh::MaterialInstanceTextureUnit* diffuseTex = p->createTextureUnit ("diffuseMap" + Ogre::StringConverter::toString(i)); - diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue("textures\\"+mLayerList[layerOffset+i]))); + diffuseTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mDiffuseMap))); + + // normal map (optional) + bool useNormalMap = mNormalMapping && !mLayerList[layerOffset+i].mNormalMap.empty() && !renderCompositeMap; + bool useParallax = useNormalMap && mParallaxMapping && layer.mParallax; + if (useNormalMap) + { + anyNormalMaps = true; + anyParallax = anyParallax || useParallax; + sh::MaterialInstanceTextureUnit* normalTex = p->createTextureUnit ("normalMap" + Ogre::StringConverter::toString(i)); + normalTex->setProperty ("direct_texture", sh::makeProperty (new sh::StringValue(layer.mNormalMap))); + } + p->mShaderProperties.setProperty ("use_normal_map_" + Ogre::StringConverter::toString(i), + sh::makeProperty (new sh::BooleanValue(useNormalMap))); + p->mShaderProperties.setProperty ("use_parallax_" + Ogre::StringConverter::toString(i), + sh::makeProperty (new sh::BooleanValue(useParallax))); + boost::hash_combine(normalMaps, useNormalMap); + boost::hash_combine(normalMaps, useNormalMap && layer.mParallax); if (i+layerOffset > 0) { int blendTextureIndex = getBlendmapIndexForLayer(layerOffset+i); std::string blendTextureComponent = getBlendmapComponentForLayer(layerOffset+i); p->mShaderProperties.setProperty ("blendmap_component_" + Ogre::StringConverter::toString(i), - sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent))); + sh::makeProperty (new sh::StringValue(Ogre::StringConverter::toString(blendTextureIndex-blendmapStart) + "." + blendTextureComponent))); } else { @@ -274,6 +311,14 @@ namespace Terrain sh::makeProperty (new sh::StringValue(""))); } } + p->mShaderProperties.setProperty ("normal_map_enabled", + sh::makeProperty (new sh::BooleanValue(anyNormalMaps))); + p->mShaderProperties.setProperty ("parallax_enabled", + sh::makeProperty (new sh::BooleanValue(anyParallax))); + // Since the permutation handler can't handle dynamic property names, + // combine normal map settings for all layers into one value + p->mShaderProperties.setProperty ("normal_maps", + sh::makeProperty (new sh::IntValue(normalMaps))); // shadow if (shadows) diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index 330ed3d14..e7e067899 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -3,6 +3,8 @@ #include +#include "storage.hpp" + namespace Terrain { @@ -15,13 +17,15 @@ namespace Terrain /// so if this parameter is true, then the supplied blend maps are expected to be packed. MaterialGenerator (bool shaders); - void setLayerList (const std::vector& layerList) { mLayerList = layerList; } + void setLayerList (const std::vector& layerList) { mLayerList = layerList; } bool hasLayers() { return mLayerList.size(); } void setBlendmapList (const std::vector& blendmapList) { mBlendmapList = blendmapList; } const std::vector& getBlendmapList() { return mBlendmapList; } void setCompositeMap (const std::string& name) { mCompositeMap = name; } void enableShadows(bool shadows) { mShadows = shadows; } + void enableNormalMapping(bool normalMapping) { mNormalMapping = normalMapping; } + void enableParallaxMapping(bool parallaxMapping) { mParallaxMapping = parallaxMapping; } void enableSplitShadows(bool splitShadows) { mSplitShadows = splitShadows; } /// Creates a material suitable for displaying a chunk of terrain using alpha-blending. @@ -43,12 +47,14 @@ namespace Terrain private: Ogre::MaterialPtr create (Ogre::MaterialPtr mat, bool renderCompositeMap, bool displayCompositeMap); - std::vector mLayerList; + std::vector mLayerList; std::vector mBlendmapList; std::string mCompositeMap; bool mShaders; bool mShadows; bool mSplitShadows; + bool mNormalMapping; + bool mParallaxMapping; }; } diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index ef2c61013..e16ae55dd 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -371,7 +371,7 @@ void QuadTreeNode::destroyChunks(bool children) for (std::vector::const_iterator it = list.begin(); it != list.end(); ++it) Ogre::TextureManager::getSingleton().remove((*it)->getName()); mMaterialGenerator->setBlendmapList(std::vector()); - mMaterialGenerator->setLayerList(std::vector()); + mMaterialGenerator->setLayerList(std::vector()); mMaterialGenerator->setCompositeMap(""); } @@ -414,7 +414,7 @@ void QuadTreeNode::ensureLayerInfo() return; std::vector blendmaps; - std::vector layerList; + std::vector layerList; mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList); mMaterialGenerator->setLayerList(layerList); @@ -427,11 +427,13 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect area) if (mIsDummy) { - // TODO - why is this completely black? // TODO - store this default material somewhere instead of creating one for each empty cell MaterialGenerator matGen(mTerrain->getShadersEnabled()); - std::vector layer; - layer.push_back("_land_default.dds"); + std::vector layer; + LayerInfo info; + info.mDiffuseMap = "textures\\_land_default.dds"; + info.mParallax = false; + layer.push_back(info); matGen.setLayerList(layer); makeQuad(sceneMgr, area.left, area.top, area.right, area.bottom, matGen.generateForCompositeMapRTT(Ogre::MaterialPtr())); return; diff --git a/components/terrain/storage.cpp b/components/terrain/storage.cpp index f00677e97..9d6b44de8 100644 --- a/components/terrain/storage.cpp +++ b/components/terrain/storage.cpp @@ -4,9 +4,10 @@ #include #include #include +#include #include -#include +#include namespace Terrain { @@ -302,7 +303,7 @@ namespace Terrain } void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter, - bool pack, std::vector &blendmaps, std::vector &layerList) + bool pack, std::vector &blendmaps, std::vector &layerList) { // TODO - blending isn't completely right yet; the blending radius appears to be // different at a cell transition (2 vertices, not 4), so we may need to create a larger blendmap @@ -336,7 +337,7 @@ namespace Terrain { int size = textureIndicesMap.size(); textureIndicesMap[*it] = size; - layerList.push_back(getTextureName(*it)); + layerList.push_back(getLayerInfo(getTextureName(*it))); } int numTextures = textureIndices.size(); @@ -466,5 +467,34 @@ namespace Terrain return land->mLandData->mHeights[y * ESM::Land::LAND_SIZE + x]; } + LayerInfo Storage::getLayerInfo(const std::string& texture) + { + // Already have this cached? + if (mLayerInfoMap.find(texture) != mLayerInfoMap.end()) + return mLayerInfoMap[texture]; + + LayerInfo info; + info.mParallax = false; + info.mDiffuseMap = "textures\\" + texture; + std::string texture_ = texture; + boost::replace_last(texture_, ".", "_nh."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) + { + info.mNormalMap = "textures\\" + texture_; + info.mParallax = true; + } + else + { + texture_ = texture; + boost::replace_last(texture_, ".", "_n."); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup("textures\\" + texture_)) + info.mNormalMap = "textures\\" + texture_; + } + + mLayerInfoMap[texture] = info; + + return info; + } + } diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index b82f6bbb6..68fae01af 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -11,6 +11,13 @@ namespace Terrain { + struct LayerInfo + { + std::string mDiffuseMap; + std::string mNormalMap; + bool mParallax; // Height info in normal map alpha channel? + }; + /// We keep storage of terrain data abstract here since we need different implementations for game and editor class Storage { @@ -58,7 +65,7 @@ namespace Terrain /// @param layerList names of the layer textures used will be written here void getBlendmaps (float chunkSize, const Ogre::Vector2& chunkCenter, bool pack, std::vector& blendmaps, - std::vector& layerList); + std::vector& layerList); float getHeightAt (const Ogre::Vector3& worldPos); @@ -77,6 +84,10 @@ namespace Terrain UniqueTextureId getVtexIndexAt(int cellX, int cellY, int x, int y); std::string getTextureName (UniqueTextureId id); + + std::map mLayerInfoMap; + + LayerInfo getLayerInfo(const std::string& texture); }; } diff --git a/files/materials/objects.shader b/files/materials/objects.shader index 20abe5b7c..fb2b30426 100644 --- a/files/materials/objects.shader +++ b/files/materials/objects.shader @@ -327,7 +327,6 @@ 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)) diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 861841a84..eda80c9e3 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -27,6 +27,16 @@ #define COMPOSITE_MAP @shPropertyBool(display_composite_map) +#define NORMAL_MAP @shPropertyBool(normal_map_enabled) +#define PARALLAX @shPropertyBool(parallax_enabled) + +#define VERTEX_LIGHTING (!NORMAL_MAP) + +#define PARALLAX_SCALE 0.04 +#define PARALLAX_BIAS -0.02 + +// This is just for the permutation handler +#define NORMAL_MAPS @shPropertyString(normal_maps) #if NEED_DEPTH @shAllocatePassthrough(1, depth) @@ -37,8 +47,13 @@ @shAllocatePassthrough(3, worldPos) #if LIGHTING +@shAllocatePassthrough(3, normalPassthrough) +#if VERTEX_LIGHTING @shAllocatePassthrough(3, lightResult) @shAllocatePassthrough(3, directionalResult) +#else +@shAllocatePassthrough(3, colourPassthrough) +#endif #if SHADOWS @shAllocatePassthrough(4, lightSpacePos0) @@ -69,12 +84,13 @@ shNormalInput(float4) shColourInput(float4) - shUniform(float, lightCount) @shAutoConstant(lightCount, light_count) +#if VERTEX_LIGHTING shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_object_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) - +#endif + #if SHADOWS shUniform(float4x4, texViewProjMatrix0) @shAutoConstant(texViewProjMatrix0, texture_viewproj_matrix) #endif @@ -122,6 +138,13 @@ @shPassthroughAssign(worldPos, worldPos.xyz); +#if LIGHTING + @shPassthroughAssign(normalPassthrough, normal.xyz); +#endif +#if LIGHTING && !VERTEX_LIGHTING + @shPassthroughAssign(colourPassthrough, colour.xyz); +#endif + #if LIGHTING #if SHADOWS @@ -139,6 +162,7 @@ #endif +#if VERTEX_LIGHTING // Lighting float3 lightDir; float d; @@ -164,6 +188,7 @@ @shPassthroughAssign(lightResult, lightResult); @shPassthroughAssign(directionalResult, directionalResult); +#endif #endif } @@ -189,6 +214,9 @@ @shForeach(@shPropertyString(num_layers)) shSampler2D(diffuseMap@shIterator) +#if @shPropertyBool(use_normal_map_@shIterator) + shSampler2D(normalMap@shIterator) +#endif @shEndForeach #endif @@ -201,6 +229,15 @@ @shPassthroughFragmentInputs #if LIGHTING + +#if !VERTEX_LIGHTING +shUniform(float4, lightPosition[@shGlobalSettingString(num_lights)]) @shAutoConstant(lightPosition, light_position_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) +#endif + #if SHADOWS shSampler2D(shadowMap0) shUniform(float2, invShadowmapSize0) @shAutoConstant(invShadowmapSize0, inverse_texture_size, @shPropertyString(shadowtexture_offset)) @@ -220,13 +257,21 @@ #if (UNDERWATER) || (FOG) shUniform(float4x4, worldMatrix) @shAutoConstant(worldMatrix, world_matrix) - shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) #endif #if UNDERWATER shUniform(float, waterLevel) @shSharedParameter(waterLevel) #endif + +// For specular +#if LIGHTING + shUniform(float3, lightSpec0) @shAutoConstant(lightSpec0, light_specular_colour, 0) + shUniform(float3, lightPos0) @shAutoConstant(lightPos0, light_position, 0) +#endif + +shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position) + SH_START_PROGRAM { @@ -237,12 +282,32 @@ float2 UV = @shPassthroughReceive(UV); float3 worldPos = @shPassthroughReceive(worldPos); - - + +#if LIGHTING + float3 normal = @shPassthroughReceive(normalPassthrough); +#endif + +#if LIGHTING && !VERTEX_LIGHTING + +#if NORMAL_MAP + // derive the tangent space basis + float3 tangent = float3(1,0, 0); + + float3 binormal = normalize(cross(tangent, normal)); + tangent = normalize(cross(normal, binormal)); // note, now we need to re-cross to derive tangent again because it wasn't orthonormal + + // derive final matrix + float3x3 tbn = float3x3(tangent, binormal, normal); + #if SH_GLSL + tbn = transpose(tbn); + #endif +#endif + +#endif + #if UNDERWATER float3 waterEyePos = intercept(worldPos, cameraPos.xyz - worldPos, float3(0,0,1), waterLevel); #endif - #if !IS_FIRST_PASS // Opacity the previous passes should have, i.e. 1 - (opacity of this pass) @@ -252,6 +317,8 @@ float previousAlpha = 1.f; shOutputColour(0) = float4(1,1,1,1); +float3 TSnormal = float3(0,0,1); + #if COMPOSITE_MAP shOutputColour(0).xyz = shSample(compositeMap, UV).xyz; #else @@ -266,39 +333,90 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; @shEndForeach - float3 albedo = float3(0,0,0); + float4 albedo = float4(0,0,0,1); float2 layerUV = UV * 16; + float2 thisLayerUV; + float4 normalTex; + + float3 eyeDir = normalize(cameraPos.xyz - worldPos); +#if PARALLAX + float3 TSeyeDir = normalize(shMatrixMult(tbn, eyeDir)); +#endif @shForeach(@shPropertyString(num_layers)) +#if @shPropertyBool(use_normal_map_@shIterator) + normalTex = shSample(normalMap@shIterator, thisLayerUV); +#if @shIterator == 0 && IS_FIRST_PASS + TSnormal = normalize(normalTex.xyz * 2 - 1); +#else + TSnormal = shLerp(TSnormal, normalTex.xyz * 2 - 1, blendValues@shPropertyString(blendmap_component_@shIterator)); +#endif +#endif + thisLayerUV = layerUV; + // required to play nicely with the tangents + thisLayerUV.y *= -1; +#if @shPropertyBool(use_parallax_@shIterator) + thisLayerUV += TSeyeDir.xy * ( normalTex.a * PARALLAX_SCALE + PARALLAX_BIAS ); +#endif #if IS_FIRST_PASS #if @shIterator == 0 // first layer of first pass is the base layer and doesn't need a blend map - albedo = shSample(diffuseMap0, layerUV).rgb; + albedo = shSample(diffuseMap0, layerUV); #else - albedo = shLerp(albedo, shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator)); + albedo = shLerp(albedo, shSample(diffuseMap@shIterator, thisLayerUV), blendValues@shPropertyString(blendmap_component_@shIterator)); #endif #else #if @shIterator == 0 - albedo = shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator); + albedo = shSample(diffuseMap@shIterator, layerUV); #else - albedo = shLerp(albedo, shSample(diffuseMap@shIterator, layerUV).rgb, blendValues@shPropertyString(blendmap_component_@shIterator)); + albedo = shLerp(albedo, shSample(diffuseMap@shIterator, thisLayerUV), blendValues@shPropertyString(blendmap_component_@shIterator)); #endif previousAlpha *= 1.f-blendValues@shPropertyString(blendmap_component_@shIterator); #endif + + @shEndForeach - shOutputColour(0).rgb *= albedo; + shOutputColour(0).rgb *= albedo.xyz; #endif #if LIGHTING + +#if VERTEX_LIGHTING // Lighting float3 lightResult = @shPassthroughReceive(lightResult); float3 directionalResult = @shPassthroughReceive(directionalResult); - +#else + +#if NORMAL_MAP + normal = normalize (shMatrixMult( transpose(tbn), TSnormal )); +#endif + + float3 colour = @shPassthroughReceive(colourPassthrough); + float3 lightDir; + float d; + float3 lightResult = float3(0,0,0); + @shForeach(@shGlobalSettingString(num_lights)) + lightDir = lightPosition[@shIterator].xyz - (worldPos * lightPosition[@shIterator].w); + d = length(lightDir); + lightDir = normalize(lightDir); + + lightResult.xyz += lightDiffuse[@shIterator].xyz + * shSaturate(1.0 / ((lightAttenuation[@shIterator].y) + (lightAttenuation[@shIterator].z * d) + (lightAttenuation[@shIterator].w * d * d))) + * max(dot(normal.xyz, lightDir), 0); +#if @shIterator == 0 + float3 directionalResult = lightResult.xyz; +#endif + @shEndForeach + lightResult.xyz += lightAmbient.xyz; + lightResult.xyz *= colour.xyz; + directionalResult.xyz *= colour.xyz; +#endif + // shadows only for the first (directional) light #if SHADOWS float4 lightSpacePos0 = @shPassthroughReceive(lightSpacePos0); @@ -325,6 +443,17 @@ float2 blendUV = (UV - 0.5) * (16.0 / (16.0+1.0)) + 0.5; shOutputColour(0).xyz *= (lightResult - directionalResult * (1.0-shadow)); #endif +#if LIGHTING && !COMPOSITE_MAP + // Specular + float3 light0Dir = normalize(lightPos0.xyz); + + float NdotL = max(dot(normal, light0Dir), 0); + float3 halfVec = normalize (light0Dir + eyeDir); + + float3 specular = pow(max(dot(normal, halfVec), 0), 32) * lightSpec0; + shOutputColour(0).xyz += specular * (1.f-albedo.a) * shadow; +#endif + #if FOG float fogValue = shSaturate((depth - fogParams.y) * fogParams.w);