Browse Source

Changed Terrain to use a material file to allow easier access to terrain materials and to enable different renderers to be written for terrain (such as a deferred rendering implementation). This also allows declaration of scene light binding in the material (as can be seen in the terrain sample).

sgrenier 12 years ago
parent
commit
53f9fcb1f2

+ 64 - 23
gameplay/src/Material.cpp

@@ -26,6 +26,11 @@ Material::~Material()
 }
 
 Material* Material::create(const char* url)
+{
+    return create(url, (PassCallback)NULL, NULL);
+}
+
+Material* Material::create(const char* url, PassCallback callback, void* cookie)
 {
     // Load the material properties from file.
     Properties* properties = Properties::create(url);
@@ -35,13 +40,18 @@ Material* Material::create(const char* url)
         return NULL;
     }
 
-    Material* material = create((strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace());
+    Material* material = create((strlen(properties->getNamespace()) > 0) ? properties : properties->getNextNamespace(), callback, cookie);
     SAFE_DELETE(properties);
 
     return material;
 }
 
 Material* Material::create(Properties* materialProperties)
+{
+    return create(materialProperties, (PassCallback)NULL, NULL);
+}
+
+Material* Material::create(Properties* materialProperties, PassCallback callback, void* cookie)
 {
     // Check if the Properties is valid and has a valid namespace.
     if (!materialProperties || !(strcmp(materialProperties->getNamespace(), "material") == 0))
@@ -53,13 +63,16 @@ Material* Material::create(Properties* materialProperties)
     // Create new material from the file passed in.
     Material* material = new Material();
 
+    // Load uniform value parameters for this material.
+    loadRenderState(material, materialProperties);
+
     // Go through all the material properties and create techniques under this material.
     Properties* techniqueProperties = NULL;
     while ((techniqueProperties = materialProperties->getNextNamespace()))
     {
         if (strcmp(techniqueProperties->getNamespace(), "technique") == 0)
         {
-            if (!loadTechnique(material, techniqueProperties))
+            if (!loadTechnique(material, techniqueProperties, callback, cookie))
             {
                 GP_ERROR("Failed to load technique for material.");
                 SAFE_RELEASE(material);
@@ -68,9 +81,6 @@ Material* Material::create(Properties* materialProperties)
         }
     }
 
-    // Load uniform value parameters for this material.
-    loadRenderState(material, materialProperties);
-
     // Set the current technique to the first found technique.
     if (material->getTechniqueCount() > 0)
     {
@@ -93,7 +103,8 @@ Material* Material::create(Effect* effect)
     Technique* technique = new Technique(NULL, material);
     material->_techniques.push_back(technique);
 
-    Pass* pass = new Pass(NULL, technique, effect);
+    Pass* pass = new Pass(NULL, technique);
+    pass->_effect = effect;
     technique->_passes.push_back(pass);
     effect->addRef();
 
@@ -104,16 +115,20 @@ Material* Material::create(Effect* effect)
 
 Material* Material::create(const char* vshPath, const char* fshPath, const char* defines)
 {
+    GP_ASSERT(vshPath);
+    GP_ASSERT(fshPath);
+
     // Create a new material with a single technique and pass for the given effect
     Material* material = new Material();
 
     Technique* technique = new Technique(NULL, material);
     material->_techniques.push_back(technique);
 
-    Pass* pass = Pass::create(NULL, technique, vshPath, fshPath, defines);
-    if (!pass)
+    Pass* pass = new Pass(NULL, technique);
+    if (!pass->initialize(vshPath, fshPath, defines))
     {
-        GP_ERROR("Failed to create pass for material.");
+        GP_WARN("Failed to create pass for material: vertexShader = %s, fragmentShader = %s, defines = %s", vshPath, fshPath, defines ? defines : "");
+        SAFE_RELEASE(pass);
         SAFE_RELEASE(material);
         return NULL;
     }
@@ -165,6 +180,16 @@ void Material::setTechnique(const char* id)
     }
 }
 
+void Material::setNodeBinding(Node* node)
+{
+    RenderState::setNodeBinding(node);
+
+    for (size_t i = 0, count = _techniques.size(); i < count; ++i)
+    {
+        _techniques[i]->setNodeBinding(node);
+    }
+}
+
 Material* Material::clone(NodeCloneContext &context) const
 {
     Material* material = new Material();
@@ -184,7 +209,7 @@ Material* Material::clone(NodeCloneContext &context) const
     return material;
 }
 
-bool Material::loadTechnique(Material* material, Properties* techniqueProperties)
+bool Material::loadTechnique(Material* material, Properties* techniqueProperties, PassCallback callback, void* cookie)
 {
     GP_ASSERT(material);
     GP_ASSERT(techniqueProperties);
@@ -192,6 +217,9 @@ bool Material::loadTechnique(Material* material, Properties* techniqueProperties
     // Create a new technique.
     Technique* technique = new Technique(techniqueProperties->getId(), material);
 
+    // Load uniform value parameters for this technique.
+    loadRenderState(technique, techniqueProperties);
+
     // Go through all the properties and create passes under this technique.
     techniqueProperties->rewind();
     Properties* passProperties = NULL;
@@ -200,7 +228,7 @@ bool Material::loadTechnique(Material* material, Properties* techniqueProperties
         if (strcmp(passProperties->getNamespace(), "pass") == 0)
         {
             // Create and load passes.
-            if (!loadPass(technique, passProperties))
+            if (!loadPass(technique, passProperties, callback, cookie))
             {
                 GP_ERROR("Failed to create pass for technique.");
                 SAFE_RELEASE(technique);
@@ -209,16 +237,13 @@ bool Material::loadTechnique(Material* material, Properties* techniqueProperties
         }
     }
 
-    // Load uniform value parameters for this technique.
-    loadRenderState(technique, techniqueProperties);
-
     // Add the new technique to the material.
     material->_techniques.push_back(technique);
 
     return true;
 }
 
-bool Material::loadPass(Technique* technique, Properties* passProperties)
+bool Material::loadPass(Technique* technique, Properties* passProperties, PassCallback callback, void* cookie)
 {
     GP_ASSERT(passProperties);
     GP_ASSERT(technique);
@@ -228,19 +253,35 @@ bool Material::loadPass(Technique* technique, Properties* passProperties)
     GP_ASSERT(vertexShaderPath);
     const char* fragmentShaderPath = passProperties->getString("fragmentShader");
     GP_ASSERT(fragmentShaderPath);
-    const char* defines = passProperties->getString("defines");
+    const char* passDefines = passProperties->getString("defines");
 
-    // Create the pass.
-    Pass* pass = Pass::create(passProperties->getId(), technique, vertexShaderPath, fragmentShaderPath, defines);
-    if (!pass)
-    {
-        GP_ERROR("Failed to create pass for technique.");
-        return false;
-    }
+    // Create the pass
+    Pass* pass = new Pass(passProperties->getId(), technique);
 
     // Load render state.
     loadRenderState(pass, passProperties);
 
+    // If a pass callback was specified, call it and add the result to our list of defines
+    std::string allDefines = passDefines ? passDefines : "";
+    if (callback)
+    {
+        std::string customDefines = callback(pass, cookie);
+        if (customDefines.length() > 0)
+        {
+            if (allDefines.length() > 0)
+                allDefines += ';';
+            allDefines += customDefines;
+        }
+    }
+
+    // Initialize/compile the effect with the full set of defines
+    if (!pass->initialize(vertexShaderPath, fragmentShaderPath, allDefines.c_str()))
+    {
+        GP_WARN("Failed to create pass for technique.");
+        SAFE_RELEASE(pass);
+        return false;
+    }
+
     // Add the new pass to the technique.
     technique->_passes.push_back(pass);
 

+ 38 - 4
gameplay/src/Material.h

@@ -1,5 +1,5 @@
-# ifndef MATERIAL_H_
-# define MATERIAL_H_
+#ifndef MATERIAL_H_
+#define MATERIAL_H_
 
 #include "RenderState.h"
 #include "Technique.h"
@@ -28,6 +28,11 @@ class Material : public RenderState
 
 public:
 
+    /**
+     * Pass creation callback function definition.
+     */
+    typedef std::string(*PassCallback)(Pass*, void*);
+
     /**
      * Creates a material using the data from the Properties object defined at the specified URL, 
      * where the URL is of the format "<file-path>.<extension>#<namespace-id>/<namespace-id>/.../<namespace-id>"
@@ -40,6 +45,25 @@ public:
      */
     static Material* create(const char* url);
 
+    /**
+     * Creates a material from a Properties file.
+     *
+     * This overloaded method allows you to pass a function pointer to be called back for each
+     * pass that is loaded for the material. The passed in callback receives a pointer to each
+     * Pass being created and returns a string of optional defines to append to the shaders
+     * being compiled for each Pass. The function is called during Pass creation, prior to
+     * compiling the shaders for that Pass. MaterialParameters can be safely modified in this
+     * callback even though the shader is not yet compiled.
+     *
+     * @param url The URL pointing to the Properties object defining the material.
+     * @param callback Function pointer to be called during Pass creation.
+     * @param cookie Optional custom parameter to be passed to the callback function.
+     *
+     * @return A new Material or NULL if there was an error.
+     * @script{create}
+     */
+    static Material* create(const char* url, PassCallback callback, void* cookie = NULL);
+
     /**
      * Creates a material from the specified properties object.
      * 
@@ -117,6 +141,11 @@ public:
      */
     void setTechnique(const char* id);
 
+    /**
+     * @see RenderState::setNodeBinding
+     */
+    void setNodeBinding(Node* node);
+
 private:
 
     /**
@@ -144,15 +173,20 @@ private:
      */
     Material* clone(NodeCloneContext &context) const;
 
+    /**
+     * Creates a new material with optional pass callback function.
+     */
+    static Material* create(Properties* materialProperties, PassCallback callback, void* cookie);
+
     /**
      * Loads a technique from the given properties object into the specified material.
      */
-    static bool loadTechnique(Material* material, Properties* techniqueProperties);
+    static bool loadTechnique(Material* material, Properties* techniqueProperties, PassCallback callback, void* cookie);
 
     /**
      * Load a pass from the given properties object into the specified technique.
      */
-    static bool loadPass(Technique* technique, Properties* passProperites);
+    static bool loadPass(Technique* technique, Properties* passProperites, PassCallback callback, void* cookie);
 
     /**
      * Loads render state from the specified properties object.

+ 0 - 18
gameplay/src/Model.cpp

@@ -398,24 +398,6 @@ void Model::setMaterialNodeBinding(Material *material)
     if (_node)
     {
         material->setNodeBinding(_node);
-
-        unsigned int techniqueCount = material->getTechniqueCount();
-        for (unsigned int i = 0; i < techniqueCount; ++i)
-        {
-            Technique* technique = material->getTechniqueByIndex(i);
-            GP_ASSERT(technique);
-            
-            technique->setNodeBinding(_node);
-
-            unsigned int passCount = technique->getPassCount();
-            for (unsigned int j = 0; j < passCount; ++j)
-            {
-                Pass* pass = technique->getPassByIndex(j);
-                GP_ASSERT(pass);
-
-                pass->setNodeBinding(_node);
-            }
-        }
     }
 }
 

+ 20 - 13
gameplay/src/Pass.cpp

@@ -7,8 +7,8 @@
 namespace gameplay
 {
 
-Pass::Pass(const char* id, Technique* technique, Effect* effect) :
-    _id(id ? id : ""), _technique(technique), _effect(effect), _vaBinding(NULL)
+Pass::Pass(const char* id, Technique* technique) :
+    _id(id ? id : ""), _technique(technique), _effect(NULL), _vaBinding(NULL)
 {
     RenderState::_parent = _technique;
 }
@@ -19,18 +19,23 @@ Pass::~Pass()
     SAFE_RELEASE(_vaBinding);
 }
 
-Pass* Pass::create(const char* id, Technique* technique, const char* vshPath, const char* fshPath, const char* defines)
+bool Pass::initialize(const char* vshPath, const char* fshPath, const char* defines)
 {
+    GP_ASSERT(vshPath);
+    GP_ASSERT(fshPath);
+
+    SAFE_RELEASE(_effect);
+    SAFE_RELEASE(_vaBinding);
+
     // Attempt to create/load the effect.
-    Effect* effect = Effect::createFromFile(vshPath, fshPath, defines);
-    if (effect == NULL)
+    _effect = Effect::createFromFile(vshPath, fshPath, defines);
+    if (_effect == NULL)
     {
-        GP_ERROR("Failed to create effect for pass.");
-        return NULL;
+        GP_WARN("Failed to create effect for pass. vertexShader = %s, fragmentShader = %s, defines = %s", vshPath, fshPath, defines ? defines : "");
+        return false;
     }
 
-    // Return the new pass.
-    return new Pass(id, technique, effect);
+    return true;
 }
 
 const char* Pass::getId() const
@@ -87,10 +92,12 @@ void Pass::unbind()
 
 Pass* Pass::clone(Technique* technique, NodeCloneContext &context) const
 {
-    Effect* effect = getEffect();
-    GP_ASSERT(effect);
-    effect->addRef();
-    Pass* pass = new Pass(getId(), technique, effect);
+    GP_ASSERT(_effect);
+    _effect->addRef();
+
+    Pass* pass = new Pass(getId(), technique);
+    pass->_effect = _effect;
+
     RenderState::cloneInto(pass, context);
     pass->_parent = technique;
     return pass;

+ 2 - 2
gameplay/src/Pass.h

@@ -73,7 +73,7 @@ private:
     /**
      * Constructor.
      */
-    Pass(const char* id, Technique* technique, Effect* effect);
+    Pass(const char* id, Technique* technique);
 
     /**
      * Hidden copy constructor.
@@ -88,7 +88,7 @@ private:
     /**
      * Creates a new pass for the given shaders.
      */
-    static Pass* create(const char* id, Technique* technique, const char* vshPath, const char* fshPath, const char* defines);
+    bool initialize(const char* vshPath, const char* fshPath, const char* defines);
 
     /**
      * Hidden copy assignment operator.

+ 11 - 11
gameplay/src/RenderState.h

@@ -512,6 +512,17 @@ public:
      */
     StateBlock* getStateBlock() const;
 
+    /**
+     * Sets the node that this render state is bound to.
+     *
+     * The specified node is used to apply auto-bindings for the render state.
+     * This is typically set to the node of the model that a material is 
+     * applied to.
+     *
+     * @param node The node to use for applying auto-bindings.
+     */
+    virtual void setNodeBinding(Node* node);
+
     /**
      * Registers a custom auto binding resolver.
      *
@@ -564,17 +575,6 @@ protected:
      */
     static void finalize();
 
-    /**
-     * Sets the node that this render state is bound to.
-     *
-     * The specified node is used to apply auto-bindings for the render state.
-     * This is typically set to the node of the model that a material is 
-     * applied to.
-     *
-     * @param node The node to use for applying auto-bindings.
-     */
-    void setNodeBinding(Node* node);
-
     /**
      * Applies the specified custom auto-binding.
      *

+ 10 - 0
gameplay/src/Technique.cpp

@@ -53,6 +53,16 @@ Pass* Technique::getPass(const char* id) const
     return NULL;
 }
 
+void Technique::setNodeBinding(Node* node)
+{
+    RenderState::setNodeBinding(node);
+
+    for (size_t i = 0, count = _passes.size(); i < count; ++i)
+    {
+        _passes[i]->setNodeBinding(node);
+    }
+}
+
 Technique* Technique::clone(Material* material, NodeCloneContext &context) const
 {
     Technique* technique = new Technique(getId(), material);

+ 5 - 0
gameplay/src/Technique.h

@@ -42,6 +42,11 @@ public:
      */
     Pass* getPass(const char* id) const;
 
+    /**
+     * @see RenderState::setNodeBinding
+     */
+    void setNodeBinding(Node* node);
+
 private:
 
     /**

+ 9 - 14
gameplay/src/Terrain.cpp

@@ -32,8 +32,7 @@ float getDefaultHeight(unsigned int width, unsigned int height);
 
 Terrain::Terrain() :
     _heightfield(NULL), _node(NULL), _normalMap(NULL), _flags(FRUSTUM_CULLING | LEVEL_OF_DETAIL),
-    _dirtyFlags(TERRAIN_DIRTY_WORLD_MATRIX | TERRAIN_DIRTY_INV_WORLD_MATRIX | TERRAIN_DIRTY_NORMAL_MATRIX),
-    _directionalLightCount(0), _pointLightCount(0), _spotLightCount(0)
+    _dirtyFlags(TERRAIN_DIRTY_WORLD_MATRIX | TERRAIN_DIRTY_INV_WORLD_MATRIX | TERRAIN_DIRTY_NORMAL_MATRIX)
 {
 }
 
@@ -69,9 +68,6 @@ Terrain* Terrain::create(const char* path, Properties* properties)
     int detailLevels = 1;
     float skirtScale = 0;
     const char* normalMap = NULL;
-    unsigned int directionalLightCount = 0;
-    unsigned int pointLightCount = 0;
-    unsigned int spotLightCount = 0;
 
     if (!p && path)
     {
@@ -262,14 +258,6 @@ Terrain* Terrain::create(HeightField* heightfield, const Vector3& scale, unsigne
     float halfHeight = (height - 1) * 0.5f;
     unsigned int maxStep = (unsigned int)std::pow(2.0, (double)(detailLevels-1));
 
-    Properties* lightingProps = properties->getNamespace("lighting", true);
-    if (lightingProps)
-    {
-        terrain->_directionalLightCount = lightingProps->getInt("directionalLights");
-        terrain->_pointLightCount = lightingProps->getInt("pointLights");
-        terrain->_spotLightCount = lightingProps->getInt("spotLights");
-    }
-
     // Create terrain patches
     unsigned int x1, x2, z1, z2;
     unsigned int row = 0, column = 0;
@@ -284,7 +272,7 @@ Terrain* Terrain::create(HeightField* heightfield, const Vector3& scale, unsigne
             x2 = std::min(x1 + patchSize, width-1);
 
             // Create this patch
-            TerrainPatch* patch = TerrainPatch::create(terrain, row, column, heightfield->getArray(), width, height, x1, z1, x2, z2, -halfWidth, -halfHeight, maxStep, skirtScale);
+            TerrainPatch* patch = TerrainPatch::create(terrain, terrain->_patches.size(), row, column, heightfield->getArray(), width, height, x1, z1, x2, z2, -halfWidth, -halfHeight, maxStep, skirtScale);
             terrain->_patches.push_back(patch);
 
             // Append the new patch's local bounds to the terrain local bounds
@@ -381,6 +369,12 @@ void Terrain::setNode(Node* node)
 
         _node = node;
 
+        // Update node bindings for all materails
+        for (size_t i = 0, count = _patches.size(); i < count; ++i)
+        {
+            _patches[i]->updateNodeBinding(_node);
+        }
+
         if (_node)
             _node->addListener(this);
 
@@ -546,6 +540,7 @@ const Matrix& Terrain::getNormalMatrix() const
     {
         _dirtyFlags &= ~TERRAIN_DIRTY_NORMAL_MATRIX;
 
+        // Note: Terrain lighting is done in world space to simplify use of object-space height normal maps.
         getInverseWorldMatrix().transpose(&_normalMatrix);
     }
 

+ 30 - 33
gameplay/src/Terrain.h

@@ -307,48 +307,48 @@ private:
     void setNode(Node* node);
 
     /**
-    * @see Transform::Listener::transformChanged.
-    *
-    * Internal use only.
-    *
-    * @script{ignore}
-    */
+     * @see Transform::Listener::transformChanged.
+     *
+     * Internal use only.
+     *
+     * @script{ignore}
+     */
     void transformChanged(Transform* transform, long cookie);
 
     /**
-    * Returns the world matrix of the terrain, factoring in terrain local scaling.
-    *
-    * @return The world matrix for the terrain.
-    */
+     * Returns the world matrix of the terrain, factoring in terrain local scaling.
+     *
+     * @return The world matrix for the terrain.
+     */
     const Matrix& getWorldMatrix() const;
 
     /**
-    * Returns the terrain's inverse world matrix.
-    *
-    * @return The inverse world matrix for the terrain.
-    */
-    const Matrix& getInverseWorldMatrix() const;
+     * Returns the world view matrix for the terrain, factoring in terrain local scaling.
+     *
+     * @return The world-view matrix for the terrain.
+     */
+    const Matrix& getWorldViewMatrix() const;
 
     /**
-    * Returns a matrix to be used for transforming normal vectors for the terrain.
-    *
-    * @return The matrix used for normal vector transformation for the terrain.
-    */
-    const Matrix& getNormalMatrix() const;
+     * Returns the world view projection matrix for the terrain, factoring in terrain local scaling.
+     *
+     * @return The world-view-projection matrix for the terrain.
+     */
+    const Matrix& getWorldViewProjectionMatrix() const;
 
     /**
-    * Returns the world view matrix for the terrain, factoring in terrain local scaling.
-    *
-    * @return The world-view matrix for the terrain.
-    */
-    const Matrix& getWorldViewMatrix() const;
+     * Returns the terrain's inverse world matrix.
+     *
+     * @return The inverse world matrix for the terrain.
+     */
+    const Matrix& getInverseWorldMatrix() const;
 
     /**
-    * Returns the world view projection matrix for the terrain, factoring in terrain local scaling.
-    *
-    * @return The world-view-projection matrix for the terrain.
-    */
-    const Matrix& getWorldViewProjectionMatrix() const;
+     * Returns a matrix to be used for transforming normal vectors for the terrain.
+     *
+     * @return The matrix used for normal vector transformation for the terrain.
+     */
+    const Matrix& getNormalMatrix() const;
 
     /**
      * Returns the local bounding box for this patch, at the base LOD level.
@@ -366,9 +366,6 @@ private:
     mutable Matrix _normalMatrix;
     mutable unsigned int _dirtyFlags;
     BoundingBox _boundingBox;
-    unsigned int _directionalLightCount;
-    unsigned int _pointLightCount;
-    unsigned int _spotLightCount;
 };
 
 }

+ 183 - 87
gameplay/src/TerrainPatch.cpp

@@ -5,9 +5,8 @@
 #include "Scene.h"
 #include "Game.h"
 
-// Default terrain shaders
-#define TERRAIN_VSH "res/shaders/terrain.vert"
-#define TERRAIN_FSH "res/shaders/terrain.frag"
+// Default terrain material
+#define TERRAIN_MATERIAL "res/materials/terrain.material"
 
 namespace gameplay
 {
@@ -27,9 +26,17 @@ template <class T> T clamp(T value, T min, T max) { return value < min ? min : (
 #define TERRAINPATCH_DIRTY_LEVEL 4
 #define TERRAINPATCH_DIRTY_ALL (TERRAINPATCH_DIRTY_MATERIAL | TERRAINPATCH_DIRTY_BOUNDS | TERRAINPATCH_DIRTY_LEVEL)
 
+static bool __autoBindingResolverRegistered = false;
+static int __currentPatchIndex = -1;
+
 TerrainPatch::TerrainPatch() :
 _terrain(NULL), _row(0), _column(0), _camera(NULL), _level(0), _bits(TERRAINPATCH_DIRTY_ALL)
 {
+    if (!__autoBindingResolverRegistered)
+    {
+        RenderState::registerAutoBindingResolver(TerrainPatch::autoBindingResolver);
+        __autoBindingResolverRegistered = true;
+    }
 }
 
 TerrainPatch::~TerrainPatch()
@@ -48,7 +55,7 @@ TerrainPatch::~TerrainPatch()
     }
 }
 
-TerrainPatch* TerrainPatch::create(Terrain* terrain,
+TerrainPatch* TerrainPatch::create(Terrain* terrain, unsigned int index,
                                    unsigned int row, unsigned int column,
                                    float* heights, unsigned int width, unsigned int height,
                                    unsigned int x1, unsigned int z1, unsigned int x2, unsigned int z2,
@@ -58,6 +65,7 @@ TerrainPatch* TerrainPatch::create(Terrain* terrain,
     // Create patch
     TerrainPatch* patch = new TerrainPatch();
     patch->_terrain = terrain;
+    patch->_index = index;
     patch->_row = row;
     patch->_column = column;
 
@@ -459,104 +467,79 @@ bool TerrainPatch::setLayer(int index, const char* texturePath, const Vector2& t
     return true;
 }
 
-bool TerrainPatch::updateMaterial()
+std::string TerrainPatch::passCallback(Pass* pass, void* cookie)
 {
-    if (!(_bits & TERRAINPATCH_DIRTY_MATERIAL))
-        return true;
+    TerrainPatch* patch = reinterpret_cast<TerrainPatch*>(cookie);
+    GP_ASSERT(patch);
 
-    _bits &= ~TERRAINPATCH_DIRTY_MATERIAL;
+    return patch->passCreated(pass);
+}
 
-    for (size_t i = 0, count = _levels.size(); i < count; ++i)
+std::string TerrainPatch::passCreated(Pass* pass)
+{
+    // Build preprocessor string to be passed to the terrain shader.
+    // NOTE: I make heavy use of preprocessor definitions, rather than passing in arrays and doing
+    // non-constant array access in the shader. This is due to the fact that non-constant array access
+    // in GLES is very slow on some hardware.
+    std::ostringstream defines;
+    defines << "LAYER_COUNT " << _layers.size();
+    defines << ";SAMPLER_COUNT " << _samplers.size();
+
+    if (_terrain->isFlagSet(Terrain::DEBUG_PATCHES))
     {
-        // Build preprocessor string to pass to shader.
-        // NOTE: I make heavy use of preprocessor definitions, rather than passing in arrays and doing
-        // non-constant array access in the shader. This is due to the fact that non-constant array access
-        // in GLES is very slow on some GLES 2.x hardware.
-        std::ostringstream defines;
-        defines << "LAYER_COUNT " << _layers.size();
-        defines << ";SAMPLER_COUNT " << _samplers.size();
-
-        if (_terrain->isFlagSet(Terrain::DEBUG_PATCHES))
-            defines << ";DEBUG_PATCHES";
-        if (_terrain->_normalMap)
-            defines << ";NORMAL_MAP";
-
-        // Append texture and blend index constants to preprocessor definition.
-        // We need to do this since older versions of GLSL only allow sampler arrays
-        // to be indexed using constant expressions (otherwise we could simply pass an
-        // array of indices to use for sampler lookup).
-        //
-        // Rebuild layer lists while we're at it.
-        //
-        int layerIndex = 0;
-        for (std::set<Layer*, LayerCompare>::iterator itr = _layers.begin(); itr != _layers.end(); ++itr, ++layerIndex)
-        {
-            Layer* layer = *itr;
-
-            defines << ";TEXTURE_INDEX_" << layerIndex << " " << layer->textureIndex;
-            defines << ";TEXTURE_REPEAT_" << layerIndex << " vec2(" << layer->textureRepeat.x << "," << layer->textureRepeat.y << ")";
+        defines << ";DEBUG_PATCHES";
+        pass->getParameter("u_row")->setFloat(_row);
+        pass->getParameter("u_column")->setFloat(_column);
+    }
 
-            if (layerIndex > 0)
-            {
-                defines << ";BLEND_INDEX_" << layerIndex << " " << layer->blendIndex;
-                defines << ";BLEND_CHANNEL_" << layerIndex << " " << layer->blendChannel;
-            }
-        }
+    if (_terrain->_normalMap)
+        defines << ";NORMAL_MAP";
+
+    // Append texture and blend index constants to preprocessor definition.
+    // We need to do this since older versions of GLSL only allow sampler arrays
+    // to be indexed using constant expressions (otherwise we could simply pass an
+    // array of indices to use for sampler lookup).
+    //
+    // Rebuild layer lists while we're at it.
+    //
+    int layerIndex = 0;
+    for (std::set<Layer*, LayerCompare>::iterator itr = _layers.begin(); itr != _layers.end(); ++itr, ++layerIndex)
+    {
+        Layer* layer = *itr;
 
-        if (_terrain->_directionalLightCount > 0)
-            defines << ";DIRECTIONAL_LIGHT_COUNT " << _terrain->_directionalLightCount;
-        if (_terrain->_pointLightCount > 0)
-            defines << ";POINT_LIGHT_COUNT " << _terrain->_pointLightCount;
-        if (_terrain->_spotLightCount > 0)
-            defines << ";SPOT_LIGHT_COUNT " << _terrain->_spotLightCount;
+        defines << ";TEXTURE_INDEX_" << layerIndex << " " << layer->textureIndex;
+        defines << ";TEXTURE_REPEAT_" << layerIndex << " vec2(" << layer->textureRepeat.x << "," << layer->textureRepeat.y << ")";
 
-        Material* material = Material::create(TERRAIN_VSH, TERRAIN_FSH, defines.str().c_str());
-        if (!material)
-            return false;
+        if (layerIndex > 0)
+        {
+            defines << ";BLEND_INDEX_" << layerIndex << " " << layer->blendIndex;
+            defines << ";BLEND_CHANNEL_" << layerIndex << " " << layer->blendChannel;
+        }
+    }
 
-        material->getStateBlock()->setCullFace(true);
-        material->getStateBlock()->setDepthTest(true);
+    return defines.str();
+}
 
-        // Set material parameter bindings
-        material->getParameter("u_worldViewProjectionMatrix")->bindValue(_terrain, &Terrain::getWorldViewProjectionMatrix);
+bool TerrainPatch::updateMaterial()
+{
+    if (!(_bits & TERRAINPATCH_DIRTY_MATERIAL))
+        return true;
 
-        if (_layers.size() > 0)
-            material->getParameter("u_surfaceLayerMaps")->setValue((const Texture::Sampler**)&_samplers[0], (unsigned int)_samplers.size());
+    _bits &= ~TERRAINPATCH_DIRTY_MATERIAL;
 
-        if (_terrain->isFlagSet(Terrain::DEBUG_PATCHES))
-        {
-            material->getParameter("u_row")->setValue((float)_row);
-            material->getParameter("u_column")->setValue((float)_column);
-        }
+    __currentPatchIndex = _index;
 
-        if (_terrain->_directionalLightCount > 0 || _terrain->_pointLightCount > 0 || _terrain->_spotLightCount > 0)
+    for (size_t i = 0, count = _levels.size(); i < count; ++i)
+    {
+        Material* material = Material::create(TERRAIN_MATERIAL, &passCallback, this);
+        if (!material)
         {
-            material->getParameter("u_ambientColor")->bindValue(this, &TerrainPatch::getAmbientColor);
-            if (_terrain->_normalMap)
-                material->getParameter("u_normalMap")->setValue(_terrain->_normalMap);
-            else
-                material->getParameter("u_normalMatrix")->bindValue(_terrain, &Terrain::getNormalMatrix);
+            __currentPatchIndex = -1;
+            return false;
         }
 
-        // Add all the parameters from the old material to the new ones render state
-        Material* prevMaterial = _levels[i]->model->getMaterial();
-        if (prevMaterial)
-        {
-            RenderState* prevStates[3] = { prevMaterial, prevMaterial->getTechnique(), prevMaterial->getTechnique()->getPassByIndex(0) };
-            RenderState* newStates[3] = { material, material->getTechnique(), material->getTechnique()->getPassByIndex(0) };
-            for (unsigned int i = 0; i < 3; ++i)
-            {
-                for (unsigned int j = 0; j < prevStates[i]->getParameterCount(); ++j)
-                {
-                    newStates[i]->addParameter(prevStates[i]->getParameterByIndex(j));
-                    if (!_terrain->isFlagSet(Terrain::DEBUG_PATCHES))
-                    {
-                        newStates[i]->removeParameter("u_row");
-                        newStates[i]->removeParameter("u_column");
-                    }
-                }
-            }
-        }
+        if (_terrain->_node)
+            material->setNodeBinding(_terrain->_node);
 
         // Set material on this lod level
         _levels[i]->model->setMaterial(material);
@@ -564,6 +547,8 @@ bool TerrainPatch::updateMaterial()
         material->release();
     }
 
+    __currentPatchIndex = -1;
+
     return true;
 }
 
@@ -712,4 +697,115 @@ bool TerrainPatch::LayerCompare::operator() (const Layer* lhs, const Layer* rhs)
     return (lhs->index < rhs->index);
 }
 
+void TerrainPatch::updateNodeBinding(Node* node)
+{
+    __currentPatchIndex = _index;
+
+    for (size_t i = 0, count = _levels.size(); i < count; ++i)
+    {
+        _levels[i]->model->getMaterial()->setNodeBinding(node);
+    }
+
+    __currentPatchIndex = -1;
+}
+
+bool TerrainPatch::autoBindingResolver(const char* autoBinding, Node* node, MaterialParameter* parameter)
+{
+    if (strcmp(autoBinding, "TERRAIN_WORLD_MATRIX") == 0)
+    {
+        Terrain* terrain = node->getTerrain();
+        if (terrain)
+        {
+            parameter->bindValue(terrain, &Terrain::getWorldMatrix);
+            return true;
+        }
+    }
+    else if (strcmp(autoBinding, "TERRAIN_WORLD_VIEW_MATRIX") == 0)
+    {
+        Terrain* terrain = node->getTerrain();
+        if (terrain)
+        {
+            parameter->bindValue(terrain, &Terrain::getWorldViewMatrix);
+            return true;
+        }
+    }
+    else if (strcmp(autoBinding, "TERRAIN_WORLD_VIEW_PROJECTION_MATRIX") == 0)
+    {
+        Terrain* terrain = node->getTerrain();
+        if (terrain)
+        {
+            parameter->bindValue(terrain, &Terrain::getWorldViewProjectionMatrix);
+            return true;
+        }
+    }
+    else if (strcmp(autoBinding, "TERRAIN_INVERSE_WORLD_MATRIX") == 0)
+    {
+        Terrain* terrain = node->getTerrain();
+        if (terrain)
+        {
+            parameter->bindValue(terrain, &Terrain::getInverseWorldMatrix);
+            return true;
+        }
+    }
+    else if (strcmp(autoBinding, "TERRAIN_NORMAL_MATRIX") == 0)
+    {
+        Terrain* terrain = node->getTerrain();
+        if (terrain)
+        {
+            parameter->bindValue(terrain, &Terrain::getNormalMatrix);
+            return true;
+        }
+    }
+    else if (strcmp(autoBinding, "TERRAIN_LAYER_MAPS") == 0)
+    {
+        Terrain* terrain = node->getTerrain();
+        if (terrain && __currentPatchIndex >= 0 && __currentPatchIndex < (int)terrain->_patches.size())
+        {
+            TerrainPatch* patch = terrain->_patches[__currentPatchIndex];
+            if (patch && patch->_layers.size() > 0)
+            {
+                parameter->setValue((const Texture::Sampler**)&patch->_samplers[0], (unsigned int)patch->_samplers.size());
+            }
+            return true;
+        }
+    }
+    else if (strcmp(autoBinding, "TERRAIN_NORMAL_MAP") == 0)
+    {
+        Terrain* terrain = node->getTerrain();
+        if (terrain && terrain->_normalMap)
+        {
+            parameter->setValue(terrain->_normalMap);
+            return true;
+        }
+    }
+    else if (strcmp(autoBinding, "TERRAIN_ROW") == 0)
+    {
+        Terrain* terrain = node->getTerrain();
+        if (terrain && __currentPatchIndex >= 0 && __currentPatchIndex < (int)terrain->_patches.size())
+        {
+            TerrainPatch* patch = terrain->_patches[__currentPatchIndex];
+            if (patch)
+            {
+                parameter->setValue((float)patch->_row);
+                return true;
+            }
+        }
+    }
+    else if (strcmp(autoBinding, "TERRAIN_COLUMN") == 0)
+    {
+        Terrain* terrain = node->getTerrain();
+        if (terrain && __currentPatchIndex >= 0 && __currentPatchIndex < (int)terrain->_patches.size())
+        {
+            TerrainPatch* patch = terrain->_patches[__currentPatchIndex];
+            if (patch)
+            {
+                parameter->setValue((float)patch->_column);
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
 }

+ 15 - 1
gameplay/src/TerrainPatch.h

@@ -43,6 +43,13 @@ public:
      */
     void cameraChanged(Camera* camera);
 
+    /**
+     * Internal use only.
+     *
+     * @script{ignore}
+     */
+    static std::string passCallback(Pass* pass, void* cookie);
+
 private:
 
     /**
@@ -96,7 +103,7 @@ private:
         bool operator() (const Layer* lhs, const Layer* rhs) const;
     };
 
-    static TerrainPatch* create(Terrain* terrain, 
+    static TerrainPatch* create(Terrain* terrain, unsigned int index,
                                 unsigned int row, unsigned int column,
                                 float* heights, unsigned int width, unsigned int height,
                                 unsigned int x1, unsigned int z1, unsigned int x2, unsigned int z2,
@@ -123,7 +130,14 @@ private:
 
     void setMaterialDirty();
 
+    void updateNodeBinding(Node* node);
+
+    std::string passCreated(Pass* pass);
+
+    static bool autoBindingResolver(const char* autoBinding, Node* node, MaterialParameter* parameter);
+
     Terrain* _terrain;
+    unsigned int _index;
     unsigned int _row;
     unsigned int _column;
     std::vector<Level*> _levels;

+ 0 - 7
samples/browser/res/common/terrain/sample.terrain

@@ -13,13 +13,6 @@ terrain
 
     normalMap = res/common/terrain/normalmap.dds
 
-    lighting
-    {
-        directionalLights = 1
-        pointLights = 0
-        spotLights = 0
-    }
-
 	layer rock
 	{
 		texture

+ 46 - 41
samples/browser/src/TerrainSample.cpp

@@ -21,11 +21,21 @@ struct TerrainHitFilter : public PhysicsController::HitFilter
     PhysicsCollisionObject* terrainObject;
 };
 
+static TerrainSample* __instance = NULL;
+
 TerrainSample::TerrainSample()
 	: _font(NULL), _scene(NULL), _terrain(NULL), _sky(NULL), _form(NULL), _formVisible(true),
 	  _wireframe(false), _debugPhysics(false), _snapToGround(true), _vsync(true),
       _mode(MODE_LOOK), _sphere(NULL), _box(NULL), _directionalLight(NULL)
 {
+    __instance = this;
+
+    static bool registered = false;
+    if (!registered)
+    {
+        RenderState::registerAutoBindingResolver(&resolveAutoBinding);
+        registered = true;
+    }
 }
 
 TerrainSample::~TerrainSample()
@@ -35,6 +45,9 @@ TerrainSample::~TerrainSample()
     SAFE_RELEASE(_form);
     SAFE_RELEASE(_font);
     SAFE_RELEASE(_scene);
+
+    if (__instance == this)
+        __instance = NULL;
 }
 
 void TerrainSample::initialize()
@@ -83,44 +96,7 @@ void TerrainSample::initialize()
     _scene->addNode(lightNode);
     lightNode->setLight(_directionalLight);
     lightNode->setRotation(Vector3(1, 0, 0), -MATH_DEG_TO_RAD(45));
-    
-    _scene->visit(this, &TerrainSample::intializeLights);
-}
-
-void initializeLight(Material* material, Light* light)
-{
-    if (material->getTechnique()->getPassByIndex(0)->getEffect()->getUniform("u_directionalLightDirection[0]"))
-    {
-        // For this sample we will only bind a single light to each object in the scene.
-        MaterialParameter* colorParam = material->getParameter("u_directionalLightColor[0]");
-        colorParam->setValue(light->getColor());
-
-        MaterialParameter* directionParam = material->getParameter("u_directionalLightDirection[0]");
-        directionParam->bindValue(light->getNode(), &Node::getForwardVectorWorld);
-    }
-}
-
-bool TerrainSample::intializeLights(Node* node)
-{
-    Model* model = node->getModel();
-    if (model)
-    {
-        initializeLight(model->getMaterial(), _directionalLight);
-    }
-
-    Terrain* terrain = node->getTerrain();
-    if (terrain)
-    {
-        unsigned int patchCount = terrain->getPatchCount();
-        for (unsigned int i = 0; i < patchCount; i++)
-        {
-            TerrainPatch* patch = terrain->getPatch(i);
-            Material* material = patch->getMaterial();
-
-            initializeLight(material, _directionalLight);
-        }
-    }
-    return true;
+    _directionalLight->release();
 }
 
 void TerrainSample::finalize()
@@ -310,9 +286,6 @@ void TerrainSample::touchEvent(Touch::TouchEvent evt, int x, int y, unsigned int
                     PhysicsRigidBody::Parameters rbParams(1);
                     clone->setCollisionObject(PhysicsCollisionObject::RIGID_BODY, rbShape, &rbParams);
                     _scene->addNode(clone);
-                    clone->getModel()->setMaterial(materialUrl);
-                    Material* material = clone->getModel()->getMaterial();
-                    initializeLight(material, _directionalLight);
                     clone->release();
 
                     _shapes.push_back(clone);
@@ -415,3 +388,35 @@ void TerrainSample::setMessage(const char* message)
     _form->getControl("messageBox")->setVisible(message ? true : false);
 }
 
+Vector3 TerrainSample::getDirectionalLightDirection() const
+{
+    // Terrain expects world-space light vectors
+    return _directionalLight->getNode()->getForwardVectorWorld();
+}
+
+Vector3 TerrainSample::getDirectionalLightColor() const
+{
+    return _directionalLight->getColor();
+}
+
+bool TerrainSample::resolveAutoBinding(const char* autoBinding, Node* node, MaterialParameter* parameter)
+{
+    if (strcmp(autoBinding, "DIRECTIONAL_LIGHT_DIRECTION") == 0)
+    {
+        if (__instance)
+        {
+            parameter->bindValue(__instance, &TerrainSample::getDirectionalLightDirection);
+            return true;
+        }
+    }
+    else if (strcmp(autoBinding, "DIRECTIONAL_LIGHT_COLOR") == 0)
+    {
+        if (__instance)
+        {
+            parameter->bindValue(__instance, &TerrainSample::getDirectionalLightColor);
+            return true;
+        }
+    }
+
+    return false;
+}

+ 6 - 0
samples/browser/src/TerrainSample.h

@@ -22,6 +22,8 @@ public:
 
     void controlEvent(Control* control, EventType evt);
 
+    static bool resolveAutoBinding(const char* autoBinding, Node* node, MaterialParameter* parameter);
+
 protected:
 
     void initialize();
@@ -47,6 +49,10 @@ private:
         MODE_DROP_BOX
     };
 
+    Vector3 getDirectionalLightDirection() const;
+    
+    Vector3 getDirectionalLightColor() const;
+
 	Font* _font;
 	Scene* _scene;
 	Terrain* _terrain;