Explorar el Código

Merge pull request #968 from AtomicWalrus/TerrainMacroAndBlendHardness_PR

Enables terrain macro maps, adds height blend "hardness" setting
Brian Roberts hace 2 años
padre
commit
b6f3c25fea

+ 105 - 42
Engine/source/terrain/glsl/terrFeatureGLSL.cpp

@@ -45,7 +45,7 @@ namespace
       FEATUREMGR->registerFeature( MFT_TerrainParallaxMap, new NamedFeatureGLSL( "Terrain Parallax Texture" ) );   
       FEATUREMGR->registerFeature( MFT_TerrainDetailMap, new TerrainDetailMapFeatGLSL );
       FEATUREMGR->registerFeature( MFT_TerrainNormalMap, new TerrainNormalMapFeatGLSL );
-      FEATUREMGR->registerFeature( MFT_TerrainMacroMap, new NamedFeatureGLSL("TerrainMacroMap Deprecated")); // new TerrainMacroMapFeatGLSL);
+      FEATUREMGR->registerFeature( MFT_TerrainMacroMap, new TerrainMacroMapFeatGLSL);
       FEATUREMGR->registerFeature( MFT_TerrainLightMap, new TerrainLightMapFeatGLSL );
       FEATUREMGR->registerFeature( MFT_TerrainSideProject, new NamedFeatureGLSL( "Terrain Side Projection" ) );
       FEATUREMGR->registerFeature(MFT_TerrainHeightBlend, new TerrainHeightMapBlendGLSL);
@@ -142,6 +142,24 @@ Var* TerrainFeatGLSL::_getDetailMapSampler()
    return detailMapSampler;
 }
 
+Var* TerrainFeatGLSL::_getMacroMapSampler()
+{
+   String name("macroMapSampler");
+   Var* detailMapSampler = (Var*)LangElement::find(name);
+
+   if (!detailMapSampler)
+   {
+      detailMapSampler = new Var;
+      detailMapSampler->setName(name);
+      detailMapSampler->setType("sampler2DArray");
+      detailMapSampler->uniform = true;
+      detailMapSampler->sampler = true;
+      detailMapSampler->constNum = Var::getTexUnitNum();
+   }
+
+   return detailMapSampler;
+}
+
 Var* TerrainFeatGLSL::_getNormalMapSampler()
 {
    String name("normalMapSampler");
@@ -200,18 +218,21 @@ Var* TerrainFeatGLSL::_getDetailIdStrengthParallax()
 
 Var* TerrainFeatGLSL::_getMacroIdStrengthParallax()
 {
-   String name(String::ToString("macroIdStrengthParallax%d", getProcessIndex()));
+   String name(String::ToString("macroIdStrengthParallax", getProcessIndex()));
 
    Var* detailInfo = (Var*)LangElement::find(name);
    if (!detailInfo)
    {
       detailInfo = new Var;
-      detailInfo->setType("vec3");
+      detailInfo->setType("vec4");
       detailInfo->setName(name);
       detailInfo->uniform = true;
       detailInfo->constSortPos = cspPotentialPrimitive;
+      detailInfo->arraySize = getProcessIndex();
    }
 
+   detailInfo->arraySize = mMax(detailInfo->arraySize, getProcessIndex() + 1);
+
    return detailInfo;
 }
 
@@ -427,6 +448,19 @@ void TerrainDetailMapFeatGLSL::processVert(  Vector<ShaderComponent*> &component
 
    detScaleAndFade->arraySize = mMax(detScaleAndFade->arraySize, detailIndex + 1);
 
+   // This is done here to make sure the macro array size/alignment is correct
+   Var* macroScaleAndFade = (Var*)LangElement::find("macroScaleAndFade");
+   if (macroScaleAndFade == NULL)
+   {
+      macroScaleAndFade = new Var;
+      macroScaleAndFade->setType("vec4");
+      macroScaleAndFade->setName("macroScaleAndFade");
+      macroScaleAndFade->uniform = true;
+      macroScaleAndFade->constSortPos = cspPotentialPrimitive;
+   }
+
+   macroScaleAndFade->arraySize = mMax(macroScaleAndFade->arraySize, detailIndex + 1);
+
    // Setup the detail coord.
    //
    // NOTE: You see here we scale the texture coord by 'xyx'
@@ -510,6 +544,9 @@ void TerrainDetailMapFeatGLSL::processPix(   Vector<ShaderComponent*> &component
    // Get the detail id.
    Var *detailInfo = _getDetailIdStrengthParallax();
 
+   // This is done here to make sure the macro arrays are the correct size
+   Var* macroInfo = _getMacroIdStrengthParallax();
+
    // Create the detail blend var.
    Var *detailBlend = new Var;
    detailBlend->setType( "float" );
@@ -568,7 +605,8 @@ void TerrainDetailMapFeatGLSL::processPix(   Vector<ShaderComponent*> &component
 		   detailColor, detailMapArray, detCoord, new IndexOp(detailInfo, detailIndex)));
    }
 
-   meta->addStatement(new GenOp("   @ *= @.y * @;\r\n", detailColor, new IndexOp(detailInfo, detailIndex), detailBlend));
+   meta->addStatement(new GenOp("   @ *= @.y;\r\n",
+      detailColor, new IndexOp(detailInfo, detailIndex)));
 
    if (!fd.features.hasFeature(MFT_TerrainNormalMap))
    {
@@ -672,18 +710,23 @@ void TerrainMacroMapFeatGLSL::processVert(  Vector<ShaderComponent*> &componentL
    outTex->setStructName( "OUT" );
    outTex->setType( "vec4" );
 
-   // Get the detail scale and fade info.
-   Var *macroScaleAndFade = new Var;
+   Var* macroScaleAndFade = (Var*)LangElement::find("macroScaleAndFade");
+   if (macroScaleAndFade == NULL)
+   {
+      macroScaleAndFade = new Var;
    macroScaleAndFade->setType( "vec4" );
-   macroScaleAndFade->setName( String::ToString( "macroScaleAndFade%d", detailIndex ) );
+      macroScaleAndFade->setName("macroScaleAndFade");
    macroScaleAndFade->uniform = true;
    macroScaleAndFade->constSortPos = cspPotentialPrimitive;
+   }
+
+   macroScaleAndFade->arraySize = mMax(macroScaleAndFade->arraySize, detailIndex + 1);
 
    // Setup the detail coord.
-   meta->addStatement( new GenOp( "   @.xyz = @ * @.xyx;\r\n", outTex, inTex, macroScaleAndFade ) );
+   meta->addStatement( new GenOp( "   @.xyz = @ * @.xyx;\r\n", outTex, inTex, new IndexOp(macroScaleAndFade, detailIndex)) );
 
    // And sneak the detail fade thru the w detailCoord.
-   meta->addStatement( new GenOp( "   @.w =  ( @.z - @ ) * @.w;\r\n",  outTex, macroScaleAndFade, dist, macroScaleAndFade ) );   
+   meta->addStatement( new GenOp( "   @.w =  ( @.z - @ ) * @.w;\r\n",  outTex, new IndexOp(macroScaleAndFade, detailIndex), dist, new IndexOp(macroScaleAndFade, detailIndex)) );
 
    output = meta;
 }
@@ -761,7 +804,7 @@ void TerrainMacroMapFeatGLSL::processPix(   Vector<ShaderComponent*> &componentL
 
    // Calculate the blend for this detail texture.
    meta->addStatement( new GenOp( "   @ = calcBlend( @.x, @.xy, @, @ );\r\n", 
-                                    new DecOp( detailBlend ), detailInfo, inTex, layerSize, layerSample ) );
+                                    new DecOp( detailBlend ), new IndexOp(detailInfo, detailIndex), inTex, layerSize, layerSample ) );
 
    // Check to see if we have a gbuffer normal.
    Var* gbNormal = (Var*)LangElement::find("gbNormal");
@@ -779,26 +822,17 @@ void TerrainMacroMapFeatGLSL::processPix(   Vector<ShaderComponent*> &componentL
          gbNormal, gbNormal, viewToTangent, detailBlend, detCoord ) );
    }
 
