瀏覽代碼

Metalness workflow PBR stuff, support light color temperatures, make light specular color tied to light color by default

rdb 10 年之前
父節點
當前提交
3393454582

+ 12 - 2
panda/src/display/graphicsStateGuardian.cxx

@@ -1075,7 +1075,8 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
       return &t;
       return &t;
     }
     }
     Material *m = target_material->get_material();
     Material *m = target_material->get_material();
-    t.set_row(3, LVecBase4(m->get_metallic(), 0, 0, m->get_roughness()));
+    t.set_row(0, m->get_base_color());
+    t.set_row(3, LVecBase4(m->get_metallic(), m->get_refractive_index(), 0, m->get_roughness()));
     return &t;
     return &t;
   }
   }
   case Shader::SMO_attr_color: {
   case Shader::SMO_attr_color: {
@@ -1494,6 +1495,7 @@ fetch_specified_part(Shader::ShaderMatInput part, InternalName *name,
 const LMatrix4 *GraphicsStateGuardian::
 const LMatrix4 *GraphicsStateGuardian::
 fetch_specified_member(const NodePath &np, CPT_InternalName attrib, LMatrix4 &t) {
 fetch_specified_member(const NodePath &np, CPT_InternalName attrib, LMatrix4 &t) {
   // This system is not ideal.  It will be improved in the future.
   // This system is not ideal.  It will be improved in the future.
+  static const CPT_InternalName IN_color("color");
   static const CPT_InternalName IN_ambient("ambient");
   static const CPT_InternalName IN_ambient("ambient");
   static const CPT_InternalName IN_diffuse("diffuse");
   static const CPT_InternalName IN_diffuse("diffuse");
   static const CPT_InternalName IN_specular("specular");
   static const CPT_InternalName IN_specular("specular");
@@ -1509,7 +1511,15 @@ fetch_specified_member(const NodePath &np, CPT_InternalName attrib, LMatrix4 &t)
   static const CPT_InternalName IN_quadraticAttenuation("quadraticAttenuation");
   static const CPT_InternalName IN_quadraticAttenuation("quadraticAttenuation");
   static const CPT_InternalName IN_shadowMatrix("shadowMatrix");
   static const CPT_InternalName IN_shadowMatrix("shadowMatrix");
 
 
-  if (attrib == IN_ambient) {
+  if (attrib == IN_color) {
+    Light *light = np.node()->as_light();
+    nassertr(light != (Light *)NULL, &LMatrix4::ident_mat());
+    LColor c = light->get_color();
+    c.componentwise_mult(_light_color_scale);
+    t.set_row(3, c);
+    return &t;
+
+  } else if (attrib == IN_ambient) {
     Light *light = np.node()->as_light();
     Light *light = np.node()->as_light();
     nassertr(light != (Light *)NULL, &LMatrix4::ident_mat());
     nassertr(light != (Light *)NULL, &LMatrix4::ident_mat());
     if (np.node()->is_of_type(AmbientLight::get_class_type())) {
     if (np.node()->is_of_type(AmbientLight::get_class_type())) {

+ 92 - 0
panda/src/egg/eggMaterial.I

@@ -13,6 +13,53 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 
 
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggMaterial::set_base
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void EggMaterial::
+set_base(const LColor &base) {
+  _base = base;
+  _flags |= F_base;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggMaterial::clear_base
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void EggMaterial::
+clear_base() {
+  _flags &= ~F_base;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggMaterial::has_base
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool EggMaterial::
+has_base() const {
+  return (_flags & F_base) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggMaterial::get_base
+//       Access: Public
+//  Description: It is legal to call this even if has_base() returns
+//               false.  If so, it simply returns the default base
+//               color.
+////////////////////////////////////////////////////////////////////
+INLINE LColor EggMaterial::
+get_base() const {
+  if (has_base()) {
+    return _base;
+  } else {
+    return LColor(1.0, 1.0, 1.0, 1.0);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggMaterial::set_diff
 //     Function: EggMaterial::set_diff
 //       Access: Public
 //       Access: Public
@@ -336,6 +383,51 @@ get_metallic() const {
   }
   }
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: EggMaterial::set_ior
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void EggMaterial::
+set_ior(double ior) {
+  _ior = ior;
+  _flags |= F_ior;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggMaterial::clear_ior
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE void EggMaterial::
+clear_ior() {
+  _flags &= ~F_ior;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggMaterial::has_ior
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE bool EggMaterial::
+has_ior() const {
+  return (_flags & F_ior) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: EggMaterial::get_ior
+//       Access: Public
+//  Description:
+////////////////////////////////////////////////////////////////////
+INLINE double EggMaterial::
+get_ior() const {
+  if (has_ior()) {
+    return _ior;
+  } else {
+    return 1.0;
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: EggMaterial::set_local
 //     Function: EggMaterial::set_local
 //       Access: Public
 //       Access: Public

+ 25 - 0
panda/src/egg/eggMaterial.cxx

@@ -39,6 +39,7 @@ EggMaterial(const string &mref_name)
 EggMaterial::
 EggMaterial::
 EggMaterial(const EggMaterial &copy)
 EggMaterial(const EggMaterial &copy)
   : EggNode(copy),
   : EggNode(copy),
+    _base(copy._base),
     _diff(copy._diff),
     _diff(copy._diff),
     _amb(copy._amb),
     _amb(copy._amb),
     _emit(copy._emit),
     _emit(copy._emit),
@@ -46,6 +47,7 @@ EggMaterial(const EggMaterial &copy)
     _shininess(copy._shininess),
     _shininess(copy._shininess),
     _roughness(copy._roughness),
     _roughness(copy._roughness),
     _metallic(copy._metallic),
     _metallic(copy._metallic),
+    _ior(copy._ior),
     _local(copy._local),
     _local(copy._local),
     _flags(copy._flags)
     _flags(copy._flags)
 {
 {
@@ -62,6 +64,19 @@ void EggMaterial::
 write(ostream &out, int indent_level) const {
 write(ostream &out, int indent_level) const {
   write_header(out, indent_level, "<Material>");
   write_header(out, indent_level, "<Material>");
 
 
+  if (has_base()) {
+    indent(out, indent_level + 2)
+      << "<Scalar> baser { " << get_base()[0] << " }\n";
+    indent(out, indent_level + 2)
+      << "<Scalar> baseg { " << get_base()[1] << " }\n";
+    indent(out, indent_level + 2)
+      << "<Scalar> baseb { " << get_base()[2] << " }\n";
+    if (get_base()[3] != 1.0) {
+      indent(out, indent_level + 2)
+        << "<Scalar> basea { " << get_base()[3] << " }\n";
+    }
+  }
+
   if (has_diff()) {
   if (has_diff()) {
     indent(out, indent_level + 2)
     indent(out, indent_level + 2)
       << "<Scalar> diffr { " << get_diff()[0] << " }\n";
       << "<Scalar> diffr { " << get_diff()[0] << " }\n";
@@ -129,6 +144,11 @@ write(ostream &out, int indent_level) const {
       << "<Scalar> metallic { " << get_metallic() << " }\n";
       << "<Scalar> metallic { " << get_metallic() << " }\n";
   }
   }
 
 
+  if (has_ior()) {
+    indent(out, indent_level + 2)
+      << "<Scalar> ior { " << get_ior() << " }\n";
+  }
+
   if (has_local()) {
   if (has_local()) {
     indent(out, indent_level + 2)
     indent(out, indent_level + 2)
       << "<Scalar> local { " << get_local() << " }\n";
       << "<Scalar> local { " << get_local() << " }\n";
@@ -159,6 +179,7 @@ bool EggMaterial::
 is_equivalent_to(const EggMaterial &other, int eq) const {
 is_equivalent_to(const EggMaterial &other, int eq) const {
   if (eq & E_attributes) {
   if (eq & E_attributes) {
     if (_flags != other._flags ||
     if (_flags != other._flags ||
+        (has_base() && get_base() != other.get_base()) ||
         (has_diff() && get_diff() != other.get_diff()) ||
         (has_diff() && get_diff() != other.get_diff()) ||
         (has_amb() && get_amb() != other.get_amb()) ||
         (has_amb() && get_amb() != other.get_amb()) ||
         (has_emit() && get_emit() != other.get_emit()) ||
         (has_emit() && get_emit() != other.get_emit()) ||
@@ -166,6 +187,7 @@ is_equivalent_to(const EggMaterial &other, int eq) const {
         (has_shininess() && get_shininess() != other.get_shininess()) ||
         (has_shininess() && get_shininess() != other.get_shininess()) ||
         (has_roughness() && get_roughness() != other.get_roughness()) ||
         (has_roughness() && get_roughness() != other.get_roughness()) ||
         (has_metallic() && get_metallic() != other.get_metallic()) ||
         (has_metallic() && get_metallic() != other.get_metallic()) ||
+        (has_ior() && get_ior() != other.get_ior()) ||
         (has_local() && get_local() != other.get_local())) {
         (has_local() && get_local() != other.get_local())) {
       return false;
       return false;
     }
     }
@@ -216,6 +238,9 @@ sorts_less_than(const EggMaterial &other, int eq) const {
     if (has_metallic() && get_metallic() != other.get_metallic()) {
     if (has_metallic() && get_metallic() != other.get_metallic()) {
       return get_metallic() < other.get_metallic();
       return get_metallic() < other.get_metallic();
     }
     }
+    if (has_ior() && get_ior() != other.get_ior()) {
+      return get_ior() < other.get_ior();
+    }
     if (has_local() && get_local() != other.get_local()) {
     if (has_local() && get_local() != other.get_local()) {
       return get_local() < other.get_local();
       return get_local() < other.get_local();
     }
     }

+ 24 - 8
panda/src/egg/eggMaterial.h

@@ -40,6 +40,11 @@ PUBLISHED:
   bool is_equivalent_to(const EggMaterial &other, int eq) const;
   bool is_equivalent_to(const EggMaterial &other, int eq) const;
   bool sorts_less_than(const EggMaterial &other, int eq) const;
   bool sorts_less_than(const EggMaterial &other, int eq) const;
 
 
+  INLINE void set_base(const LColor &base);
+  INLINE void clear_base();
+  INLINE bool has_base() const;
+  INLINE LColor get_base() const;
+
   INLINE void set_diff(const LColor &diff);
   INLINE void set_diff(const LColor &diff);
   INLINE void clear_diff();
   INLINE void clear_diff();
   INLINE bool has_diff() const;
   INLINE bool has_diff() const;
@@ -75,12 +80,18 @@ PUBLISHED:
   INLINE bool has_metallic() const;
   INLINE bool has_metallic() const;
   INLINE double get_metallic() const;
   INLINE double get_metallic() const;
 
 
+  INLINE void set_ior(double ior);
+  INLINE void clear_ior();
+  INLINE bool has_ior() const;
+  INLINE double get_ior() const;
+
   INLINE void set_local(bool local);
   INLINE void set_local(bool local);
   INLINE void clear_local();
   INLINE void clear_local();
   INLINE bool has_local() const;
   INLINE bool has_local() const;
   INLINE bool get_local() const;
   INLINE bool get_local() const;
 
 
 PUBLISHED:
 PUBLISHED:
+  MAKE_PROPERTY2(base, has_base, get_base, set_base, clear_base);
   MAKE_PROPERTY2(diff, has_diff, get_diff, set_diff, clear_diff);
   MAKE_PROPERTY2(diff, has_diff, get_diff, set_diff, clear_diff);
   MAKE_PROPERTY2(amb, has_amb, get_amb, set_amb, clear_amb);
   MAKE_PROPERTY2(amb, has_amb, get_amb, set_amb, clear_amb);
   MAKE_PROPERTY2(emit, has_emit, get_emit, set_emit, clear_emit);
   MAKE_PROPERTY2(emit, has_emit, get_emit, set_emit, clear_emit);
@@ -88,21 +99,25 @@ PUBLISHED:
   MAKE_PROPERTY2(shininess, has_shininess, get_shininess, set_shininess, clear_shininess);
   MAKE_PROPERTY2(shininess, has_shininess, get_shininess, set_shininess, clear_shininess);
   MAKE_PROPERTY2(roughness, has_roughness, get_roughness, set_roughness, clear_roughness);
   MAKE_PROPERTY2(roughness, has_roughness, get_roughness, set_roughness, clear_roughness);
   MAKE_PROPERTY2(metallic, has_metallic, get_metallic, set_metallic, clear_metallic);
   MAKE_PROPERTY2(metallic, has_metallic, get_metallic, set_metallic, clear_metallic);
+  MAKE_PROPERTY2(ior, has_ior, get_ior, set_ior, clear_ior);
 
 
   MAKE_PROPERTY2(local, has_local, get_local, set_local, clear_local);
   MAKE_PROPERTY2(local, has_local, get_local, set_local, clear_local);
 
 
 private:
 private:
   enum Flags {
   enum Flags {
-    F_diff      = 0x001,
-    F_amb       = 0x002,
-    F_emit      = 0x004,
-    F_spec      = 0x008,
-    F_shininess = 0x010,
-    F_roughness = 0x020,
-    F_metallic  = 0x040,
-    F_local     = 0x080
+    F_base      = 0x001,
+    F_diff      = 0x002,
+    F_amb       = 0x004,
+    F_emit      = 0x008,
+    F_spec      = 0x010,
+    F_shininess = 0x020,
+    F_roughness = 0x040,
+    F_metallic  = 0x080,
+    F_ior       = 0x100,
+    F_local     = 0x200
   };
   };
 
 
+  LColor _base;
   LColor _diff;
   LColor _diff;
   LColor _amb;
   LColor _amb;
   LColor _emit;
   LColor _emit;
@@ -110,6 +125,7 @@ private:
   double _shininess;
   double _shininess;
   double _roughness;
   double _roughness;
   double _metallic;
   double _metallic;
+  double _ior;
   bool _local;
   bool _local;
   int _flags;
   int _flags;
 
 

+ 6 - 0
panda/src/egg2pg/eggRenderState.cxx

@@ -495,6 +495,9 @@ get_material_attrib(const EggMaterial *egg_mat, bool bface) {
   // Ok, this is the first time we've seen this particular
   // Ok, this is the first time we've seen this particular
   // EggMaterial.  Create a new Material that matches it.
   // EggMaterial.  Create a new Material that matches it.
   PT(Material) mat = new Material(egg_mat->get_name());
   PT(Material) mat = new Material(egg_mat->get_name());
+  if (egg_mat->has_base()) {
+    mat->set_base_color(egg_mat->get_base());
+  }
   if (egg_mat->has_diff()) {
   if (egg_mat->has_diff()) {
     mat->set_diffuse(egg_mat->get_diff());
     mat->set_diffuse(egg_mat->get_diff());
     // By default, ambient is the same as diffuse, if diffuse is
     // By default, ambient is the same as diffuse, if diffuse is
@@ -519,6 +522,9 @@ get_material_attrib(const EggMaterial *egg_mat, bool bface) {
   if (egg_mat->has_metallic()) {
   if (egg_mat->has_metallic()) {
     mat->set_metallic(egg_mat->get_metallic());
     mat->set_metallic(egg_mat->get_metallic());
   }
   }
+  if (egg_mat->has_ior()) {
+    mat->set_refractive_index(egg_mat->get_ior());
+  }
   if (egg_mat->has_local()) {
   if (egg_mat->has_local()) {
     mat->set_local(egg_mat->get_local());
     mat->set_local(egg_mat->get_local());
   }
   }

+ 8 - 0
panda/src/egg2pg/eggSaver.cxx

@@ -1031,6 +1031,10 @@ EggMaterial *EggSaver::
 get_egg_material(Material *mat) {
 get_egg_material(Material *mat) {
   if (mat != (Material *)NULL) {
   if (mat != (Material *)NULL) {
     EggMaterial temp(mat->get_name());
     EggMaterial temp(mat->get_name());
+    if (mat->has_base_color()) {
+      temp.set_base(mat->get_base_color());
+    }
+
     if (mat->has_ambient()) {
     if (mat->has_ambient()) {
       temp.set_amb(mat->get_ambient());
       temp.set_amb(mat->get_ambient());
     }
     }
@@ -1057,6 +1061,10 @@ get_egg_material(Material *mat) {
       temp.set_metallic(mat->get_metallic());
       temp.set_metallic(mat->get_metallic());
     }
     }
 
 
+    if (mat->has_refractive_index()) {
+      temp.set_ior(mat->get_refractive_index());
+    }
+
     temp.set_local(mat->get_local());
     temp.set_local(mat->get_local());
 
 
     return _materials.create_unique_material(temp, ~EggMaterial::E_mref_name);
     return _materials.create_unique_material(temp, ~EggMaterial::E_mref_name);

+ 30 - 1
panda/src/glstuff/glShaderContext_src.cxx

@@ -914,7 +914,19 @@ reflect_uniform(int i, char *name_buffer, GLsizei name_buflen) {
       bind._arg[1] = NULL;
       bind._arg[1] = NULL;
       bind._dep[1] = Shader::SSD_NONE;
       bind._dep[1] = Shader::SSD_NONE;
 
 
-      if (noprefix == "Material.ambient") {
+      if (noprefix == "Material.baseColor") {
+        if (param_type != GL_FLOAT_VEC4) {
+          GLCAT.error()
+            << "p3d_Material.baseColor should be vec4\n";
+        }
+        bind._part[0] = Shader::SMO_attr_material2;
+        bind._piece = Shader::SMP_row0;
+        bind._dep[0] |= Shader::SSD_color;
+        _shader->_mat_spec.push_back(bind);
+        _shader->_mat_deps |= bind._dep[0];
+        return;
+
+      } else if (noprefix == "Material.ambient") {
         if (param_type != GL_FLOAT_VEC4) {
         if (param_type != GL_FLOAT_VEC4) {
           GLCAT.error()
           GLCAT.error()
             << "p3d_Material.ambient should be vec4\n";
             << "p3d_Material.ambient should be vec4\n";
@@ -985,6 +997,17 @@ reflect_uniform(int i, char *name_buffer, GLsizei name_buflen) {
         _shader->_mat_spec.push_back(bind);
         _shader->_mat_spec.push_back(bind);
         _shader->_mat_deps |= bind._dep[0];
         _shader->_mat_deps |= bind._dep[0];
         return;
         return;
+
+      } else if (noprefix == "Material.refractiveIndex") {
+        if (param_type != GL_FLOAT) {
+          GLCAT.error()
+            << "p3d_Material.refractiveIndex should be float\n";
+        }
+        bind._part[0] = Shader::SMO_attr_material2;
+        bind._piece = Shader::SMP_cell13;
+        _shader->_mat_spec.push_back(bind);
+        _shader->_mat_deps |= bind._dep[0];
+        return;
       }
       }
     }
     }
     if (noprefix == "ColorScale") {
     if (noprefix == "ColorScale") {
@@ -2013,6 +2036,12 @@ issue_parameters(int altered) {
       case Shader::SMP_cell15:
       case Shader::SMP_cell15:
         _glgsg->_glUniform1fv(p, 1, data+15);
         _glgsg->_glUniform1fv(p, 1, data+15);
         continue;
         continue;
+      case Shader::SMP_cell14:
+        _glgsg->_glUniform1fv(p, 1, data+14);
+        continue;
+      case Shader::SMP_cell13:
+        _glgsg->_glUniform1fv(p, 1, data+13);
+        continue;
       }
       }
     }
     }
   }
   }

+ 50 - 31
panda/src/gobj/material.I

@@ -20,6 +20,7 @@
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE Material::
 INLINE Material::
 Material(const string &name) : Namable(name) {
 Material(const string &name) : Namable(name) {
+  _base_color.set(1.0f, 1.0f, 1.0f, 1.0f);
   _ambient.set(1.0f, 1.0f, 1.0f, 1.0f);
   _ambient.set(1.0f, 1.0f, 1.0f, 1.0f);
   _diffuse.set(1.0f, 1.0f, 1.0f, 1.0f);
   _diffuse.set(1.0f, 1.0f, 1.0f, 1.0f);
   _specular.set(0.0f, 0.0f, 0.0f, 1.0f);
   _specular.set(0.0f, 0.0f, 0.0f, 1.0f);
@@ -27,6 +28,7 @@ Material(const string &name) : Namable(name) {
   _shininess = 0;
   _shininess = 0;
   _roughness = 1;
   _roughness = 1;
   _metallic = 0;
   _metallic = 0;
+  _refractive_index = 1;
   _flags = 0;
   _flags = 0;
 }
 }
 
 
@@ -62,6 +64,29 @@ get_default() {
   return _default;
   return _default;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Material::has_base_color
+//       Access: Published
+//  Description: Returns true if the base_color color has been explicitly
+//               set for this material, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool Material::
+has_base_color() const {
+  return (_flags & F_base_color) != 0;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Material::get_base_color
+//       Access: Published
+//  Description: Returns the base_color color setting, if it has been
+//               set.  Returns (0,0,0,0) if the base_color color has not
+//               been set.
+////////////////////////////////////////////////////////////////////
+INLINE const LColor &Material::
+get_base_color() const {
+  return _base_color;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Material::has_ambient
 //     Function: Material::has_ambient
 //       Access: Published
 //       Access: Published
@@ -96,7 +121,7 @@ clear_ambient() {
     nassertv(!is_attrib_locked());
     nassertv(!is_attrib_locked());
   }
   }
   _flags &= ~F_ambient;
   _flags &= ~F_ambient;
-  _ambient.set(0.0f, 0.0f, 0.0f, 0.0f);
+  _ambient = _base_color;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -133,7 +158,7 @@ clear_diffuse() {
     nassertv(!is_attrib_locked());
     nassertv(!is_attrib_locked());
   }
   }
   _flags &= ~F_diffuse;
   _flags &= ~F_diffuse;
-  _diffuse.set(1.0f, 1.0f, 1.0f, 1.0f);
+  _diffuse = _base_color * (1 - _metallic);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -159,20 +184,6 @@ get_specular() const {
   return _specular;
   return _specular;
 }
 }
 
 
-////////////////////////////////////////////////////////////////////
-//     Function: Material::clear_specular
-//       Access: Published
-//  Description: Removes the explicit specular color from the material.
-////////////////////////////////////////////////////////////////////
-INLINE void Material::
-clear_specular() {
-  if (enforce_attrib_lock) {
-    nassertv(!is_attrib_locked());
-  }
-  _flags &= ~F_specular;
-  _specular.set(0.0f, 0.0f, 0.0f, 0.0f);
-}
-
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Material::has_emission
 //     Function: Material::has_emission
 //       Access: Published
 //       Access: Published
@@ -220,6 +231,17 @@ get_shininess() const {
   return _shininess;
   return _shininess;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Material::has_roughness
+//       Access: Published
+//  Description: Returns true if the roughness has been explicitly
+//               set for this material, false otherwise.
+////////////////////////////////////////////////////////////////////
+INLINE bool Material::
+has_roughness() const {
+  return (_flags & F_roughness) != 0;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Material::has_metallic
 //     Function: Material::has_metallic
 //       Access: Published
 //       Access: Published
@@ -243,28 +265,25 @@ get_metallic() const {
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: Material::clear_metallic
+//     Function: Material::has_refractive_index
 //       Access: Published
 //       Access: Published
-//  Description: Removes the explicit metallic setting from the material.
+//  Description: Returns true if a refractive index has explicitly
+//               been specified for this material.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE void Material::
-clear_metallic() {
-  if (enforce_attrib_lock) {
-    nassertv(!is_attrib_locked());
-  }
-  _flags &= ~F_metallic;
-  _metallic = 0;
+INLINE bool Material::
+has_refractive_index() const {
+  return (_flags & F_refractive_index) != 0;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-//     Function: Material::has_roughness
+//     Function: Material::get_refractive_index
 //       Access: Published
 //       Access: Published
-//  Description: Returns true if the roughness has been explicitly
-//               set for this material, false otherwise.
+//  Description: Returns the index of refraction, or 1 if none has
+//               been set for this material.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
-INLINE bool Material::
-has_roughness() const {
-  return (_flags & F_roughness) != 0;
+INLINE PN_stdfloat Material::
+get_refractive_index() const {
+  return _refractive_index;
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////

+ 252 - 33
panda/src/gobj/material.cxx

@@ -31,6 +31,7 @@ PT(Material) Material::_default;
 void Material::
 void Material::
 operator = (const Material &copy) {
 operator = (const Material &copy) {
   Namable::operator = (copy);
   Namable::operator = (copy);
+  _base_color = copy._base_color;
   _ambient = copy._ambient;
   _ambient = copy._ambient;
   _diffuse = copy._diffuse;
   _diffuse = copy._diffuse;
   _specular = copy._specular;
   _specular = copy._specular;
@@ -38,9 +39,80 @@ operator = (const Material &copy) {
   _shininess = copy._shininess;
   _shininess = copy._shininess;
   _roughness = copy._roughness;
   _roughness = copy._roughness;
   _metallic = copy._metallic;
   _metallic = copy._metallic;
+  _refractive_index = copy._refractive_index;
   _flags = copy._flags & (~F_attrib_lock);
   _flags = copy._flags & (~F_attrib_lock);
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Material::set_base_color
+//       Access: Published
+//  Description: Specifies the base color of the material.  In
+//               conjunction with set_metallic, this is an alternate
+//               way to specify the color of a material.  For
+//               dielectrics, this will determine the value of the
+//               diffuse color, and for metals, this will determine
+//               the value of the specular color.
+//
+//               Setting this will clear an explicit specular,
+//               diffuse or ambient color assignment.
+//
+//               If this is not set, the object color will be used.
+////////////////////////////////////////////////////////////////////
+void Material::
+set_base_color(const LColor &color) {
+  if (enforce_attrib_lock) {
+    if ((_flags & F_base_color) == 0) {
+      nassertv(!is_attrib_locked());
+    }
+  }
+  _base_color = color;
+  _flags |= F_base_color | F_metallic;
+  _flags &= ~(F_ambient | F_diffuse | F_specular);
+
+  // Recalculate the diffuse and specular colors.
+  _ambient = _base_color;
+  _diffuse = _base_color * (1 - _metallic);
+
+  PN_stdfloat f0 = 0;
+  if (_refractive_index >= 1) {
+    f0 = (_refractive_index - 1) / (_refractive_index + 1);
+    f0 *= f0;
+    f0 *= (1 - _metallic);
+  }
+  _specular.set(f0, f0, f0, 0);
+  _specular += _base_color * _metallic;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Material::clear_base_color
+//       Access: Published
+//  Description: Removes the explicit base_color color from the material.
+////////////////////////////////////////////////////////////////////
+void Material::
+clear_base_color() {
+  if (enforce_attrib_lock) {
+    nassertv(!is_attrib_locked());
+  }
+  _flags &= ~F_base_color;
+  _base_color.set(0.0f, 0.0f, 0.0f, 0.0f);
+
+  if ((_flags & F_ambient) == 0) {
+    _ambient.set(0, 0, 0, 0);
+  }
+  if ((_flags & F_diffuse) == 0) {
+    _diffuse.set(0, 0, 0, 0);
+  }
+  if ((_flags & F_specular) == 0) {
+    // Recalculate the specular color.
+    PN_stdfloat f0 = 0;
+    if (_refractive_index >= 1) {
+      f0 = (_refractive_index - 1) / (_refractive_index + 1);
+      f0 *= f0;
+    }
+    _specular.set(f0, f0, f0, 0);
+  }
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Material::set_ambient
 //     Function: Material::set_ambient
 //       Access: Published
 //       Access: Published
@@ -92,7 +164,7 @@ set_diffuse(const LColor &color) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Material::set_specular
 //     Function: Material::set_specular
 //       Access: Published
 //       Access: Published
-//  Description: Specifies the diffuse color setting of the material.
+//  Description: Specifies the specular color setting of the material.
 //               This will be multiplied by any lights in effect on
 //               This will be multiplied by any lights in effect on
 //               the material to compute the color of specular
 //               the material to compute the color of specular
 //               highlights on the object.
 //               highlights on the object.
@@ -100,7 +172,9 @@ set_diffuse(const LColor &color) {
 //               This is the highlight color of an object: the color
 //               This is the highlight color of an object: the color
 //               of small highlight reflections.
 //               of small highlight reflections.
 //
 //
-//               If this is not set, highlights will not appear.
+//               If this is not set, the specular color is taken from
+//               the index of refraction, which is 1 by default
+//               (meaning no specular reflections are generated).
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Material::
 void Material::
 set_specular(const LColor &color) {
 set_specular(const LColor &color) {
@@ -113,6 +187,29 @@ set_specular(const LColor &color) {
   _flags |= F_specular;
   _flags |= F_specular;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Material::clear_specular
+//       Access: Published
+//  Description: Removes the explicit specular color from the material.
+////////////////////////////////////////////////////////////////////
+void Material::
+clear_specular() {
+  if (enforce_attrib_lock) {
+    nassertv(!is_attrib_locked());
+  }
+  _flags &= ~F_specular;
+
+  // Recalculate the specular color from the refractive index.
+  PN_stdfloat f0 = 0;
+  if (_refractive_index >= 1) {
+    f0 = (_refractive_index - 1) / (_refractive_index + 1);
+    f0 *= f0;
+    f0 *= (1 - _metallic);
+  }
+  _specular.set(f0, f0, f0, 0);
+  _specular += _base_color * _metallic;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Material::set_emission
 //     Function: Material::set_emission
 //       Access: Published
 //       Access: Published
@@ -211,8 +308,10 @@ set_roughness(PN_stdfloat roughness) {
 //  Description: Sets the metallic setting of the material, which is
 //  Description: Sets the metallic setting of the material, which is
 //               is used for physically-based rendering models.
 //               is used for physically-based rendering models.
 //               This is usually 0 for dielectric materials and 1
 //               This is usually 0 for dielectric materials and 1
-//               for metals.  It usually does not make sense to set
-//               this to a value other than 0 or 1.
+//               for metals.  It really does not make sense to set
+//               this to a value other than 0 or 1, but it is
+//               nonetheless a float for compatibility with tools
+//               that allow setting this to values other than 0 or 1.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Material::
 void Material::
 set_metallic(PN_stdfloat metallic) {
 set_metallic(PN_stdfloat metallic) {
@@ -223,6 +322,80 @@ set_metallic(PN_stdfloat metallic) {
   }
   }
   _metallic = metallic;
   _metallic = metallic;
   _flags |= F_metallic;
   _flags |= F_metallic;
+
+  // Recalculate the diffuse and specular.
+  if ((_flags & F_diffuse) == 0) {
+    _diffuse = _base_color * (1 - _metallic);
+  }
+  if ((_flags & F_specular) == 0) {
+    // Recalculate the specular color.
+    PN_stdfloat f0 = 0;
+    if (_refractive_index >= 1) {
+      f0 = (_refractive_index - 1) / (_refractive_index + 1);
+      f0 *= f0;
+      f0 *= (1 - _metallic);
+    }
+    _specular.set(f0, f0, f0, 0);
+    _specular += _base_color * _metallic;
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Material::clear_metallic
+//       Access: Published
+//  Description: Removes the explicit metallic setting from the material.
+////////////////////////////////////////////////////////////////////
+void Material::
+clear_metallic() {
+  if (enforce_attrib_lock) {
+    nassertv(!is_attrib_locked());
+  }
+  _flags &= ~F_metallic;
+  _metallic = 0;
+
+  // If we had a base color, recalculate the diffuse and specular.
+  if (_flags & F_base_color) {
+    if ((_flags & F_diffuse) == 0) {
+      _diffuse = _base_color;
+    }
+    if ((_flags & F_specular) == 0) {
+      // Recalculate the specular color.
+      PN_stdfloat f0 = 0;
+      if (_refractive_index >= 1) {
+        f0 = (_refractive_index - 1) / (_refractive_index + 1);
+        f0 *= f0;
+      }
+      _specular.set(f0, f0, f0, 0);
+    }
+  }
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Material::set_refractive_index
+//       Access: Published
+//  Description: Sets the index of refraction of the material, which
+//               is used to determine the specular color in absence
+//               of an explicit specular color assignment.
+//               This is usually 1.5 for dielectric materials.  It
+//               is not very useful for metals, since they cannot
+//               be described as easily with a single number.
+//
+//               Should be 1 or higher.  The default is 1.
+////////////////////////////////////////////////////////////////////
+void Material::
+set_refractive_index(PN_stdfloat refractive_index) {
+  _refractive_index = refractive_index;
+  _flags |= F_refractive_index;
+
+  if ((_flags & F_specular) == 0) {
+    // Recalculate the specular color.
+    PN_stdfloat f0 = 0;
+    if (_refractive_index >= 1) {
+      f0 = (_refractive_index - 1) / (_refractive_index + 1);
+      f0 *= f0;
+    }
+    _specular.set(f0, f0, f0, 0);
+  }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -239,6 +412,9 @@ compare_to(const Material &other) const {
   if (_flags != other._flags) {
   if (_flags != other._flags) {
     return _flags - other._flags;
     return _flags - other._flags;
   }
   }
+  if (has_base_color() && get_base_color() != other.get_base_color()) {
+    return get_base_color().compare_to(other.get_base_color());
+  }
   if (has_ambient() && get_ambient() != other.get_ambient()) {
   if (has_ambient() && get_ambient() != other.get_ambient()) {
     return get_ambient().compare_to(other.get_ambient());
     return get_ambient().compare_to(other.get_ambient());
   }
   }
@@ -254,6 +430,12 @@ compare_to(const Material &other) const {
   if (get_shininess() != other.get_shininess()) {
   if (get_shininess() != other.get_shininess()) {
     return get_shininess() < other.get_shininess() ? -1 : 1;
     return get_shininess() < other.get_shininess() ? -1 : 1;
   }
   }
+  if (get_metallic() != other.get_metallic()) {
+    return get_metallic() < other.get_metallic() ? -1 : 1;
+  }
+  if (get_refractive_index() != other.get_refractive_index()) {
+    return get_refractive_index() < other.get_refractive_index() ? -1 : 1;
+  }
 
 
   return strcmp(get_name().c_str(), other.get_name().c_str());
   return strcmp(get_name().c_str(), other.get_name().c_str());
 }
 }
@@ -266,14 +448,21 @@ compare_to(const Material &other) const {
 void Material::
 void Material::
 output(ostream &out) const {
 output(ostream &out) const {
   out << "Material " << get_name();
   out << "Material " << get_name();
-  if (has_ambient()) {
-    out << " a(" << get_ambient() << ")";
-  }
-  if (has_diffuse()) {
-    out << " d(" << get_diffuse() << ")";
+  if (has_base_color()) {
+    out << " c(" << get_base_color() << ")";
+  } else {
+    if (has_ambient()) {
+      out << " a(" << get_ambient() << ")";
+    }
+    if (has_diffuse()) {
+      out << " d(" << get_diffuse() << ")";
+    }
+    if (has_specular()) {
+      out << " s(" << get_specular() << ")";
+    }
   }
   }
-  if (has_specular()) {
-    out << " s(" << get_specular() << ")";
+  if (has_refractive_index()) {
+    out << " ior" << get_refractive_index();
   }
   }
   if (has_emission()) {
   if (has_emission()) {
     out << " e(" << get_emission() << ")";
     out << " e(" << get_emission() << ")";
@@ -283,7 +472,7 @@ output(ostream &out) const {
   } else {
   } else {
     out << " s" << get_shininess();
     out << " s" << get_shininess();
   }
   }
-  if (has_metallic()) {
+  if (_flags & F_metallic) {
     out << " m" << _metallic;
     out << " m" << _metallic;
   }
   }
   out << " l" << get_local()
   out << " l" << get_local()
@@ -298,6 +487,9 @@ output(ostream &out) const {
 void Material::
 void Material::
 write(ostream &out, int indent_level) const {
 write(ostream &out, int indent_level) const {
   indent(out, indent_level) << "Material " << get_name() << "\n";
   indent(out, indent_level) << "Material " << get_name() << "\n";
+  if (has_base_color()) {
+    indent(out, indent_level + 2) << "base_color = " << get_ambient() << "\n";
+  }
   if (has_ambient()) {
   if (has_ambient()) {
     indent(out, indent_level + 2) << "ambient = " << get_ambient() << "\n";
     indent(out, indent_level + 2) << "ambient = " << get_ambient() << "\n";
   }
   }
@@ -306,6 +498,8 @@ write(ostream &out, int indent_level) const {
   }
   }
   if (has_specular()) {
   if (has_specular()) {
     indent(out, indent_level + 2) << "specular = " << get_specular() << "\n";
     indent(out, indent_level + 2) << "specular = " << get_specular() << "\n";
+  } else {
+    indent(out, indent_level + 2) << "refractive_index = " << get_refractive_index() << "\n";
   }
   }
   if (has_emission()) {
   if (has_emission()) {
     indent(out, indent_level + 2) << "emission = " << get_emission() << "\n";
     indent(out, indent_level + 2) << "emission = " << get_emission() << "\n";
@@ -343,11 +537,19 @@ register_with_read_factory() {
 void Material::
 void Material::
 write_datagram(BamWriter *manager, Datagram &me) {
 write_datagram(BamWriter *manager, Datagram &me) {
   me.add_string(get_name());
   me.add_string(get_name());
-  _ambient.write_datagram(me);
-  _diffuse.write_datagram(me);
-  _specular.write_datagram(me);
+
+  me.add_int32(_flags);
+
+  if (_flags & F_metallic) {
+    // Metalness workflow.
+    _base_color.write_datagram(me);
+    me.add_stdfloat(_metallic);
+  } else {
+    _ambient.write_datagram(me);
+    _diffuse.write_datagram(me);
+    _specular.write_datagram(me);
+  }
   _emission.write_datagram(me);
   _emission.write_datagram(me);
-  me.add_stdfloat(_shininess);
 
 
   if (_flags & F_roughness) {
   if (_flags & F_roughness) {
     me.add_stdfloat(_roughness);
     me.add_stdfloat(_roughness);
@@ -355,11 +557,7 @@ write_datagram(BamWriter *manager, Datagram &me) {
     me.add_stdfloat(_shininess);
     me.add_stdfloat(_shininess);
   }
   }
 
 
-  me.add_int32(_flags);
-
-  if (_flags & F_metallic) {
-    me.add_stdfloat(_metallic);
-  }
+  me.add_stdfloat(_refractive_index);
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -389,19 +587,40 @@ make_Material(const FactoryParams &params) {
 void Material::
 void Material::
 fillin(DatagramIterator &scan, BamReader *manager) {
 fillin(DatagramIterator &scan, BamReader *manager) {
   set_name(scan.get_string());
   set_name(scan.get_string());
-  _ambient.read_datagram(scan);
-  _diffuse.read_datagram(scan);
-  _specular.read_datagram(scan);
-  _emission.read_datagram(scan);
-  _shininess = scan.get_stdfloat();
-  _flags = scan.get_int32();
 
 
-  if (_flags & F_roughness) {
-    // The shininess we read is actually a roughness value.
-    set_roughness(_shininess);
-  }
+  if (manager->get_file_minor_ver() >= 39) {
+    _flags = scan.get_int32();
 
 
-  if (_flags & F_metallic) {
-    _metallic = scan.get_stdfloat();
+    if (_flags & F_metallic) {
+      // Metalness workflow: read base color and metallic
+      _base_color.read_datagram(scan);
+      set_metallic(scan.get_stdfloat());
+
+    } else {
+      _ambient.read_datagram(scan);
+      _diffuse.read_datagram(scan);
+      _specular.read_datagram(scan);
+    }
+    _emission.read_datagram(scan);
+
+    if (_flags & F_roughness) {
+      set_roughness(_shininess);
+    } else {
+      _shininess = scan.get_stdfloat();
+    }
+    _refractive_index = scan.get_stdfloat();
+
+  } else {
+    _ambient.read_datagram(scan);
+    _diffuse.read_datagram(scan);
+    _specular.read_datagram(scan);
+    _emission.read_datagram(scan);
+    _shininess = scan.get_stdfloat();
+    _flags = scan.get_int32();
+
+    if (_flags & F_roughness) {
+      // The shininess we read is actually a roughness value.
+      set_roughness(_shininess);
+    }
   }
   }
 }
 }

+ 31 - 2
panda/src/gobj/material.h

@@ -30,6 +30,18 @@ class FactoryParams;
 // Description : Defines the way an object appears in the presence of
 // Description : Defines the way an object appears in the presence of
 //               lighting.  A material is only necessary if lighting
 //               lighting.  A material is only necessary if lighting
 //               is to be enabled; otherwise, the material isn't used.
 //               is to be enabled; otherwise, the material isn't used.
+//
+//               There are two workflows that are supported: the
+//               "classic" workflow of providing separate ambient,
+//               diffuse and specular colors, and the "metalness"
+//               workflow, in which a base color is specified along
+//               with a "metallic" value that indicates whether the
+//               material is a metal or a dielectric.
+//
+//               The size of the specular highlight can be specified
+//               by either specifying the specular exponent (shininess)
+//               or by specifying a roughness value that in perceptually
+//               linear in the range of 0-1.
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 class EXPCL_PANDA_GOBJ Material : public TypedWritableReferenceCount, public Namable {
 class EXPCL_PANDA_GOBJ Material : public TypedWritableReferenceCount, public Namable {
 PUBLISHED:
 PUBLISHED:
@@ -40,6 +52,11 @@ PUBLISHED:
 
 
   INLINE static Material *get_default();
   INLINE static Material *get_default();
 
 
+  INLINE bool has_base_color() const;
+  INLINE const LColor &get_base_color() const;
+  void set_base_color(const LColor &color);
+  void clear_base_color();
+
   INLINE bool has_ambient() const;
   INLINE bool has_ambient() const;
   INLINE const LColor &get_ambient() const;
   INLINE const LColor &get_ambient() const;
   void set_ambient(const LColor &color);
   void set_ambient(const LColor &color);
@@ -53,7 +70,7 @@ PUBLISHED:
   INLINE bool has_specular() const;
   INLINE bool has_specular() const;
   INLINE const LColor &get_specular() const;
   INLINE const LColor &get_specular() const;
   void set_specular(const LColor &color);
   void set_specular(const LColor &color);
-  INLINE void clear_specular();
+  void clear_specular();
 
 
   INLINE bool has_emission() const;
   INLINE bool has_emission() const;
   INLINE const LColor &get_emission() const;
   INLINE const LColor &get_emission() const;
@@ -70,7 +87,11 @@ PUBLISHED:
   INLINE bool has_metallic() const;
   INLINE bool has_metallic() const;
   INLINE PN_stdfloat get_metallic() const;
   INLINE PN_stdfloat get_metallic() const;
   void set_metallic(PN_stdfloat metallic);
   void set_metallic(PN_stdfloat metallic);
-  INLINE void clear_metallic();
+  void clear_metallic();
+
+  INLINE bool has_refractive_index() const;
+  INLINE PN_stdfloat get_refractive_index() const;
+  void set_refractive_index(PN_stdfloat refractive_index);
 
 
   INLINE bool get_local() const;
   INLINE bool get_local() const;
   INLINE void set_local(bool local);
   INLINE void set_local(bool local);
@@ -90,6 +111,8 @@ PUBLISHED:
   INLINE void set_attrib_lock();
   INLINE void set_attrib_lock();
 
 
 PUBLISHED:
 PUBLISHED:
+  MAKE_PROPERTY2(base_color, has_base_color, get_base_color,
+                             set_base_color, clear_base_color);
   MAKE_PROPERTY2(ambient, has_ambient, get_ambient,
   MAKE_PROPERTY2(ambient, has_ambient, get_ambient,
                           set_ambient, clear_ambient);
                           set_ambient, clear_ambient);
   MAKE_PROPERTY2(diffuse, has_diffuse, get_diffuse,
   MAKE_PROPERTY2(diffuse, has_diffuse, get_diffuse,
@@ -102,11 +125,14 @@ PUBLISHED:
   MAKE_PROPERTY(shininess, get_shininess, set_shininess);
   MAKE_PROPERTY(shininess, get_shininess, set_shininess);
   MAKE_PROPERTY(roughness, get_roughness, set_roughness);
   MAKE_PROPERTY(roughness, get_roughness, set_roughness);
   MAKE_PROPERTY(metallic, get_metallic, set_metallic);
   MAKE_PROPERTY(metallic, get_metallic, set_metallic);
+  MAKE_PROPERTY(refractive_index, get_refractive_index,
+                                  set_refractive_index);
 
 
   MAKE_PROPERTY(local, get_local, set_local);
   MAKE_PROPERTY(local, get_local, set_local);
   MAKE_PROPERTY(twoside, get_twoside, set_twoside);
   MAKE_PROPERTY(twoside, get_twoside, set_twoside);
 
 
 private:
 private:
+  LColor _base_color;
   LColor _ambient;
   LColor _ambient;
   LColor _diffuse;
   LColor _diffuse;
   LColor _specular;
   LColor _specular;
@@ -114,6 +140,7 @@ private:
   PN_stdfloat _shininess;
   PN_stdfloat _shininess;
   PN_stdfloat _roughness;
   PN_stdfloat _roughness;
   PN_stdfloat _metallic;
   PN_stdfloat _metallic;
+  PN_stdfloat _refractive_index;
 
 
   static PT(Material) _default;
   static PT(Material) _default;
 
 
@@ -127,6 +154,8 @@ private:
     F_attrib_lock = 0x040,
     F_attrib_lock = 0x040,
     F_roughness   = 0x080,
     F_roughness   = 0x080,
     F_metallic    = 0x100,
     F_metallic    = 0x100,
+    F_base_color  = 0x200,
+    F_refractive_index = 0x400,
   };
   };
   int _flags;
   int _flags;
 
 

+ 1 - 1
panda/src/gobj/shader.cxx

@@ -405,7 +405,7 @@ cp_dependency(ShaderMatInput inp) {
   if (inp == SMO_INVALID) {
   if (inp == SMO_INVALID) {
     return SSD_NONE;
     return SSD_NONE;
   }
   }
-  if (inp == SMO_attr_material) {
+  if (inp == SMO_attr_material || inp == SMO_attr_material2) {
     dep |= SSD_material;
     dep |= SSD_material;
   }
   }
   if (inp == SMO_attr_color) {
   if (inp == SMO_attr_color) {

+ 2 - 0
panda/src/gobj/shader.h

@@ -275,6 +275,8 @@ public:
     SMP_upper3x3,
     SMP_upper3x3,
     SMP_transpose3x3,
     SMP_transpose3x3,
     SMP_cell15,
     SMP_cell15,
+    SMP_cell14,
+    SMP_cell13,
   };
   };
 
 
   enum ShaderStateDep {
   enum ShaderStateDep {

+ 30 - 1
panda/src/pgraph/light.I

@@ -45,7 +45,9 @@ CData(const Light::CData &copy) :
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE Light::
 INLINE Light::
 Light() :
 Light() :
-  _priority(0)
+  _priority(0),
+  _has_color_temperature(true),
+  _color_temperature(6500)
 {
 {
 }
 }
 
 
@@ -57,6 +59,8 @@ Light() :
 INLINE Light::
 INLINE Light::
 Light(const Light &copy) :
 Light(const Light &copy) :
   _priority(copy._priority),
   _priority(copy._priority),
+  _has_color_temperature(copy._has_color_temperature),
+  _color_temperature(copy._color_temperature),
   _cycler(copy._cycler)
   _cycler(copy._cycler)
 {
 {
 }
 }
@@ -81,9 +85,34 @@ INLINE void Light::
 set_color(const LColor &color) {
 set_color(const LColor &color) {
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
   cdata->_color = color;
   cdata->_color = color;
+  _has_color_temperature = false;
   mark_viz_stale();
   mark_viz_stale();
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Light::has_color_temperature
+//       Access: Published
+//  Description: Returns true if the color was specified as a
+//               temperature in kelvins, and get_color_temperature
+//               is defined.
+////////////////////////////////////////////////////////////////////
+INLINE bool Light::
+has_color_temperature() const {
+  return _has_color_temperature;
+}
+
+////////////////////////////////////////////////////////////////////
+//     Function: Light::get_color_temperature
+//       Access: Published
+//  Description: Returns the basic color temperature of the light,
+//               assuming has_color_temperature() returns true.
+////////////////////////////////////////////////////////////////////
+INLINE PN_stdfloat Light::
+get_color_temperature() const {
+  nassertr(_has_color_temperature, _color_temperature);
+  return _color_temperature;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Light::set_priority
 //     Function: Light::set_priority
 //       Access: Published
 //       Access: Published

+ 80 - 2
panda/src/pgraph/light.cxx

@@ -76,6 +76,70 @@ is_ambient_light() const {
   return false;
   return false;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Light::set_color_temperature
+//       Access: Published
+//  Description: Sets the color temperature of the light in kelvins.
+//               This will recalculate the light's color.
+//
+//               The default value is 6500 K, corresponding to a
+//               perfectly white light assuming a D65 white point.
+////////////////////////////////////////////////////////////////////
+void Light::
+set_color_temperature(PN_stdfloat temperature) {
+  if (_has_color_temperature && _color_temperature == temperature) {
+    return;
+  }
+
+  _has_color_temperature = true;
+  _color_temperature = temperature;
+
+  // Recalculate the color.
+  PN_stdfloat x, y;
+
+  if (temperature == 6500) {
+    // sRGB D65 white point.
+    x = 0.31271;
+    y = 0.32902;
+
+  } else {
+    PN_stdfloat mm = 1000.0 / temperature;
+    PN_stdfloat mm2 = mm * mm;
+    PN_stdfloat mm3 = mm2 * mm;
+
+    if (temperature < 4000) {
+      x = -0.2661239 * mm3 - 0.2343580 * mm2 + 0.8776956 * mm + 0.179910;
+    } else {
+      x = -3.0258469 * mm3 + 2.1070379 * mm2 + 0.2226347 * mm + 0.240390;
+    }
+
+    PN_stdfloat x2 = x * x;
+    PN_stdfloat x3 = x2 * x;
+    if (temperature < 2222) {
+      y = -1.1063814 * x3 - 1.34811020 * x2 + 2.18555832 * x - 0.20219683;
+    } else if (temperature < 4000) {
+      y = -0.9549476 * x3 - 1.37418593 * x2 + 2.09137015 * x - 0.16748867;
+    } else {
+      y =  3.0817580 * x3 - 5.87338670 * x2 + 3.75112997 * x - 0.37001483;
+    }
+  }
+
+  // xyY to XYZ, assuming Y=1.
+  LVecBase3 xyz(x / y, 1, (1 - x - y) / y);
+
+  // Convert XYZ to linearized sRGB.
+  const static LMatrix3 xyz_to_rgb(
+    3.2406255, -0.9689307, 0.0557101,
+    -1.537208, 1.8757561, -0.2040211,
+    -0.4986286, 0.0415175, 1.0569959);
+
+  LColor color(xyz_to_rgb.xform(xyz), 1);
+
+  CDWriter cdata(_cycler);
+  cdata->_color = color;
+  mark_viz_stale();
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Light::get_exponent
 //     Function: Light::get_exponent
 //       Access: Public, Virtual
 //       Access: Public, Virtual
@@ -175,7 +239,12 @@ fill_viz_geom(GeomNode *) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Light::
 void Light::
 write_datagram(BamWriter *manager, Datagram &dg) {
 write_datagram(BamWriter *manager, Datagram &dg) {
-  manager->write_cdata(dg, _cycler);
+  dg.add_bool(_has_color_temperature);
+  if (_has_color_temperature) {
+    dg.add_stdfloat(_color_temperature);
+  } else {
+    manager->write_cdata(dg, _cycler);
+  }
   dg.add_int32(_priority);
   dg.add_int32(_priority);
 }
 }
 
 
@@ -188,6 +257,15 @@ write_datagram(BamWriter *manager, Datagram &dg) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 void Light::
 void Light::
 fillin(DatagramIterator &scan, BamReader *manager) {
 fillin(DatagramIterator &scan, BamReader *manager) {
-  manager->read_cdata(scan, _cycler);
+  if (manager->get_file_minor_ver() >= 39) {
+    _has_color_temperature = scan.get_bool();
+  } else {
+    _has_color_temperature = false;
+  }
+  if (_has_color_temperature) {
+    set_color_temperature(scan.get_stdfloat());
+  } else {
+    manager->read_cdata(scan, _cycler);
+  }
   _priority = scan.get_int32();
   _priority = scan.get_int32();
 }
 }

+ 11 - 0
panda/src/pgraph/light.h

@@ -52,6 +52,12 @@ PUBLISHED:
   INLINE void set_color(const LColor &color);
   INLINE void set_color(const LColor &color);
   MAKE_PROPERTY(color, get_color, set_color);
   MAKE_PROPERTY(color, get_color, set_color);
 
 
+  INLINE bool has_color_temperature() const;
+  INLINE PN_stdfloat get_color_temperature() const;
+  void set_color_temperature(PN_stdfloat temperature);
+  MAKE_PROPERTY(color_temperature, get_color_temperature,
+                                   set_color_temperature);
+
   virtual PN_stdfloat get_exponent() const;
   virtual PN_stdfloat get_exponent() const;
   virtual const LColor &get_specular_color() const;
   virtual const LColor &get_specular_color() const;
   virtual const LVecBase3 &get_attenuation() const;
   virtual const LVecBase3 &get_attenuation() const;
@@ -98,6 +104,11 @@ private:
   int _priority;
   int _priority;
   static UpdateSeq _sort_seq;
   static UpdateSeq _sort_seq;
 
 
+  // The color temperature is not cycled either, because we only need
+  // to pass down the computed color anyway.
+  bool _has_color_temperature;
+  PN_stdfloat _color_temperature;
+
   // This is the data that must be cycled between pipeline stages.
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA_PGRAPH CData : public CycleData {
   class EXPCL_PANDA_PGRAPH CData : public CycleData {
   public:
   public:

+ 19 - 3
panda/src/pgraphnodes/directionalLight.I

@@ -43,12 +43,16 @@ CData(const DirectionalLight::CData &copy) :
 //     Function: DirectionalLight::get_specular_color
 //     Function: DirectionalLight::get_specular_color
 //       Access: Public, Final
 //       Access: Public, Final
 //  Description: Returns the color of specular highlights generated by
 //  Description: Returns the color of specular highlights generated by
-//               the light.
+//               the light.  This is usually the same as get_color().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE const LColor &DirectionalLight::
 INLINE const LColor &DirectionalLight::
 get_specular_color() const {
 get_specular_color() const {
-  CDReader cdata(_cycler);
-  return cdata->_specular_color;
+  if (_has_specular_color) {
+    CDReader cdata(_cycler);
+    return cdata->_specular_color;
+  } else {
+    return get_color();
+  }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -60,9 +64,21 @@ get_specular_color() const {
 INLINE void DirectionalLight::
 INLINE void DirectionalLight::
 set_specular_color(const LColor &color) {
 set_specular_color(const LColor &color) {
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
+  _has_specular_color = true;
   cdata->_specular_color = color;
   cdata->_specular_color = color;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: DirectionalLight::clear_specular_color
+//       Access: Public
+//  Description: Clears a custom specular color setting, meaning that
+//               the specular color will now come from the color.
+////////////////////////////////////////////////////////////////////
+INLINE void DirectionalLight::
+clear_specular_color() {
+  _has_specular_color = false;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: DirectionalLight::get_point
 //     Function: DirectionalLight::get_point
 //       Access: Public
 //       Access: Public

+ 15 - 3
panda/src/pgraphnodes/directionalLight.cxx

@@ -66,7 +66,8 @@ fillin(DatagramIterator &scan, BamReader *) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 DirectionalLight::
 DirectionalLight::
 DirectionalLight(const string &name) : 
 DirectionalLight(const string &name) : 
-  LightLensNode(name, new OrthographicLens())
+  LightLensNode(name, new OrthographicLens()),
+  _has_specular_color(false)
 {
 {
 }
 }
 
 
@@ -80,6 +81,7 @@ DirectionalLight(const string &name) :
 DirectionalLight::
 DirectionalLight::
 DirectionalLight(const DirectionalLight &copy) :
 DirectionalLight(const DirectionalLight &copy) :
   LightLensNode(copy),
   LightLensNode(copy),
+  _has_specular_color(copy._has_specular_color),
   _cycler(copy._cycler)
   _cycler(copy._cycler)
 {
 {
 }
 }
@@ -124,8 +126,10 @@ write(ostream &out, int indent_level) const {
   indent(out, indent_level) << *this << ":\n";
   indent(out, indent_level) << *this << ":\n";
   indent(out, indent_level + 2)
   indent(out, indent_level + 2)
     << "color " << get_color() << "\n";
     << "color " << get_color() << "\n";
-  indent(out, indent_level + 2)
-    << "specular color " << get_specular_color() << "\n";
+  if (_has_specular_color) {
+    indent(out, indent_level + 2)
+      << "specular color " << get_specular_color() << "\n";
+  }
   indent(out, indent_level + 2)
   indent(out, indent_level + 2)
     << "direction " << get_direction() << "\n";
     << "direction " << get_direction() << "\n";
 }
 }
@@ -201,6 +205,7 @@ register_with_read_factory() {
 void DirectionalLight::
 void DirectionalLight::
 write_datagram(BamWriter *manager, Datagram &dg) {
 write_datagram(BamWriter *manager, Datagram &dg) {
   LightLensNode::write_datagram(manager, dg);
   LightLensNode::write_datagram(manager, dg);
+  dg.add_bool(_has_specular_color);
   manager->write_cdata(dg, _cycler);
   manager->write_cdata(dg, _cycler);
 }
 }
 
 
@@ -234,5 +239,12 @@ make_from_bam(const FactoryParams &params) {
 void DirectionalLight::
 void DirectionalLight::
 fillin(DatagramIterator &scan, BamReader *manager) {
 fillin(DatagramIterator &scan, BamReader *manager) {
   LightLensNode::fillin(scan, manager);
   LightLensNode::fillin(scan, manager);
+
+  if (manager->get_file_minor_ver() >= 39) {
+    _has_specular_color = scan.get_bool();
+  } else {
+    _has_specular_color = true;
+  }
+
   manager->read_cdata(scan, _cycler);
   manager->read_cdata(scan, _cycler);
 }
 }

+ 3 - 0
panda/src/pgraphnodes/directionalLight.h

@@ -43,6 +43,7 @@ public:
 PUBLISHED:
 PUBLISHED:
   INLINE const LColor &get_specular_color() const FINAL;
   INLINE const LColor &get_specular_color() const FINAL;
   INLINE void set_specular_color(const LColor &color);
   INLINE void set_specular_color(const LColor &color);
+  INLINE void clear_specular_color();
   MAKE_PROPERTY(specular_color, get_specular_color, set_specular_color);
   MAKE_PROPERTY(specular_color, get_specular_color, set_specular_color);
 
 
   INLINE const LPoint3 &get_point() const;
   INLINE const LPoint3 &get_point() const;
@@ -60,6 +61,8 @@ public:
                     int light_id);
                     int light_id);
 
 
 private:
 private:
+  bool _has_specular_color;
+
   // This is the data that must be cycled between pipeline stages.
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA_PGRAPHNODES CData : public CycleData {
   class EXPCL_PANDA_PGRAPHNODES CData : public CycleData {
   public:
   public:

+ 19 - 3
panda/src/pgraphnodes/pointLight.I

@@ -43,12 +43,16 @@ CData(const PointLight::CData &copy) :
 //     Function: PointLight::get_specular_color
 //     Function: PointLight::get_specular_color
 //       Access: Public, Final
 //       Access: Public, Final
 //  Description: Returns the color of specular highlights generated by
 //  Description: Returns the color of specular highlights generated by
-//               the light.
+//               the light.  This is usually the same as get_color().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE const LColor &PointLight::
 INLINE const LColor &PointLight::
 get_specular_color() const {
 get_specular_color() const {
-  CDReader cdata(_cycler);
-  return cdata->_specular_color;
+  if (_has_specular_color) {
+    CDReader cdata(_cycler);
+    return cdata->_specular_color;
+  } else {
+    return get_color();
+  }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -60,9 +64,21 @@ get_specular_color() const {
 INLINE void PointLight::
 INLINE void PointLight::
 set_specular_color(const LColor &color) {
 set_specular_color(const LColor &color) {
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
+  _has_specular_color = true;
   cdata->_specular_color = color;
   cdata->_specular_color = color;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: PointLight::clear_specular_color
+//       Access: Public
+//  Description: Clears a custom specular color setting, meaning that
+//               the specular color will now come from the color.
+////////////////////////////////////////////////////////////////////
+INLINE void PointLight::
+clear_specular_color() {
+  _has_specular_color = false;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: PointLight::get_attenuation
 //     Function: PointLight::get_attenuation
 //       Access: Public, Final
 //       Access: Public, Final

+ 15 - 3
panda/src/pgraphnodes/pointLight.cxx

@@ -65,7 +65,8 @@ fillin(DatagramIterator &scan, BamReader *) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 PointLight::
 PointLight::
 PointLight(const string &name) : 
 PointLight(const string &name) : 
-  LightLensNode(name) 
+  LightLensNode(name),
+  _has_specular_color(false)
 {
 {
   PT(Lens) lens;
   PT(Lens) lens;
   lens = new PerspectiveLens(90, 90);
   lens = new PerspectiveLens(90, 90);
@@ -98,6 +99,7 @@ PointLight(const string &name) :
 PointLight::
 PointLight::
 PointLight(const PointLight &copy) :
 PointLight(const PointLight &copy) :
   LightLensNode(copy),
   LightLensNode(copy),
+  _has_specular_color(copy._has_specular_color),
   _cycler(copy._cycler)
   _cycler(copy._cycler)
 {
 {
 }
 }
@@ -141,8 +143,10 @@ write(ostream &out, int indent_level) const {
   indent(out, indent_level) << *this << ":\n";
   indent(out, indent_level) << *this << ":\n";
   indent(out, indent_level + 2)
   indent(out, indent_level + 2)
     << "color " << get_color() << "\n";
     << "color " << get_color() << "\n";
-  indent(out, indent_level + 2)
-    << "specular color " << get_specular_color() << "\n";
+  if (_has_specular_color) {
+    indent(out, indent_level + 2)
+      << "specular color " << get_specular_color() << "\n";
+  }
   indent(out, indent_level + 2)
   indent(out, indent_level + 2)
     << "attenuation " << get_attenuation() << "\n";
     << "attenuation " << get_attenuation() << "\n";
 }
 }
@@ -219,6 +223,7 @@ register_with_read_factory() {
 void PointLight::
 void PointLight::
 write_datagram(BamWriter *manager, Datagram &dg) {
 write_datagram(BamWriter *manager, Datagram &dg) {
   LightLensNode::write_datagram(manager, dg);
   LightLensNode::write_datagram(manager, dg);
+  dg.add_bool(_has_specular_color);
   manager->write_cdata(dg, _cycler);
   manager->write_cdata(dg, _cycler);
 }
 }
 
 
@@ -252,5 +257,12 @@ make_from_bam(const FactoryParams &params) {
 void PointLight::
 void PointLight::
 fillin(DatagramIterator &scan, BamReader *manager) {
 fillin(DatagramIterator &scan, BamReader *manager) {
   LightLensNode::fillin(scan, manager);
   LightLensNode::fillin(scan, manager);
+
+  if (manager->get_file_minor_ver() >= 39) {
+    _has_specular_color = scan.get_bool();
+  } else {
+    _has_specular_color = true;
+  }
+
   manager->read_cdata(scan, _cycler);
   manager->read_cdata(scan, _cycler);
 }
 }

+ 3 - 0
panda/src/pgraphnodes/pointLight.h

@@ -43,6 +43,7 @@ public:
 PUBLISHED:
 PUBLISHED:
   INLINE const LColor &get_specular_color() const FINAL;
   INLINE const LColor &get_specular_color() const FINAL;
   INLINE void set_specular_color(const LColor &color);
   INLINE void set_specular_color(const LColor &color);
+  INLINE void clear_specular_color();
   MAKE_PROPERTY(specular_color, get_specular_color, set_specular_color);
   MAKE_PROPERTY(specular_color, get_specular_color, set_specular_color);
 
 
   INLINE const LVecBase3 &get_attenuation() const FINAL;
   INLINE const LVecBase3 &get_attenuation() const FINAL;
@@ -60,6 +61,8 @@ public:
                     int light_id);
                     int light_id);
 
 
 private:
 private:
+  bool _has_specular_color;
+
   // This is the data that must be cycled between pipeline stages.
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA_PGRAPHNODES CData : public CycleData {
   class EXPCL_PANDA_PGRAPHNODES CData : public CycleData {
   public:
   public:

+ 19 - 3
panda/src/pgraphnodes/spotlight.I

@@ -73,12 +73,16 @@ set_exponent(PN_stdfloat exponent) {
 //     Function: Spotlight::get_specular_color
 //     Function: Spotlight::get_specular_color
 //       Access: Public, Final
 //       Access: Public, Final
 //  Description: Returns the color of specular highlights generated by
 //  Description: Returns the color of specular highlights generated by
-//               the light.
+//               the light.  This is usually the same as get_color().
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 INLINE const LColor &Spotlight::
 INLINE const LColor &Spotlight::
 get_specular_color() const {
 get_specular_color() const {
-  CDReader cdata(_cycler);
-  return cdata->_specular_color;
+  if (_has_specular_color) {
+    CDReader cdata(_cycler);
+    return cdata->_specular_color;
+  } else {
+    return get_color();
+  }
 }
 }
 
 
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
@@ -90,9 +94,21 @@ get_specular_color() const {
 INLINE void Spotlight::
 INLINE void Spotlight::
 set_specular_color(const LColor &color) {
 set_specular_color(const LColor &color) {
   CDWriter cdata(_cycler);
   CDWriter cdata(_cycler);
+  _has_specular_color = true;
   cdata->_specular_color = color;
   cdata->_specular_color = color;
 }
 }
 
 
+////////////////////////////////////////////////////////////////////
+//     Function: Spotlight::clear_specular_color
+//       Access: Public
+//  Description: Clears a custom specular color setting, meaning that
+//               the specular color will now come from the color.
+////////////////////////////////////////////////////////////////////
+INLINE void Spotlight::
+clear_specular_color() {
+  _has_specular_color = false;
+}
+
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 //     Function: Spotlight::get_attenuation
 //     Function: Spotlight::get_attenuation
 //       Access: Public, Final
 //       Access: Public, Final

+ 15 - 3
panda/src/pgraphnodes/spotlight.cxx

@@ -69,7 +69,8 @@ fillin(DatagramIterator &scan, BamReader *) {
 ////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////
 Spotlight::
 Spotlight::
 Spotlight(const string &name) : 
 Spotlight(const string &name) : 
-  LightLensNode(name) 
+  LightLensNode(name),
+  _has_specular_color(false)
 {
 {
 }
 }
 
 
@@ -83,6 +84,7 @@ Spotlight(const string &name) :
 Spotlight::
 Spotlight::
 Spotlight(const Spotlight &copy) :
 Spotlight(const Spotlight &copy) :
   LightLensNode(copy),
   LightLensNode(copy),
+  _has_specular_color(copy._has_specular_color),
   _cycler(copy._cycler)
   _cycler(copy._cycler)
 {
 {
 }
 }
@@ -124,8 +126,10 @@ write(ostream &out, int indent_level) const {
   indent(out, indent_level) << *this << ":\n";
   indent(out, indent_level) << *this << ":\n";
   indent(out, indent_level + 2)
   indent(out, indent_level + 2)
     << "color " << get_color() << "\n";
     << "color " << get_color() << "\n";
-  indent(out, indent_level + 2)
-    << "specular color " << get_specular_color() << "\n";
+  if (_has_specular_color) {
+    indent(out, indent_level + 2)
+      << "specular color " << get_specular_color() << "\n";
+  }
   indent(out, indent_level + 2)
   indent(out, indent_level + 2)
     << "attenuation " << get_attenuation() << "\n";
     << "attenuation " << get_attenuation() << "\n";
   indent(out, indent_level + 2)
   indent(out, indent_level + 2)
@@ -286,6 +290,7 @@ register_with_read_factory() {
 void Spotlight::
 void Spotlight::
 write_datagram(BamWriter *manager, Datagram &dg) {
 write_datagram(BamWriter *manager, Datagram &dg) {
   LightLensNode::write_datagram(manager, dg);
   LightLensNode::write_datagram(manager, dg);
+  dg.add_bool(_has_specular_color);
   manager->write_cdata(dg, _cycler);
   manager->write_cdata(dg, _cycler);
 }
 }
 
 
@@ -319,5 +324,12 @@ make_from_bam(const FactoryParams &params) {
 void Spotlight::
 void Spotlight::
 fillin(DatagramIterator &scan, BamReader *manager) {
 fillin(DatagramIterator &scan, BamReader *manager) {
   LightLensNode::fillin(scan, manager);
   LightLensNode::fillin(scan, manager);
+
+  if (manager->get_file_minor_ver() >= 39) {
+    _has_specular_color = scan.get_bool();
+  } else {
+    _has_specular_color = true;
+  }
+
   manager->read_cdata(scan, _cycler);
   manager->read_cdata(scan, _cycler);
 }
 }

+ 3 - 0
panda/src/pgraphnodes/spotlight.h

@@ -57,6 +57,7 @@ PUBLISHED:
 
 
   INLINE const LColor &get_specular_color() const FINAL;
   INLINE const LColor &get_specular_color() const FINAL;
   INLINE void set_specular_color(const LColor &color);
   INLINE void set_specular_color(const LColor &color);
+  INLINE void clear_specular_color();
   MAKE_PROPERTY(specular_color, get_specular_color, set_specular_color);
   MAKE_PROPERTY(specular_color, get_specular_color, set_specular_color);
 
 
   INLINE const LVecBase3 &get_attenuation() const FINAL;
   INLINE const LVecBase3 &get_attenuation() const FINAL;
@@ -79,6 +80,8 @@ private:
   CPT(RenderState) get_viz_state();
   CPT(RenderState) get_viz_state();
 
 
 private:
 private:
+  bool _has_specular_color;
+
   // This is the data that must be cycled between pipeline stages.
   // This is the data that must be cycled between pipeline stages.
   class EXPCL_PANDA_PGRAPHNODES CData : public CycleData {
   class EXPCL_PANDA_PGRAPHNODES CData : public CycleData {
   public:
   public:

+ 2 - 1
panda/src/putil/bam.h

@@ -33,7 +33,7 @@ static const unsigned short _bam_major_ver = 6;
 // Bumped to major version 6 on 2/11/06 to factor out PandaNode::CData.
 // Bumped to major version 6 on 2/11/06 to factor out PandaNode::CData.
 
 
 static const unsigned short _bam_first_minor_ver = 14;
 static const unsigned short _bam_first_minor_ver = 14;
-static const unsigned short _bam_minor_ver = 38;
+static const unsigned short _bam_minor_ver = 39;
 // Bumped to minor version 14 on 12/19/07 to change default ColorAttrib.
 // Bumped to minor version 14 on 12/19/07 to change default ColorAttrib.
 // Bumped to minor version 15 on 4/9/08 to add TextureAttrib::_implicit_sort.
 // Bumped to minor version 15 on 4/9/08 to add TextureAttrib::_implicit_sort.
 // Bumped to minor version 16 on 5/13/08 to add Texture::_quality_level.
 // Bumped to minor version 16 on 5/13/08 to add Texture::_quality_level.
@@ -59,5 +59,6 @@ static const unsigned short _bam_minor_ver = 38;
 // Bumped to minor version 36 on 12/9/14 to add samplers and lod settings.
 // Bumped to minor version 36 on 12/9/14 to add samplers and lod settings.
 // Bumped to minor version 37 on 1/22/15 to add GeomVertexArrayFormat::_divisor.
 // Bumped to minor version 37 on 1/22/15 to add GeomVertexArrayFormat::_divisor.
 // Bumped to minor version 38 on 4/15/15 to add various Bullet classes.
 // Bumped to minor version 38 on 4/15/15 to add various Bullet classes.
+// Bumped to minor version 39 on 1/9/16 to change lights and materials.
 
 
 #endif
 #endif