Blender V5.0
obj_import_mtl.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BKE_image.hh"
10#include "BKE_main.hh"
11#include "BKE_node.hh"
13
14#include "BLI_math_vector.h"
15#include "BLI_path_utils.hh"
16#include "BLI_string.h"
17
18#include "DNA_material_types.h"
19#include "DNA_node_types.h"
20
21#include "NOD_shader.h"
22
23#include "obj_export_mtl.hh"
24#include "obj_import_mtl.hh"
25
26#include "CLG_log.h"
27static CLG_LogRef LOG = {"io.obj"};
28
29namespace blender::io::obj {
30
36 const char *socket_id,
37 Span<float> value,
38 bNode *r_node)
39{
40 BLI_assert(r_node);
41 bNodeSocket *socket{bke::node_find_socket(*r_node, SOCK_IN, socket_id)};
42 BLI_assert(socket && socket->type == property_type);
43 switch (property_type) {
44 case SOCK_FLOAT: {
45 BLI_assert(value.size() == 1);
46 static_cast<bNodeSocketValueFloat *>(socket->default_value)->value = value[0];
47 break;
48 }
49 case SOCK_RGBA: {
50 /* Alpha will be added manually. It is not read from the MTL file either. */
51 BLI_assert(value.size() == 3);
52 copy_v3_v3(static_cast<bNodeSocketValueRGBA *>(socket->default_value)->value, value.data());
53 static_cast<bNodeSocketValueRGBA *>(socket->default_value)->value[3] = 1.0f;
54 break;
55 }
56 case SOCK_VECTOR: {
57 BLI_assert(value.size() == 3);
59 value.data());
60 break;
61 }
62 default: {
63 BLI_assert(0);
64 break;
65 }
66 }
67}
68
69static Image *load_image_at_path(Main *bmain, const std::string &path, bool relative_paths)
70{
71 Image *image = BKE_image_load_exists(bmain, path.c_str());
72 if (!image) {
73 CLOG_WARN(&LOG, "Cannot load image file: '%s'", path.c_str());
74 return nullptr;
75 }
76 CLOG_INFO(&LOG, "Loaded image from: '%s'", path.c_str());
77 if (relative_paths) {
80 }
81 return image;
82}
83
84static Image *create_placeholder_image(Main *bmain, const std::string &path)
85{
86 const float color[4] = {0, 0, 0, 1};
87 Image *image = BKE_image_add_generated(bmain,
88 32,
89 32,
90 BLI_path_basename(path.c_str()),
91 24,
92 false,
94 color,
95 false,
96 false,
97 false);
98 STRNCPY(image->filepath, path.c_str());
99 image->source = IMA_SRC_FILE;
100 return image;
101}
102
103static Image *load_texture_image(Main *bmain, const MTLTexMap &tex_map, bool relative_paths)
104{
105 Image *image = nullptr;
106
107 /* Remove quotes. */
108 std::string image_path{tex_map.image_path};
109 auto end_pos = std::remove(image_path.begin(), image_path.end(), '"');
110 image_path.erase(end_pos, image_path.end());
111
112 /* First try treating texture path as relative. */
113 std::string tex_path{tex_map.mtl_dir_path + image_path};
114 image = load_image_at_path(bmain, tex_path, relative_paths);
115 if (image != nullptr) {
116 return image;
117 }
118 /* Then try using it directly as absolute path. */
119 image = load_image_at_path(bmain, image_path, relative_paths);
120 if (image != nullptr) {
121 return image;
122 }
123 /* Try replacing underscores with spaces. */
124 std::string no_underscore_path{image_path};
125 std::replace(no_underscore_path.begin(), no_underscore_path.end(), '_', ' ');
126 if (!ELEM(no_underscore_path, image_path, tex_path)) {
127 image = load_image_at_path(bmain, no_underscore_path, relative_paths);
128 if (image != nullptr) {
129 return image;
130 }
131 }
132 /* Try taking just the basename from input path. */
133 std::string base_path{tex_map.mtl_dir_path + BLI_path_basename(image_path.c_str())};
134 if (base_path != tex_path) {
135 image = load_image_at_path(bmain, base_path, relative_paths);
136 if (image != nullptr) {
137 return image;
138 }
139 }
140
141 image = create_placeholder_image(bmain, tex_path);
142 return image;
143}
144
145/* Nodes are arranged in columns by type, with manually placed x coordinates
146 * based on node widths. */
147const float node_locx_texcoord = -880.0f;
148const float node_locx_mapping = -680.0f;
149const float node_locx_image = -480.0f;
150const float node_locx_normalmap = -200.0f;
151const float node_locx_bsdf = 0.0f;
152const float node_locx_output = 280.0f;
153
154/* Nodes are arranged in rows; one row for each image being used. */
155const float node_locy_top = 300.0f;
156const float node_locy_step = 300.0f;
157
158/* Add a node of the given type at the given location. */
159static bNode *add_node(bNodeTree *ntree, int type, float x, float y)
160{
161 bNode *node = bke::node_add_static_node(nullptr, *ntree, type);
162 node->location[0] = x;
163 node->location[1] = y;
164 return node;
165}
166
167static void link_sockets(bNodeTree *ntree,
168 bNode *from_node,
169 const char *from_node_id,
170 bNode *to_node,
171 const char *to_node_id)
172{
173 bNodeSocket *from_sock{bke::node_find_socket(*from_node, SOCK_OUT, from_node_id)};
174 bNodeSocket *to_sock{bke::node_find_socket(*to_node, SOCK_IN, to_node_id)};
175 BLI_assert(from_sock && to_sock);
176 bke::node_add_link(*ntree, *from_node, *from_sock, *to_node, *to_sock);
177}
178
179static void set_bsdf_socket_values(bNode *bsdf, Material *mat, const MTLMaterial &mtl_mat)
180{
181 const int illum = mtl_mat.illum_mode;
182 bool do_highlight = false;
183 bool do_tranparency = false;
184 bool do_reflection = false;
185 bool do_glass = false;
186 /* See https://wikipedia.org/wiki/Wavefront_.obj_file for possible values of illum. */
187 switch (illum) {
188 case -1:
189 case 1:
190 /* Base color on, ambient on. */
191 break;
192 case 2: {
193 /* Highlight on. */
194 do_highlight = true;
195 break;
196 }
197 case 3: {
198 /* Reflection on and Ray trace on. */
199 do_reflection = true;
200 break;
201 }
202 case 4: {
203 /* Transparency: Glass on, Reflection: Ray trace on. */
204 do_glass = true;
205 do_reflection = true;
206 do_tranparency = true;
207 break;
208 }
209 case 5: {
210 /* Reflection: Fresnel on and Ray trace on. */
211 do_reflection = true;
212 break;
213 }
214 case 6: {
215 /* Transparency: Refraction on, Reflection: Fresnel off and Ray trace on. */
216 do_reflection = true;
217 do_tranparency = true;
218 break;
219 }
220 case 7: {
221 /* Transparency: Refraction on, Reflection: Fresnel on and Ray trace on. */
222 do_reflection = true;
223 do_tranparency = true;
224 break;
225 }
226 case 8: {
227 /* Reflection on and Ray trace off. */
228 do_reflection = true;
229 break;
230 }
231 case 9: {
232 /* Transparency: Glass on, Reflection: Ray trace off. */
233 do_glass = true;
234 do_reflection = false;
235 do_tranparency = true;
236 break;
237 }
238 default: {
239 CLOG_WARN(&LOG,
240 "Material illum value '%d' is not supported by the Principled BSDF shader.",
241 illum);
242 break;
243 }
244 }
245 /* Approximations for trying to map obj/mtl material model into
246 * Principled BSDF: */
247 /* Specular: average of Ks components. */
248 float specular = (mtl_mat.spec_color[0] + mtl_mat.spec_color[1] + mtl_mat.spec_color[2]) / 3;
249 if (specular < 0.0f) {
250 specular = do_highlight ? 1.0f : 0.0f;
251 }
252 /* Roughness: map 0..1000 range to 1..0 and apply non-linearity. */
253 float roughness;
254 if (mtl_mat.spec_exponent < 0.0f) {
255 roughness = do_highlight ? 0.0f : 1.0f;
256 }
257 else {
258 float clamped_ns = std::max(0.0f, std::min(1000.0f, mtl_mat.spec_exponent));
259 roughness = 1.0f - sqrt(clamped_ns / 1000.0f);
260 }
261 /* Metallic: average of `Ka` components. */
262 float metallic = (mtl_mat.ambient_color[0] + mtl_mat.ambient_color[1] +
263 mtl_mat.ambient_color[2]) /
264 3;
265 if (do_reflection) {
266 if (metallic < 0.0f) {
267 metallic = 1.0f;
268 }
269 }
270 else {
271 metallic = 0.0f;
272 }
273
274 float ior = mtl_mat.ior;
275 if (ior < 0) {
276 if (do_tranparency) {
277 ior = 1.0f;
278 }
279 if (do_glass) {
280 ior = 1.5f;
281 }
282 }
283 float alpha = mtl_mat.alpha;
284 if (do_tranparency && alpha < 0) {
285 alpha = 1.0f;
286 }
287
288 /* PBR values, when present, override the ones calculated above. */
289 if (mtl_mat.roughness >= 0) {
290 roughness = mtl_mat.roughness;
291 }
292 if (mtl_mat.metallic >= 0) {
293 metallic = mtl_mat.metallic;
294 }
295
296 float3 base_color = mtl_mat.color;
297 if (base_color.x >= 0 && base_color.y >= 0 && base_color.z >= 0) {
298 set_property_of_socket(SOCK_RGBA, "Base Color", {base_color, 3}, bsdf);
299 /* Viewport shading uses legacy r,g,b base color. */
300 mat->r = base_color.x;
301 mat->g = base_color.y;
302 mat->b = base_color.z;
303 }
304
306 set_property_of_socket(SOCK_FLOAT, "Emission Strength", {1.0f}, bsdf);
307 }
308
309 float3 emission_color = mtl_mat.emission_color;
310 if (emission_color.x >= 0 && emission_color.y >= 0 && emission_color.z >= 0) {
311 float emission_strength = fmax(emission_color.x, fmax(emission_color.y, emission_color.z));
312 if (emission_strength > 1.0f) {
313 /* For colors brighter than 1.0, change color to be in 0..1 range, and set emission
314 * strength accordingly. */
316 SOCK_RGBA, "Emission Color", {emission_color / emission_strength, 3}, bsdf);
317 set_property_of_socket(SOCK_FLOAT, "Emission Strength", {emission_strength}, bsdf);
318 }
319 else {
320 set_property_of_socket(SOCK_RGBA, "Emission Color", {emission_color, 3}, bsdf);
321 set_property_of_socket(SOCK_FLOAT, "Emission Strength", {1.0f}, bsdf);
322 }
323 }
324
325 set_property_of_socket(SOCK_FLOAT, "Specular IOR Level", {specular}, bsdf);
326 set_property_of_socket(SOCK_FLOAT, "Roughness", {roughness}, bsdf);
327 mat->roughness = roughness;
328 set_property_of_socket(SOCK_FLOAT, "Metallic", {metallic}, bsdf);
329 mat->metallic = metallic;
330 /* Some files have `Ni 0`, ignore those values. */
331 if (ior > 0.0f) {
332 set_property_of_socket(SOCK_FLOAT, "IOR", {ior}, bsdf);
333 }
334 if (alpha != -1) {
335 set_property_of_socket(SOCK_FLOAT, "Alpha", {alpha}, bsdf);
336 }
337 if (do_tranparency || (alpha >= 0.0f && alpha < 1.0f)) {
340 }
341
342 if (mtl_mat.sheen >= 0) {
343 set_property_of_socket(SOCK_FLOAT, "Sheen Weight", {mtl_mat.sheen}, bsdf);
344 }
345 if (mtl_mat.cc_thickness >= 0) {
346 /* Clearcoat used to include an implicit 0.25 factor, so stay compatible to old versions. */
347 set_property_of_socket(SOCK_FLOAT, "Coat Weight", {0.25f * mtl_mat.cc_thickness}, bsdf);
348 }
349 if (mtl_mat.cc_roughness >= 0) {
350 set_property_of_socket(SOCK_FLOAT, "Coat Roughness", {mtl_mat.cc_roughness}, bsdf);
351 }
352 if (mtl_mat.aniso >= 0) {
353 set_property_of_socket(SOCK_FLOAT, "Anisotropic", {mtl_mat.aniso}, bsdf);
354 }
355 if (mtl_mat.aniso_rot >= 0) {
356 set_property_of_socket(SOCK_FLOAT, "Anisotropic Rotation", {mtl_mat.aniso_rot}, bsdf);
357 }
358
359 /* Transmission: average of transmission color. */
360 float transmission = (mtl_mat.transmit_color[0] + mtl_mat.transmit_color[1] +
361 mtl_mat.transmit_color[2]) /
362 3;
363 if (transmission >= 0) {
364 set_property_of_socket(SOCK_FLOAT, "Transmission Weight", {transmission}, bsdf);
365 }
366}
367
368static void add_image_textures(Main *bmain,
369 bNodeTree *ntree,
370 bNode *bsdf,
371 Material *mat,
372 const MTLMaterial &mtl_mat,
373 bool relative_paths)
374{
375 float node_locy = node_locy_top;
376 for (int key = 0; key < int(MTLTexMapType::Count); ++key) {
377 const MTLTexMap &value = mtl_mat.texture_maps[key];
378 if (!value.is_valid()) {
379 /* No Image texture node of this map type can be added to this material. */
380 continue;
381 }
382
383 Image *image = load_texture_image(bmain, value, relative_paths);
384 if (image == nullptr) {
385 continue;
386 }
387
388 bNode *image_node = add_node(ntree, SH_NODE_TEX_IMAGE, node_locx_image, node_locy);
389 BLI_assert(image_node);
390 image_node->id = &image->id;
391 static_cast<NodeTexImage *>(image_node->storage)->projection = value.projection_type;
392
393 /* Add normal map node if needed. */
394 bNode *normal_map = nullptr;
395 if (key == int(MTLTexMapType::Normal)) {
396 normal_map = add_node(ntree, SH_NODE_NORMAL_MAP, node_locx_normalmap, node_locy);
397 const float bump = std::max(0.0f, mtl_mat.normal_strength);
398 set_property_of_socket(SOCK_FLOAT, "Strength", {bump}, normal_map);
399 }
400
401 /* Add UV mapping & coordinate nodes only if needed. */
402 if (value.translation != float3(0, 0, 0) || value.scale != float3(1, 1, 1)) {
403 bNode *texcoord = add_node(ntree, SH_NODE_TEX_COORD, node_locx_texcoord, node_locy);
404 bNode *mapping = add_node(ntree, SH_NODE_MAPPING, node_locx_mapping, node_locy);
405 set_property_of_socket(SOCK_VECTOR, "Location", {value.translation, 3}, mapping);
406 set_property_of_socket(SOCK_VECTOR, "Scale", {value.scale, 3}, mapping);
407
408 link_sockets(ntree, texcoord, "UV", mapping, "Vector");
409 link_sockets(ntree, mapping, "Vector", image_node, "Vector");
410 }
411
412 if (normal_map) {
413 link_sockets(ntree, image_node, "Color", normal_map, "Color");
414 link_sockets(ntree, normal_map, "Normal", bsdf, "Normal");
415 }
416 else if (key == int(MTLTexMapType::Alpha)) {
417 link_sockets(ntree, image_node, "Alpha", bsdf, tex_map_type_to_socket_id[key]);
420 }
421 else {
422 link_sockets(ntree, image_node, "Color", bsdf, tex_map_type_to_socket_id[key]);
423 }
424
425 /* Next layout row: goes downwards on the screen. */
426 node_locy -= node_locy_step;
427 }
428}
429
431 const MTLMaterial &mtl_mat,
432 Material *mat,
433 bool relative_paths)
434{
436 nullptr, &mat->id, "Shader Nodetree", ntreeType_Shader->idname);
437
440
441 set_bsdf_socket_values(bsdf, mat, mtl_mat);
442 add_image_textures(bmain, ntree, bsdf, mat, mtl_mat, relative_paths);
443 link_sockets(ntree, bsdf, "BSDF", output, "Surface");
445
446 return ntree;
447}
448
449} // namespace blender::io::obj
Image * BKE_image_load_exists(Main *bmain, const char *filepath, bool *r_exists=nullptr)
Image * BKE_image_add_generated(Main *bmain, unsigned int width, unsigned int height, const char *name, int depth, int floatbuf, short gen_type, const float color[4], bool stereo3d, bool is_data, bool tiled)
const char * BKE_main_blendfile_path(const Main *bmain) ATTR_NONNULL()
Definition main.cc:887
#define SH_NODE_TEX_IMAGE
#define SH_NODE_BSDF_PRINCIPLED
#define SH_NODE_TEX_COORD
#define SH_NODE_NORMAL_MAP
#define SH_NODE_OUTPUT_MATERIAL
#define SH_NODE_MAPPING
#define BLI_assert(a)
Definition BLI_assert.h:46
MINLINE void copy_v4_v4(float r[4], const float a[4])
MINLINE void copy_v3_v3(float r[3], const float a[3])
void void void const char * BLI_path_basename(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
int BLI_path_normalize(char *path) ATTR_NONNULL(1)
bool void BLI_path_rel(char path[FILE_MAX], const char *basepath) ATTR_NONNULL(1)
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
#define ELEM(...)
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:189
#define CLOG_INFO(clg_ref,...)
Definition CLG_log.h:190
@ IMA_SRC_FILE
@ IMA_GENTYPE_BLANK
@ MA_BL_HIDE_BACKFACE
@ MA_BM_BLEND
@ SOCK_OUT
@ SOCK_IN
eNodeSocketDatatype
@ SOCK_VECTOR
@ SOCK_FLOAT
@ SOCK_RGBA
struct blender::bke::bNodeTreeType * ntreeType_Shader
constexpr const T * data() const
Definition BLI_span.hh:215
constexpr int64_t size() const
Definition BLI_span.hh:252
#define output
#define sqrt
#define LOG(level)
Definition log.h:97
bNodeTree * node_tree_add_tree_embedded(Main *bmain, ID *owner_id, StringRefNull name, StringRefNull idname)
Definition node.cc:4098
bNodeSocket * node_find_socket(bNode &node, eNodeSocketInOut in_out, StringRef identifier)
Definition node.cc:2532
bNode * node_add_static_node(const bContext *C, bNodeTree &ntree, int type)
Definition node.cc:3500
bNodeLink & node_add_link(bNodeTree &ntree, bNode &fromnode, bNodeSocket &fromsock, bNode &tonode, bNodeSocket &tosock)
Definition node.cc:3810
void node_set_active(bNodeTree &ntree, bNode &node)
Definition node.cc:4724
const char * tex_map_type_to_socket_id[]
const float node_locy_step
const float node_locx_normalmap
static void add_image_textures(Main *bmain, bNodeTree *ntree, bNode *bsdf, Material *mat, const MTLMaterial &mtl_mat, bool relative_paths)
const float node_locx_image
static bNode * add_node(bNodeTree *ntree, int type, float x, float y)
const float node_locx_mapping
const float node_locx_bsdf
static void set_bsdf_socket_values(bNode *bsdf, Material *mat, const MTLMaterial &mtl_mat)
static Image * create_placeholder_image(Main *bmain, const std::string &path)
bNodeTree * create_mtl_node_tree(Main *bmain, const MTLMaterial &mtl_mat, Material *mat, bool relative_paths)
const float node_locx_texcoord
static void set_property_of_socket(eNodeSocketDatatype property_type, const char *socket_id, Span< float > value, bNode *r_node)
const float node_locx_output
const float node_locy_top
static void link_sockets(bNodeTree *ntree, bNode *from_node, const char *from_node_id, bNode *to_node, const char *to_node_id)
static Image * load_image_at_path(Main *bmain, const std::string &path, bool relative_paths)
static Image * load_texture_image(Main *bmain, const MTLTexMap &tex_map, bool relative_paths)
VecBase< float, 3 > float3
char filepath[1024]
short source
void * default_value
float location[2]
struct ID * id
void * storage
const MTLTexMap & tex_map_of_type(MTLTexMapType key) const
MTLTexMap texture_maps[int(MTLTexMapType::Count)]