-   Var *detailColor = (Var*)LangElement::find( "macroColor" ); 
+   Var* detailColor = (Var*)LangElement::find(String::ToString("macroColor%d", detailIndex));
    if ( !detailColor )
    {
       detailColor = new Var;
       detailColor->setType( "vec4" );
-      detailColor->setName( "macroColor" );
+      detailColor->setName(String::ToString("macroColor%d", detailIndex));
       meta->addStatement( new GenOp( "   @;\r\n", new DecOp( detailColor ) ) );
    }
 
    // Get the detail texture.
-   Var *detailMapArray = new Var;
-   detailMapArray->setType( "sampler2D" );
-   detailMapArray->setName( String::ToString( "macroMap%d", detailIndex ) );
-   detailMapArray->uniform = true;
-   detailMapArray->sampler = true;
-   detailMapArray->constNum = Var::getTexUnitNum();     // used as texture unit num here
-
-   meta->addStatement( new GenOp( "   if ( @ > 0.0f )\r\n", detailBlend ) );
-
-   meta->addStatement( new GenOp( "   {\r\n" ) );
+   Var* detailMapArray = _getMacroMapSampler();
 
    // Note that we're doing the standard greyscale detail 
    // map technique here which can darken and lighten the 
@@ -818,20 +852,12 @@ void TerrainMacroMapFeatGLSL::processPix(   Vector<ShaderComponent*> &componentL
    }
    else
    {
-      meta->addStatement( new GenOp( "      @ = ( tex2D( @, @.xy ) * 2.0 ) - 1.0;\r\n", 
-                                       detailColor, detailMapArray, detCoord) );
+	   meta->addStatement(new GenOp("   @ = ( tex2D( @, vec3(@.xy, @.x) ) * 2.0 ) - 1.0;\r\n",
+		   detailColor, detailMapArray, detCoord, new IndexOp(detailInfo, detailIndex)));
    }
 
-   meta->addStatement( new GenOp( "      @ *= @.y * @.w;\r\n", detailColor, detailInfo, detCoord) );
-
-   ShaderFeature::OutputTarget target = (fd.features[MFT_isDeferred]) ? RenderTarget1 : DefaultTarget;
-
-   Var *outColor = (Var*)LangElement::find( getOutputTargetVarName(target) );
-
-   meta->addStatement(new GenOp("      @ += @ * @;\r\n",
-                                    outColor, detailColor, detailBlend));
-
-   meta->addStatement( new GenOp( "   }\r\n" ) );
+   meta->addStatement(new GenOp("   @ *= @.y;\r\n",
+      detailColor, new IndexOp(detailInfo, detailIndex)));
 
    output = meta;
 }
