Add normal, specular & parallax mapping for terrain

pull/37/head
scrawl 11 years ago
parent 9ab8fe1038
commit 69c0bb1723

@ -4,6 +4,8 @@
#include <OgreTechnique.h>
#include <OgrePass.h>
#include <boost/functional/hash.hpp>
#include <extern/shiny/Main/Factory.hpp>
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<Ogre::TexturePtr>::iterator blend = mBlendmapList.begin();
for (std::vector<std::string>::iterator layer = mLayerList.begin(); layer != mLayerList.end(); ++layer)
for (std::vector<LayerInfo>::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)

@ -3,6 +3,8 @@
#include <OgreMaterial.h>
#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<std::string>& layerList) { mLayerList = layerList; }
void setLayerList (const std::vector<LayerInfo>& layerList) { mLayerList = layerList; }
bool hasLayers() { return mLayerList.size(); }
void setBlendmapList (const std::vector<Ogre::TexturePtr>& blendmapList) { mBlendmapList = blendmapList; }
const std::vector<Ogre::TexturePtr>& 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<std::string> mLayerList;
std::vector<LayerInfo> mLayerList;
std::vector<Ogre::TexturePtr> mBlendmapList;
std::string mCompositeMap;
bool mShaders;
bool mShadows;
bool mSplitShadows;
bool mNormalMapping;
bool mParallaxMapping;
};
}

@ -371,7 +371,7 @@ void QuadTreeNode::destroyChunks(bool children)
for (std::vector<Ogre::TexturePtr>::const_iterator it = list.begin(); it != list.end(); ++it)
Ogre::TextureManager::getSingleton().remove((*it)->getName());
mMaterialGenerator->setBlendmapList(std::vector<Ogre::TexturePtr>());
mMaterialGenerator->setLayerList(std::vector<std::string>());
mMaterialGenerator->setLayerList(std::vector<LayerInfo>());
mMaterialGenerator->setCompositeMap("");
}
@ -414,7 +414,7 @@ void QuadTreeNode::ensureLayerInfo()
return;
std::vector<Ogre::TexturePtr> blendmaps;
std::vector<std::string> layerList;
std::vector<LayerInfo> layerList;
mTerrain->getStorage()->getBlendmaps(mSize, mCenter, mTerrain->getShadersEnabled(), blendmaps, layerList);
mMaterialGenerator->setLayerList(layerList);
@ -427,11 +427,13 @@ void QuadTreeNode::prepareForCompositeMap(Ogre::TRect<float> 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<std::string> layer;
layer.push_back("_land_default.dds");
std::vector<LayerInfo> 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;

@ -4,9 +4,10 @@
#include <OgreTextureManager.h>
#include <OgreStringConverter.h>
#include <OgreRenderSystem.h>
#include <OgreResourceGroupManager.h>
#include <OgreRoot.h>
#include <boost/multi_array.hpp>
#include <boost/algorithm/string.hpp>
namespace Terrain
{
@ -302,7 +303,7 @@ namespace Terrain
}
void Storage::getBlendmaps(float chunkSize, const Ogre::Vector2 &chunkCenter,
bool pack, std::vector<Ogre::TexturePtr> &blendmaps, std::vector<std::string> &layerList)
bool pack, std::vector<Ogre::TexturePtr> &blendmaps, std::vector<LayerInfo> &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;
}
}

@ -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<Ogre::TexturePtr>& blendmaps,
std::vector<std::string>& layerList);
std::vector<LayerInfo>& 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<std::string, LayerInfo> mLayerInfoMap;
LayerInfo getLayerInfo(const std::string& texture);
};
}

@ -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))

@ -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);

Loading…
Cancel
Save