diff options
author | Aras Pranckevicius <aras@nesnausk.org> | 2022-06-14 10:19:02 +0300 |
---|---|---|
committer | Aras Pranckevicius <aras@nesnausk.org> | 2022-06-14 10:19:02 +0300 |
commit | 1b4f35f6a5889d8d2e65c6231e84f8b1744c4f96 (patch) | |
tree | cfae52865680308729ad955ad623c3151188097a /source/blender/io | |
parent | 827fa8176737f822b7f8d2354b05e59976c7101a (diff) |
obj: vertex colors support in importer and exporter
Adds support for vertex colors to OBJ I/O.
Importer:
- Supports both "xyzrgb" and "MRGB" vertex color formats.
- Whenever vertex color is present in the file for a model, it is
imported and a Color attribute is created (per-vertex, full float
color data type). Color coming from the file is assumed to be sRGB,
and is converted to linear upon import.
Exporter:
- Option to export the vertex colors. Defaults to "off", since not
all 3rd party software supports vertex colors.
- When the option is "on", if a mesh has a color attribute layer,
the active one is exported in "xyzrgb" form. If the mesh has
per-face-corner colors, they are averaged on the vertices.
Colors are converted from linear to sRGB upon export.
Reviewed By: Howard Trickey
Differential Revision: https://developer.blender.org/D15159
Diffstat (limited to 'source/blender/io')
13 files changed, 260 insertions, 10 deletions
diff --git a/source/blender/io/wavefront_obj/IO_wavefront_obj.h b/source/blender/io/wavefront_obj/IO_wavefront_obj.h index 0a78cdc714d..a719dff2126 100644 --- a/source/blender/io/wavefront_obj/IO_wavefront_obj.h +++ b/source/blender/io/wavefront_obj/IO_wavefront_obj.h @@ -45,6 +45,7 @@ struct OBJExportParams { eEvaluationMode export_eval_mode; bool export_uv; bool export_normals; + bool export_colors; bool export_materials; bool export_triangulated_mesh; bool export_curves_as_nurbs; diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc index 11d1bafdafe..cb95c561547 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc @@ -8,7 +8,9 @@ #include <cstdio> #include "BKE_blender_version.h" +#include "BKE_geometry_set.hh" +#include "BLI_color.hh" #include "BLI_enumerable_thread_specific.hh" #include "BLI_path_util.h" #include "BLI_task.hh" @@ -241,13 +243,38 @@ void obj_parallel_chunked_output(FormatHandler<eFileType::OBJ> &fh, } void OBJWriter::write_vertex_coords(FormatHandler<eFileType::OBJ> &fh, - const OBJMesh &obj_mesh_data) const + const OBJMesh &obj_mesh_data, + bool write_colors) const { const int tot_count = obj_mesh_data.tot_vertices(); - obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler<eFileType::OBJ> &buf, int i) { - float3 vertex = obj_mesh_data.calc_vertex_coords(i, export_params_.scaling_factor); - buf.write<eOBJSyntaxElement::vertex_coords>(vertex[0], vertex[1], vertex[2]); - }); + + Mesh *mesh = obj_mesh_data.get_mesh(); + CustomDataLayer *colors_layer = nullptr; + if (write_colors) { + colors_layer = BKE_id_attributes_active_color_get(&mesh->id); + } + if (write_colors && (colors_layer != nullptr)) { + MeshComponent component; + component.replace(mesh, GeometryOwnershipType::ReadOnly); + VArray<ColorGeometry4f> attribute = component.attribute_get_for_read<ColorGeometry4f>( + colors_layer->name, ATTR_DOMAIN_POINT, {0.0f, 0.0f, 0.0f, 0.0f}); + + BLI_assert(tot_count == attribute.size()); + obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler<eFileType::OBJ> &buf, int i) { + float3 vertex = obj_mesh_data.calc_vertex_coords(i, export_params_.scaling_factor); + ColorGeometry4f linear = attribute.get(i); + float srgb[3]; + linearrgb_to_srgb_v3_v3(srgb, linear); + buf.write<eOBJSyntaxElement::vertex_coords_color>( + vertex[0], vertex[1], vertex[2], srgb[0], srgb[1], srgb[2]); + }); + } + else { + obj_parallel_chunked_output(fh, tot_count, [&](FormatHandler<eFileType::OBJ> &buf, int i) { + float3 vertex = obj_mesh_data.calc_vertex_coords(i, export_params_.scaling_factor); + buf.write<eOBJSyntaxElement::vertex_coords>(vertex[0], vertex[1], vertex[2]); + }); + } } void OBJWriter::write_uv_coords(FormatHandler<eFileType::OBJ> &fh, OBJMesh &r_obj_mesh_data) const diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh index 77da7b44276..97c23484426 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh @@ -72,9 +72,11 @@ class OBJWriter : NonMovable, NonCopyable { */ void write_mtllib_name(const StringRefNull mtl_filepath) const; /** - * Write vertex coordinates for all vertices as "v x y z". + * Write vertex coordinates for all vertices as "v x y z" or "v x y z r g b". */ - void write_vertex_coords(FormatHandler<eFileType::OBJ> &fh, const OBJMesh &obj_mesh_data) const; + void write_vertex_coords(FormatHandler<eFileType::OBJ> &fh, + const OBJMesh &obj_mesh_data, + bool write_colors) const; /** * Write UV vertex coordinates for all vertices as `vt u v`. * \note UV indices are stored here, but written with polygons later. diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh index f0263989bfc..157d7760307 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh @@ -30,6 +30,7 @@ enum class eFileType { enum class eOBJSyntaxElement { vertex_coords, + vertex_coords_color, uv_vertex_coords, normal, poly_element_begin, @@ -130,6 +131,9 @@ constexpr FormattingSyntax syntax_elem_to_formatting(const eOBJSyntaxElement key case eOBJSyntaxElement::vertex_coords: { return {"v {:.6f} {:.6f} {:.6f}\n", 3, is_type_float<T...>}; } + case eOBJSyntaxElement::vertex_coords_color: { + return {"v {:.6f} {:.6f} {:.6f} {:.6f} {:.6f} {:.6f}\n", 6, is_type_float<T...>}; + } case eOBJSyntaxElement::uv_vertex_coords: { return {"vt {:.6f} {:.6f}\n", 2, is_type_float<T...>}; } diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh index 91213ec8152..ee2e6227700 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh @@ -241,6 +241,11 @@ class OBJMesh : NonCopyable { return i < 0 || i >= poly_order_.size() ? i : poly_order_[i]; } + Mesh *get_mesh() const + { + return export_mesh_eval_; + } + private: /** * Free the mesh if _the exporter_ created it. diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc index b6e636b389d..b0938084efb 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc @@ -195,7 +195,7 @@ static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_me auto &fh = buffers[i]; obj_writer.write_object_name(fh, obj); - obj_writer.write_vertex_coords(fh, obj); + obj_writer.write_vertex_coords(fh, obj, export_params.export_colors); if (obj.tot_polygons() > 0) { if (export_params.export_smooth_groups) { diff --git a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc index a627e7261e3..4a27d734819 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc @@ -5,12 +5,15 @@ */ #include "BLI_map.hh" +#include "BLI_math_color.h" #include "BLI_string_ref.hh" #include "BLI_vector.hh" #include "obj_import_file_reader.hh" #include "obj_import_string_utils.hh" +#include <charconv> + namespace blender::io::obj { using std::string; @@ -34,6 +37,7 @@ static Geometry *create_geometry(Geometry *const prev_geometry, g->geom_type_ = new_type; g->geometry_name_ = name.is_empty() ? "New object" : name; g->vertex_start_ = global_vertices.vertices.size(); + g->vertex_color_start_ = global_vertices.vertex_colors.size(); r_offset.set_index_offset(g->vertex_start_); return g; }; @@ -71,9 +75,51 @@ static void geom_add_vertex(Geometry *geom, GlobalVertices &r_global_vertices) { float3 vert; - parse_floats(p, end, 0.0f, vert, 3); + p = parse_floats(p, end, 0.0f, vert, 3); r_global_vertices.vertices.append(vert); geom->vertex_count_++; + /* OBJ extension: "xyzrgb" vertex colors, when the vertex position + * is followed by 3 more RGB color components. See + * http://paulbourke.net/dataformats/obj/colour.html */ + if (p < end) { + float3 srgb; + p = parse_floats(p, end, -1.0f, srgb, 3); + if (srgb.x >= 0 && srgb.y >= 0 && srgb.z >= 0) { + float3 linear; + srgb_to_linearrgb_v3_v3(linear, srgb); + r_global_vertices.vertex_colors.append(linear); + geom->vertex_color_count_++; + } + } +} + +static void geom_add_mrgb_colors(Geometry *geom, + const char *p, + const char *end, + GlobalVertices &r_global_vertices) +{ + /* MRGB color extension, in the form of + * "#MRGB MMRRGGBBMMRRGGBB ..." + * http://paulbourke.net/dataformats/obj/colour.html */ + p = drop_whitespace(p, end); + const int mrgb_length = 8; + while (p + mrgb_length <= end) { + uint32_t value = 0; + std::from_chars_result res = std::from_chars(p, p + mrgb_length, value, 16); + if (res.ec == std::errc::invalid_argument || res.ec == std::errc::result_out_of_range) { + return; + } + unsigned char srgb[4]; + srgb[0] = (value >> 16) & 0xFF; + srgb[1] = (value >> 8) & 0xFF; + srgb[2] = value & 0xFF; + srgb[3] = 0xFF; + float linear[4]; + srgb_to_linearrgb_uchar4(linear, srgb); + r_global_vertices.vertex_colors.append({linear[0], linear[1], linear[2]}); + geom->vertex_color_count_++; + p += mrgb_length; + } } static void geom_add_vertex_normal(Geometry *geom, @@ -482,6 +528,9 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, else if (parse_keyword(p, end, "mtllib")) { add_mtl_library(StringRef(p, end).trim()); } + else if (parse_keyword(p, end, "#MRGB")) { + geom_add_mrgb_colors(curr_geom, p, end, r_global_vertices); + } /* Comments. */ else if (*p == '#') { /* Nothing to do. */ diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc index 854ab96e39f..acc35ad46e1 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_mesh.cc @@ -8,6 +8,7 @@ #include "DNA_mesh_types.h" #include "DNA_scene_types.h" +#include "BKE_attribute.h" #include "BKE_customdata.h" #include "BKE_material.h" #include "BKE_mesh.h" @@ -50,6 +51,7 @@ Object *MeshFromGeometry::create_mesh(Main *bmain, create_edges(mesh); create_uv_verts(mesh); create_normals(mesh); + create_colors(mesh); create_materials(bmain, materials, created_materials, obj); if (import_params.validate_meshes || mesh_geometry_.has_invalid_polys_) { @@ -345,4 +347,26 @@ void MeshFromGeometry::create_normals(Mesh *mesh) MEM_freeN(loop_normals); } +void MeshFromGeometry::create_colors(Mesh *mesh) +{ + /* Nothing to do if we don't have vertex colors. */ + if (mesh_geometry_.vertex_color_count_ < 1) { + return; + } + if (mesh_geometry_.vertex_color_count_ != mesh_geometry_.vertex_count_) { + std::cerr << "Mismatching number of vertices (" << mesh_geometry_.vertex_count_ + << ") and colors (" << mesh_geometry_.vertex_color_count_ << ") on object '" + << mesh_geometry_.geometry_name_ << "', ignoring colors." << std::endl; + return; + } + + CustomDataLayer *color_layer = BKE_id_attribute_new( + &mesh->id, "Color", CD_PROP_COLOR, ATTR_DOMAIN_POINT, nullptr); + float4 *colors = (float4 *)color_layer->data; + for (int i = 0; i < mesh_geometry_.vertex_color_count_; ++i) { + float3 c = global_vertices_.vertex_colors[mesh_geometry_.vertex_color_start_ + i]; + colors[i] = float4(c.x, c.y, c.z, 1.0f); + } +} + } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh b/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh index cf4a2aee394..216717f3578 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh +++ b/source/blender/io/wavefront_obj/importer/obj_import_mesh.hh @@ -65,6 +65,7 @@ class MeshFromGeometry : NonMovable, NonCopyable { Map<std::string, Material *> &created_materials, Object *obj); void create_normals(Mesh *mesh); + void create_colors(Mesh *mesh); }; } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/importer/obj_import_objects.hh b/source/blender/io/wavefront_obj/importer/obj_import_objects.hh index b67ba46af03..69babc26bb0 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_objects.hh +++ b/source/blender/io/wavefront_obj/importer/obj_import_objects.hh @@ -26,6 +26,7 @@ struct GlobalVertices { Vector<float3> vertices; Vector<float2> uv_vertices; Vector<float3> vertex_normals; + Vector<float3> vertex_colors; }; /** @@ -102,6 +103,8 @@ struct Geometry { int vertex_start_ = 0; int vertex_count_ = 0; + int vertex_color_start_ = 0; + int vertex_color_count_ = 0; /** Edges written in the file in addition to (or even without polygon) elements. */ Vector<MEdge> edges_; diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc index b384d934c82..6aec848573f 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc @@ -436,6 +436,19 @@ TEST_F(obj_exporter_regression_test, cubes_positioned) _export.params); } +TEST_F(obj_exporter_regression_test, cubes_vertex_colors) +{ + OBJExportParamsDefault _export; + _export.params.export_colors = true; + _export.params.export_normals = false; + _export.params.export_uv = false; + _export.params.export_materials = false; + compare_obj_export_to_golden("io_tests/blend_geometry/cubes_vertex_colors.blend", + "io_tests/obj/cubes_vertex_colors.obj", + "", + _export.params); +} + TEST_F(obj_exporter_regression_test, cubes_with_textures_strip) { OBJExportParamsDefault _export; @@ -494,6 +507,7 @@ TEST_F(obj_exporter_regression_test, all_objects) _export.params.forward_axis = IO_AXIS_Y; _export.params.up_axis = IO_AXIS_Z; _export.params.export_smooth_groups = true; + _export.params.export_colors = true; compare_obj_export_to_golden("io_tests/blend_scene/all_objects.blend", "io_tests/obj/all_objects.obj", "io_tests/obj/all_objects.mtl", diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh index 42f7620999d..7d3b41ed527 100644 --- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh +++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.hh @@ -26,6 +26,7 @@ struct OBJExportParamsDefault { params.export_selected_objects = false; params.export_uv = true; params.export_normals = true; + params.export_colors = false; params.export_materials = true; params.path_mode = PATH_REFERENCE_AUTO; params.export_triangulated_mesh = false; diff --git a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc index 6d09f522028..7cc528d4f3d 100644 --- a/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_importer_tests.cc @@ -39,6 +39,7 @@ struct Expectation { float3 vert_first, vert_last; float3 normal_first; float2 uv_first; + float4 color_first = {-1, -1, -1, -1}; }; class obj_importer_test : public BlendfileLoadingBaseTest { @@ -98,6 +99,15 @@ class obj_importer_test : public BlendfileLoadingBaseTest { CustomData_get_layer(&mesh->ldata, CD_MLOOPUV)); float2 uv_first = mloopuv ? float2(mloopuv->uv) : float2(0, 0); EXPECT_V2_NEAR(uv_first, exp.uv_first, 0.0001f); + if (exp.color_first.x >= 0) { + const float4 *colors = (const float4 *)(CustomData_get_layer(&mesh->vdata, + CD_PROP_COLOR)); + EXPECT_TRUE(colors != nullptr); + EXPECT_V4_NEAR(colors[0], exp.color_first, 0.0001f); + } + else { + EXPECT_FALSE(CustomData_has_layer(&mesh->vdata, CD_PROP_COLOR)); + } } if (object->type == OB_CURVES_LEGACY) { Curve *curve = static_cast<Curve *>(DEG_get_evaluated_object(depsgraph, object)->data); @@ -434,7 +444,17 @@ TEST_F(obj_importer_test, import_all_objects) float3(16, 1, -1), float3(14, 1, 1), float3(0, 0, 1)}, - {"OBVColCube", OB_MESH, 8, 13, 7, 26, float3(13, 1, -1), float3(11, 1, 1), float3(0, 0, 1)}, + {"OBVColCube", + OB_MESH, + 8, + 13, + 7, + 26, + float3(13, 1, -1), + float3(11, 1, 1), + float3(0, 0, 1), + float2(0, 0), + float4(0.0f, 0.002125f, 1.0f, 1.0f)}, {"OBUVCube", OB_MESH, 8, @@ -490,4 +510,103 @@ TEST_F(obj_importer_test, import_all_objects) import_and_check("all_objects.obj", expect, std::size(expect), 7); } +TEST_F(obj_importer_test, import_cubes_vertex_colors) +{ + Expectation expect[] = { + {"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, + {"OBCubeVertexByte", + OB_MESH, + 8, + 12, + 6, + 24, + float3(1.0f, 1.0f, -1.0f), + float3(-1.0f, -1.0f, 1.0f), + float3(0, 0, 0), + float2(0, 0), + float4(0.846873f, 0.027321f, 0.982251f, 1.0f)}, + {"OBCubeVertexFloat", + OB_MESH, + 8, + 12, + 6, + 24, + float3(3.392028f, 1.0f, -1.0f), + float3(1.392028f, -1.0f, 1.0f), + float3(0, 0, 0), + float2(0, 0), + float4(49.99558f, 0.027321f, 0.982251f, 1.0f)}, + {"OBCubeCornerByte", + OB_MESH, + 8, + 12, + 6, + 24, + float3(1.0f, 1.0f, -3.812445f), + float3(-1.0f, -1.0f, -1.812445f), + float3(0, 0, 0), + float2(0, 0), + float4(0.89627f, 0.036889f, 0.47932f, 1.0f)}, + {"OBCubeCornerFloat", + OB_MESH, + 8, + 12, + 6, + 24, + float3(3.481967f, 1.0f, -3.812445f), + float3(1.481967f, -1.0f, -1.812445f), + float3(0, 0, 0), + float2(0, 0), + float4(1.564582f, 0.039217f, 0.664309f, 1.0f)}, + {"OBCubeMultiColorAttribs", + OB_MESH, + 8, + 12, + 6, + 24, + float3(-4.725068f, -1.0f, 1.0f), + float3(-2.725068f, 1.0f, -1.0f), + float3(0, 0, 0), + float2(0, 0), + float4(0.270498f, 0.47932f, 0.262251f, 1.0f)}, + {"OBCubeNoColors", + OB_MESH, + 8, + 12, + 6, + 24, + float3(-4.550208f, -1.0f, -1.918042f), + float3(-2.550208f, 1.0f, -3.918042f)}, + }; + import_and_check("cubes_vertex_colors.obj", expect, std::size(expect), 0); +} + +TEST_F(obj_importer_test, import_cubes_vertex_colors_mrgb) +{ + Expectation expect[] = {{"OBCube", OB_MESH, 8, 12, 6, 24, float3(1, 1, -1), float3(-1, 1, 1)}, + {"OBCubeXYZRGB", + OB_MESH, + 8, + 12, + 6, + 24, + float3(1, 1, -1), + float3(-1, -1, 1), + float3(0, 0, 0), + float2(0, 0), + float4(0.6038f, 0.3185f, 0.1329f, 1.0f)}, + {"OBCubeMRGB", + OB_MESH, + 8, + 12, + 6, + 24, + float3(4, 1, -1), + float3(2, -1, 1), + float3(0, 0, 0), + float2(0, 0), + float4(0.8714f, 0.6308f, 0.5271f, 1.0f)}}; + import_and_check("cubes_vertex_colors_mrgb.obj", expect, std::size(expect), 0); +} + } // namespace blender::io::obj |