@@ -844,14 +870,13 @@ ShaderFeature::Resources TerrainMacroMapFeatGLSL::getResources( const MaterialFe
    {
       // If this is the first detail pass then we 
       // samples from the layer tex.
-      res.numTex += 1;
-   }
+      res.numTex = 1;
+      res.numTexReg = 1;
 
+      // Add Detail TextureArray
       res.numTex += 1;
-
-   // Finally we always send the detail texture 
-   // coord to the pixel shader.
-   res.numTexReg += 1;
+      res.numTexReg += 1;
+   }
 
    return res;
 }
@@ -1252,7 +1277,7 @@ void TerrainHeightMapBlendGLSL::processPix(Vector<ShaderComponent*>& componentLi
    }
 
    Var* heightRange = new Var("heightRange", "vec2");
-   meta->addStatement(new GenOp("   @ = vec2(2.0f,0);//x=min, y=max\r\n", new DecOp(heightRange)));
+   meta->addStatement(new GenOp("   @ = vec2(0,0);//x=min, y=max\r\n", new DecOp(heightRange)));
    // Compute blend factors
    for (S32 idx = 0; idx < detailCount; ++idx)
    {
@@ -1301,7 +1326,20 @@ void TerrainHeightMapBlendGLSL::processPix(Vector<ShaderComponent*>& componentLi
       {
          Var* detailBlend = (Var*)LangElement::find(String::ToString("detailBlend%d", idx));
          Var* detailH = (Var*)LangElement::find(String::ToString("detailH%d", idx));
-         meta->addStatement(new GenOp("   @ = (@[email protected])/(@[email protected])[email protected];\r\n", detailH, detailH, heightRange, heightRange, heightRange));
+         Var* blendHardness = (Var*)LangElement::find(String::ToString("blendHardness%d", idx));
+         if (!blendHardness)
+         {
+            blendHardness = new Var;
+            blendHardness->setType("float");
+            blendHardness->setName(String::ToString("blendHardness%d", idx));
+            blendHardness->uniform = true;
+            blendHardness->constSortPos = cspPrimitive;
+         }
+         meta->addStatement(new GenOp("   @ = (@[email protected])/(@[email protected])[email protected];\r\n", detailH, detailH, heightRange, heightRange, heightRange, heightRange));
+         // Note that blendHardness is clamped between 0-0.99 at the terrain material level to avoid a divide by zero
+         meta->addStatement(new GenOp("   @ = 1.0f / (1.0f - @) * (@ - @);\r\n", detailH, blendHardness, detailH, blendHardness));
+         // This line equation will go out of our 0-1 range, clamp it
+         meta->addStatement(new GenOp("   @ = clamp(@, 0.0f, 1.0f);\r\n", detailH, detailH));
 
       }
       meta->addStatement(new GenOp("\r\n"));
@@ -1333,6 +1371,31 @@ void TerrainHeightMapBlendGLSL::processPix(Vector<ShaderComponent*>& componentLi
 
    meta->addStatement(new GenOp(");\r\n"));
 
+   // Macro textures, if they exist
+   bool didMacro = false;
+   for (S32 idx = 0; idx < detailCount; ++idx)
+   {
+      Var* detailColor = (Var*)LangElement::find(String::ToString("macroColor%d", idx));
+      if (detailColor) // only do this if the macro map exists for this layer
+      {
+         if (!didMacro)
+            meta->addStatement(new GenOp("   @.rgb += (", outColor));
+
+         Var* detailH = (Var*)LangElement::find(String::ToString("detailH%d", idx));
+         Var* detCoord = (Var*)LangElement::find(String::ToString("macroCoord%d", idx));
+
+         if (idx > 0 && didMacro)
+         {
+            meta->addStatement(new GenOp(" + "));
+         }
+
+         meta->addStatement(new GenOp("((@.rgb * @)*max(@.w,0))", detailColor, detailH, detCoord));
+         didMacro = true;
+      }
+   }
+   if (didMacro)
+      meta->addStatement(new GenOp(");\r\n"));
+
    // Compute ORM
    Var* ormOutput;
    if (fd.features[MFT_isDeferred])

+ 1 - 0
Engine/source/terrain/glsl/terrFeatureGLSL.h

@@ -45,6 +45,7 @@ public:
    Var* _getInMacroCoord(Vector<ShaderComponent*> &componentList );
 
    Var* _getDetailMapSampler();
+   Var* _getMacroMapSampler();
    Var* _getNormalMapSampler();
    Var* _getOrmMapSampler();
 

+ 109 - 37
Engine/source/terrain/hlsl/terrFeatureHLSL.cpp

@@ -46,7 +46,7 @@ namespace
       FEATUREMGR->registerFeature( MFT_TerrainParallaxMap, new NamedFeatureHLSL( "Terrain Parallax Texture" ) );   
       FEATUREMGR->registerFeature( MFT_TerrainDetailMap, new TerrainDetailMapFeatHLSL );
       FEATUREMGR->registerFeature( MFT_TerrainNormalMap, new TerrainNormalMapFeatHLSL );
-      FEATUREMGR->registerFeature( MFT_TerrainMacroMap, new NamedFeatureHLSL("TerrainMacroMap Deprecated")); // new TerrainMacroMapFeatHLSL);
+      FEATUREMGR->registerFeature( MFT_TerrainMacroMap, new TerrainMacroMapFeatHLSL);
       FEATUREMGR->registerFeature( MFT_TerrainLightMap, new TerrainLightMapFeatHLSL );
       FEATUREMGR->registerFeature( MFT_TerrainSideProject, new NamedFeatureHLSL( "Terrain Side Projection" ) );
       FEATUREMGR->registerFeature( MFT_TerrainHeightBlend, new TerrainHeightMapBlendHLSL );
@@ -160,6 +160,42 @@ Var* TerrainFeatHLSL::_getDetailMapArray()
    return detailMapArray;
 }
 
+Var* TerrainFeatHLSL::_getMacroMapSampler()
+{
+   String name("macroMapSampler");
+   Var* detailMapSampler = (Var*)LangElement::find(name);
+
+   if (!detailMapSampler)
+   {
+      detailMapSampler = new Var;
+      detailMapSampler->setName(name);
+      detailMapSampler->setType("SamplerState");
+      detailMapSampler->uniform = true;
+      detailMapSampler->sampler = true;
+      detailMapSampler->constNum = Var::getTexUnitNum();
+   }
+
+   return detailMapSampler;
+}
+
+Var* TerrainFeatHLSL::_getMacroMapArray()
+{
+   String name("macroMapArray");
+   Var* detailMapArray = (Var*)LangElement::find(name);
+
+   if (!detailMapArray)
+   {
+      detailMapArray = new Var;
+      detailMapArray->setName(name);
+      detailMapArray->setType("Texture2DArray");
+      detailMapArray->uniform = true;
+      detailMapArray->texture = true;
+      detailMapArray->constNum = _getMacroMapSampler()->constNum;
+   }
+
+   return detailMapArray;
+}
+
 Var* TerrainFeatHLSL::_getNormalMapSampler()
 {
    String name("normalMapSampler");
@@ -254,18 +290,20 @@ Var* TerrainFeatHLSL::_getDetailIdStrengthParallax()
 
 Var* TerrainFeatHLSL::_getMacroIdStrengthParallax()
 {
-   String name( String::ToString( "macroIdStrengthParallax%d", getProcessIndex() ) );
+   String name( String::ToString( "macroIdStrengthParallax", getProcessIndex() ) );
 
    Var *detailInfo = (Var*)LangElement::find( name );
    if ( !detailInfo )
    {
       detailInfo = new Var;
-      detailInfo->setType( "float3" );
+      detailInfo->setType( "float4" );
       detailInfo->setName( name );
       detailInfo->uniform = true;
       detailInfo->constSortPos = cspPotentialPrimitive;
    }
 
+   detailInfo->arraySize = mMax(detailInfo->arraySize, getProcessIndex() + 1);
+
    return detailInfo;
 }
 
@@ -483,6 +521,19 @@ void TerrainDetailMapFeatHLSL::processVert(  Vector<ShaderComponent*> &component
 
    detScaleAndFade->arraySize = mMax(detScaleAndFade->arraySize, detailIndex + 1);
 
+   // Done here to keep array indexes aligned
+   Var* macroScaleAndFade = (Var*)LangElement::find("macroScaleAndFade");
+   if (macroScaleAndFade == NULL)
+   {
+      macroScaleAndFade = new Var;
+      macroScaleAndFade->setType("float4");
+      macroScaleAndFade->setName("macroScaleAndFade");
+      macroScaleAndFade->uniform = true;
+      macroScaleAndFade->constSortPos = cspPotentialPrimitive;
+   }
+
+   macroScaleAndFade->arraySize = mMax(macroScaleAndFade->arraySize, detailIndex + 1);
+
    // Setup the detail coord.
    //
    // NOTE: You see here we scale the texture coord by 'xyx'
@@ -572,6 +623,9 @@ void TerrainDetailMapFeatHLSL::processPix(   Vector<ShaderComponent*> &component
    // Get the detail id.
    Var *detailInfo = _getDetailIdStrengthParallax();
 
+   // Done here to keep array indexes aligned
+   Var* macroInfo = _getMacroIdStrengthParallax();
+
    // Create the detail blend var.
    Var *detailBlend = new Var;
    detailBlend->setType( "float" );
@@ -630,7 +684,8 @@ void TerrainDetailMapFeatHLSL::processPix(   Vector<ShaderComponent*> &component
          detailColor, detailMapArray, detailMapSampler, detCoord, new IndexOp(detailInfo, detailIndex)));
    }
 
-   meta->addStatement(new GenOp("   @ *= @.y * @;\r\n", detailColor, new IndexOp(detailInfo, detailIndex), detailBlend));
+   meta->addStatement(new GenOp("   @ *= @.y;\r\n",
+      detailColor, new IndexOp(detailInfo, detailIndex)));
 
    if (!fd.features.hasFeature(MFT_TerrainNormalMap))
    {
@@ -854,24 +909,13 @@ void TerrainMacroMapFeatHLSL::processPix(   Vector<ShaderComponent*> &componentL
          gbNormal, gbNormal, viewToTangent, detailBlend, inDet ) );
    }
    
-   Var *detailColor = (Var*)LangElement::find("macroColor");
-   if (!detailColor)
-   {
-      detailColor = new Var;
+   Var* detailColor = new Var;
       detailColor->setType( "float4" );
-      detailColor->setName( "macroColor" );
-      meta->addStatement( new GenOp( "   @;\r\n", new DecOp( detailColor ) ) );
-   }
+   detailColor->setName( String::ToString("macroColor%d", detailIndex) );
+   meta->addStatement( new GenOp( "   @ = float4(0.5f, 0.5f, 0.5f, 1.0f);\r\n", new DecOp( detailColor ) ) );
 
-   // If we're using SM 3.0 then take advantage of 
-   // dynamic branching to skip layers per-pixel.
-   if ( GFX->getPixelShaderVersion() >= 3.0f )
-      meta->addStatement( new GenOp( "   if ( @ > 0.0f )\r\n", detailBlend ) );
-
-   meta->addStatement( new GenOp( "   {\r\n" ) );
-
-   Var* detailMapArray = _getDetailMapArray();
-   Var* detailMapSampler = _getDetailMapSampler();
+   Var* detailMapArray = _getMacroMapArray();
+   Var* detailMapSampler = _getMacroMapSampler();
 
    // Note that we're doing the standard greyscale detail 
    // map technique here which can darken and lighten the 
@@ -895,17 +939,8 @@ void TerrainMacroMapFeatHLSL::processPix(   Vector<ShaderComponent*> &componentL
          detailColor, detailMapArray, detailMapSampler, inDet, new IndexOp(detailInfo, detailIndex)));
    }
 
