Explorar el Código

Default materials:
- Adds support for default materials on models that have no explicitly bound materials.
- By default, a built-in solid pink material will be used for all models that do not have explicitly set materials.
- A custom default material can be used by specifying one in the game.config/graphics/defaultMaterial property.
- Custom materials can be disabled by setting 'defaultMaterial = none' in game.config (this has been done for all current samples).

Multisampling:
- Moved 'samples' property in game.config from the 'window' section to the new 'graphics' section.

Fixes #346
Fixes #269

Steve Grenier hace 13 años
padre
commit
7d447226a6

+ 111 - 1
gameplay/src/Material.cpp

@@ -6,12 +6,18 @@
 #include "Pass.h"
 #include "Properties.h"
 #include "Node.h"
+#include "Game.h"
+
+#define MATERIAL_DEFAULT_BIT 1
+#define MATERIAL_BUILTIN_BIT 2
 
 namespace gameplay
 {
 
+static Effect* __defaultEffect = NULL;
+
 Material::Material() :
-    _currentTechnique(NULL)
+    _currentTechnique(NULL), _bits(0)
 {
 }
 
@@ -23,6 +29,13 @@ Material::~Material()
         Technique* technique = _techniques[i];
         SAFE_RELEASE(technique);
     }
+
+    // If we're the last material that was using __defaultEffect, then release
+    // and unassign it.
+    if ((_bits & MATERIAL_DEFAULT_BIT) && __defaultEffect && __defaultEffect->getRefCount() == 1)
+    {
+        SAFE_RELEASE(__defaultEffect);
+    }
 }
 
 Material* Material::create(const char* url)
@@ -124,6 +137,103 @@ Material* Material::create(const char* vshPath, const char* fshPath, const char*
     return material;
 }
 
