/** * PANDA 3D SOFTWARE * Copyright (c) Carnegie Mellon University. All rights reserved. * * All use of this software is subject to the terms of the revised BSD * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * * @file material.cxx * @author mike * @date 1997-01-09 */ #include "pandabase.h" #include "material.h" #include "indent.h" #include "datagram.h" #include "datagramIterator.h" #include "bamReader.h" #include "bamWriter.h" TypeHandle Material::_type_handle; PT(Material) Material::_default; /** * */ void Material:: operator = (const Material ©) { Namable::operator = (copy); _base_color = copy._base_color; _ambient = copy._ambient; _diffuse = copy._diffuse; _specular = copy._specular; _emission = copy._emission; _shininess = copy._shininess; _roughness = copy._roughness; _metallic = copy._metallic; _refractive_index = copy._refractive_index; _flags = copy._flags & (~F_attrib_lock); } /** * 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; } /** * 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); } } /** * Specifies the ambient color setting of the material. This will be the * multiplied by any ambient lights in effect on the material to set its base * color. * * This is the color of the object as it appears in the absence of direct * light. * * If this is not set, the object color will be used. */ void Material:: set_ambient(const LColor &color) { if (enforce_attrib_lock) { if ((_flags & F_ambient)==0) { nassertv(!is_attrib_locked()); } } _ambient = color; _flags |= F_ambient; } /** * Specifies the diffuse color setting of the material. This will be * multiplied by any lights in effect on the material to get the color in the * parts of the object illuminated by the lights. * * This is the primary color of an object; the color of the object as it * appears in direct light, in the absence of highlights. * * If this is not set, the object color will be used. */ void Material:: set_diffuse(const LColor &color) { if (enforce_attrib_lock) { if ((_flags & F_diffuse)==0) { nassertv(!is_attrib_locked()); } } _diffuse = color; _flags |= F_diffuse; } /** * Specifies the specular color setting of the material. This will be * multiplied by any lights in effect on the material to compute the color of * specular highlights on the object. * * This is the highlight color of an object: the color of small highlight * reflections. * * 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:: set_specular(const LColor &color) { if (enforce_attrib_lock) { if ((_flags & F_specular)==0) { nassertv(!is_attrib_locked()); } } _specular = color; _flags |= F_specular; } /** * 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; } /** * Specifies the emission color setting of the material. This is the color of * the object as it appears in the absence of any light whatsover, including * ambient light. It is as if the object is glowing by this color (although * of course it will not illuminate neighboring objects). * * If this is not set, the object will not glow by its own light and will only * appear visible in the presence of one or more lights. */ void Material:: set_emission(const LColor &color) { if (enforce_attrib_lock) { if ((_flags & F_emission)==0) { nassertv(!is_attrib_locked()); } } _emission = color; _flags |= F_emission; } /** * Sets the shininess exponent of the material. This controls the size of the * specular highlight spot. In general, larger number produce a smaller * specular highlight, which makes the object appear shinier. Smaller numbers * produce a larger highlight, which makes the object appear less shiny. * * This is usually in the range 0..128. * * Setting a shininess value removes any previous roughness assignment. */ void Material:: set_shininess(PN_stdfloat shininess) { _shininess = shininess; _flags &= ~F_roughness; } /** * Returns the roughness previously specified by set_roughness. If none was * previously set, this value is computed from the shininess value. */ PN_stdfloat Material:: get_roughness() const { if ((_flags & F_roughness) == 0) { // Calculate roughness from blinn-phong shininess. return csqrt(csqrt(2 / (_shininess + 2))); } else { return _roughness; } } /** * Sets the roughness exponent of the material, where 0 is completely shiny * (infinite shininess), and 1 is a completely dull object (0 shininess). * This is a different, more perceptually intuitive way of controlling the * size of the specular spot, and more commonly used in physically-based * rendering. * * Setting a roughness recalculates the shininess value. */ void Material:: set_roughness(PN_stdfloat roughness) { _roughness = roughness; _flags |= F_roughness; // Calculate the specular exponent from the roughness as it is used in // Blinn-Phong shading model. We use the popular Disney method of squaring // the roughness to get a more perceptually linear scale. From: // http://graphicrants.blogspot.de/2013/08/specular-brdf-reference.html if (roughness <= 0 || IS_NEARLY_ZERO(roughness)) { _shininess = make_inf((PN_stdfloat)0); } else { PN_stdfloat alpha = roughness * roughness; _shininess = 2 / (alpha * alpha) - 2; } } /** * Sets the metallic setting of the material, which is is used for physically- * based rendering models. This is usually 0 for dielectric materials and 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:: set_metallic(PN_stdfloat metallic) { if (enforce_attrib_lock) { if ((_flags & F_metallic) == 0) { nassertv(!is_attrib_locked()); } } _metallic = 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; } } /** * 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); } } } /** * 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); } } /** * Returns a number less than zero if this material sorts before the other * one, greater than zero if it sorts after, or zero if they are equivalent. * The sorting order is arbitrary and largely meaningless, except to * differentiate different materials. */ int Material:: compare_to(const Material &other) const { if (_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()) { return get_ambient().compare_to(other.get_ambient()); } if (has_diffuse() && get_diffuse() != other.get_diffuse()) { return get_diffuse().compare_to(other.get_diffuse()); } if (has_specular() && get_specular() != other.get_specular()) { return get_specular().compare_to(other.get_specular()); } if (has_emission() && get_emission() != other.get_emission()) { return get_emission().compare_to(other.get_emission()); } if (get_shininess() != other.get_shininess()) { 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()); } /** * */ void Material:: output(ostream &out) const { out << "Material " << get_name(); 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_refractive_index()) { out << " ior" << get_refractive_index(); } if (has_emission()) { out << " e(" << get_emission() << ")"; } if (_flags & F_roughness) { out << " r" << get_roughness(); } else { out << " s" << get_shininess(); } if (_flags & F_metallic) { out << " m" << _metallic; } out << " l" << get_local() << " t" << get_twoside(); } /** * */ void Material:: write(ostream &out, int indent_level) const { 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()) { indent(out, indent_level + 2) << "ambient = " << get_ambient() << "\n"; } if (has_diffuse()) { indent(out, indent_level + 2) << "diffuse = " << get_diffuse() << "\n"; } if (has_specular()) { indent(out, indent_level + 2) << "specular = " << get_specular() << "\n"; } else { indent(out, indent_level + 2) << "refractive_index = " << get_refractive_index() << "\n"; } if (has_emission()) { indent(out, indent_level + 2) << "emission = " << get_emission() << "\n"; } if (_flags & F_roughness) { indent(out, indent_level + 2) << "roughness = " << get_roughness() << "\n"; } else { indent(out, indent_level + 2) << "shininess = " << get_shininess() << "\n"; } if (has_metallic()) { indent(out, indent_level + 2) << "metallic = " << get_metallic() << "\n"; } indent(out, indent_level + 2) << "local = " << get_local() << "\n"; indent(out, indent_level + 2) << "twoside = " << get_twoside() << "\n"; } /** * Factory method to generate a Material object */ void Material:: register_with_read_factory() { BamReader::get_factory()->register_factory(get_class_type(), make_Material); } /** * Function to write the important information in the particular object to a * Datagram */ void Material:: write_datagram(BamWriter *manager, Datagram &me) { me.add_string(get_name()); if (manager->get_file_minor_ver() >= 39) { 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); if (_flags & F_roughness) { me.add_stdfloat(_roughness); } else { me.add_stdfloat(_shininess); } me.add_stdfloat(_refractive_index); } else { _ambient.write_datagram(me); _diffuse.write_datagram(me); _specular.write_datagram(me); _emission.write_datagram(me); me.add_stdfloat(_shininess); me.add_int32(_flags & 0x7f); } } /** * Factory method to generate a Material object */ TypedWritable *Material:: make_Material(const FactoryParams ¶ms) { Material *me = new Material; DatagramIterator scan; BamReader *manager; parse_params(params, scan, manager); me->fillin(scan, manager); return me; } /** * Function that reads out of the datagram (or asks manager to read) all of * the data that is needed to re-create this object and stores it in the * appropiate place */ void Material:: fillin(DatagramIterator &scan, BamReader *manager) { set_name(scan.get_string()); if (manager->get_file_minor_ver() >= 39) { _flags = scan.get_int32(); 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(scan.get_stdfloat()); } 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); } } }