-   meta->addStatement( new GenOp( "   @ *= @.y * @.w;\r\n",
-                                    detailColor, new IndexOp(detailInfo, detailIndex), inDet ) );
-
-   ShaderFeature::OutputTarget target = (fd.features[MFT_isDeferred]) ? RenderTarget1 : DefaultTarget;
-
-   Var *outColor = (Var*)LangElement::find( getOutputTargetVarName(target) );
-
-   meta->addStatement(new GenOp("      @ += @ * @;\r\n",
-                                    outColor, detailColor, detailBlend));
-
-   meta->addStatement( new GenOp( "   }\r\n" ) );
+   meta->addStatement(new GenOp("   @ *= @.y;\r\n",
+      detailColor, new IndexOp(detailInfo, detailIndex)));
 
    output = meta;
 }
@@ -919,13 +954,12 @@ ShaderFeature::Resources TerrainMacroMapFeatHLSL::getResources( const MaterialFe
       // If this is the first detail pass then we 
       // samples from the layer tex.
       res.numTex += 1;
-   }
+      res.numTexReg += 1;
 
+      // Add Detail TextureArray
       res.numTex += 1;
-
-   // Finally we always send the detail texture 
-   // coord to the pixel shader.
-   res.numTexReg += 1;
+      res.numTexReg += 1;
+   }
 
    return res;
 }