+bool isDefaultMaterialEnabled(const char*& customMaterial)
+{
+    customMaterial = NULL;
+
+    // Default materials are always enabled unless explicitly turned off in config
+    Properties* config = Game::getInstance()->getConfig()->getNamespace("graphics", true);
+    if (config)
+    {
+        const char* str = config->getString("defaultMaterial");
+        if (str && strlen(str) > 0)
+        {
+            // User set 'defaultMaterial = none', which disabled default materials
+            if (strcmp(str, "none") == 0)
+                return false;
+
+            customMaterial = str;
+        }
+    }
+
+    return true;
+}
+
+Material* Material::createDefault()
+{
+    // Check whether default materials are enabled only once, for performance reasons
+    static const char* customMaterial = NULL;
+    static bool enabled = isDefaultMaterialEnabled(customMaterial);
+    if (!enabled)
+        return NULL;
+
+    // If we've already loaded __defaultEffect, use that.
+    if (__defaultEffect)
+    {
+        Material* material = create(__defaultEffect);
+        if (material)
+        {
+            material->_bits = MATERIAL_DEFAULT_BIT | MATERIAL_BUILTIN_BIT;
+            return material;
+        }
+    }
+
+    // Attempt to load a custom material (if configured)
+    if (customMaterial)
+    {
+        Material* material = create(customMaterial);
+        if (material)
+        {
+            material->_bits = MATERIAL_DEFAULT_BIT;
+            return material;
+        }
+    }
+
+    // Create the built-in default material
+    const char* vshSource =
+        "uniform mat4 u_worldViewProjectionMatrix;\n" \
+        "attribute vec3 a_position;\n" \
+        "void main()\n" \
+        "{\n" \
+            "gl_Position = u_worldViewProjectionMatrix * vec4(a_position, 1);\n" \
+        "}\n";
+
+    const char* fshSource =
+        "#ifdef OPENGL_ES\n" \
+        "precision highp float;\n" \
+        "#endif\n" \
+        "void main()\n" \
+        "{\n" \
+            "gl_FragColor = vec4(0.97, 0.88, 0.83, 1.0);\n" \
+        "}\n";
+
+    // Create our default effect and leave the initial reference count so that it is not
+    // automatically released when the last Material/Pass referencing it is released.
+    // We use this to perform additional cleanup in ~Material() when we detect a
+    // default material being released and __defaultEffect has a ref count == 1.
+    __defaultEffect = Effect::createFromSource(vshSource, fshSource);
+    if (!__defaultEffect)
+        return NULL;
+
+    Material* material = create(__defaultEffect);
+    if (material)
+    {
+        material->_bits = MATERIAL_DEFAULT_BIT | MATERIAL_BUILTIN_BIT;
+    }
+
+    return material;
+}
+
+bool Material::isDefault() const
+{
+    return (_bits & MATERIAL_DEFAULT_BIT) == MATERIAL_DEFAULT_BIT;
+}
+
+bool Material::isBuiltin() const
+{
+    return (_bits & MATERIAL_BUILTIN_BIT) == MATERIAL_BUILTIN_BIT;
+}
+
 unsigned int Material::getTechniqueCount() const
 {
     return _techniques.size();

+ 32 - 0
gameplay/src/Material.h

@@ -78,6 +78,37 @@ public:
      */
     static Material* create(const char* vshPath, const char* fshPath, const char* defines = NULL);
 
+    /**
+     * Creates a default material.
+     *
+     * The material returned from this method is normally used to draw
+     * models that have no explicitly defined material. By default, this
+     * method returns a material that draws objects using a solid pink color,
+     * which in intended to be easily spotted in a scene. However, if the
+     * graphics/defaultMaterial property is specified in a game.confg file,
+     * that material will be used as the default instead of the built-in one.
+     * It is also valid to specify 'none' for the defaultMaterial config
+     * property, in which case this method will return NULL and no default
+     * material will be used by models without explicitly set materials.
+     *
+     * @return A new default material, or NULL if no default material is available.
+     */
+    static Material* createDefault();
+
+    /**
+     * Determines if this is the default material (i.e. returned from createDefault).
+     *
+     * @return True if this is the default material, false otherwise.
+     */
+    bool isDefault() const;
+
+    /**
+     * Determines if this is the default built-in material (i.e. returned from createDefault).
+     *
+     * @return True if this is the default built-in material, false otherwise.
+     */
+    bool isBuiltin() const;
+
     /**
      * Returns the number of techniques in the material.
      *
@@ -161,6 +192,7 @@ private:
 
     Technique* _currentTechnique;
     std::vector<Technique*> _techniques;
+    int _bits;
 };
 
 }

+ 53 - 20
gameplay/src/Model.cpp

@@ -54,24 +54,12 @@ unsigned int Model::getMeshPartCount() const
 
 Material* Model::getMaterial(int partIndex)
 {
-    GP_ASSERT(partIndex == -1 || (partIndex >= 0 && partIndex < (int)getMeshPartCount()));
-
-    Material* m = NULL;
+    Material* m = getMaterialInternal(partIndex, false);
 
-    if (partIndex >= 0 && partIndex < (int)_partCount)
-    {
-        // Look up explicitly specified part material.
-        if (_partMaterials)
-        {
-            m = _partMaterials[partIndex];
-        }
-    }
-
-    if (m == NULL)
-    {
-        // Return the shared material.
-         m = _material;
-    }
+    // If our material is the default one, return NULL to signify that we
+    // don't actually have a specified material.
+    if (m && m->isDefault())
+        return NULL;
 
     return m;
 }
@@ -253,6 +241,50 @@ void Model::setNode(Node* node)
     }
 }
 
+Material* Model::getMaterialInternal(int partIndex, bool draw)
+{
+    GP_ASSERT(partIndex == -1 || (partIndex >= 0 && partIndex < (int)getMeshPartCount()));
+
+    Material* m = NULL;
+
+    if (partIndex >= 0 && partIndex < (int)_partCount)
+    {
+        // Look up explicitly specified part material.
+        if (_partMaterials)
+        {
+            m = _partMaterials[partIndex];
+        }
+    }
+
+    if (m == NULL)
+    {
+        // Return the shared material.
+         m = _material;
+    }
+
+    // If we were called during a draw operation and no material is currently assigned,
+    // attempt to load a default material.
+    if (m == NULL && draw)
+    {
+        m = Material::createDefault();
+        if (m)
+        {
+            GP_WARN("No material defined for model in node '%s'. Setting default material.", _node->getId());
+
+            setMaterial(m);
+            m->release();
+
+            // If the returned material is the built-in one, setup auto-bindings
+            if (m->isBuiltin())
+            {
+                m->setParameterAutoBinding("u_worldViewProjectionMatrix", RenderState::WORLD_VIEW_PROJECTION_MATRIX);
+            }
+        }
+    }
+
+    return m;
+}
+
 void Model::draw(bool wireframe)
 {
     GP_ASSERT(_mesh);
@@ -261,9 +293,10 @@ void Model::draw(bool wireframe)
     if (partCount == 0)
     {
         // No mesh parts (index buffers).
-        if (_material)
+        Material* material = getMaterialInternal(-1, true);
+        if (material)
         {
-            Technique* technique = _material->getTechnique();
+            Technique* technique = material->getTechnique();
             GP_ASSERT(technique);
             unsigned int passCount = technique->getPassCount();
             for (unsigned int i = 0; i < passCount; ++i)
@@ -296,7 +329,7 @@ void Model::draw(bool wireframe)
             GP_ASSERT(part);
 
             // Get the material for this mesh part.
-            Material* material = getMaterial(i);
+            Material* material = getMaterialInternal(i, true);
             if (material)
             {
                 Technique* technique = material->getTechnique();

+ 7 - 0
gameplay/src/Model.h

@@ -182,6 +182,13 @@ private:
      */
     void setMaterialNodeBinding(Material *m);
 
+    /**
+     * Gets the material used to draw with (this is potentially different than
+     * the result of the public getMaterial method if no explicit material is
+     * defined and a default material is being used.
+     */
+    Material* getMaterialInternal(int partIndex, bool draw);
+
     void validatePartCount();
 
     /**

+ 1 - 1
gameplay/src/PlatformAndroid.cpp

@@ -109,7 +109,7 @@ static EGLenum checkErrorEGL(const char* msg)
 static bool initEGL()
 {
 	int samples = 0;
-	Properties* config = Game::getInstance()->getConfig()->getNamespace("window", true);
+	Properties* config = Game::getInstance()->getConfig()->getNamespace("graphics", true);
 	if (config)
 	{
 		samples = std::max(config->getInt("samples"), 0);

+ 1 - 1
gameplay/src/PlatformMacOSX.mm

@@ -680,7 +680,7 @@ static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTime
 {
     _game = Game::getInstance();
     
-    Properties* config = _game->getConfig()->getNamespace("window", true);
+    Properties* config = _game->getConfig()->getNamespace("graphics", true);
     int samples = config ? config->getInt("samples") : 0;
     if (samples < 0)
         samples = 0;

+ 1 - 1
gameplay/src/PlatformQNX.cpp

@@ -546,7 +546,7 @@ Platform* Platform::create(Game* game, void* attachToWindow)
 
     // Query game config
     int samples = 0;
-    Properties* config = Game::getInstance()->getConfig()->getNamespace("window", true);
+    Properties* config = Game::getInstance()->getConfig()->getNamespace("graphics", true);
     if (config)
     {
         samples = std::max(config->getInt("samples"), 0);

+ 3 - 1
gameplay/src/PlatformWin32.cpp

@@ -889,7 +889,9 @@ Platform* Platform::create(Game* game, void* attachToWindow)
             params.fullscreen = config->getBool("fullscreen");
 
             // Read multisampling state.
-            params.samples = config->getInt("samples");
+            Properties* graphicsConfig = game->getConfig()->getNamespace("graphics", true);
+            if (graphicsConfig)
+                params.samples = std::max(graphicsConfig->getInt("samples"), 0);
         }
     }
 

+ 1 - 1
gameplay/src/PlatformiOS.mm

@@ -226,7 +226,7 @@ int getKey(unichar keyCode);
     NSLog(@"width: %d, height: %d", framebufferWidth, framebufferHeight);
     
     // If multisampling is enabled in config, create and setup a multisample buffer
-    Properties* config = Game::getInstance()->getConfig()->getNamespace("window", true);
+    Properties* config = Game::getInstance()->getConfig()->getNamespace("graphics", true);
     int samples = config ? config->getInt("samples") : 0;
     if (samples < 0)
         samples = 0;