@@ -1333,7 +1367,7 @@ void TerrainHeightMapBlendHLSL::processPix(Vector<ShaderComponent*>& componentLi
    }
 
    Var* heightRange = new Var("heightRange", "float2");
-   meta->addStatement(new GenOp("   @ = float2(2.0f,0);//x=min, y=max\r\n", new DecOp(heightRange)));
+   meta->addStatement(new GenOp("   @ = float2(0,0);//x=min, y=max\r\n", new DecOp(heightRange)));
    // Compute blend factors
    for (S32 idx = 0; idx < detailCount; ++idx)
    {
@@ -1382,7 +1416,20 @@ void TerrainHeightMapBlendHLSL::processPix(Vector<ShaderComponent*>& componentLi
       {
          Var* detailBlend = (Var*)LangElement::find(String::ToString("detailBlend%d", idx));
          Var* detailH = (Var*)LangElement::find(String::ToString("detailH%d", idx));
+         Var* blendHardness = (Var*)LangElement::find(String::ToString("blendHardness%d", idx));
+         if (!blendHardness)
+         {
+            blendHardness = new Var;
+            blendHardness->setType("float");
+            blendHardness->setName(String::ToString("blendHardness%d", idx));
+            blendHardness->uniform = true;
+            blendHardness->constSortPos = cspPrimitive;
+         }
          meta->addStatement(new GenOp("   @ = (@[email protected])/(@[email protected])[email protected];\r\n", detailH, detailH, heightRange, heightRange, heightRange, heightRange));
+         // Note that blendHardness is clamped between 0-0.99 at the terrain material level to avoid a divide by zero
+         meta->addStatement(new GenOp("   @ = 1.0f / (1.0f - @) * (@ - @);\r\n", detailH, blendHardness, detailH, blendHardness));
+         // This line equation will go out of our 0-1 range, clamp it
+         meta->addStatement(new GenOp("   @ = clamp(@, 0.0f, 1.0f);\r\n", detailH, detailH));
       }
       meta->addStatement(new GenOp("\r\n"));
    }
@@ -1413,6 +1460,31 @@ void TerrainHeightMapBlendHLSL::processPix(Vector<ShaderComponent*>& componentLi
 
    meta->addStatement(new GenOp(");\r\n"));
 
+   // Macro textures, if they exist
+   bool didMacro = false;
+   for (S32 idx = 0; idx < detailCount; ++idx)
+   {
+      Var* detailColor = (Var*)LangElement::find(String::ToString("macroColor%d", idx));
+      if (detailColor) // only do this if the macro map exists for this layer
+      {
+         if (!didMacro)
+            meta->addStatement(new GenOp("   @.rgb += (", outColor));
+
+         Var* detailH = (Var*)LangElement::find(String::ToString("detailH%d", idx));
+         Var* detCoord = (Var*)LangElement::find(String::ToString("macroCoord%d", idx));
+
+         if (idx > 0 && didMacro)
+         {
+            meta->addStatement(new GenOp(" + "));
+         }
+
+         meta->addStatement(new GenOp("((@.rgb * @)*max(@.w,0))", detailColor, detailH, detCoord));
+         didMacro = true;
+      }
+   }
+   if (didMacro)
+      meta->addStatement(new GenOp(");\r\n"));
+
    // Compute ORM
    Var* ormOutput;
    if (fd.features[MFT_isDeferred])

+ 2 - 0
Engine/source/terrain/hlsl/terrFeatureHLSL.h

@@ -47,6 +47,8 @@ public:
 
    Var* _getDetailMapSampler();
    Var* _getDetailMapArray();
+   Var* _getMacroMapSampler();
+   Var* _getMacroMapArray();
    Var* _getNormalMapSampler();
    Var* _getNormalMapArray();
    Var* _getOrmMapSampler();

+ 38 - 1
Engine/source/terrain/terrCellMaterial.cpp

@@ -548,7 +548,7 @@ bool TerrainCellMaterial::_initShader(bool deferredMat,
 
    mDetailInfoVArrayConst = mShader->getShaderConstHandle("$detailScaleAndFade");
    mDetailInfoPArrayConst = mShader->getShaderConstHandle("$detailIdStrengthParallax");
-   mMacroInfoVArrayConst = mShader->getShaderConstHandle("$macroIdStrengthParallax");
+   mMacroInfoVArrayConst = mShader->getShaderConstHandle("$macroScaleAndFade");
    mMacroInfoPArrayConst = mShader->getShaderConstHandle("$macroIdStrengthParallax");
 
    mDetailTexArrayConst = mShader->getShaderConstHandle("$detailMapSampler");
@@ -637,6 +637,7 @@ bool TerrainCellMaterial::_initShader(bool deferredMat,
 
       mMaterialInfos[i]->mBlendDepthConst = mShader->getShaderConstHandle(avar("$blendDepth%d", i));
       mMaterialInfos[i]->mBlendContrastConst = mShader->getShaderConstHandle(avar("$blendContrast%d", i));
+      mMaterialInfos[i]->mBlendHardnessConst = mShader->getShaderConstHandle(avar("$blendHardness%d", i));
    }
 
    // If we're doing deferred it requires some 
@@ -689,6 +690,9 @@ void TerrainCellMaterial::_updateMaterialConsts( )
    AlignedArray<Point4F> detailInfoArray(detailMatCount, sizeof(Point4F));
    AlignedArray<Point4F> detailScaleAndFadeArray(detailMatCount, sizeof(Point4F));
 
+   AlignedArray<Point4F> macroInfoArray(detailMatCount, sizeof(Point4F));
+   AlignedArray<Point4F> macroScaleAndFadeArray(detailMatCount, sizeof(Point4F));
+
    int detailIndex = 0;
    for (MaterialInfo* matInfo : mMaterialInfos)
    {
@@ -745,12 +749,45 @@ void TerrainCellMaterial::_updateMaterialConsts( )
       {
          mConsts->setSafe(matInfo->mBlendContrastConst, matInfo->mat->getBlendContrast());
       }
+
+      if (matInfo->mBlendHardnessConst != NULL)
+      {
+         mConsts->setSafe(matInfo->mBlendHardnessConst, matInfo->mat->getBlendHardness());
+      }
+
+      // macro texture info
+
+      F32 macroSize = matInfo->mat->getMacroSize();
+      F32 macroScale = 1.0f;
+      if (!mIsZero(macroSize))
+         macroScale = mTerrain->getWorldBlockSize() / macroSize;
+
+      // Scale the distance by the global scalar.
+      const F32 macroDistance = mTerrain->smDetailScale * matInfo->mat->getMacroDistance();
+
+      Point4F macroScaleAndFade(macroScale,
+         -macroScale,
+         macroDistance,
+         0);
+
+      if (!mIsZero(macroDistance))
+         macroScaleAndFade.w = 1.0f / macroDistance;
+
+      Point4F macroIdStrengthParallax(matInfo->layerId,
+         matInfo->mat->getMacroStrength(),
+         0, 0);
+
+      macroScaleAndFadeArray[detailIndex] = macroScaleAndFade;
+      macroInfoArray[detailIndex] = macroIdStrengthParallax;
+
       detailIndex++;
    }
 
    mConsts->setSafe(mDetailInfoVArrayConst, detailScaleAndFadeArray);
    mConsts->setSafe(mDetailInfoPArrayConst, detailInfoArray);
 
+   mConsts->setSafe(mMacroInfoVArrayConst, macroScaleAndFadeArray);
+   mConsts->setSafe(mMacroInfoPArrayConst, macroInfoArray);
 }
 
 bool TerrainCellMaterial::setupPass(   const SceneRenderState *state, 

+ 3 - 1
Engine/source/terrain/terrCellMaterial.h

@@ -59,7 +59,7 @@ protected:
    public:
 
       MaterialInfo()
-         :mat(NULL), layerId(0), mBlendDepthConst(NULL), mBlendContrastConst(NULL)
+         :mat(NULL), layerId(0), mBlendDepthConst(NULL), mBlendContrastConst(NULL), mBlendHardnessConst(NULL)
       {
       }
 
@@ -71,6 +71,7 @@ protected:
       U32 layerId;
       GFXShaderConstHandle *mBlendDepthConst;
       GFXShaderConstHandle *mBlendContrastConst;
+      GFXShaderConstHandle* mBlendHardnessConst;
    };
 
    ///
@@ -119,6 +120,7 @@ protected:
    GFXShaderConstHandle *mOrmTexArrayConst;
 
    GFXShaderConstHandle* mBlendDepthConst;
+   GFXShaderConstHandle* mBlendHeightFloorConst;
 
    TerrainBlock *mTerrain;
 

+ 7 - 0
Engine/source/terrain/terrMaterial.cpp

@@ -25,6 +25,7 @@
 #include "console/consoleTypes.h"
 #include "gfx/gfxTextureManager.h"
 #include "gfx/bitmap/gBitmap.h"
+#include "console/typeValidators.h"
 
 #ifdef TORQUE_TOOLS
 #include "console/persistenceManager.h"
@@ -76,6 +77,7 @@ TerrainMaterial::TerrainMaterial()
       mParallaxScale( 0.0f ),
       mBlendDepth( 0.0f ),
       mBlendContrast( 1.0f ),
+      mBlendHardness( 0.0f ),
       mIsSRGB(false),
       mInvertRoughness(false)
 {
@@ -90,6 +92,8 @@ TerrainMaterial::~TerrainMaterial()
 {
 }
 
+FRangeValidator hardnessValidator(0.0f, 0.999f);
+
 void TerrainMaterial::initPersistFields()
 {
    docsURL;
@@ -106,6 +110,9 @@ void TerrainMaterial::initPersistFields()
    addField("blendHeightContrast", TypeF32, Offset(mBlendContrast, TerrainMaterial), "A fixed value to add while blending using heightmap-based blending."
       "Higher numbers = larger blend radius.");
 
+   addFieldV("blendHeightHardness", TypeF32, Offset(mBlendHardness, TerrainMaterial), &hardnessValidator, "How sharply this layer blends with other textures."
+      "0->1, soft->hard.");
+
    INITPERSISTFIELD_IMAGEASSET(DetailMap, TerrainMaterial, "Raises and lowers the RGB result of the Base Albedo up close.");
    addField( "detailSize", TypeF32, Offset( mDetailSize, TerrainMaterial ), "Used to scale the detail map to the material square" );
    addField( "detailStrength", TypeF32, Offset( mDetailStrength, TerrainMaterial ), "Exponentially sharpens or lightens the detail map rendering on the material" );

+ 4 - 0
Engine/source/terrain/terrMaterial.h

@@ -97,6 +97,8 @@ protected:
 
    F32 mBlendContrast;
 
+   F32 mBlendHardness;
+
 public:
 
    TerrainMaterial();
@@ -138,6 +140,8 @@ public:
 
    F32 getBlendContrast() const { return mBlendContrast; }
 
+   F32 getBlendHardness() const { return mBlendHardness; }
+
    bool getIsSRGB() const { return mIsSRGB; }
 
    bool getInvertRoughness() const { return mInvertRoughness; }

+ 36 - 8
Templates/BaseGame/game/tools/worldEditor/gui/guiTerrainMaterialDlg.ed.gui

@@ -394,7 +394,7 @@ $guiContent = new GuiControl(TerrainMaterialDlg,EditorGuiGroup) {
          };
          new GuiContainer(NormalMapContainer) {
             position = "6 205";
-                  extent = "261 100";
+                  extent = "261 120";
             horizSizing = "width";
             profile = "ToolsGuiDefaultProfile";
             tooltipProfile = "ToolsGuiToolTipProfile";
@@ -527,17 +527,45 @@ $guiContent = new GuiControl(TerrainMaterialDlg,EditorGuiGroup) {
                isContainer = "0";
                internalName = "blendHeightContrastTextEditCtrl";
             };
+            new GuiTextEditCtrl(TerrainMaterialDlgBlendHeightHardnessTextEdit) {
+               text = "0";
+               anchorTop = "0";
+               anchorLeft = "0";
+               position = "1 99";
+               extent = "35 18";
+               profile = "ToolsGuiTextEditProfile";
+               tooltipProfile = "ToolsGuiToolTipProfile";
+               isContainer = "0";
+               internalName = "blendHeightHardnessTextEditCtrl";
+            };
+            new GuiTextCtrl() {
+               text = "Blend Hardness";
+               position = "115 101";
+               extent = "76 15";
+               profile = "ToolsGuiTextProfile";
+               tooltipProfile = "ToolsGuiToolTipProfile";
+            };
+            new GuiSliderCtrl(TerrainMaterialDlgBlendHeightHardnessSlider) {
+               range = "0 0.999";
+               ticks = "0";
+               value = "0";
+               position = "39 101";
+               extent = "70 14";
+               profile = "ToolsGuiSliderProfile";
+               tooltipProfile = "ToolsGuiToolTipProfile";
+               internalName = "blendHeightHardnessSliderCtrl";
+            };
          };
          new GuiBitmapCtrl() {
-                  BitmapAsset = "ToolsModule:separator_v_image";
-            position = "6 307";
-                  extent = "266 2";
+            BitmapAsset = "ToolsModule:separator_v_image";
+            position = "6 323";
+            extent = "266 2";
             horizSizing = "width";
             profile = "ToolsGuiDefaultProfile";
                tooltipProfile = "ToolsGuiToolTipProfile";
             };
          new GuiContainer(ORMConfigMapContainer) {
-            position = "6 314";
+                  position = "6 330";
                   extent = "261 64";
                horizSizing = "width";
                profile = "ToolsGuiDefaultProfile";
@@ -615,14 +643,14 @@ $guiContent = new GuiControl(TerrainMaterialDlg,EditorGuiGroup) {
          };
          new GuiBitmapCtrl() {
                   BitmapAsset = "ToolsModule:separator_v_image";
-            position = "6 381";
+                  position = "6 397";
                   extent = "266 2";
             horizSizing = "width";
             profile = "ToolsGuiDefaultProfile";
             tooltipProfile = "ToolsGuiToolTipProfile";
          };
          new GuiContainer(MacroMapContainer) {
-            position = "6 388";
+                  position = "6 404";
                   extent = "261 72";
             horizSizing = "width";
             profile = "ToolsGuiDefaultProfile";
@@ -746,7 +774,7 @@ $guiContent = new GuiControl(TerrainMaterialDlg,EditorGuiGroup) {
             };
          };
                new GuiContainer(TerrainEffectsContainer) {
-                  position = "6 460";
+                  position = "6 476";
                   extent = "265 97";
                   horizSizing = "width";
                   profile = "ToolsGuiDefaultProfile";

+ 28 - 0
Templates/BaseGame/game/tools/worldEditor/scripts/interfaces/terrainMaterialDlg.ed.tscript

@@ -477,6 +477,10 @@ function TerrainMaterialDlg::setActiveMaterial( %this, %mat )
       %this-->blendHeightContrastTextEditCtrl.setText( %blendHeightContrast );
       %this-->blendHeightContrastSliderCtrl.setValue( %mat.blendHeightContrast );
 
+		%blendHeightHardness = mFloor(%mat.blendHeightHardness * 1000)/1000;
+		%this-->blendHeightHardnessTextEditCtrl.setText( %blendHeightHardness );
+		%this-->blendHeightHardnessSliderCtrl.setValue( %mat.blendHeightHardness );
+
       %this-->macroSizeCtrl.setText( %mat.macroSize );
       %this-->macroStrengthCtrl.setText( %mat.macroStrength );
       %this-->macroDistanceCtrl.setText( %mat.macroDistance );      
@@ -686,6 +690,7 @@ function TerrainMaterialDlg::saveDirtyMaterial( %this, %materialAssetId )
    %parallaxScale = %this-->parallaxScaleCtrl.getText();
    %blendHeightBase = %this-->blendHeightBaseTextEditCtrl.getText();
    %blendHeightContrast = %this-->blendHeightContrastTextEditCtrl.getText();
+	%blendHeightHardness = %this-->blendHeightHardnessTextEditCtrl.getText();
 
    %macroSize = %this-->macroSizeCtrl.getText();      
    %macroStrength = %this-->macroStrengthCtrl.getText();
@@ -725,6 +730,7 @@ function TerrainMaterialDlg::saveDirtyMaterial( %this, %materialAssetId )
          %mat.parallaxScale == %parallaxScale &&
          %mat.blendHeightBase == %blendHeightBase &&
          %mat.blendHeightContrast == %blendHeightContrast &&
+			%mat.blendHeightHardness == %blendHeightHardness &&
          %mat.isSRGB == %isSRGB &&         
          %mat.invertRoughness == %invertRoughness &&
          %fxMat.effectColor[0] == %effectColor0 &&
@@ -772,6 +778,7 @@ function TerrainMaterialDlg::saveDirtyMaterial( %this, %materialAssetId )
    %mat.parallaxScale = %parallaxScale;
    %mat.blendHeightBase = %blendHeightBase;
    %mat.blendHeightContrast = %blendHeightContrast;
+	%mat.blendHeightHardness = %blendHeightHardness;
    %mat.isSRGB = %isSRGB;
    %mat.invertRoughness = %invertRoughness;
    
@@ -861,6 +868,7 @@ function TerrainMaterialDlg::snapshotMaterials( %this )
          parallaxScale = %mat.parallaxScale;
          blendHeightBase = %mat.blendHeightBase;
          blendHeightContrast = %mat.blendHeightContrast;
+			blendHeightHardness = %mat.blendHeightHardness;
          isSRGB = %mat.isSRGB;
          invertRoughness = %mat.invertRoughness;
       };
@@ -901,6 +909,7 @@ function TerrainMaterialDlg::restoreMaterials( %this )
       %mat.parallaxScale = %obj.parallaxScale;
       %mat.blendHeightBase = %obj.blendHeightBase;
       %mat.blendHeightContrast = %obj.blendHeightContrast;
+		%mat.blendHeightHardness = %obj.blendHeightHardness;
       %mat.isSRGB = %obj.isSRGB;
       %mat.invertRoughness = %obj.invertRoughness;
    }
@@ -971,6 +980,25 @@ function TerrainMaterialDlgBlendHeightContrastTextEdit::onValidate(%this)
    TerrainMaterialDlg.matDirty = true;
 }
 
+function TerrainMaterialDlgBlendHeightHardnessSlider::onMouseDragged(%this)
+{
+   %value = mClamp(%this.value, 0.0, 0.999);
+	%value = mFloor(%value * 1000)/1000;
+   TerrainMaterialDlgBlendHeightHardnessTextEdit.setText(%value);
+   TerrainMaterialDlg.activeMat.blendHeightHardness = %value;
+   TerrainMaterialDlg.matDirty = true;
+}
+
+function TerrainMaterialDlgBlendHeightHardnessTextEdit::onValidate(%this)
+{
+	%value = mClamp(%this.getText(), 0.0, 0.999);
+	%value = mFloor(%value * 1000)/1000;
+	%this.setText(%value);
+   TerrainMaterialDlgBlendHeightHardnessSlider.setValue(%value);
+   TerrainMaterialDlg.activeMat.blendHeightHardness = %value;
+   TerrainMaterialDlg.matDirty = true;
+}
+
 //
 //
 function terrMatEdDragNDropMapAssignment(%mapName, %payload)